8000 [3.12] gh-113188: Fix shutil.copymode() and shutil.copystat() on Windows by serhiy-storchaka · Pull Request #113285 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def copymode(src, dst, *, follow_symlinks=True):
return
else:
if os.name == 'nt' and os.path.islink(dst):
dst = os.realpath(dst, strict=True)
dst = os.path.realpath(dst, strict=True)
stat_func, chmod_func = _stat, os.chmod

st = stat_func(src)
Expand Down
18 changes: 11 additions & 7 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1059,9 +1059,10 @@ def test_copymode_follow_symlinks(self):
shutil.copymode(src_link, dst_link)
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') or os.name == 'nt', 'requires os.lchmod')
@os_helper.skip_unless_symlink
def test_copymode_symlink_to_symlink(self):
_lchmod = os.chmod if os.name == 'nt' else os.lchmod
tmp_dir = self.mkdtemp()
src = os.path.join(tmp_dir, 'foo')
dst = os.path.join(tmp_dir, 'bar')
Expand All @@ -1073,20 +1074,20 @@ def test_copymode_symlink_to_symlink(self):
os.symlink(dst, dst_link)
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)
os.chmod(dst, stat.S_IRWXU)
os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG)
_lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG)
# link to link
os.lchmod(dst_link, stat.S_IRWXO)
_lchmod(dst_link, stat.S_IRWXO)
old_mode = os.stat(dst).st_mode
shutil.copymode(src_link, dst_link, follow_symlinks=False)
self.assertEqual(os.lstat(src_link).st_mode,
os.lstat(dst_link).st_mode)
self.assertEqual(os.stat(dst).st_mode, old_mode)
# src link - use chmod
os.lchmod(dst_link, stat.S_IRWXO)
_lchmod(dst_link, stat.S_IRWXO)
shutil.copymode(src_link, dst, follow_symlinks=False)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
# dst link - use chmod
os.lchmod(dst_link, stat.S_IRWXO)
_lchmod(dst_link, stat.S_IRWXO)
shutil.copymode(src, dst_link, follow_symlinks=False)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)

Expand All @@ -1108,6 +1109,7 @@ def test_copymode_symlink_to_symlink_wo_lchmod(self):

@os_helper.skip_unless_symlink
def test_copystat_symlinks(self):
_lchmod = os.chmod if os.name == 'nt' else getattr(os, 'lchmod', None)
tmp_dir = self.mkdtemp()
src = os.path.join(tmp_dir, 'foo')
dst = os.path.join(tmp_dir, 'bar')
Expand All @@ -1123,11 +1125,13 @@ def test_copystat_symlinks(self):
os.symlink(dst, dst_link)
if hasattr(os, 'lchmod'):
os.lchmod(src_link, stat.S_IRWXO)
elif os.name == 'nt':
os.chmod(src_link, stat.S_IRWXO)
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):
os.lchflags(src_link, stat.UF_NODUMP)
src_link_stat = os.lstat(src_link)
# follow
if hasattr(os, 'lchmod'):
if hasattr(os, 'lchmod') or os.name == 'nt':
shutil.copystat(src_link, dst_link, follow_symlinks=True)
self.assertNotEqual(src_link_stat.st_mode, os.stat(dst).st_mode)
# don't follow
Expand All @@ -1138,7 +1142,7 @@ def test_copystat_symlinks(self):
# The modification times may be truncated in the new file.
self.assertLessEqual(getattr(src_link_stat, attr),
getattr(dst_link_stat, attr) + 1)
if hasattr(os, 'lchmod'):
if hasattr(os, 'lchmod') or os.name == 'nt':
self.assertEqual(src_link_stat.st_mode, dst_link_stat.st_mode)
if hasattr(os, 'lchflags') and hasattr(src_link_stat, 'st_flags'):
self.assertEqual(src_link_stat.st_flags, dst_link_stat.st_flags)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Fix :func:`shutil.copymode` on Windows. Previously it worked differenly if
*dst* is a symbolic link: it modified the permission bits of *dst* itself
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.
0