summaryrefslogtreecommitdiff
path: root/rdiff-backup/rdiff_backup
diff options
context:
space:
mode:
Diffstat (limited to 'rdiff-backup/rdiff_backup')
-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
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)