]> granicus.if.org Git - python/commitdiff
- Issue #15238: shutil.copystat now copies Linux "extended attributes".
authorLarry Hastings <larry@hastings.org>
Sun, 15 Jul 2012 00:55:11 +0000 (17:55 -0700)
committerLarry Hastings <larry@hastings.org>
Sun, 15 Jul 2012 00:55:11 +0000 (17:55 -0700)
Doc/library/shutil.rst
Lib/shutil.py
Lib/test/test_shutil.py
Misc/NEWS

index c3947bbc79efbfd87e42bc28c3e0363506ecc20a..e8dde06d43714a47a55d57055c0b2d07d2f36a36 100644 (file)
@@ -86,10 +86,11 @@ Directory and files operations
    from *src* to *dst*.  The file contents, owner, and group are unaffected.  *src*
    and *dst* are path names given as strings.  If *src* and *dst* are both
    symbolic links and *symlinks* true, the stats of the link will be copied as
-   far as the platform allows.
+   far as the platform allows.  On Linux, :func:`copystat` also copies the
+   "extended attributes" where possible.
 
    .. versionchanged:: 3.3
-      Added *symlinks* argument.
+      Added *symlinks* argument and support for Linux extended attributes.
 
 .. function:: copy(src, dst, symlinks=False)
 
index 6d80feed673066deeb757afd958752b1d07cdd4e..2b2a9be5085d3997427c4a3b56bf42b013101177 100644 (file)
@@ -132,6 +132,27 @@ def copymode(src, dst, symlinks=False):
     st = stat_func(src)
     chmod_func(dst, stat.S_IMODE(st.st_mode))
 
+if hasattr(os, 'listxattr'):
+    def _copyxattr(src, dst, symlinks=False):
+        """Copy extended filesystem attributes from `src` to `dst`.
+
+        Overwrite existing attributes.
+
+        If the optional flag `symlinks` is set, symlinks won't be followed.
+
+        """
+
+        for name in os.listxattr(src, follow_symlinks=symlinks):
+            try:
+                value = os.getxattr(src, name, follow_symlinks=symlinks)
+                os.setxattr(dst, name, value, follow_symlinks=symlinks)
+            except OSError as e:
+                if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA):
+                    raise
+else:
+    def _copyxattr(*args, **kwargs):
+        pass
+
 def copystat(src, dst, symlinks=False):
     """Copy all stat info (mode bits, atime, mtime, flags) from src to dst.
 
@@ -184,27 +205,7 @@ def copystat(src, dst, symlinks=False):
                     break
             else:
                 raise
-
-if hasattr(os, 'listxattr'):
-    def _copyxattr(src, dst, symlinks=False):
-        """Copy extended filesystem attributes from `src` to `dst`.
-
-        Overwrite existing attributes.
-
-        If the optional flag `symlinks` is set, symlinks won't be followed.
-
-        """
-
-        for name in os.listxattr(src, follow_symlinks=symlinks):
-            try:
-                value = os.getxattr(src, name, follow_symlinks=symlinks)
-                os.setxattr(dst, name, value, follow_symlinks=symlinks)
-            except OSError as e:
-                if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA):
-                    raise
-else:
-    def _copyxattr(*args, **kwargs):
-        pass
+    _copyxattr(src, dst, symlinks=follow)
 
 def copy(src, dst, symlinks=False):
     """Copy data and mode bits ("cp src dst"). Return the file's destination.
@@ -235,7 +236,6 @@ def copy2(src, dst, symlinks=False):
         dst = os.path.join(dst, os.path.basename(src))
     copyfile(src, dst, symlinks=symlinks)
     copystat(src, dst, symlinks=symlinks)
-    _copyxattr(src, dst, symlinks=symlinks)
     return dst
 
 def ignore_patterns(*patterns):
index a2b6e88afec0ef37bd53cc83134779f4b702a0f2..cbbc36f0f64e51a85075a0a60d762a0d35315fb5 100644 (file)
@@ -410,6 +410,16 @@ class TestShutil(unittest.TestCase):
         finally:
             os.setxattr = orig_setxattr
 
+        # test that shutil.copystat copies xattrs
+        src = os.path.join(tmp_dir, 'the_original')
+        write_file(src, src)
+        os.setxattr(src, 'user.the_value', b'fiddly')
+        dst = os.path.join(tmp_dir, 'the_copy')
+        write_file(dst, dst)
+        shutil.copystat(src, dst)
+        self.assertEqual(os.listxattr(src), ['user.the_value'])
+        self.assertEqual(os.getxattr(src, 'user.the_value'), b'fiddly')
+
     @support.skip_unless_symlink
     @support.skip_unless_xattr
     @unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() == 0,
index 0100651eaf9401286c57252747434b4593b997e1..c99cbd27b79fdd124d96d52d871b7a60b8493200 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -35,6 +35,8 @@ Core and Builtins
 Library
 -------
 
+- Issue #15238: shutil.copystat now copies Linux "extended attributes".
+
 - Issue #15230: runpy.run_path now correctly sets __package__ as described
   in the documentation