From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Tue, 14 May 2019 05:30:22 +0000 (-0700) Subject: bpo-24538: Fix bug in shutil involving the copying of xattrs to read-only files.... X-Git-Tag: v3.7.4rc1~154 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0a5b88e7f23b671d63896619b13148b0e4e2b5dd;p=thirdparty%2FPython%2Fcpython.git bpo-24538: Fix bug in shutil involving the copying of xattrs to read-only files. (PR-13212) (#13234) Extended attributes can only be set on user-writeable files, but shutil previously first chmod()ed the destination file to the source's permissions and then tried to copy xattrs. This will cause failures if attempting to copy read-only files with xattrs, as occurs with Git clones on Lustre FS. (cherry picked from commit 79efbb719383386051c72f2ee932eeca8e033e6b) Co-authored-by: Olexa Bilaniuk --- diff --git a/Lib/shutil.py b/Lib/shutil.py index b0a53dba3a34..4c6fdd7d33d4 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -203,6 +203,9 @@ def copystat(src, dst, *, follow_symlinks=True): mode = stat.S_IMODE(st.st_mode) lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), follow_symlinks=follow) + # We must copy extended attributes before the file is (potentially) + # chmod()'ed read-only, otherwise setxattr() will error with -EACCES. + _copyxattr(src, dst, follow_symlinks=follow) try: lookup("chmod")(dst, mode, follow_symlinks=follow) except NotImplementedError: @@ -226,7 +229,6 @@ def copystat(src, dst, *, follow_symlinks=True): break else: raise - _copyxattr(src, dst, follow_symlinks=follow) def copy(src, dst, *, follow_symlinks=True): """Copy data and mode bits ("cp src dst"). Return the file's destination. diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 197dd130a964..ef9323962cae 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -464,12 +464,20 @@ class TestShutil(unittest.TestCase): # test that shutil.copystat copies xattrs src = os.path.join(tmp_dir, 'the_original') + srcro = os.path.join(tmp_dir, 'the_original_ro') write_file(src, src) + write_file(srcro, srcro) os.setxattr(src, 'user.the_value', b'fiddly') + os.setxattr(srcro, 'user.the_value', b'fiddly') + os.chmod(srcro, 0o444) dst = os.path.join(tmp_dir, 'the_copy') + dstro = os.path.join(tmp_dir, 'the_copy_ro') write_file(dst, dst) + write_file(dstro, dstro) shutil.copystat(src, dst) + shutil.copystat(srcro, dstro) self.assertEqual(os.getxattr(dst, 'user.the_value'), b'fiddly') + self.assertEqual(os.getxattr(dstro, 'user.the_value'), b'fiddly') @support.skip_unless_symlink @support.skip_unless_xattr diff --git a/Misc/ACKS b/Misc/ACKS index 4581f32dd5f8..8998c7bf6b61 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -147,6 +147,7 @@ Stephen Bevan Ron Bickers Natalia B. Bidart Adrian von Bidder +Olexa Bilaniuk David Binger Dominic Binks Philippe Biondi diff --git a/Misc/NEWS.d/next/Library/2019-05-09-08-35-18.bpo-24538.WK8Y-k.rst b/Misc/NEWS.d/next/Library/2019-05-09-08-35-18.bpo-24538.WK8Y-k.rst new file mode 100644 index 000000000000..e799f931bcec --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-09-08-35-18.bpo-24538.WK8Y-k.rst @@ -0,0 +1,3 @@ +In `shutil.copystat()`, first copy extended file attributes and then file +permissions, since extended attributes can only be set on the destination +while it is still writeable.