From ad5ae0456e64036d1f39a15c214c2f394a8fb9c3 Mon Sep 17 00:00:00 2001 From: Larry Hastings Date: Sat, 14 Jul 2012 17:55:11 -0700 Subject: [PATCH] - Issue #15238: shutil.copystat now copies Linux "extended attributes". --- Doc/library/shutil.rst | 5 +++-- Lib/shutil.py | 44 ++++++++++++++++++++--------------------- Lib/test/test_shutil.py | 10 ++++++++++ Misc/NEWS | 2 ++ 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index c3947bbc79e..e8dde06d437 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -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) diff --git a/Lib/shutil.py b/Lib/shutil.py index 6d80feed673..2b2a9be5085 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -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): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index a2b6e88afec..cbbc36f0f64 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -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, diff --git a/Misc/NEWS b/Misc/NEWS index 0100651eaf9..c99cbd27b79 100644 --- 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