summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-09-15 03:00:11 +0000
committerbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-09-15 03:00:11 +0000
commitad458a91dc96e7ba50bff6a45205db69b6befa41 (patch)
tree7cb15a9aeff8a5a5558d40596902ef003ceb5ba4
parent5137df996ccc2faf82a473b16e45d4a371a183f9 (diff)
downloadrdiff-backup-ad458a91dc96e7ba50bff6a45205db69b6befa41.tar.gz
Changed handling of ownership, added --user/group-mapping-file options
git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@436 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
-rw-r--r--rdiff-backup/CHANGELOG14
-rw-r--r--rdiff-backup/TODO2
-rwxr-xr-xrdiff-backup/dist/makedist3
-rw-r--r--rdiff-backup/rdiff-backup.163
-rw-r--r--rdiff-backup/rdiff_backup/Globals.py5
-rw-r--r--rdiff-backup/rdiff_backup/Main.py36
-rw-r--r--rdiff-backup/rdiff_backup/connection.py2
-rw-r--r--rdiff-backup/rdiff_backup/increment.py7
-rw-r--r--rdiff-backup/rdiff_backup/metadata.py8
-rw-r--r--rdiff-backup/rdiff_backup/rpath.py63
-rw-r--r--rdiff-backup/rdiff_backup/user_group.py9
-rw-r--r--rdiff-backup/testing/commontest.py7
-rw-r--r--rdiff-backup/testing/roottest.py45
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):