diff -r a1c8302e6b27 Doc/library/shutil.rst --- a/Doc/library/shutil.rst Sun Jul 01 12:24:20 2012 +0200 +++ b/Doc/library/shutil.rst Tue Jul 03 09:05:12 2012 +0200 @@ -86,10 +86,11 @@ 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 -r a1c8302e6b27 Lib/shutil.py --- a/Lib/shutil.py Sun Jul 01 12:24:20 2012 +0200 +++ b/Lib/shutil.py Tue Jul 03 09:05:12 2012 +0200 @@ -132,6 +132,27 @@ 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 @@ 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 @@ 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 -r a1c8302e6b27 Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py Sun Jul 01 12:24:20 2012 +0200 +++ b/Lib/test/test_shutil.py Tue Jul 03 09:05:12 2012 +0200 @@ -410,6 +410,14 @@ 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']) @support.skip_unless_symlink @support.skip_unless_xattr @unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() == 0,