diff options
Diffstat (limited to 'rdiff-backup/rdiff_backup')
-rw-r--r-- | rdiff-backup/rdiff_backup/Globals.py | 5 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/Main.py | 36 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/connection.py | 2 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/increment.py | 7 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/metadata.py | 8 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/rpath.py | 63 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/user_group.py | 9 |
7 files changed, 92 insertions, 38 deletions
diff --git a/rdiff-backup/rdiff_backup/Globals.py b/rdiff-backup/rdiff_backup/Globals.py index 9bcabb7..e40fd0a 100644 --- a/rdiff-backup/rdiff_backup/Globals.py +++ b/rdiff-backup/rdiff_backup/Globals.py @@ -207,11 +207,6 @@ compare_inode = 1 # guarantee that any changes have been committed to disk. fsync_directories = 1 -# If set, directory increments are given the same permissions as the -# directories they represent. Otherwise they have the default -# permissions. -change_dir_inc_perms = 1 - def get(name): """Return the value of something in this module""" diff --git a/rdiff-backup/rdiff_backup/Main.py b/rdiff-backup/rdiff_backup/Main.py index 6d5dec8..68832d3 100644 --- a/rdiff-backup/rdiff_backup/Main.py +++ b/rdiff-backup/rdiff_backup/Main.py @@ -32,6 +32,7 @@ remote_cmd, remote_schema = None, None force = None select_opts = [] select_files = [] +user_mapping_filename, group_mapping_filename = None, None # These are global because they are set while we are trying to figure # whether to restore or to backup restore_root, restore_index, restore_root_set = None, None, 0 @@ -40,6 +41,7 @@ def parse_cmdlineoptions(arglist): """Parse argument list and set global preferences""" global args, action, force, restore_timestr, remote_cmd, remote_schema global remove_older_than_string + global user_mapping_filename, group_mapping_filename def sel_fl(filename): """Helper function for including/excluding filelists below""" try: return open(filename, "r") @@ -51,20 +53,19 @@ def parse_cmdlineoptions(arglist): "exclude-filelist=", "exclude-filelist-stdin", "exclude-globbing-filelist=", "exclude-mirror=", "exclude-other-filesystems", "exclude-regexp=", - "exclude-special-files", "force", "include=", - "include-filelist=", "include-filelist-stdin", + "exclude-special-files", "force", "group-mapping-file=", + "include=", "include-filelist=", "include-filelist-stdin", "include-globbing-filelist=", "include-regexp=", "list-at-time=", "list-changed-since=", "list-increments", "list-increment-sizes", "no-compare-inode", - "no-change-dir-inc-perms", "no-compression", - "no-compression-regexp=", "no-file-statistics", - "no-hard-links", "null-separator", + "no-compression", "no-compression-regexp=", + "no-file-statistics", "no-hard-links", "null-separator", "override-chars-to-quote=", "parsable-output", "print-statistics", "remote-cmd=", "remote-schema=", "remove-older-than=", "restore-as-of=", "restrict=", "restrict-read-only=", "restrict-update-only=", "server", "ssh-no-compression", "terminal-verbosity=", "test-server", - "verbosity=", "version"]) + "user-mapping-file=", "verbosity=", "version"]) except getopt.error, e: commandline_error("Bad commandline options: %s" % str(e)) @@ -89,6 +90,7 @@ def parse_cmdlineoptions(arglist): opt == "--exclude-regexp" or opt == "--exclude-special-files"): select_opts.append((opt, arg)) elif opt == "--force": force = 1 + elif opt == "--group-mapping-file": group_mapping_filename = arg elif opt == "--include": select_opts.append((opt, arg)) elif opt == "--include-filelist": select_opts.append((opt, arg)) @@ -107,8 +109,6 @@ def parse_cmdlineoptions(arglist): elif opt == "-l" or opt == "--list-increments": action = "list-increments" elif opt == '--list-increment-sizes': action = 'list-increment-sizes' - elif opt == '--no-change-dir-inc-perms': - Globals.set('change_dir_inc_perms', 0) elif opt == "--no-compare-inode": Globals.set("compare_inode", 0) elif opt == "--no-compression": Globals.set("compression", None) elif opt == "--no-compression-regexp": @@ -141,6 +141,7 @@ def parse_cmdlineoptions(arglist): Globals.set('ssh_compression', None) elif opt == "--terminal-verbosity": Log.setterm_verbosity(arg) elif opt == "--test-server": action = "test-server" + elif opt == "--user-mapping-file": user_mapping_filename = arg elif opt == "-V" or opt == "--version": print "rdiff-backup " + Globals.version sys.exit(0) @@ -190,6 +191,21 @@ def misc_setup(rps): conn.robust.install_signal_handlers() conn.Hardlink.initialize_dictionaries() +def init_user_group_mapping(destination_conn): + """Initialize user and group mapping on destination connection""" + global user_mapping_filename, group_mapping_filename + def get_string_from_file(filename): + if not filename: return None + rp = rpath.RPath(Globals.local_connection, filename) + try: return rp.get_data() + except OSError, e: + log.FatalError("Error '%s' reading mapping file '%s'" % + (str(e), filename)) + user_mapping_string = get_string_from_file(user_mapping_filename) + destination_conn.user_group.init_user_mapping(user_mapping_string) + group_mapping_string = get_string_from_file(group_mapping_filename) + destination_conn.user_group.init_group_mapping(group_mapping_string) + def take_action(rps): """Do whatever action says""" if action == "server": @@ -237,6 +253,7 @@ def Backup(rpin, rpout): backup_set_fs_globals(rpin, rpout) if Globals.chars_to_quote: rpout = backup_quoted_rpaths(rpout) backup_final_init(rpout) + init_user_group_mapping(rpout.conn) backup_set_select(rpin) if prevtime: rpout.conn.Main.backup_touch_curmirror_local(rpin, rpout) @@ -363,7 +380,6 @@ def backup_set_fs_globals(rpin, rpout): update_bool_global('write_eas', Globals.read_eas and dest_fsa.eas) update_bool_global('write_resource_forks', Globals.read_resource_forks and dest_fsa.resource_forks) - update_bool_global('change_dir_inc_perms', dest_fsa.dir_inc_perms) SetConnections.UpdateGlobal('chars_to_quote', dest_fsa.chars_to_quote) if Globals.chars_to_quote: for conn in Globals.connections: @@ -417,6 +433,7 @@ def Restore(src_rp, dest_rp, restore_as_of = None): try: time = Time.genstrtotime(restore_timestr, rp = inc_rpath) except Time.TimeException, exc: Log.FatalError(str(exc)) else: time = src_rp.getinctime() + init_user_group_mapping(dest_rp.conn) restore_set_select(restore_root, dest_rp) restore_start_log(src_rp, dest_rp, time) restore.Restore(restore_root.new_index(restore_index), @@ -664,6 +681,7 @@ def CheckDest(dest_rp): elif need_check == 0: Log.FatalError("Destination dir %s does not need checking" % (dest_rp.path,)) + init_user_group_mapping(dest_rp.conn) dest_rp.conn.regress.Regress(dest_rp) def checkdest_need_check(dest_rp): diff --git a/rdiff-backup/rdiff_backup/connection.py b/rdiff-backup/rdiff_backup/connection.py index 818e5d8..3961a54 100644 --- a/rdiff-backup/rdiff_backup/connection.py +++ b/rdiff-backup/rdiff_backup/connection.py @@ -519,7 +519,7 @@ import Globals, Time, Rdiff, Hardlink, FilenameMapping, C, Security, \ Main, rorpiter, selection, increment, statistics, manage, lazy, \ iterfile, rpath, robust, restore, manage, backup, connection, \ TempFile, SetConnections, librsync, log, regress, fs_abilities, \ - eas_acls + eas_acls, user_group Globals.local_connection = LocalConnection() Globals.connections.append(Globals.local_connection) diff --git a/rdiff-backup/rdiff_backup/increment.py b/rdiff-backup/rdiff_backup/increment.py index ea7a81d..cf580a0 100644 --- a/rdiff-backup/rdiff_backup/increment.py +++ b/rdiff-backup/rdiff_backup/increment.py @@ -86,17 +86,14 @@ def makediff(new, mirror, incpref): Rdiff.write_delta(new, mirror, diff, compress) new.chmod(old_new_perms) else: Rdiff.write_delta(new, mirror, diff, compress) - rpath.copy_attribs(mirror, diff) + rpath.copy_attribs_inc(mirror, diff) return diff def makedir(mirrordir, incpref): """Make file indicating directory mirrordir has changed""" dirsign = get_inc(incpref, "dir") dirsign.touch() - if Globals.change_dir_inc_perms: - # Below, don't copy acls because directories can have more of - # them than ordinary files (they have default acls also). - rpath.copy_attribs(mirrordir, dirsign, acls = 0) + rpath.copy_attribs_inc(mirrordir, dirsign) return dirsign def get_inc(rp, typestr, time = None): diff --git a/rdiff-backup/rdiff_backup/metadata.py b/rdiff-backup/rdiff_backup/metadata.py index 2388cfa..b7ff395 100644 --- a/rdiff-backup/rdiff_backup/metadata.py +++ b/rdiff-backup/rdiff_backup/metadata.py @@ -107,7 +107,9 @@ def RORP2Record(rorpath): # Add user, group, and permission information uid, gid = rorpath.getuidgid() str_list.append(" Uid %s\n" % uid) + str_list.append(" Uname %s\n" % rorpath.getuname() or ":") str_list.append(" Gid %s\n" % gid) + str_list.append(" Gname %s\n" % rorpath.getgname() or ":") str_list.append(" Permissions %s\n" % rorpath.getperms()) return "".join(str_list) @@ -140,6 +142,12 @@ def Record2RORP(record_string): elif field == "ModTime": data_dict['mtime'] = long(data) elif field == "Uid": data_dict['uid'] = int(data) elif field == "Gid": data_dict['gid'] = int(data) + elif field == "Uname": + if data == ":": data_dict['uname'] = None + else: data_dict['uname'] = data + elif field == "Gname": + if data == ':': data_dict['gname'] = None + else: data_dict['gname'] = data elif field == "Permissions": data_dict['perms'] = int(data) else: raise ParsingError("Unknown field in line '%s'" % line) return rpath.RORPath(index, data_dict) diff --git a/rdiff-backup/rdiff_backup/rpath.py b/rdiff-backup/rdiff_backup/rpath.py index 6c84df9..de2d2f7 100644 --- a/rdiff-backup/rdiff_backup/rpath.py +++ b/rdiff-backup/rdiff_backup/rpath.py @@ -36,7 +36,7 @@ are dealing with are local or remote. """ import os, stat, re, sys, shutil, gzip, socket, time -import Globals, Time, static, log +import Globals, Time, static, log, user_group class SkipFileException(Exception): @@ -143,7 +143,7 @@ def cmp(rpin, rpout): elif rpin.issock(): return rpout.issock() else: raise RPathException("File %s has unknown type" % rpin.path) -def copy_attribs(rpin, rpout, acls = 1): +def copy_attribs(rpin, rpout): """Change file attributes of rpout to match rpin Only changes the chmoddable bits, uid/gid ownership, and @@ -151,14 +151,36 @@ def copy_attribs(rpin, rpout, acls = 1): """ log.Log("Copying attributes from %s to %s" % (rpin.index, rpout.path), 7) + assert rpin.lstat() == rpout.lstat() is not None, "different file types" + if rpin.issym(): return # symlinks have no valid attributes + if Globals.write_resource_forks and rpin.isreg(): + rpout.write_resource_fork(rpin.get_resource_fork()) + if Globals.write_eas: rpout.write_ea(rpin.get_ea()) + if Globals.change_ownership: rpout.chown(*user_group.map_rpath(rpin)) + rpout.chmod(rpin.getperms()) + if Globals.write_acls: rpout.write_acl(rpin.get_acl()) + if not rpin.isdev(): rpout.setmtime(rpin.getmtime()) + +def copy_attribs_inc(rpin, rpout): + """Change file attributes of rpout to match rpin + + Like above, but used to give increments the same attributes as the + originals. Therefore, don't copy all directory acl and + permissions. + + """ + log.Log("Copying inc attrs from %s to %s" % (rpin.index, rpout.path), 7) check_for_files(rpin, rpout) if rpin.issym(): return # symlinks have no valid attributes if Globals.write_resource_forks and rpin.isreg() and rpout.isreg(): rpout.write_resource_fork(rpin.get_resource_fork()) if Globals.write_eas: rpout.write_ea(rpin.get_ea()) if Globals.change_ownership: apply(rpout.chown, rpin.getuidgid()) - rpout.chmod(rpin.getperms()) - if Globals.write_acls and acls: rpout.write_acl(rpin.get_acl()) + if rpin.isdir() and not rpout.isdir(): + rpout.chmod(rpin.getperms() & 0777) + else: rpout.chmod(rpin.getperms()) + if Globals.write_acls and not (rpin.isdir() and not rpout.isdir()): + rpout.write_acl(rpin.get_acl()) if not rpin.isdev(): rpout.setmtime(rpin.getmtime()) def cmp_attribs(rp1, rp2): @@ -265,7 +287,7 @@ class RORPath: if self.index != other.index: return None for key in self.data.keys(): # compare dicts key by key - if (key == 'uid' or key == 'gid') and self.issym(): + if self.issym() and key in ('uid', 'gid', 'uname', 'gname'): pass # Don't compare gid/uid for symlinks elif key == 'atime' and not Globals.preserve_atime: pass elif key == 'ctime': pass @@ -293,11 +315,7 @@ class RORPath: """ for key in self.data.keys(): # compare dicts key by key - if ((key == 'uid' or key == 'gid') and - (self.issym() or not Globals.change_ownership)): - # Don't compare gid/uid for symlinks, and only root - # can change ownership - pass + if key in ('uid', 'gid', 'uname', 'gname'): pass elif (key == 'type' and self.isspecial() and other.isreg() and other.getsize() == 0): pass # Special files may be replaced with empty regular files @@ -312,6 +330,11 @@ class RORPath: pass elif (not other.data.has_key(key) or self.data[key] != other.data[key]): return 0 + + if self.lstat() and not self.issym() and Globals.change_ownership: + # Now compare ownership. Symlinks don't have ownership + if user_group.map_rpath(self) != other.getuidgid(): return 0 + return 1 def equal_verbose(self, other, check_index = 1, @@ -323,7 +346,7 @@ class RORPath: return None for key in self.data.keys(): # compare dicts key by key - if ((key == 'uid' or key == 'gid') and + if (key in ('uid', 'gid', 'uname', 'gname') and (self.issym() or not compare_ownership)): # Don't compare gid/uid for symlinks, or if told not to pass @@ -425,6 +448,14 @@ class RORPath: """Return permission block of file""" return self.data['perms'] + def getuname(self): + """Return username that owns the file""" + return self.data['uname'] + + def getgname(self): + """Return groupname that owns the file""" + return self.data['gname'] + def hassize(self): """True if rpath has a size parameter""" return self.data.has_key('size') @@ -620,10 +651,11 @@ class RPath(RORPath): def setdata(self): """Set data dictionary using C extension""" self.data = self.conn.C.make_file_dict(self.path) - if Globals.read_eas and self.lstat(): - self.data['ea'] = self.conn.rpath.ea_get(self) - if Globals.read_acls and self.lstat(): - self.data['acl'] = self.conn.rpath.acl_get(self) + if not self.lstat(): return + self.data['uname'] = self.conn.user_group.uid2uname(self.data['uid']) + self.data['gname'] = self.conn.user_group.gid2gname(self.data['gid']) + if Globals.read_eas: self.data['ea'] = self.conn.rpath.ea_get(self) + if Globals.read_acls: self.data['acl'] = self.conn.rpath.acl_get(self) if Globals.read_resource_forks and self.isreg(): self.get_resource_fork() @@ -1063,3 +1095,4 @@ class RPathFileHook: # problems. def acl_get(rp): assert 0 def ea_get(rp): assert 0 + diff --git a/rdiff-backup/rdiff_backup/user_group.py b/rdiff-backup/rdiff_backup/user_group.py index cff2943..2baeda2 100644 --- a/rdiff-backup/rdiff_backup/user_group.py +++ b/rdiff-backup/rdiff_backup/user_group.py @@ -65,7 +65,6 @@ class Map: """Used for mapping names and id on source side to dest side""" def __init__(self, name2id_func): """Map initializer, set dictionaries""" - assert Globals.isdest, "Should run on destination connection" self.name2id_dict = {} self.name2id_func = name2id_func @@ -150,5 +149,9 @@ def init_group_mapping(mapping_string): else: GroupMap = Map(name2id_func) - - +def map_rpath(rp): + """Return (uid, gid) of mapped ownership of given rpath""" + old_uid, old_gid = rp.getuidgid() + new_uid = UserMap.get_id(old_uid, rp.getuname()) + new_gid = GroupMap.get_id(old_gid, rp.getgname()) + return (new_uid, new_gid) |