Commit 3b7e5592 authored by bescoto's avatar bescoto

Changed handling of ownership, added --user/group-mapping-file options


git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup@436 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
parent a89c36c2
New in v0.13.2 (??????????) 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 Specified socket type as SOCK_STREAM. (Error reported by Erik
Forsberg.) Forsberg.)
...@@ -13,9 +16,11 @@ If there is data missing from the destination dir (for instance if a ...@@ -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 user mistakenly deletes it), only warn when restoring, instead of
exiting with error. exiting with error.
Fixed bug in EA/ACL restoring, noticed by Greg Freemyer. Also updated Fixed bug in EA/ACL restoring, noticed by Greg Freemyer.
quoting of filenames and extended attributes names to match
forthcoming attr/facl utilities. 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 Fixed problems with --restrict options that would cause proper
sessions to fail. Thanks to Randall Nortman for error report. sessions to fail. Thanks to Randall Nortman for error report.
...@@ -27,6 +32,9 @@ by Alan Bailward. ...@@ -27,6 +32,9 @@ by Alan Bailward.
File examples.html added to distribution; examples section removed File examples.html added to distribution; examples section removed
from man page. 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) New in v0.13.1 (2003/08/08)
--------------------------- ---------------------------
......
Look at manual page examples.
Consider adding --datadir option (Jean-Sébastien GOETSCHY) Consider adding --datadir option (Jean-Sébastien GOETSCHY)
See if regressing takes too much memory (large directories). See if regressing takes too much memory (large directories).
......
...@@ -116,7 +116,8 @@ def MakeTar(): ...@@ -116,7 +116,8 @@ def MakeTar():
"robust.py", "rorpiter.py", "rpath.py", "robust.py", "rorpiter.py", "rpath.py",
"Security.py", "selection.py", "Security.py", "selection.py",
"SetConnections.py", "static.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" % assert not os.system("cp %s/%s %s/rdiff_backup" %
(SourceDir, filename, tardir)), filename (SourceDir, filename, tardir)), filename
......
...@@ -134,6 +134,13 @@ Exclude all device files, fifos, sockets, and symlinks. ...@@ -134,6 +134,13 @@ Exclude all device files, fifos, sockets, and symlinks.
Authorize the updating or overwriting of a destination path. Authorize the updating or overwriting of a destination path.
rdiff-backup will generally tell you if it needs this. rdiff-backup will generally tell you if it needs this.
.TP .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 .BI "--include " shell_pattern
Similar to Similar to
.B --exclude .B --exclude
...@@ -198,10 +205,6 @@ to --remove-older-than. Specifying a subdirectory is allowable; then ...@@ -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 only the sizes of the mirror and increments pertaining to that
subdirectory will be listed. subdirectory will be listed.
.TP .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 .B --no-compare-inode
This relatively esoteric option prevents rdiff-backup from flagging a This relatively esoteric option prevents rdiff-backup from flagging a
file as changed when its inode changes. This option may be useful if 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 ...@@ -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 in the following host::filename argument(s). The filename section
will be ignored. will be ignored.
.TP .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] .BI -v [0-9] ", --verbosity " [0-9]
Specify verbosity level (0 is totally silent, 3 is the default, and 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. 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 ...@@ -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 which aren't followed by 'foo'. However, it wouldn't match /home even
if /home/ben/1234567 existed. 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, Every session rdiff-backup saves various statistics into two files,
the session statistics file at the session statistics file at
rdiff-backup-data/session_statistics.<time>.data and the directory 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 ...@@ -766,7 +818,6 @@ The log file is not compressed and can become quite large if
rdiff-backup is run with high verbosity. rdiff-backup is run with high verbosity.
.SH EXIT STATUS .SH EXIT STATUS
If rdiff-backup finishes successfully, the exit status will be 0. If 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 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 this specific value). When setting up rdiff-backup to run
......
...@@ -207,11 +207,6 @@ compare_inode = 1 ...@@ -207,11 +207,6 @@ compare_inode = 1
# guarantee that any changes have been committed to disk. # guarantee that any changes have been committed to disk.
fsync_directories = 1 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): def get(name):
"""Return the value of something in this module""" """Return the value of something in this module"""
......
...@@ -32,6 +32,7 @@ remote_cmd, remote_schema = None, None ...@@ -32,6 +32,7 @@ remote_cmd, remote_schema = None, None
force = None force = None
select_opts = [] select_opts = []
select_files = [] select_files = []
user_mapping_filename, group_mapping_filename = None, None
# These are global because they are set while we are trying to figure # These are global because they are set while we are trying to figure
# whether to restore or to backup # whether to restore or to backup
restore_root, restore_index, restore_root_set = None, None, 0 restore_root, restore_index, restore_root_set = None, None, 0
...@@ -40,6 +41,7 @@ def parse_cmdlineoptions(arglist): ...@@ -40,6 +41,7 @@ def parse_cmdlineoptions(arglist):
"""Parse argument list and set global preferences""" """Parse argument list and set global preferences"""
global args, action, force, restore_timestr, remote_cmd, remote_schema global args, action, force, restore_timestr, remote_cmd, remote_schema
global remove_older_than_string global remove_older_than_string
global user_mapping_filename, group_mapping_filename
def sel_fl(filename): def sel_fl(filename):
"""Helper function for including/excluding filelists below""" """Helper function for including/excluding filelists below"""
try: return open(filename, "r") try: return open(filename, "r")
...@@ -51,20 +53,19 @@ def parse_cmdlineoptions(arglist): ...@@ -51,20 +53,19 @@ def parse_cmdlineoptions(arglist):
"exclude-filelist=", "exclude-filelist-stdin", "exclude-filelist=", "exclude-filelist-stdin",
"exclude-globbing-filelist=", "exclude-mirror=", "exclude-globbing-filelist=", "exclude-mirror=",
"exclude-other-filesystems", "exclude-regexp=", "exclude-other-filesystems", "exclude-regexp=",
"exclude-special-files", "force", "include=", "exclude-special-files", "force", "group-mapping-file=",
"include-filelist=", "include-filelist-stdin", "include=", "include-filelist=", "include-filelist-stdin",
"include-globbing-filelist=", "include-regexp=", "include-globbing-filelist=", "include-regexp=",
"list-at-time=", "list-changed-since=", "list-increments", "list-at-time=", "list-changed-since=", "list-increments",
"list-increment-sizes", "no-compare-inode", "list-increment-sizes", "no-compare-inode",
"no-change-dir-inc-perms", "no-compression", "no-compression", "no-compression-regexp=",
"no-compression-regexp=", "no-file-statistics", "no-file-statistics", "no-hard-links", "null-separator",
"no-hard-links", "null-separator",
"override-chars-to-quote=", "parsable-output", "override-chars-to-quote=", "parsable-output",
"print-statistics", "remote-cmd=", "remote-schema=", "print-statistics", "remote-cmd=", "remote-schema=",
"remove-older-than=", "restore-as-of=", "restrict=", "remove-older-than=", "restore-as-of=", "restrict=",
"restrict-read-only=", "restrict-update-only=", "server", "restrict-read-only=", "restrict-update-only=", "server",
"ssh-no-compression", "terminal-verbosity=", "test-server", "ssh-no-compression", "terminal-verbosity=", "test-server",
"verbosity=", "version"]) "user-mapping-file=", "verbosity=", "version"])
except getopt.error, e: except getopt.error, e:
commandline_error("Bad commandline options: %s" % str(e)) commandline_error("Bad commandline options: %s" % str(e))
...@@ -89,6 +90,7 @@ def parse_cmdlineoptions(arglist): ...@@ -89,6 +90,7 @@ def parse_cmdlineoptions(arglist):
opt == "--exclude-regexp" or opt == "--exclude-regexp" or
opt == "--exclude-special-files"): select_opts.append((opt, arg)) opt == "--exclude-special-files"): select_opts.append((opt, arg))
elif opt == "--force": force = 1 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": select_opts.append((opt, arg))
elif opt == "--include-filelist": elif opt == "--include-filelist":
select_opts.append((opt, arg)) select_opts.append((opt, arg))
...@@ -107,8 +109,6 @@ def parse_cmdlineoptions(arglist): ...@@ -107,8 +109,6 @@ def parse_cmdlineoptions(arglist):
elif opt == "-l" or opt == "--list-increments": elif opt == "-l" or opt == "--list-increments":
action = "list-increments" action = "list-increments"
elif opt == '--list-increment-sizes': action = 'list-increment-sizes' 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-compare-inode": Globals.set("compare_inode", 0)
elif opt == "--no-compression": Globals.set("compression", None) elif opt == "--no-compression": Globals.set("compression", None)
elif opt == "--no-compression-regexp": elif opt == "--no-compression-regexp":
...@@ -141,6 +141,7 @@ def parse_cmdlineoptions(arglist): ...@@ -141,6 +141,7 @@ def parse_cmdlineoptions(arglist):
Globals.set('ssh_compression', None) Globals.set('ssh_compression', None)
elif opt == "--terminal-verbosity": Log.setterm_verbosity(arg) elif opt == "--terminal-verbosity": Log.setterm_verbosity(arg)
elif opt == "--test-server": action = "test-server" elif opt == "--test-server": action = "test-server"
elif opt == "--user-mapping-file": user_mapping_filename = arg
elif opt == "-V" or opt == "--version": elif opt == "-V" or opt == "--version":
print "rdiff-backup " + Globals.version print "rdiff-backup " + Globals.version
sys.exit(0) sys.exit(0)
...@@ -190,6 +191,21 @@ def misc_setup(rps): ...@@ -190,6 +191,21 @@ def misc_setup(rps):
conn.robust.install_signal_handlers() conn.robust.install_signal_handlers()
conn.Hardlink.initialize_dictionaries() 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): def take_action(rps):
"""Do whatever action says""" """Do whatever action says"""
if action == "server": if action == "server":
...@@ -237,6 +253,7 @@ def Backup(rpin, rpout): ...@@ -237,6 +253,7 @@ def Backup(rpin, rpout):
backup_set_fs_globals(rpin, rpout) backup_set_fs_globals(rpin, rpout)
if Globals.chars_to_quote: rpout = backup_quoted_rpaths(rpout) if Globals.chars_to_quote: rpout = backup_quoted_rpaths(rpout)
backup_final_init(rpout) backup_final_init(rpout)
init_user_group_mapping(rpout.conn)
backup_set_select(rpin) backup_set_select(rpin)
if prevtime: if prevtime:
rpout.conn.Main.backup_touch_curmirror_local(rpin, rpout) rpout.conn.Main.backup_touch_curmirror_local(rpin, rpout)
...@@ -363,7 +380,6 @@ def backup_set_fs_globals(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_eas', Globals.read_eas and dest_fsa.eas)
update_bool_global('write_resource_forks', update_bool_global('write_resource_forks',
Globals.read_resource_forks and dest_fsa.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) SetConnections.UpdateGlobal('chars_to_quote', dest_fsa.chars_to_quote)
if Globals.chars_to_quote: if Globals.chars_to_quote:
for conn in Globals.connections: for conn in Globals.connections:
...@@ -417,6 +433,7 @@ def Restore(src_rp, dest_rp, restore_as_of = None): ...@@ -417,6 +433,7 @@ def Restore(src_rp, dest_rp, restore_as_of = None):
try: time = Time.genstrtotime(restore_timestr, rp = inc_rpath) try: time = Time.genstrtotime(restore_timestr, rp = inc_rpath)
except Time.TimeException, exc: Log.FatalError(str(exc)) except Time.TimeException, exc: Log.FatalError(str(exc))
else: time = src_rp.getinctime() else: time = src_rp.getinctime()
init_user_group_mapping(dest_rp.conn)
restore_set_select(restore_root, dest_rp) restore_set_select(restore_root, dest_rp)
restore_start_log(src_rp, dest_rp, time) restore_start_log(src_rp, dest_rp, time)
restore.Restore(restore_root.new_index(restore_index), restore.Restore(restore_root.new_index(restore_index),
...@@ -664,6 +681,7 @@ def CheckDest(dest_rp): ...@@ -664,6 +681,7 @@ def CheckDest(dest_rp):
elif need_check == 0: elif need_check == 0:
Log.FatalError("Destination dir %s does not need checking" % Log.FatalError("Destination dir %s does not need checking" %
(dest_rp.path,)) (dest_rp.path,))
init_user_group_mapping(dest_rp.conn)
dest_rp.conn.regress.Regress(dest_rp) dest_rp.conn.regress.Regress(dest_rp)
def checkdest_need_check(dest_rp): def checkdest_need_check(dest_rp):
......
...@@ -519,7 +519,7 @@ import Globals, Time, Rdiff, Hardlink, FilenameMapping, C, Security, \ ...@@ -519,7 +519,7 @@ import Globals, Time, Rdiff, Hardlink, FilenameMapping, C, Security, \
Main, rorpiter, selection, increment, statistics, manage, lazy, \ Main, rorpiter, selection, increment, statistics, manage, lazy, \
iterfile, rpath, robust, restore, manage, backup, connection, \ iterfile, rpath, robust, restore, manage, backup, connection, \
TempFile, SetConnections, librsync, log, regress, fs_abilities, \ TempFile, SetConnections, librsync, log, regress, fs_abilities, \
eas_acls eas_acls, user_group
Globals.local_connection = LocalConnection() Globals.local_connection = LocalConnection()
Globals.connections.append(Globals.local_connection) Globals.connections.append(Globals.local_connection)
......
...@@ -86,17 +86,14 @@ def makediff(new, mirror, incpref): ...@@ -86,17 +86,14 @@ def makediff(new, mirror, incpref):
Rdiff.write_delta(new, mirror, diff, compress) Rdiff.write_delta(new, mirror, diff, compress)
new.chmod(old_new_perms) new.chmod(old_new_perms)
else: Rdiff.write_delta(new, mirror, diff, compress) else: Rdiff.write_delta(new, mirror, diff, compress)
rpath.copy_attribs(mirror, diff) rpath.copy_attribs_inc(mirror, diff)
return diff return diff
def makedir(mirrordir, incpref): def makedir(mirrordir, incpref):
"""Make file indicating directory mirrordir has changed""" """Make file indicating directory mirrordir has changed"""
dirsign = get_inc(incpref, "dir") dirsign = get_inc(incpref, "dir")
dirsign.touch() dirsign.touch()
if Globals.change_dir_inc_perms: rpath.copy_attribs_inc(mirrordir, dirsign)
# 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)
return dirsign return dirsign
def get_inc(rp, typestr, time = None): def get_inc(rp, typestr, time = None):
......
...@@ -107,7 +107,9 @@ def RORP2Record(rorpath): ...@@ -107,7 +107,9 @@ def RORP2Record(rorpath):
# Add user, group, and permission information # Add user, group, and permission information
uid, gid = rorpath.getuidgid() uid, gid = rorpath.getuidgid()
str_list.append(" Uid %s\n" % uid) 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(" Gid %s\n" % gid)
str_list.append(" Gname %s\n" % rorpath.getgname() or ":")
str_list.append(" Permissions %s\n" % rorpath.getperms()) str_list.append(" Permissions %s\n" % rorpath.getperms())
return "".join(str_list) return "".join(str_list)
...@@ -140,6 +142,12 @@ def Record2RORP(record_string): ...@@ -140,6 +142,12 @@ def Record2RORP(record_string):
elif field == "ModTime": data_dict['mtime'] = long(data) elif field == "ModTime": data_dict['mtime'] = long(data)
elif field == "Uid": data_dict['uid'] = int(data) elif field == "Uid": data_dict['uid'] = int(data)
elif field == "Gid": data_dict['gid'] = 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) elif field == "Permissions": data_dict['perms'] = int(data)
else: raise ParsingError("Unknown field in line '%s'" % line) else: raise ParsingError("Unknown field in line '%s'" % line)
return rpath.RORPath(index, data_dict) return rpath.RORPath(index, data_dict)
......
...@@ -36,7 +36,7 @@ are dealing with are local or remote. ...@@ -36,7 +36,7 @@ are dealing with are local or remote.
""" """
import os, stat, re, sys, shutil, gzip, socket, time import os, stat, re, sys, shutil, gzip, socket, time
import Globals, Time, static, log import Globals, Time, static, log, user_group
class SkipFileException(Exception): class SkipFileException(Exception):
...@@ -143,7 +143,7 @@ def cmp(rpin, rpout): ...@@ -143,7 +143,7 @@ def cmp(rpin, rpout):
elif rpin.issock(): return rpout.issock() elif rpin.issock(): return rpout.issock()
else: raise RPathException("File %s has unknown type" % rpin.path) 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 """Change file attributes of rpout to match rpin
Only changes the chmoddable bits, uid/gid ownership, and Only changes the chmoddable bits, uid/gid ownership, and
...@@ -151,14 +151,36 @@ def copy_attribs(rpin, rpout, acls = 1): ...@@ -151,14 +151,36 @@ def copy_attribs(rpin, rpout, acls = 1):
""" """
log.Log("Copying attributes from %s to %s" % (rpin.index, rpout.path), 7) 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) check_for_files(rpin, rpout)
if rpin.issym(): return # symlinks have no valid attributes if rpin.issym(): return # symlinks have no valid attributes
if Globals.write_resource_forks and rpin.isreg() and rpout.isreg(): if Globals.write_resource_forks and rpin.isreg() and rpout.isreg():
rpout.write_resource_fork(rpin.get_resource_fork()) rpout.write_resource_fork(rpin.get_resource_fork())
if Globals.write_eas: rpout.write_ea(rpin.get_ea()) if Globals.write_eas: rpout.write_ea(rpin.get_ea())
if Globals.change_ownership: apply(rpout.chown, rpin.getuidgid()) if Globals.change_ownership: apply(rpout.chown, rpin.getuidgid())
rpout.chmod(rpin.getperms()) if rpin.isdir() and not rpout.isdir():
if Globals.write_acls and acls: rpout.write_acl(rpin.get_acl()) 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()) if not rpin.isdev(): rpout.setmtime(rpin.getmtime())
def cmp_attribs(rp1, rp2): def cmp_attribs(rp1, rp2):
...@@ -265,7 +287,7 @@ class RORPath: ...@@ -265,7 +287,7 @@ class RORPath:
if self.index != other.index: return None if self.index != other.index: return None
for key in self.data.keys(): # compare dicts key by key 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 pass # Don't compare gid/uid for symlinks
elif key == 'atime' and not Globals.preserve_atime: pass elif key == 'atime' and not Globals.preserve_atime: pass
elif key == 'ctime': pass elif key == 'ctime': pass
...@@ -293,11 +315,7 @@ class RORPath: ...@@ -293,11 +315,7 @@ class RORPath:
""" """
for key in self.data.keys(): # compare dicts key by key 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'): pass
(self.issym() or not Globals.change_ownership)):
# Don't compare gid/uid for symlinks, and only root
# can change ownership
pass
elif (key == 'type' and self.isspecial() and elif (key == 'type' and self.isspecial() and
other.isreg() and other.getsize() == 0): other.isreg() and other.getsize() == 0):
pass # Special files may be replaced with empty regular files pass # Special files may be replaced with empty regular files
...@@ -312,6 +330,11 @@ class RORPath: ...@@ -312,6 +330,11 @@ class RORPath:
pass pass
elif (not other.data.has_key(key) or elif (not other.data.has_key(key) or
self.data[key] != other.data[key]): return 0 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 return 1
def equal_verbose(self, other, check_index = 1, def equal_verbose(self, other, check_index = 1,
...@@ -323,7 +346,7 @@ class RORPath: ...@@ -323,7 +346,7 @@ class RORPath:
return None return None
for key in self.data.keys(): # compare dicts key by key 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)): (self.issym() or not compare_ownership)):
# Don't compare gid/uid for symlinks, or if told not to # Don't compare gid/uid for symlinks, or if told not to
pass pass
...@@ -425,6 +448,14 @@ class RORPath: ...@@ -425,6 +448,14 @@ class RORPath:
"""Return permission block of file""" """Return permission block of file"""
return self.data['perms'] 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): def hassize(self):
"""True if rpath has a size parameter""" """True if rpath has a size parameter"""
return self.data.has_key('size') return self.data.has_key('size')
...@@ -620,10 +651,11 @@ class RPath(RORPath): ...@@ -620,10 +651,11 @@ class RPath(RORPath):
def setdata(self): def setdata(self):
"""Set data dictionary using C extension""" """Set data dictionary using C extension"""
self.data = self.conn.C.make_file_dict(self.path) self.data = self.conn.C.make_file_dict(self.path)
if Globals.read_eas and self.lstat(): if not self.lstat(): return
self.data['ea'] = self.conn.rpath.ea_get(self) self.data['uname'] = self.conn.user_group.uid2uname(self.data['uid'])
if Globals.read_acls and self.lstat(): self.data['gname'] = self.conn.user_group.gid2gname(self.data['gid'])
self.data['acl'] = self.conn.rpath.acl_get(self) 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(): if Globals.read_resource_forks and self.isreg():
self.get_resource_fork() self.get_resource_fork()
...@@ -1063,3 +1095,4 @@ class RPathFileHook: ...@@ -1063,3 +1095,4 @@ class RPathFileHook:
# problems. # problems.
def acl_get(rp): assert 0 def acl_get(rp): assert 0
def ea_get(rp): assert 0 def ea_get(rp): assert 0
...@@ -65,7 +65,6 @@ class Map: ...@@ -65,7 +65,6 @@ class Map:
"""Used for mapping names and id on source side to dest side""" """Used for mapping names and id on source side to dest side"""
def __init__(self, name2id_func): def __init__(self, name2id_func):
"""Map initializer, set dictionaries""" """Map initializer, set dictionaries"""
assert Globals.isdest, "Should run on destination connection"
self.name2id_dict = {} self.name2id_dict = {}
self.name2id_func = name2id_func self.name2id_func = name2id_func
...@@ -150,5 +149,9 @@ def init_group_mapping(mapping_string): ...@@ -150,5 +149,9 @@ def init_group_mapping(mapping_string):
else: GroupMap = Map(name2id_func) 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)
...@@ -51,11 +51,10 @@ def rdiff_backup(source_local, dest_local, src_dir, dest_dir, ...@@ -51,11 +51,10 @@ def rdiff_backup(source_local, dest_local, src_dir, dest_dir,
""" """
if not source_local: if not source_local:
src_dir = ("cd test1; ../%s/rdiff-backup --server::../%s" % src_dir = ("'cd test1; ../%s --server'::../%s" % (RBBin, src_dir))
(SourceDir, src_dir))
if not dest_local: if not dest_local:
dest_dir = ("test2/tmp; ../../%s/rdiff-backup --server::../../%s" % dest_dir = ("'cd test2/tmp; ../../%s --server'::../../%s" %
(SourceDir, dest_dir)) (RBBin, dest_dir))
cmdargs = [RBBin, extra_options] cmdargs = [RBBin, extra_options]
if not (source_local and dest_local): cmdargs.append("--remote-schema %s") if not (source_local and dest_local): cmdargs.append("--remote-schema %s")
......
...@@ -14,6 +14,7 @@ Globals.counter = 0 ...@@ -14,6 +14,7 @@ Globals.counter = 0
verbosity = 6 verbosity = 6
log.Log.setverbosity(verbosity) log.Log.setverbosity(verbosity)
user = 'ben' # Non-root user to su to user = 'ben' # Non-root user to su to
userid = 500 # id of user above
assert os.getuid() == 0, "Run this test as root!" assert os.getuid() == 0, "Run this test as root!"
def Run(cmd): def Run(cmd):
...@@ -28,6 +29,50 @@ class RootTest(unittest.TestCase): ...@@ -28,6 +29,50 @@ class RootTest(unittest.TestCase):
def testLocal2(self): BackupRestoreSeries(1, 1, self.dirlist2) def testLocal2(self): BackupRestoreSeries(1, 1, self.dirlist2)
def testRemote(self): BackupRestoreSeries(None, None, self.dirlist1) 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): class HalfRoot(unittest.TestCase):
"""Backing up files where origin is root and destination is non-root""" """Backing up files where origin is root and destination is non-root"""
def make_dirs(self): def make_dirs(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment