1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 import os
17 import shutil
18 import pwd
19 import grp
20 import sys
21 import errno
22 import shutil
23
24 from config_common import file_utils, utils, cfg_exceptions
25 from config_common.rhn_log import log_debug
26 from spacewalk.common.usix import raise_with_tb
27
31
32 try:
33 from selinux import lsetfilecon
34 except:
38
39 BACKUP_PREFIX = '/var/lib/rhncfg/backups'
40 BACKUP_EXTENSION = '.rhn-cfg-backup'
41
43
44 - def __init__(self, transaction_root=None, auto_rollback=0):
45
46 self.auto_rollback = auto_rollback
47
48 self.transaction_root = transaction_root
49
50 self.files = []
51 self.dirs = []
52 self.symlinks = []
53 self.new_dirs = []
54 self.backup_by_path = {}
55 self.newtemp_by_path = {}
56 self.changed_dir_info = {}
57
58 self.deployment_cb = None
59
60
63
64
66 """renames a file to it's new backup name"""
67
68
69 if path in self.backup_by_path:
70 raise DuplicateDeployment("Error: attempted to backup %s twice" % path)
71
72
73 new_path = None
74
75 if os.path.exists(path):
76
77 if os.path.isfile(path) or os.path.islink(path):
78 new_path = self._generate_backup_path(path)
79 log_debug(6, "renaming %s to backup %s ..." % (path, new_path))
80
81
82 try:
83 log_debug(9, "trying to use os.renames")
84 oumask = os.umask(int('022', 8))
85 os.renames(path, new_path)
86 os.umask(oumask)
87 except OSError:
88 e = sys.exc_info()[1]
89 if e.errno == 18:
90 log_debug(9, "os.renames failed, using shutil functions")
91 path_dir, path_file = os.path.split(path)
92 new_path_dir, new_path_file = os.path.split(new_path)
93 if os.path.isdir(new_path_dir):
94 if os.path.islink(path):
95 log_debug(9, "copying symlink %s to %s"% (path,new_path_dir))
96 linkto = os.readlink(path)
97 if os.path.lexists(new_path):
98 log_debug(9, "backup %s exists, removing it"% (new_path))
99 os.unlink(new_path)
100 os.symlink(linkto,new_path)
101 else:
102 log_debug(9, "backup directory %s exists, copying %s to it" % (new_path_dir, new_path_file))
103 if os.path.lexists(new_path):
104 log_debug(9, "backup %s exists, removing it"% (new_path))
105 os.unlink(new_path)
106 shutil.copy(path, new_path)
107 else:
108 log_debug(9, "backup directory does not exist, creating the tree now")
109 shutil.copytree(path_dir, new_path_dir, symlinks=0)
110 shutil.copy(path, new_path)
111 else:
112 raise
113 self.backup_by_path[path] = new_path
114 log_debug(9, "backed up to %s" % new_path)
115 else:
116 raise TargetNotFile("Error: %s is not a valid file, cannot create backup copy" % path)
117 return new_path
118
119
121 self.deployment_cb = cb
122
124 if file_info['filetype'] != 'symlink':
125 uid = file_info.get('uid')
126 if uid is None:
127 if 'username' in file_info:
128
129
130 try:
131 user_record = pwd.getpwnam(file_info['username'])
132 uid = user_record[2]
133 except Exception:
134 e = sys.exc_info()[1]
135
136 try:
137 uid = int(file_info['username'])
138 except ValueError:
139 raise_with_tb(cfg_exceptions.UserNotFound(file_info['username']), sys.exc_info()[2])
140 else:
141
142 uid = 0
143
144 gid = file_info.get('gid')
145 if gid is None:
146 if 'groupname' in file_info:
147
148 try:
149 group_record = grp.getgrnam(file_info['groupname'])
150 gid = group_record[2]
151 except Exception:
152 e = sys.exc_info()[1]
153 try:
154 gid = int(file_info['groupname'])
155 except ValueError:
156 raise_with_tb(cfg_exceptions.GroupNotFound(file_info['groupname']), sys.exc_info()[2])
157
158 else:
159
160 gid = 0
161
162 try:
163 if file_info['filetype'] != 'symlink':
164 os.chown(temp_file_path, uid, gid)
165
166 mode = '600'
167 if 'filemode' in file_info:
168 if file_info['filemode'] is "":
169 mode='000'
170 else:
171 mode = file_info['filemode']
172
173 mode = int(str(mode), 8)
174 os.chmod(temp_file_path, mode)
175
176 if 'selinux_ctx' in file_info:
177 sectx = file_info.get('selinux_ctx')
178 if sectx is not None and sectx is not "":
179 log_debug(1, "selinux context: " + sectx);
180 try:
181 if lsetfilecon(temp_file_path, sectx) < 0:
182 raise Exception("failed to set selinux context on %s" % dest_path)
183 except OSError:
184 e = sys.exc_info()[1]
185 raise_with_tb(Exception("failed to set selinux context on %s" % dest_path, e), sys.exc_info()[2])
186
187 except OSError:
188 e = sys.exc_info()[1]
189 if e.errno == errno.EPERM and not strict_ownership:
190 sys.stderr.write("cannonical file ownership and permissions lost on %s\n" % dest_path)
191 else:
192 raise
193
194
195
197 if self.transaction_root:
198 path = utils.normalize_path(self.transaction_root + os.sep + path)
199 return path
200
201 - def add_preprocessed(self, dest_path, processed_file_path, file_info, dirs_created, strict_ownership=1):
202 """preprocess the file if needed, and add the entry to the correct list"""
203 dest_path = self._normalize_path_to_root(dest_path)
204 log_debug(3, "preprocessing entry")
205
206
207 if dirs_created:
208 self.new_dirs.extend(dirs_created)
209
210
211
212
213 if file_info.get('filetype') == 'directory':
214 self.dirs.append(file_info)
215 else:
216 if "dest_path" in self.newtemp_by_path:
217 raise DuplicateDeployment("Error: %s already added to transaction" % dest_path)
218 self.newtemp_by_path[dest_path] = processed_file_path
219 self._chown_chmod_chcon(processed_file_path, dest_path, file_info, strict_ownership=strict_ownership)
220
221 - def add(self, file_info):
222 """add a file to the deploy transaction"""
223 for k in file_utils.FileProcessor.file_struct_fields:
224 if k not in file_info:
225 raise Exception("needed key %s mising from file structure" % k)
226
227 file_info['path'] = self._normalize_path_to_root(file_info['path'])
228
229
230
231 if file_info.get('filetype') == 'directory':
232 self.dirs.append(file_info)
233 elif file_info.get('filetype') == 'symlink':
234 self.files.append(file_info)
235 else:
236 self.files.append(file_info)
237
238
240 """revert the transaction"""
241 log_debug(3, "rolling back")
242
243
244 for path in self.backup_by_path.keys():
245 log_debug(6, "restoring %s from %s ..." % (path, self.backup_by_path[path]))
246
247
248 try:
249 os.rename(self.backup_by_path[path], path)
250 except OSError:
251 e = sys.exc_info()[1]
252 if e.errno == 18:
253 log_debug(9, "os.rename failed, using shutil.copy")
254 shutil.copy(self.backup_by_path[path], path)
255 else:
256 raise
257 log_debug(9, "%s restored" % path)
258
259
260 for tmp_file_path in self.newtemp_by_path.values():
261 log_debug(6, "removing tmp file %s ..." % tmp_file_path)
262 os.unlink(tmp_file_path)
263 log_debug(9, "tmp file removed")
264
265
266 for d, val in self.changed_dir_info.items():
267 log_debug(6, "reverting owner and perms of %s" % d)
268 self._chown_chmod_chcon(d, d, val)
269 log_debug(9, "directory reverted")
270
271
272 self.new_dirs.reverse()
273 for i in range(len(self.new_dirs)):
274 remove_dir = self.new_dirs[i]
275 log_debug(6, "removing directory %s that was created during transaction ..." % remove_dir)
276 if os.path.islink(remove_dir) == True:
277 os.remove(remove_dir)
278 else:
279 os.rmdir(remove_dir)
280 log_debug(9, "directory removed")
281
282 log_debug(3, "rollback successful")
283
285 """attempt deployment; will rollback if auto_rollback is set"""
286 fp = file_utils.FileProcessor()
287
288 log_debug(3, "deploying transaction")
289
290 for dep_file in self.files:
291 if dep_file['filetype'] == 'symlink':
292 self.symlinks.append(dep_file)
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308 try:
309
310
311 if self.dirs:
312 for directory in self.dirs:
313 dirname = self._normalize_path_to_root(directory['path'])
314 dirmode = directory['filemode']
315 if os.path.isfile(dirname):
316 raise cfg_exceptions.DirectoryEntryIsFile(dirname)
317 if os.path.isdir(dirname):
318 s = os.stat(dirname)
319 entry = { 'filemode': "%o" % (s[0] & int('07777', 8)),
320 'uid': s[4],
321 'gid': s[5],
322 'filetype': 'directory',
323 }
324 self.changed_dir_info[dirname] = entry
325 log_debug(3, "directory found, chowning and chmoding to %s as needed: %s" % (dirmode, dirname))
326 self._chown_chmod_chcon(dirname, dirname, directory)
327 else:
328 log_debug(3, "directory not found, creating: %s" % dirname)
329 dirs_created = utils.mkdir_p(dirname, None, self.symlinks, self.files)
330 self.new_dirs.extend(dirs_created)
331 self._chown_chmod_chcon(dirname, dirname, directory)
332 if self.deployment_cb:
333 self.deployment_cb(dirname)
334
335 log_debug(6, "changed_dir_info: %s" % self.changed_dir_info)
336 log_debug(4, "new_dirs: ", self.new_dirs)
337
338
339 if not self.newtemp_by_path and not self.files:
340 log_debug(4, "directory creation complete, no files found to create")
341 return
342 else:
343 log_debug(4, "done with directory creation, moving on to files")
344
345
346 for dep_file in self.files:
347 path = dep_file['path']
348
349 log_debug(6, "writing new version of %s to tmp file ..." % path)
350
351
352
353
354
355 (directory, filename) = os.path.split(path)
356 if os.path.isdir(path) and not os.path.islink(path):
357 raise cfg_exceptions.FileEntryIsDirectory(path)
358 if not os.path.exists(directory):
359 log_debug(7, "creating directories for %s ..." % directory)
360 dirs_created = utils.mkdir_p(directory, None, self.symlinks, self.files)
361 self.new_dirs.extend(dirs_created)
362 log_debug(7, "directories created and added to list for rollback")
363
364
365
366 self.newtemp_by_path[path], temp_new_dirs = fp.process(dep_file, os.path.sep)
367 self.new_dirs.extend(temp_new_dirs or [])
368
369
370 self._chown_chmod_chcon(self.newtemp_by_path[path], path, dep_file)
371 log_debug(9, "tempfile written: %s" % self.newtemp_by_path[path])
372
373
374
375 paths = list(self.newtemp_by_path.keys())
376
377
378 for path in paths:
379 if os.path.isdir(path) and not os.path.islink(path):
380 raise cfg_exceptions.FileEntryIsDirectory(path)
381 else:
382 self._rename_to_backup(path)
383 if path in self.backup_by_path:
384 log_debug(9, "backup file %s written" % self.backup_by_path[path])
385
386
387 paths.sort(key = lambda s: s.count(os.path.sep))
388 for path in paths:
389 if self.deployment_cb:
390 self.deployment_cb(path)
391 log_debug(6, "deploying %s ..." % path)
392 os.rename(self.newtemp_by_path[path], path)
393
394 del self.newtemp_by_path[path]
395 log_debug(9, "new version of %s deployed" % path)
396
397 log_debug(3, "deploy transaction successful")
398
399 except Exception:
400
401
402 if self.auto_rollback:
403 self.rollback()
404 raise
405