bpo-24538: Fix bug in shutil involving the copying of xattrs to read-only files. (PR-13212)

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.
This commit is contained in:
Olexa Bilaniuk 2019-05-09 22:22:06 -05:00 committed by Giampaolo Rodola
parent 948ed8c96b
commit 79efbb7193
4 changed files with 15 additions and 1 deletions

View File

@ -359,6 +359,9 @@ def copystat(src, dst, *, follow_symlinks=True):
mode = stat.S_IMODE(st.st_mode) mode = stat.S_IMODE(st.st_mode)
lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns),
follow_symlinks=follow) 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: try:
lookup("chmod")(dst, mode, follow_symlinks=follow) lookup("chmod")(dst, mode, follow_symlinks=follow)
except NotImplementedError: except NotImplementedError:
@ -382,7 +385,6 @@ def copystat(src, dst, *, follow_symlinks=True):
break break
else: else:
raise raise
_copyxattr(src, dst, follow_symlinks=follow)
def copy(src, dst, *, follow_symlinks=True): def copy(src, dst, *, follow_symlinks=True):
"""Copy data and mode bits ("cp src dst"). Return the file's destination. """Copy data and mode bits ("cp src dst"). Return the file's destination.

View File

@ -531,12 +531,20 @@ class TestShutil(unittest.TestCase):
# test that shutil.copystat copies xattrs # test that shutil.copystat copies xattrs
src = os.path.join(tmp_dir, 'the_original') src = os.path.join(tmp_dir, 'the_original')
srcro = os.path.join(tmp_dir, 'the_original_ro')
write_file(src, src) write_file(src, src)
write_file(srcro, srcro)
os.setxattr(src, 'user.the_value', b'fiddly') 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') dst = os.path.join(tmp_dir, 'the_copy')
dstro = os.path.join(tmp_dir, 'the_copy_ro')
write_file(dst, dst) write_file(dst, dst)
write_file(dstro, dstro)
shutil.copystat(src, dst) shutil.copystat(src, dst)
shutil.copystat(srcro, dstro)
self.assertEqual(os.getxattr(dst, 'user.the_value'), b'fiddly') 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_symlink
@support.skip_unless_xattr @support.skip_unless_xattr

View File

@ -149,6 +149,7 @@ Stephen Bevan
Ron Bickers Ron Bickers
Natalia B. Bidart Natalia B. Bidart
Adrian von Bidder Adrian von Bidder
Olexa Bilaniuk
David Binger David Binger
Dominic Binks Dominic Binks
Philippe Biondi Philippe Biondi

View File

@ -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.