Commit d3585235 authored by bescoto's avatar bescoto

Added --never-drop-acls, ACL entry dropping. Final for 0.13.2


git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup@442 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
parent 5f0f4516
New in v0.13.2 (??????????) New in v0.13.2 (2003/09/16)
--------------------------- ---------------------------
Change ownership policy and added --user-mapping-file and Change ownership policy and added --user-mapping-file and
--group-mapping-file switches. See man page for more information. --group-mapping-file switches. See man page for more information.
Added option --never-drop-acls to cause fatal error instead of
dropping any acls or acl entries. Thanks to Greg Freemyer for
suggestion.
Specified socket type as SOCK_STREAM. (Error reported by Erik Specified socket type as SOCK_STREAM. (Error reported by Erik
Forsberg.) Forsberg.)
......
...@@ -10,8 +10,6 @@ by Andrew Bressen. ...@@ -10,8 +10,6 @@ by Andrew Bressen.
Profile 0.13.x Profile 0.13.x
Look at EAs when unames change.
Examine regress handling of acls/eas/resource forks. Examine regress handling of acls/eas/resource forks.
Look into that strange regress error July 22nd on Look into that strange regress error July 22nd on
......
...@@ -205,6 +205,12 @@ to --remove-older-than. Specifying a subdirectory is allowable; then ...@@ -205,6 +205,12 @@ 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 --never-drop-acls
Exit with error instead of dropping acls or acl entries. Normally
this may happen (with a warning) because the destination does not
support them or because the relevant user/group names do not exist on
the destination side.
.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
......
...@@ -207,6 +207,9 @@ compare_inode = 1 ...@@ -207,6 +207,9 @@ 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, exit with error instead of dropping ACLs or ACL entries.
never_drop_acls = None
def get(name): def get(name):
"""Return the value of something in this module""" """Return the value of something in this module"""
......
...@@ -57,9 +57,10 @@ def parse_cmdlineoptions(arglist): ...@@ -57,9 +57,10 @@ def parse_cmdlineoptions(arglist):
"include=", "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", "never-drop-acls",
"no-compression", "no-compression-regexp=", "no-compare-inode", "no-compression",
"no-file-statistics", "no-hard-links", "null-separator", "no-compression-regexp=", "no-file-statistics",
"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=",
...@@ -109,6 +110,7 @@ def parse_cmdlineoptions(arglist): ...@@ -109,6 +110,7 @@ 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 == "--never-drop-acls": Globals.set("never_drop_acls", 1)
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":
...@@ -368,6 +370,9 @@ def backup_set_fs_globals(rpin, rpout): ...@@ -368,6 +370,9 @@ def backup_set_fs_globals(rpin, rpout):
dest_fsa = rpout.conn.fs_abilities.get_fsabilities_readwrite( dest_fsa = rpout.conn.fs_abilities.get_fsabilities_readwrite(
'destination', Globals.rbdir, 1, Globals.chars_to_quote) 'destination', Globals.rbdir, 1, Globals.chars_to_quote)
Log(str(dest_fsa), 3) Log(str(dest_fsa), 3)
if Globals.never_drop_acls and not dest_fsa.acls:
Log.FatalError("--never-drop-acls specified, but ACL support\n"
"disabled on destination filesystem")
update_bool_global('read_acls', src_fsa.acls) update_bool_global('read_acls', src_fsa.acls)
update_bool_global('read_eas', src_fsa.eas) update_bool_global('read_eas', src_fsa.eas)
...@@ -462,6 +467,9 @@ def restore_set_fs_globals(target): ...@@ -462,6 +467,9 @@ def restore_set_fs_globals(target):
mirror_fsa = Globals.rbdir.conn.fs_abilities.get_fsabilities_restoresource( mirror_fsa = Globals.rbdir.conn.fs_abilities.get_fsabilities_restoresource(
Globals.rbdir) Globals.rbdir)
Log(str(mirror_fsa), 3) Log(str(mirror_fsa), 3)
if Globals.never_drop_acls and not target_fsa.acls:
Log.FatalError("--never-drop-acls specified, but ACL support\n"
"disabled on destination filesystem")
update_bool_global('read_acls', target_fsa.acls) update_bool_global('read_acls', target_fsa.acls)
update_bool_global('write_acls', target_fsa.acls) update_bool_global('write_acls', target_fsa.acls)
......
...@@ -33,6 +33,11 @@ except ImportError: pass ...@@ -33,6 +33,11 @@ except ImportError: pass
import static, Globals, metadata, connection, rorpiter, log, C, \ import static, Globals, metadata, connection, rorpiter, log, C, \
rpath, user_group rpath, user_group
# When an ACL gets dropped, put name in dropped_acl_names. This is
# only used so that only the first dropped ACL for any given name
# triggers a warning.
dropped_acl_names = {}
class ExtendedAttributes: class ExtendedAttributes:
"""Hold a file's extended attribute information""" """Hold a file's extended attribute information"""
def __init__(self, index, attr_dict = None): def __init__(self, index, attr_dict = None):
...@@ -288,6 +293,7 @@ class AccessControlLists: ...@@ -288,6 +293,7 @@ class AccessControlLists:
""" """
assert isinstance(acl, self.__class__) assert isinstance(acl, self.__class__)
if self.index != acl.index: return 0 if self.index != acl.index: return 0
if self.is_basic(): return acl.is_basic()
return (self.cmp_entry_list(self.entry_list, acl.entry_list) and return (self.cmp_entry_list(self.entry_list, acl.entry_list) and
self.cmp_entry_list(self.default_entry_list, self.cmp_entry_list(self.default_entry_list,
acl.default_entry_list)) acl.default_entry_list))
...@@ -327,21 +333,23 @@ class AccessControlLists: ...@@ -327,21 +333,23 @@ class AccessControlLists:
self.entry_list, self.default_entry_list = \ self.entry_list, self.default_entry_list = \
rp.conn.eas_acls.get_acl_lists_from_rp(rp) rp.conn.eas_acls.get_acl_lists_from_rp(rp)
def write_to_rp(self, rp): def write_to_rp(self, rp, map_names = 1):
"""Write current access control list to RPath rp""" """Write current access control list to RPath rp"""
rp.conn.eas_acls.set_rp_acl(rp, self.entry_list, rp.conn.eas_acls.set_rp_acl(rp, self.entry_list,
self.default_entry_list) self.default_entry_list, map_names)
def set_rp_acl(rp, entry_list = None, default_entry_list = None): def set_rp_acl(rp, entry_list = None, default_entry_list = None,
map_names = 1):
"""Set given rp with ACL that acl_text defines. rp should be local""" """Set given rp with ACL that acl_text defines. rp should be local"""
assert rp.conn is Globals.local_connection assert rp.conn is Globals.local_connection
if entry_list: acl = list_to_acl(entry_list) if entry_list: acl = list_to_acl(entry_list, map_names)
else: acl = posix1e.ACL() else: acl = posix1e.ACL()
acl.applyto(rp.path) acl.applyto(rp.path)
if rp.isdir(): if rp.isdir():
if default_entry_list: def_acl = list_to_acl(default_entry_list) if default_entry_list:
def_acl = list_to_acl(default_entry_list, map_names)
else: def_acl = posix1e.ACL() else: def_acl = posix1e.ACL()
def_acl.applyto(rp.path, posix1e.ACL_TYPE_DEFAULT) def_acl.applyto(rp.path, posix1e.ACL_TYPE_DEFAULT)
...@@ -388,18 +396,55 @@ def acl_to_list(acl): ...@@ -388,18 +396,55 @@ def acl_to_list(acl):
return (entry.tag_type, owner_pair, perms) return (entry.tag_type, owner_pair, perms)
return map(entry_to_tuple, acl) return map(entry_to_tuple, acl)
def list_to_acl(entry_list): def list_to_acl(entry_list, map_names = 1):
"""Return posix1e.ACL object from list representation""" """Return posix1e.ACL object from list representation
If map_names is true, use user_group to update the names for the
current system, and drop if not available. Otherwise just use the
same id.
"""
def warn_drop(name):
"""Warn about acl with name getting dropped"""
global dropped_acl_names
if Globals.never_drop_acls:
log.Log.FatalError(
"--never-drop-acls specified but cannot map name\n"
"%s occurring inside an ACL." % (name,))
if dropped_acl_names.has_key(name): return
log.Log("Warning: name %s not found on system, dropping ACL entry.\n"
"Further ACL entries dropped with this name will not "
"trigger further warnings" % (name,), 2)
dropped_acl_names[name] = name
def map_id_name(owner_pair, group = None):
"""Return id of mapped id and user given original owner_pair"""
id, name = owner_pair
Map = group and user_group.GroupMap or user_group.UserMap
if name: return Map.get_id_from_name(name)
else:
assert id is not None
return Map.get_id_from_id(id)
acl = posix1e.ACL() acl = posix1e.ACL()
for tag, owner_pair, perms in entry_list: for tag, owner_pair, perms in entry_list:
entry = posix1e.Entry(acl) id = None
entry.tag_type = tag
if owner_pair: if owner_pair:
if tag == posix1e.ACL_USER: if map_names:
entry.qualifier = user_group.UserMap.get_id(*owner_pair) if tag == posix1e.ACL_USER: id = map_id_name(owner_pair, 0)
else:
assert tag == posix1e.ACL_GROUP, (tag, owner_pair, perms)
id = map_id_name(owner_pair, 1)
if id is None:
warn_drop(owner_pair[1])
continue
else: else:
assert tag == posix1e.ACL_GROUP, (tag, owner_pair, perms) assert owner_pair[0] is not None, (tag, owner_pair, perms)
entry.qualifier = user_group.GroupMap.get_id(*owner_pair) id = owner_pair[0]
entry = posix1e.Entry(acl)
entry.tag_type = tag
if id is not None: entry.qualifier = id
entry.permset.read = perms >> 2 entry.permset.read = perms >> 2
entry.permset.write = perms >> 1 & 1 entry.permset.write = perms >> 1 & 1
entry.permset.execute = perms & 1 entry.permset.execute = perms & 1
......
...@@ -179,8 +179,7 @@ def copy_attribs_inc(rpin, rpout): ...@@ -179,8 +179,7 @@ def copy_attribs_inc(rpin, rpout):
if rpin.isdir() and not rpout.isdir(): if rpin.isdir() and not rpout.isdir():
rpout.chmod(rpin.getperms() & 0777) rpout.chmod(rpin.getperms() & 0777)
else: rpout.chmod(rpin.getperms()) else: rpout.chmod(rpin.getperms())
if Globals.write_acls and not (rpin.isdir() and not rpout.isdir()): if Globals.write_acls: rpout.write_acl(rpin.get_acl(), map_names = 0)
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):
...@@ -302,7 +301,8 @@ class RORPath: ...@@ -302,7 +301,8 @@ class RORPath:
not Globals.compare_inode or not Globals.compare_inode or
not Globals.preserve_hardlinks)): pass not Globals.preserve_hardlinks)): pass
elif (not other.data.has_key(key) or elif (not other.data.has_key(key) or
self.data[key] != other.data[key]): return None self.data[key] != other.data[key]):
return None
return 1 return 1
def equal_loose(self, other): def equal_loose(self, other):
...@@ -1035,9 +1035,13 @@ class RPath(RORPath): ...@@ -1035,9 +1035,13 @@ class RPath(RORPath):
except KeyError: acl = self.data['acl'] = acl_get(self) except KeyError: acl = self.data['acl'] = acl_get(self)
return acl return acl
def write_acl(self, acl): def write_acl(self, acl, map_names = 1):
"""Change access control list of rp""" """Change access control list of rp
acl.write_to_rp(self)
If map_names is true, map the ids in acl by user/group names.
"""
acl.write_to_rp(self, map_names)
self.data['acl'] = acl self.data['acl'] = acl
def get_ea(self): def get_ea(self):
......
...@@ -60,6 +60,16 @@ def gid2gname(gid): ...@@ -60,6 +60,16 @@ def gid2gname(gid):
gid2gname_dict[gid] = gname gid2gname_dict[gid] = gname
return gname return gname
def uname2uid(uname):
"""Given uname, return uid or None if cannot find"""
try: uname = pwd.getpwnam(uname)[2]
except KeyError: return None
def gname2gid(gname):
"""Given gname, return gid or None if cannot find"""
try: gname = grp.getgrnam(gname)[2]
except KeyError: return None
class Map: 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"""
...@@ -77,12 +87,25 @@ class Map: ...@@ -77,12 +87,25 @@ class Map:
self.name2id_dict[name] = out_id self.name2id_dict[name] = out_id
return out_id return out_id
def get_id_from_name(self, name):
"""Return mapped id from name only, or None if cannot"""
try: return self.name2id_dict[name]
except KeyError:
out_id = self.find_id_from_name(name)
self.name2id_dict[name] = out_id
return out_id
def get_id_from_id(self, id): return id def get_id_from_id(self, id): return id
def find_id(self, id, name): def find_id(self, id, name):
"""Find the proper id to use with given id and name""" """Find the proper id to use with given id and name"""
try: return self.name2id_func(name) try: return self.name2id_func(name)
except KeyError: return id except KeyError: return id
def find_id_from_name(self, name):
"""Look up proper id to use with name, or None"""
try: return self.name2id_func(name)
except KeyError: return None
class DefinedMap(Map): class DefinedMap(Map):
"""Map names and ids on source side to appropriate ids on dest side """Map names and ids on source side to appropriate ids on dest side
...@@ -134,6 +157,11 @@ class DefinedMap(Map): ...@@ -134,6 +157,11 @@ class DefinedMap(Map):
try: return self.id_mapping_dict[id] try: return self.id_mapping_dict[id]
except KeyError: return Map.find_id(self, id, name) except KeyError: return Map.find_id(self, id, name)
def find_id_from_name(self, name):
"""Find id to map name to, or None if we can't"""
try: return self.name_mapping_dict[name]
except KeyError: return Map.find_id_from_name(name)
def init_user_mapping(mapping_string = None): def init_user_mapping(mapping_string = None):
"""Initialize user mapping with given mapping string or None""" """Initialize user mapping with given mapping string or None"""
global UserMap global UserMap
......
import unittest, os, time, cStringIO import unittest, os, time, cStringIO, posix1e, pwd, grp
from commontest import * from commontest import *
from rdiff_backup.eas_acls import * from rdiff_backup.eas_acls import *
from rdiff_backup import Globals, rpath, Time, user_group from rdiff_backup import Globals, rpath, Time, user_group
...@@ -422,6 +422,75 @@ other::--- ...@@ -422,6 +422,75 @@ other::---
assert CompareRecursive(self.acl_testdir2, restore_dir, assert CompareRecursive(self.acl_testdir2, restore_dir,
compare_acls = 1) compare_acls = 1)
def test_acl_mapping(self):
"""Test mapping ACL names"""
def make_dir(rootrp):
if rootrp.lstat(): rootrp.delete()
rootrp.mkdir()
rp = rootrp.append('1')
rp.touch()
acl = AccessControlLists(('1',), """user::rwx
user:root:rwx
user:ben:---
user:bin:r--
group::r-x
group:root:r-x
group:ben:-w-
mask::r-x
other::---""")
rp.write_acl(acl)
return rp
def write_mapping_file(rootrp):
map_rp = rootrp.append('mapping_file')
map_rp.write_string("root:ben\nben:bin\nbin:root")
return map_rp
def get_perms_of_user(acl, user):
"""Return the permissions of ACL_USER in acl, or None"""
for type, owner_pair, perms in acl.entry_list:
if type == posix1e.ACL_USER and owner_pair[1] == user:
return perms
return None
self.make_temp()
rootrp = rpath.RPath(Globals.local_connection,
'testfiles/acl_map_test')
rp = make_dir(rootrp)
map_rp = write_mapping_file(rootrp)
rdiff_backup(1, 1, rootrp.path, tempdir.path,
extra_options = "--user-mapping-file %s" % (map_rp.path,))
out_rp = tempdir.append('1')
assert out_rp.isreg()
out_acl = tempdir.append('1').get_acl()
assert get_perms_of_user(out_acl, 'root') == 4
assert get_perms_of_user(out_acl, 'ben') == 7
assert get_perms_of_user(out_acl, 'bin') == 0
def test_acl_dropping(self):
"""Test dropping of ACL names"""
self.make_temp()
rp = tempdir.append('1')
rp.touch()
acl = AccessControlLists(('1',), """user::rwx
user:aoensutheu:r--
group::r-x
group:aeuai:r-x
group:enutohnh:-w-
other::---""")
rp.write_acl(acl)
rp2 = tempdir.append('1')
acl2 = AccessControlLists(('1',))
acl2.read_from_rp(rp2)
assert acl2.is_basic()
Globals.never_drop_acls = 1
try: rp.write_acl(acl)
except SystemExit: pass
else: assert 0, "Above should have exited with fatal error"
Globals.never_drop_acls = None
class CombinedTest(unittest.TestCase): class CombinedTest(unittest.TestCase):
"""Test backing up and restoring directories with both EAs and ACLs""" """Test backing up and restoring directories with both EAs and ACLs"""
......
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