gh-113188: Fix shutil.copymode() on Windows (GH-113189)

Previously it worked differently if dst is a symbolic link:
it modified the permission bits of dst itself rather than the file
it points to if follow_symlinks is true or src is not a symbolic link,
and did nothing if follow_symlinks is false and src is a symbolic link.

Also document similar changes in shutil.copystat().
This commit is contained in:
Serhiy Storchaka 2023-12-23 13:07:54 +02:00 committed by GitHub
parent bdc8d667ab
commit 6e02d79f96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 24 additions and 14 deletions

View file

@ -306,7 +306,12 @@ def copymode(src, dst, *, follow_symlinks=True):
else: else:
return return
else: else:
stat_func, chmod_func = _stat, os.chmod stat_func = _stat
if os.name == 'nt' and os.path.islink(dst):
def chmod_func(*args):
os.chmod(*args, follow_symlinks=True)
else:
chmod_func = os.chmod
st = stat_func(src) st = stat_func(src)
chmod_func(dst, stat.S_IMODE(st.st_mode)) chmod_func(dst, stat.S_IMODE(st.st_mode))

View file

@ -1101,19 +1101,18 @@ class TestCopy(BaseTest, unittest.TestCase):
shutil.copymode(src, dst) shutil.copymode(src, dst)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
# On Windows, os.chmod does not follow symlinks (issue #15411) # On Windows, os.chmod does not follow symlinks (issue #15411)
if os.name != 'nt': # follow src link
# follow src link os.chmod(dst, stat.S_IRWXO)
os.chmod(dst, stat.S_IRWXO) shutil.copymode(src_link, dst)
shutil.copymode(src_link, dst) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) # follow dst link
# follow dst link os.chmod(dst, stat.S_IRWXO)
os.chmod(dst, stat.S_IRWXO) shutil.copymode(src, dst_link)
shutil.copymode(src, dst_link) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) # follow both links
# follow both links os.chmod(dst, stat.S_IRWXO)
os.chmod(dst, stat.S_IRWXO) shutil.copymode(src_link, dst_link)
shutil.copymode(src_link, dst_link) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
@unittest.skipUnless(hasattr(os, 'lchmod'), 'requires os.lchmod') @unittest.skipUnless(hasattr(os, 'lchmod'), 'requires os.lchmod')
@os_helper.skip_unless_symlink @os_helper.skip_unless_symlink

View file

@ -0,0 +1,6 @@
Fix :func:`shutil.copymode` and :func:`shutil.copystat` on Windows.
Previously they worked differenly if *dst* is a symbolic link:
they modified the permission bits of *dst* itself
rather than the file it points to if *follow_symlinks* is true or *src* is
not a symbolic link, and did not modify the permission bits if
*follow_symlinks* is false and *src* is a symbolic link.