소스 검색

xattr: implement set_all to complement get_all

also: follow_symlinks param defaults to False (we do never use True)

fix tests, xattrs are set via FD now.
Thomas Waldmann 7 년 전
부모
커밋
394d59e6d8
3개의 변경된 파일56개의 추가작업 그리고 34개의 파일을 삭제
  1. 3 29
      src/borg/archive.py
  2. 3 4
      src/borg/testsuite/archiver.py
  3. 50 1
      src/borg/xattr.py

+ 3 - 29
src/borg/archive.py

@@ -1,4 +1,3 @@
-import errno
 import json
 import os
 import socket
@@ -674,7 +673,6 @@ Utilization of max. archive size: {csize_max:.0%}
         Does not access the repository.
         """
         backup_io.op = 'attrs'
-        path_bytes = os.fsencode(path)
         uid = gid = None
         if not self.numeric_owner:
             uid = user2uid(item.user)
@@ -724,33 +722,9 @@ Utilization of max. archive size: {csize_max:.0%}
         acl_set(path, item, self.numeric_owner)
         # 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('xattrs', {})
-        for k, v in xattrs.items():
-            try:
-                # the key k is a bytes object due to msgpack unpacking it as such.
-                # if we have a None value, it means "empty", so give b'' to setxattr in that case:
-                xattr.setxattr(fd or path_bytes, k, v or b'', follow_symlinks=False)
-            except OSError as e:
-                k_str = k.decode()
-                if e.errno == errno.E2BIG:
-                    logger.warning('%s: Value or key of extended attribute %s is too big for this filesystem' %
-                                   (path, k_str))
-                    set_ec(EXIT_WARNING)
-                elif e.errno == errno.ENOTSUP:
-                    logger.warning('%s: Extended attributes are not supported on this filesystem' % path)
-                    set_ec(EXIT_WARNING)
-                elif e.errno == errno.EACCES:
-                    # permission denied to set this specific xattr (this may happen related to security.* keys)
-                    logger.warning('%s: Permission denied when setting extended attribute %s' % (path, k_str))
-                    set_ec(EXIT_WARNING)
-                elif e.errno == errno.ENOSPC:
-                    # ext4 reports ENOSPC when trying to set an xattr with >4kiB while ext4 can only support 4kiB xattrs
-                    # (in this case, this is NOT a "disk full" error, just a ext4 limitation).
-                    logger.warning('%s: No space left on device while setting extended attribute %s (len = %d)' % (
-                        path, k_str, len(v)))
-                    set_ec(EXIT_WARNING)
-                else:
-                    raise
+        warning = xattr.set_all(fd or path, item.get('xattrs', {}), follow_symlinks=False)
+        if warning:
+            set_ec(EXIT_WARNING)
         # bsdflags include the immutable flag and need to be set last:
         if not self.nobsdflags and 'bsdflags' in item:
             try:

+ 3 - 4
src/borg/testsuite/archiver.py

@@ -1264,16 +1264,15 @@ class ArchiverTestCase(ArchiverTestCaseBase):
             input_abspath = os.path.abspath('input/file')
             with patch.object(xattr, 'setxattr', patched_setxattr_E2BIG):
                 out = self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING)
-                assert out == (input_abspath + ': Value or key of extended attribute attribute is too big for this '
-                                               'filesystem\n')
+                assert '>: Value or key of extended attribute attribute is too big for this filesystem\n' in out
             os.remove(input_abspath)
             with patch.object(xattr, 'setxattr', patched_setxattr_ENOTSUP):
                 out = self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING)
-                assert out == (input_abspath + ': Extended attributes are not supported on this filesystem\n')
+                assert '>: Extended attributes are not supported on this filesystem\n' in out
             os.remove(input_abspath)
             with patch.object(xattr, 'setxattr', patched_setxattr_EACCES):
                 out = self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING)
-                assert out == (input_abspath + ': Permission denied when setting extended attribute attribute\n')
+                assert '>: Permission denied when setting extended attribute attribute\n' in out
             assert os.path.isfile(input_abspath)
 
     def test_path_normalization(self):

+ 50 - 1
src/borg/xattr.py

@@ -4,6 +4,10 @@ import errno
 import os
 import tempfile
 
+from .logger import create_logger
+
+logger = create_logger()
+
 from .platform import listxattr, getxattr, setxattr, ENOATTR
 
 XATTR_FAKEROOT = True  # fakeroot with xattr support required (>= 1.20.2?)
@@ -20,7 +24,7 @@ def is_enabled(path=None):
         return getxattr(fd.fileno(), b'user.name') == b'value'
 
 
-def get_all(path, follow_symlinks=True):
+def get_all(path, follow_symlinks=False):
     """
     Return all extended attributes on *path* as a mapping.
 
@@ -51,3 +55,48 @@ def get_all(path, follow_symlinks=True):
     except OSError as e:
         if e.errno in (errno.ENOTSUP, errno.EPERM):
             return {}
+
+
+def set_all(path, xattrs, follow_symlinks=False):
+    """
+    Set all extended attributes on *path* from a mapping.
+
+    *path* can either be a path (str or bytes) or an open file descriptor (int).
+    *follow_symlinks* indicates whether symlinks should be followed
+    and only applies when *path* is not an open file descriptor.
+    *xattrs* is mapping maps xattr names (bytes) to values (bytes or None).
+    None indicates, as a xattr value, an empty value, i.e. a value of length zero.
+
+    Return warning status (True means a non-fatal exception has happened and was dealt with).
+    """
+    if isinstance(path, str):
+        path = os.fsencode(path)
+    warning = False
+    for k, v in xattrs.items():
+        try:
+            # the key k is a bytes object due to msgpack unpacking it as such.
+            # if we have a None value, it means "empty", so give b'' to setxattr in that case:
+            setxattr(path, k, v or b'', follow_symlinks=follow_symlinks)
+        except OSError as e:
+            warning = True
+            k_str = k.decode()
+            if isinstance(path, int):
+                path_str = '<FD %d>' % path
+            else:
+                path_str = os.fsdecode(path)
+            if e.errno == errno.E2BIG:
+                logger.warning('%s: Value or key of extended attribute %s is too big for this filesystem' % (
+                               path_str, k_str))
+            elif e.errno == errno.ENOTSUP:
+                logger.warning('%s: Extended attributes are not supported on this filesystem' % path_str)
+            elif e.errno == errno.EACCES:
+                # permission denied to set this specific xattr (this may happen related to security.* keys)
+                logger.warning('%s: Permission denied when setting extended attribute %s' % (path_str, k_str))
+            elif e.errno == errno.ENOSPC:
+                # ext4 reports ENOSPC when trying to set an xattr with >4kiB while ext4 can only support 4kiB xattrs
+                # (in this case, this is NOT a "disk full" error, just a ext4 limitation).
+                logger.warning('%s: No space left on device while setting extended attribute %s (len = %d)' % (
+                               path_str, k_str, len(v)))
+            else:
+                raise
+    return warning