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
--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
Forsberg.)
......
......@@ -10,8 +10,6 @@ by Andrew Bressen.
Profile 0.13.x
Look at EAs when unames change.
Examine regress handling of acls/eas/resource forks.
Look into that strange regress error July 22nd on
......
......@@ -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
subdirectory will be listed.
.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
This relatively esoteric option prevents rdiff-backup from flagging a
file as changed when its inode changes. This option may be useful if
......
......@@ -207,6 +207,9 @@ compare_inode = 1
# guarantee that any changes have been committed to disk.
fsync_directories = 1
# If set, exit with error instead of dropping ACLs or ACL entries.
never_drop_acls = None
def get(name):
"""Return the value of something in this module"""
......
......@@ -57,9 +57,10 @@ def parse_cmdlineoptions(arglist):
"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-compression", "no-compression-regexp=",
"no-file-statistics", "no-hard-links", "null-separator",
"list-increment-sizes", "never-drop-acls",
"no-compare-inode", "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=",
......@@ -109,6 +110,7 @@ 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 == "--never-drop-acls": Globals.set("never_drop_acls", 1)
elif opt == "--no-compare-inode": Globals.set("compare_inode", 0)
elif opt == "--no-compression": Globals.set("compression", None)
elif opt == "--no-compression-regexp":
......@@ -368,6 +370,9 @@ def backup_set_fs_globals(rpin, rpout):
dest_fsa = rpout.conn.fs_abilities.get_fsabilities_readwrite(
'destination', Globals.rbdir, 1, Globals.chars_to_quote)
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_eas', src_fsa.eas)
......@@ -462,6 +467,9 @@ def restore_set_fs_globals(target):
mirror_fsa = Globals.rbdir.conn.fs_abilities.get_fsabilities_restoresource(
Globals.rbdir)
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('write_acls', target_fsa.acls)
......
......@@ -33,6 +33,11 @@ except ImportError: pass
import static, Globals, metadata, connection, rorpiter, log, C, \
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:
"""Hold a file's extended attribute information"""
def __init__(self, index, attr_dict = None):
......@@ -288,6 +293,7 @@ class AccessControlLists:
"""
assert isinstance(acl, self.__class__)
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
self.cmp_entry_list(self.default_entry_list,
acl.default_entry_list))
......@@ -327,21 +333,23 @@ class AccessControlLists:
self.entry_list, self.default_entry_list = \
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"""
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"""
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()
acl.applyto(rp.path)
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()
def_acl.applyto(rp.path, posix1e.ACL_TYPE_DEFAULT)
......@@ -388,18 +396,55 @@ def acl_to_list(acl):
return (entry.tag_type, owner_pair, perms)
return map(entry_to_tuple, acl)
def list_to_acl(entry_list):
"""Return posix1e.ACL object from list representation"""
def list_to_acl(entry_list, map_names = 1):
"""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()
for tag, owner_pair, perms in entry_list:
entry = posix1e.Entry(acl)
entry.tag_type = tag
id = None
if owner_pair:
if tag == posix1e.ACL_USER:
entry.qualifier = user_group.UserMap.get_id(*owner_pair)
if map_names:
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:
assert tag == posix1e.ACL_GROUP, (tag, owner_pair, perms)
entry.qualifier = user_group.GroupMap.get_id(*owner_pair)
assert owner_pair[0] is not None, (tag, owner_pair, perms)
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.write = perms >> 1 & 1
entry.permset.execute = perms & 1
......
......@@ -179,8 +179,7 @@ def copy_attribs_inc(rpin, rpout):
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 Globals.write_acls: rpout.write_acl(rpin.get_acl(), map_names = 0)
if not rpin.isdev(): rpout.setmtime(rpin.getmtime())
def cmp_attribs(rp1, rp2):
......@@ -302,7 +301,8 @@ class RORPath:
not Globals.compare_inode or
not Globals.preserve_hardlinks)): pass
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
def equal_loose(self, other):
......@@ -1035,9 +1035,13 @@ class RPath(RORPath):
except KeyError: acl = self.data['acl'] = acl_get(self)
return acl
def write_acl(self, acl):
"""Change access control list of rp"""
acl.write_to_rp(self)
def write_acl(self, acl, map_names = 1):
"""Change access control list of rp
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
def get_ea(self):
......
......@@ -60,6 +60,16 @@ def gid2gname(gid):
gid2gname_dict[gid] = 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:
"""Used for mapping names and id on source side to dest side"""
......@@ -77,12 +87,25 @@ class Map:
self.name2id_dict[name] = 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 find_id(self, id, name):
"""Find the proper id to use with given id and name"""
try: return self.name2id_func(name)
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):
"""Map names and ids on source side to appropriate ids on dest side
......@@ -134,6 +157,11 @@ class DefinedMap(Map):
try: return self.id_mapping_dict[id]
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):
"""Initialize user mapping with given mapping string or None"""
global UserMap
......
import unittest, os, time, cStringIO
import unittest, os, time, cStringIO, posix1e, pwd, grp
from commontest import *
from rdiff_backup.eas_acls import *
from rdiff_backup import Globals, rpath, Time, user_group
......@@ -422,6 +422,75 @@ other::---
assert CompareRecursive(self.acl_testdir2, restore_dir,
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):
"""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