2
0
Эх сурвалжийг харах

Merge pull request #943 from enkore/merge/1.0-maint

Merge/1.0 maint
TW 9 жил өмнө
parent
commit
4f507c093b

+ 13 - 11
borg/archive.py

@@ -396,17 +396,6 @@ Number of files: {0.stats.nfiles}'''.format(
             raise Exception('Unknown archive item type %r' % item[b'mode'])
 
     def restore_attrs(self, path, item, symlink=False, fd=None):
-        xattrs = item.get(b'xattrs', {})
-        for k, v in xattrs.items():
-            try:
-                xattr.setxattr(fd or path, k, v, follow_symlinks=False)
-            except OSError as e:
-                if e.errno not in (errno.ENOTSUP, errno.EACCES, ):
-                    # only raise if the errno is not on our ignore list:
-                    # ENOTSUP == xattrs not supported here
-                    # EACCES == permission denied to set this specific xattr
-                    #           (this may happen related to security.* keys)
-                    raise
         uid = gid = None
         if not self.numeric_owner:
             uid = user2uid(item[b'user'])
@@ -444,6 +433,19 @@ Number of files: {0.stats.nfiles}'''.format(
                 os.lchflags(path, item[b'bsdflags'])
             except OSError:
                 pass
+        # chown removes Linux capabilities, so set the extended attributes at the end, after chown, since they include
+        # the Linux capabilities in the "security.capability" attribute.
+        xattrs = item.get(b'xattrs', {})
+        for k, v in xattrs.items():
+            try:
+                xattr.setxattr(fd or path, k, v, follow_symlinks=False)
+            except OSError as e:
+                if e.errno not in (errno.ENOTSUP, errno.EACCES):
+                    # only raise if the errno is not on our ignore list:
+                    # ENOTSUP == xattrs not supported here
+                    # EACCES == permission denied to set this specific xattr
+                    #           (this may happen related to security.* keys)
+                    raise
 
     def set_meta(self, key, value):
         metadata = StableDict(self._load_meta(self.id))

+ 6 - 4
borg/archiver.py

@@ -775,11 +775,13 @@ class Archiver:
     @with_repository()
     def do_prune(self, args, repository, manifest, key):
         """Prune repository archives according to specified rules"""
-        archives = manifest.list_archive_infos(sort_by='ts', reverse=True)  # just a ArchiveInfo list
-        if args.hourly + args.daily + args.weekly + args.monthly + args.yearly == 0 and args.within is None:
-            self.print_error('At least one of the "keep-within", "keep-hourly", "keep-daily", "keep-weekly", '
-                             '"keep-monthly" or "keep-yearly" settings must be specified')
+        if not any((args.hourly, args.daily,
+                    args.weekly, args.monthly, args.yearly, args.within)):
+            self.print_error('At least one of the "keep-within", "keep-last", '
+                             '"keep-hourly", "keep-daily", '
+                             '"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.')
             return self.exit_code
+        archives = manifest.list_archive_infos(sort_by='ts', reverse=True)  # just a ArchiveInfo list
         if args.prefix:
             archives = [archive for archive in archives if archive.name.startswith(args.prefix)]
         keep = []

+ 21 - 0
borg/testsuite/archiver.py

@@ -729,6 +729,27 @@ class ArchiverTestCase(ArchiverTestCaseBase):
                  '--exclude-caches', '--keep-tag-files', self.repository_location + '::test')
         self._assert_test_keep_tagged()
 
+    @pytest.mark.skipif(not xattr.XATTR_FAKEROOT, reason='Linux capabilities test, requires fakeroot >= 1.20.2')
+    def test_extract_capabilities(self):
+        fchown = os.fchown
+
+        # We need to manually patch chown to get the behaviour Linux has, since fakeroot does not
+        # accurately model the interaction of chown(2) and Linux capabilities, i.e. it does not remove them.
+        def patched_fchown(fd, uid, gid):
+            xattr.setxattr(fd, 'security.capability', None, follow_symlinks=False)
+            fchown(fd, uid, gid)
+
+        # The capability descriptor used here is valid and taken from a /usr/bin/ping
+        capabilities = b'\x01\x00\x00\x02\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        self.create_regular_file('file')
+        xattr.setxattr('input/file', 'security.capability', capabilities)
+        self.cmd('init', self.repository_location)
+        self.cmd('create', self.repository_location + '::test', 'input')
+        with changedir('output'):
+            with patch.object(os, 'fchown', patched_fchown):
+                self.cmd('extract', self.repository_location + '::test')
+            assert xattr.getxattr('input/file', 'security.capability') == capabilities
+
     def test_path_normalization(self):
         self.cmd('init', self.repository_location)
         self.create_regular_file('dir1/dir2/file', size=1024 * 80)

+ 18 - 0
borg/xattr.py

@@ -2,10 +2,12 @@
 """
 import errno
 import os
+import subprocess
 import sys
 import tempfile
 from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_int, c_uint32, get_errno
 from ctypes.util import find_library
+from distutils.version import LooseVersion
 
 from .logger import create_logger
 logger = create_logger()
@@ -46,6 +48,22 @@ if libc_name is None:
         logger.error(msg)
         raise Exception(msg)
 
+# If we are running with fakeroot on Linux, then use the xattr functions of fakeroot. This is needed by
+# the 'test_extract_capabilities' test, but also allows xattrs to work with fakeroot on Linux in normal use.
+# TODO: Check whether fakeroot supports xattrs on all platforms supported below.
+# TODO: If that's the case then we can make Borg fakeroot-xattr-compatible on these as well.
+LD_PRELOAD = os.environ.get('LD_PRELOAD', '')
+XATTR_FAKEROOT = False
+if sys.platform.startswith('linux') and 'fakeroot' in LD_PRELOAD:
+    fakeroot_version = LooseVersion(subprocess.check_output(['fakeroot', '-v']).decode('ascii').split()[-1])
+    if fakeroot_version >= LooseVersion("1.20.2"):
+        # 1.20.2 has been confirmed to have xattr support
+        # 1.18.2 has been confirmed not to have xattr support
+        # Versions in-between are unknown
+        libc_name = LD_PRELOAD
+        XATTR_FAKEROOT = True
+
+
 try:
     libc = CDLL(libc_name, use_errno=True)
 except OSError as e: