diff options
-rw-r--r-- | rdiff-backup/CHANGELOG | 14 | ||||
-rw-r--r-- | rdiff-backup/TODO | 2 | ||||
-rwxr-xr-x | rdiff-backup/dist/makedist | 3 | ||||
-rw-r--r-- | rdiff-backup/rdiff-backup.1 | 63 | ||||
-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 | ||||
-rw-r--r-- | rdiff-backup/testing/commontest.py | 7 | ||||
-rw-r--r-- | rdiff-backup/testing/roottest.py | 45 |
13 files changed, 210 insertions, 54 deletions
diff --git a/rdiff-backup/CHANGELOG b/rdiff-backup/CHANGELOG index 8189e6e..3b7546e 100644 --- a/rdiff-backup/CHANGELOG +++ b/rdiff-backup/CHANGELOG @@ -1,6 +1,9 @@ New in v0.13.2 (??????????) --------------------------- +Change ownership policy and added --user-mapping-file and +--group-mapping-file switches. See man page for more information. + Specified socket type as SOCK_STREAM. (Error reported by Erik Forsberg.) @@ -13,9 +16,11 @@ If there is data missing from the destination dir (for instance if a user mistakenly deletes it), only warn when restoring, instead of exiting with error. -Fixed bug in EA/ACL restoring, noticed by Greg Freemyer. Also updated -quoting of filenames and extended attributes names to match -forthcoming attr/facl utilities. +Fixed bug in EA/ACL restoring, noticed by Greg Freemyer. + +Updated quoting of filenames and extended attributes names to match +forthcoming attr/facl utilities. Strange characters should now be +properly escaped. Fixed problems with --restrict options that would cause proper sessions to fail. Thanks to Randall Nortman for error report. @@ -27,6 +32,9 @@ by Alan Bailward. File examples.html added to distribution; examples section removed from man page. +Removed option --no-change-dir-inc-perms. Instead when copying +permissions to directory increments, mask with 0777. + New in v0.13.1 (2003/08/08) --------------------------- diff --git a/rdiff-backup/TODO b/rdiff-backup/TODO index 50204ca..e958f5e 100644 --- a/rdiff-backup/TODO +++ b/rdiff-backup/TODO @@ -1,5 +1,3 @@ -Look at manual page examples. - Consider adding --datadir option (Jean-Sébastien GOETSCHY) See if regressing takes too much memory (large directories). diff --git a/rdiff-backup/dist/makedist b/rdiff-backup/dist/makedist index 93375ab..d0c2189 100755 --- a/rdiff-backup/dist/makedist +++ b/rdiff-backup/dist/makedist @@ -116,7 +116,8 @@ def MakeTar(): "robust.py", "rorpiter.py", "rpath.py", "Security.py", "selection.py", "SetConnections.py", "static.py", - "statistics.py", "TempFile.py", "Time.py"]: + "statistics.py", "TempFile.py", "Time.py", + "user_group.py"]: assert not os.system("cp %s/%s %s/rdiff_backup" % (SourceDir, filename, tardir)), filename diff --git a/rdiff-backup/rdiff-backup.1 b/rdiff-backup/rdiff-backup.1 index 865154d..31f93b5 100644 --- a/rdiff-backup/rdiff-backup.1 +++ b/rdiff-backup/rdiff-backup.1 @@ -134,6 +134,13 @@ Exclude all device files, fifos, sockets, and symlinks. Authorize the updating or overwriting of a destination path. rdiff-backup will generally tell you if it needs this. .TP +.BI "--group-mapping-file " filename +Map group names and ids according the the group mapping file +.IR filename . +See the +.B USERS AND GROUPS +section for more information. +.TP .BI "--include " shell_pattern Similar to .B --exclude @@ -198,10 +205,6 @@ to --remove-older-than. Specifying a subdirectory is allowable; then only the sizes of the mirror and increments pertaining to that subdirectory will be listed. .TP -.B --no-change-dir-inc-perms -Do not change the permissions of the directory increments to match the -directories they represent. -.TP .B --no-compare-inode This relatively esoteric option prevents rdiff-backup from flagging a file as changed when its inode changes. This option may be useful if @@ -338,6 +341,13 @@ Test for the presence of a compatible rdiff-backup server as specified in the following host::filename argument(s). The filename section will be ignored. .TP +.BI "--user-mapping-file " filename +Map user names and ids according to the user mapping file +.IR filename . +See the +.B USERS and GROUPS +section for more information. +.TP .BI -v [0-9] ", --verbosity " [0-9] Specify verbosity level (0 is totally silent, 3 is the default, and 9 is noisiest). This determines how much is written to the log file. @@ -735,8 +745,50 @@ matches any files whose full pathnames contain 7 consecutive digits which aren't followed by 'foo'. However, it wouldn't match /home even if /home/ben/1234567 existed. -.SH STATISTICS +.SH USERS AND GROUPS +There can be complications preserving ownership across systems. For +instance the username that owns a file on the source system may not +exist on the destination. Here is how rdiff-backup maps ownership on +the source to the destination: + +.TP +.B 1. +Attempt to preserve the user and group names for ownership and in +ACLs. This may result in files having different uids and gids across +systems. +.TP +.B 2. +If this fails (e.g. because the username does not exist), preserve the +original id, but only in cases of user and group ownership. For ACLs, +omit any entry that has a bad user or group name. +.TP +.B 3. +However, the +.B --user-mapping-file +and +.B --group-mapping-file +options can override this behavior. If either of these options is +given, the policy descriped in 1 and 2 above will be followed, but +with the mapped user and group instead of the original. + +.RE +The user and group mapping files both have the same form: + +.RS +old_name_or_id1:new_name_or_id1 +.RE +.RS +old_name_or_id2:new_name_or_id2 +.RE +.RS +<etc> +.RE +Each line should contain a name or id, followed by a colon ":", +followed by another name or id. If a name or id is not listed, they +are treated in the default way described above. + +.SH STATISTICS Every session rdiff-backup saves various statistics into two files, the session statistics file at rdiff-backup-data/session_statistics.<time>.data and the directory @@ -766,7 +818,6 @@ The log file is not compressed and can become quite large if rdiff-backup is run with high verbosity. .SH EXIT STATUS - If rdiff-backup finishes successfully, the exit status will be 0. If there is an error, it will be non-zero (usually 1, but don't depend on this specific value). When setting up rdiff-backup to run 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) diff --git a/rdiff-backup/testing/commontest.py b/rdiff-backup/testing/commontest.py index 4cfac90..e80d699 100644 --- a/rdiff-backup/testing/commontest.py +++ b/rdiff-backup/testing/commontest.py @@ -51,11 +51,10 @@ def rdiff_backup(source_local, dest_local, src_dir, dest_dir, """ if not source_local: - src_dir = ("cd test1; ../%s/rdiff-backup --server::../%s" % - (SourceDir, src_dir)) + src_dir = ("'cd test1; ../%s --server'::../%s" % (RBBin, src_dir)) if not dest_local: - dest_dir = ("test2/tmp; ../../%s/rdiff-backup --server::../../%s" % - (SourceDir, dest_dir)) + dest_dir = ("'cd test2/tmp; ../../%s --server'::../../%s" % + (RBBin, dest_dir)) cmdargs = [RBBin, extra_options] if not (source_local and dest_local): cmdargs.append("--remote-schema %s") diff --git a/rdiff-backup/testing/roottest.py b/rdiff-backup/testing/roottest.py index 73fc4fd..176d4ad 100644 --- a/rdiff-backup/testing/roottest.py +++ b/rdiff-backup/testing/roottest.py @@ -14,6 +14,7 @@ Globals.counter = 0 verbosity = 6 log.Log.setverbosity(verbosity) user = 'ben' # Non-root user to su to +userid = 500 # id of user above assert os.getuid() == 0, "Run this test as root!" def Run(cmd): @@ -28,6 +29,50 @@ class RootTest(unittest.TestCase): def testLocal2(self): BackupRestoreSeries(1, 1, self.dirlist2) def testRemote(self): BackupRestoreSeries(None, None, self.dirlist1) + def test_ownership_mapping(self): + """Test --user-mapping-file and --group-mapping-file options""" + def write_ownership_dir(): + """Write the directory testfiles/root_mapping""" + rp = rpath.RPath(Globals.local_connection, + "testfiles/root_mapping") + if rp.lstat(): Myrm(rp.path) + rp.mkdir() + rp1 = rp.append('1') + rp1.touch() + rp2 = rp.append('2') + rp2.touch() + rp2.chown(userid, 1) # use groupid 1, usually bin + return rp + + def write_mapping_files(dir_rp): + """Write user and group mapping files, return paths""" + user_map_rp = dir_rp.append('user_map') + group_map_rp = dir_rp.append('group_map') + user_map_rp.write_string('root:%s\n%s:root' % (user, user)) + group_map_rp.write_string('0:1') + return user_map_rp.path, group_map_rp.path + + def get_ownership(dir_rp): + """Return pair (ids of dir_rp/1, ids of dir_rp2) of ids""" + rp1, rp2 = map(dir_rp.append, ('1', '2')) + assert rp1.isreg() and rp2.isreg(), (rp1.isreg(), rp2.isreg()) + return (rp1.getuidgid(), rp2.getuidgid()) + + in_rp = write_ownership_dir() + user_map, group_map = write_mapping_files(in_rp) + out_rp = rpath.RPath(Globals.local_connection, 'testfiles/output') + if out_rp.lstat(): Myrm(out_rp.path) + + assert get_ownership(in_rp) == ((0,0), (userid, 1)), \ + get_ownership(in_rp) + rdiff_backup(1, 0, in_rp.path, out_rp.path, + extra_options = ("--user-mapping-file %s " + "--group-mapping-file %s" % + (user_map, group_map))) + assert get_ownership(out_rp) == ((userid, 1), (0, 1)), \ + get_ownership(in_rp) + + class HalfRoot(unittest.TestCase): """Backing up files where origin is root and destination is non-root""" def make_dirs(self): |