Package config_common :: Module file_utils
[hide private]
[frames] | no frames]

Source Code for Module config_common.file_utils

  1  # 
  2  # Copyright (c) 2008--2020 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15   
 16  import os 
 17  import sys 
 18  import stat 
 19  import time 
 20  import tempfile 
 21  import base64 
 22  import difflib 
 23  import pwd 
 24  import grp 
 25  try: 
 26      from selinux import lgetfilecon 
 27  except: 
 28      # on rhel4 we do not support selinux 
29 - def lgetfilecon(path):
30 return [0, '']
31 32 from config_common import utils 33 from config_common.local_config import get as get_config 34 from rhn.i18n import bstr, sstr 35 36 decodestring = base64.decodestring 37 if hasattr(base64, 'decodebytes'): 38 decodestring = base64.decodebytes 39
40 -class FileProcessor:
41 file_struct_fields = { 42 'file_contents' : None, 43 'delim_start' : None, 44 'delim_end' : None, 45 }
46 - def __init__(self):
47 pass
48
49 - def process(self, file_struct, directory=None, strict_ownership=1):
50 # Older servers will not return directories; if filetype is missing, 51 # assume file 52 53 if file_struct.get('filetype') == 'directory': 54 if directory is None: 55 directory = "" 56 return None, utils.mkdir_p(directory + file_struct['path']) 57 58 if directory: 59 directory += os.path.split(file_struct['path'])[0] 60 if file_struct.get('filetype') == 'symlink': 61 if 'symlink' not in file_struct: 62 raise Exception("Missing key symlink") 63 64 (fullpath, dirs_created, fd) = maketemp(prefix=".rhn-cfg-tmp", directory=directory) 65 os.close(fd) 66 os.unlink(fullpath) 67 os.symlink(file_struct['symlink'], fullpath) 68 return fullpath, dirs_created 69 70 for k in self.file_struct_fields.keys(): 71 if k not in file_struct: 72 # XXX 73 raise Exception("Missing key %s" % k) 74 75 encoding = '' 76 77 if 'encoding' in file_struct: 78 encoding = file_struct['encoding'] 79 80 contents = file_struct['file_contents'] 81 82 if contents and (encoding == 'base64'): 83 contents = decodestring(bstr(contents)) 84 85 delim_start = file_struct['delim_start'] 86 delim_end = file_struct['delim_end'] 87 88 if ('checksum' in file_struct 89 and 'checksum_type' in file_struct 90 and 'verify_contents' in file_struct 91 and file_struct['verify_contents']): 92 if file_struct['checksum'] != utils.getContentChecksum( 93 file_struct['checksum_type'], contents): 94 raise Exception("Corrupt file received: Content checksums do not match!") 95 elif ('md5sum' in file_struct and 'verify_contents' in file_struct 96 and file_struct['verify_contents']): 97 if file_struct['md5sum'] != utils.getContentChecksum( 98 'md5', contents): 99 raise Exception("Corrupt file received: Content checksums do not match!") 100 elif ('verify_contents' in file_struct 101 and file_struct['verify_contents']): 102 raise Exception("Corrupt file received: missing checksum information!") 103 104 105 (fullpath, dirs_created, fd) = maketemp(prefix=".rhn-cfg-tmp", directory=directory) 106 107 try: 108 os.write(fd, bstr(contents)) 109 except Exception: 110 raise 111 finally: 112 os.close(fd) 113 114 # try to set mtime and ctime of the file to 115 # the last modified time on the server 116 if 'modified' in file_struct: 117 try: 118 modified = xmlrpc_time(file_struct['modified'].value) 119 epoch_time = time.mktime(modified) 120 os.utime(fullpath, (epoch_time, epoch_time)) 121 except (ValueError, AttributeError): 122 # we can't parse modified time 123 pass 124 125 return fullpath, dirs_created
126 127
128 - def diff(self, file_struct):
129 self._validate_struct(file_struct) 130 131 temp_file, temp_dirs = self.process(file_struct) 132 path = file_struct['path'] 133 sectx_result = '' 134 owner_result = '' 135 group_result = '' 136 perm_result = '' 137 result = '' 138 139 stat_err = 0 140 141 try: 142 cur_stat = os.lstat(path) 143 except: 144 stat_err = 1 145 146 if file_struct['filetype'] != 'symlink': 147 if not stat_err: 148 #check for owner differences 149 cur_uid = cur_stat[stat.ST_UID] 150 try: 151 cur_user = pwd.getpwuid(cur_uid)[0] 152 except KeyError: 153 #Orphan UID with no name,return unknown 154 cur_user = "unknown(UID %d)" % (cur_uid,) 155 else: 156 cur_user = "missing" 157 158 if cur_user == file_struct['username']: 159 owner_result = "" 160 161 else: 162 owner_result = "User name differ: actual: [%s], expected: [%s]\n" % (cur_user, file_struct['username']) 163 164 if not stat_err: 165 #check for group differences 166 cur_gid = cur_stat[stat.ST_GID] 167 try: 168 cur_group = grp.getgrgid(cur_gid)[0] 169 except KeyError: 170 #Orphan GID with no name,return unknown 171 cur_group = "unknown(GID %d)" % (cur_gid,) 172 else: 173 cur_group = "missing" 174 175 if cur_group == file_struct['groupname']: 176 group_result = "" 177 else: 178 group_result = "Group name differ: actual: [%s], expected: [%s]\n" % (cur_group, file_struct['groupname']) 179 180 #check for permissions differences 181 if not stat_err: 182 cur_perm = str(oct(stat.S_IMODE(cur_stat[stat.ST_MODE]))) 183 else: 184 cur_perm = "missing" 185 186 #rip off the leading '0' from the mode returned by stat() 187 if cur_perm[0] == '0': 188 cur_perm = cur_perm[2:] if cur_perm[1] == 'o' else cur_perm[1:] 189 190 #perm_status gets displayed with the verbose option. 191 if cur_perm == str(file_struct['filemode']): 192 perm_result = "" 193 else: 194 perm_result = "File mode differ: actual: [%s], expected: [%s]\n" % (cur_perm, file_struct['filemode']) 195 196 try: 197 cur_sectx = lgetfilecon(path)[1] 198 except OSError: # workarounding BZ 690238 199 cur_sectx = None 200 201 if cur_sectx == None: 202 cur_sectx = '' 203 204 if 'selinux_ctx' in file_struct and file_struct['selinux_ctx']: 205 if cur_sectx != file_struct['selinux_ctx']: 206 sectx_result = "SELinux contexts differ: actual: [%s], expected: [%s]\n" % (cur_sectx, file_struct['selinux_ctx']) 207 208 if file_struct['filetype'] == 'directory': 209 if os.path.isdir(file_struct['path']): 210 result = '' 211 else: 212 result = "Deployed directory is no longer a directory!" 213 elif file_struct['filetype'] == 'symlink': 214 try: 215 curlink = os.readlink(path) 216 newlink = os.readlink(temp_file) 217 if curlink == newlink: 218 result = '' 219 else: 220 result = "Link targets differ for [%s]: actual: [%s], expected: [%s]\n" % (path, curlink, newlink) 221 except OSError: 222 e = sys.exc_info()[1] 223 if e.errno == 22: 224 result = "Deployed symlink is no longer a symlink!" 225 else: 226 raise e 227 else: 228 result = ''.join(diff(temp_file, path, display_diff=get_config('display_diff'), 229 is_binary=True if file_struct['is_binary'] == 'Y' else False)) 230 231 if temp_file: 232 os.unlink(temp_file) 233 return owner_result + group_result + perm_result + sectx_result + result
234
235 - def _validate_struct(self, file_struct):
236 for k in self.file_struct_fields.keys(): 237 if k not in file_struct: 238 # XXX 239 raise Exception("Missing key %s" % k)
240 241
242 -def diff(src, dst, srcname=None, dstname=None, display_diff=False, is_binary=False):
243 def f_content(path, name, is_binary): 244 statinfo = None 245 if os.access(path, os.R_OK): 246 f = open(path, ('r' if int(sys.version[0]) == 3 else 'U') + ('b' if is_binary else '')) 247 content = [sstr(i) for i in f.readlines()] 248 f.close() 249 statinfo = os.stat(path) 250 f_time = time.ctime(statinfo.st_mtime) 251 if not is_binary and content and content[-1] and content[-1][-1] != "\n": 252 content[-1] += "\n" 253 else: 254 content = [] 255 f_time = time.ctime(0) 256 if not name: 257 name = path 258 return (content, name, f_time, statinfo)
259 260 (src_content, src_name, src_time, src_stat) = f_content(src, srcname, is_binary) 261 (dst_content, dst_name, dst_time, dst_stat) = f_content(dst, dstname, is_binary) 262 263 diff_u = difflib.unified_diff(src_content, dst_content, 264 src_name, dst_name, 265 src_time, dst_time) 266 267 ret_list = list(diff_u) 268 # don't return the diff if the file is not readable by everyone 269 # for security reasons. 270 if (len(ret_list) > 0 # if differences exist 271 and not display_diff # and we have not explicitly decided to display 272 and (dst_stat == None # file is not there or not readable to root 273 or (dst_stat.st_uid == 0 # file is owned by root 274 and not dst_stat.st_mode & stat.S_IROTH))): # not read-all 275 ret_list = [ 276 "Differences exist in a file %s that is not readable by all. " % dst, 277 "Re-deployment of configuration file is recommended.\n"] 278 return ret_list 279 280 281
282 -def maketemp(prefix=None, directory=None):
283 """Creates a temporary file (guaranteed to be new), using the 284 specified prefix. 285 286 Returns the filename and a file descriptor 287 """ 288 if not directory: 289 directory = tempfile.gettempdir() 290 291 dirs_created = None 292 if not os.path.exists(directory): 293 dirs_created = utils.mkdir_p(directory) 294 295 if not prefix: 296 # Create the file in /tmp by default 297 prefix = 'rhncfg-tempfile' 298 299 file_prefix = "%s-%s-" % (prefix, os.getpid()) 300 (fd, filename) = tempfile.mkstemp(prefix=file_prefix, dir=directory) 301 302 return filename, dirs_created, fd
303 304 305 # Duplicated from backend/common/fileutils.py to remove dependency requirement. 306 # If making changes make them there too. 307 FILETYPE2CHAR = { 308 'file' : '-', 309 'directory' : 'd', 310 'symlink' : 'l', 311 'chardev' : 'c', 312 'blockdev' : 'b', 313 } 314 315 # Duplicated from backend/common/fileutils.py to remove dependency requirement. 316 # If making changes make them there too.
317 -def _ifelse(cond, thenval, elseval):
318 if cond: 319 return thenval 320 else: 321 return elseval
322 323 # Duplicated from backend/common/fileutils.py to remove dependency requirement. 324 # If making changes make them there too.
325 -def ostr_to_sym(octstr, ftype):
326 """ Convert filemode in octets (like '644') to string like "ls -l" ("-rwxrw-rw-") 327 ftype is one of: file, directory, symlink, chardev, blockdev. 328 """ 329 mode = int(str(octstr), 8) 330 331 symstr = FILETYPE2CHAR.get(ftype, '?') 332 333 symstr += _ifelse(mode & stat.S_IRUSR, 'r', '-') 334 symstr += _ifelse(mode & stat.S_IWUSR, 'w', '-') 335 symstr += _ifelse(mode & stat.S_IXUSR, 336 _ifelse(mode & stat.S_ISUID, 's', 'x'), 337 _ifelse(mode & stat.S_ISUID, 'S', '-')) 338 symstr += _ifelse(mode & stat.S_IRGRP, 'r', '-') 339 symstr += _ifelse(mode & stat.S_IWGRP, 'w', '-') 340 symstr += _ifelse(mode & stat.S_IXGRP, 341 _ifelse(mode & stat.S_ISGID, 's', 'x'), 342 _ifelse(mode & stat.S_ISGID, 'S', '-')) 343 symstr += _ifelse(mode & stat.S_IROTH, 'r', '-') 344 symstr += _ifelse(mode & stat.S_IWOTH, 'w', '-') 345 symstr += _ifelse(mode & stat.S_IXOTH, 346 _ifelse(mode & stat.S_ISVTX, 't', 'x'), 347 _ifelse(mode & stat.S_ISVTX, 'T', '-')) 348 return symstr
349 350 # Duplicated from backend/common/fileutils.py to remove dependency requirement. 351 # If making changes make them there too.
352 -def f_date(dbiDate):
353 return "%04d-%02d-%02d %02d:%02d:%02d" % (dbiDate.year, dbiDate.month, 354 dbiDate.day, dbiDate.hour, dbiDate.minute, dbiDate.second)
355
356 -def xmlrpc_time(xtime):
357 if xtime[8] == 'T': 358 # oracle backend: 20130304T23:19:17 359 timefmt='%Y%m%dT%H:%M:%S' 360 else: 361 # postresql backend format: 2014-02-28 18:47:31.506953+01:00 362 timefmt='%Y-%m-%d %H:%M:%S' 363 xtime = xtime[:19] 364 365 return time.strptime(xtime, timefmt)
366