# Public License as described in the bup LICENSE file.
from __future__ import absolute_import, print_function
-from binascii import hexlify
from copy import deepcopy
from errno import EACCES, EINVAL, ENOTTY, ENOSYS, EOPNOTSUPP
from io import BytesIO
from time import gmtime, strftime
-import errno, os, sys, stat, time, pwd, grp, socket, struct
+import errno, os, sys, stat, time, socket, struct
-from bup import compat, vint, xstat
-from bup.compat import py_maj
+from bup import vint, xstat
from bup.drecurse import recursive_dirlist
from bup.helpers import add_error, mkdirp, log, is_superuser, format_filesize
from bup.io import path_msg
log('Warning: python-xattr module is too old; '
'upgrade or install python-pyxattr instead.\n')
-posix1e = None
-if not (sys.platform.startswith('cygwin') \
- or sys.platform.startswith('darwin') \
- or sys.platform.startswith('netbsd')):
- try:
- import posix1e
- except ImportError:
- log('Warning: POSIX ACL support missing; install python-pylibacl.\n')
+try:
+ from bup._helpers import read_acl, apply_acl
+except ImportError:
+ read_acl = apply_acl = None
try:
from bup._helpers import get_linux_file_attr, set_linux_file_attr
if _have_lchmod:
try:
os.lchmod(path, stat.S_IMODE(self.mode))
- except errno.ENOSYS: # Function not implemented
- pass
+ except OSError as e:
+ # - "Function not implemented"
+ # - "Operation not supported" might be generated by glibc
+ if e.errno in (errno.ENOSYS, errno.EOPNOTSUPP):
+ pass
+ else:
+ raise
elif not stat.S_ISLNK(self.mode):
os.chmod(path, stat.S_IMODE(self.mode))
try:
if stat.S_ISLNK(st.st_mode):
self.symlink_target = os.readlink(path)
+ # might have read a different link than the
+ # one that was in place when we did stat()
+ self.size = len(self.symlink_target)
except OSError as e:
add_error('readlink: %s' % e)
# The numeric/text distinction only matters when reading/restoring
# a stored record.
def _add_posix1e_acl(self, path, st):
- if not posix1e or not posix1e.HAS_EXTENDED_CHECK:
+ if not read_acl:
return
if not stat.S_ISLNK(st.st_mode):
- acls = None
- def_acls = None
- try:
- if posix1e.has_extended(path):
- acl = posix1e.ACL(file=path)
- acls = [acl, acl] # txt and num are the same
- if stat.S_ISDIR(st.st_mode):
- def_acl = posix1e.ACL(filedef=(path if py_maj < 3
- else path.decode('iso-8859-1')))
- def_acls = [def_acl, def_acl]
- except EnvironmentError as e:
- if e.errno not in (errno.EOPNOTSUPP, errno.ENOSYS):
- raise
- if acls:
- txt_flags = posix1e.TEXT_ABBREVIATE
- num_flags = posix1e.TEXT_ABBREVIATE | posix1e.TEXT_NUMERIC_IDS
- acl_rep = [acls[0].to_any_text('', b'\n', txt_flags),
- acls[1].to_any_text('', b'\n', num_flags)]
- if def_acls:
- acl_rep.append(def_acls[0].to_any_text('', b'\n', txt_flags))
- acl_rep.append(def_acls[1].to_any_text('', b'\n', num_flags))
- self.posix1e_acl = acl_rep
+ isdir = 1 if stat.S_ISDIR(st.st_mode) else 0
+ self.posix1e_acl = read_acl(path, isdir)
def _same_posix1e_acl(self, other):
"""Return true or false to indicate similarity in the hardlink sense."""
self.posix1e_acl = acl_rep
def _apply_posix1e_acl_rec(self, path, restore_numeric_ids=False):
- def apply_acl(acl_rep, kind):
- try:
- acl = posix1e.ACL(text=acl_rep.decode('ascii'))
- except IOError as e:
- if e.errno == 0:
- # pylibacl appears to return an IOError with errno
- # set to 0 if a group referred to by the ACL rep
- # doesn't exist on the current system.
- raise ApplyError("POSIX1e ACL: can't create %r for %r"
- % (acl_rep, path_msg(path)))
- else:
- raise
- try:
- acl.applyto(path, kind)
- except IOError as e:
- if e.errno == errno.EPERM or e.errno == errno.EOPNOTSUPP:
- raise ApplyError('POSIX1e ACL applyto: %s' % e)
- else:
- raise
+ if not self.posix1e_acl:
+ return
- if not posix1e:
- if self.posix1e_acl:
- add_error("%s: can't restore ACLs; posix1e support missing.\n"
- % path_msg(path))
+ if not apply_acl:
+ add_error("%s: can't restore ACLs; posix1e support missing.\n"
+ % path_msg(path))
return
- if self.posix1e_acl:
+
+ try:
acls = self.posix1e_acl
+ offs = 1 if restore_numeric_ids else 0
if len(acls) > 2:
- if restore_numeric_ids:
- apply_acl(acls[3], posix1e.ACL_TYPE_DEFAULT)
- else:
- apply_acl(acls[2], posix1e.ACL_TYPE_DEFAULT)
- if restore_numeric_ids:
- apply_acl(acls[1], posix1e.ACL_TYPE_ACCESS)
+ apply_acl(path, acls[offs], acls[offs + 2])
else:
- apply_acl(acls[0], posix1e.ACL_TYPE_ACCESS)
+ apply_acl(path, acls[offs])
+ except IOError as e:
+ if e.errno == errno.EINVAL:
+ # libacl returns with errno set to EINVAL if a user
+ # (or group) doesn't exist
+ raise ApplyError("POSIX1e ACL: can't create %r for %r"
+ % (acls, path_msg(path)))
+ elif e.errno == errno.EPERM or e.errno == errno.EOPNOTSUPP:
+ raise ApplyError('POSIX1e ACL applyto: %s' % e)
+ else:
+ raise
## Linux attributes (lsattr(1), chattr(1))
return ''.join(result)
def write(self, port, include_path=True):
+ port.write(self.encode(include_path=include_path))
+
+ def encode(self, include_path=True):
+ ret = []
records = include_path and [(_rec_tag_path, self._encode_path())] or []
records.extend([(_rec_tag_common_v3, self._encode_common()),
(_rec_tag_symlink_target,
(_rec_tag_linux_xattr, self._encode_linux_xattr())])
for tag, data in records:
if data:
- vint.write_vuint(port, tag)
- vint.write_bvec(port, data)
- vint.write_vuint(port, _rec_tag_end)
-
- def encode(self, include_path=True):
- port = BytesIO()
- self.write(port, include_path)
- return port.getvalue()
+ ret.extend((vint.encode_vuint(tag),
+ vint.encode_bvec(data)))
+ ret.append(vint.encode_vuint(_rec_tag_end))
+ return b''.join(ret)
def copy(self):
return deepcopy(self)
def from_path(path, statinfo=None, archive_path=None,
save_symlinks=True, hardlink_target=None,
- normalized=False):
+ normalized=False, after_stat=None):
+ # This function is also a test hook; see test-save-errors
"""Return the metadata associated with the path. When normalized is
true, return the metadata appropriate for a typical save, which
may or may not be all of it."""
result = Metadata()
result.path = archive_path
st = statinfo or xstat.lstat(path)
+ if after_stat:
+ after_stat(path)
result._add_common(path, st)
if save_symlinks:
result._add_symlink_target(path, st)