summaryrefslogtreecommitdiffstats
path: root/Lib/shutil.py
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2019-08-21 22:27:33 (GMT)
committerGitHub <noreply@github.com>2019-08-21 22:27:33 (GMT)
commitdf2d4a6f3d5da2839c4fc11d31511c8e028daf2c (patch)
treee60154de9e835976aed87ab51ac3d5d9fda7f45f /Lib/shutil.py
parentbcc446f525421156fe693139140e7051d000592e (diff)
downloadcpython-df2d4a6f3d5da2839c4fc11d31511c8e028daf2c.zip
cpython-df2d4a6f3d5da2839c4fc11d31511c8e028daf2c.tar.gz
cpython-df2d4a6f3d5da2839c4fc11d31511c8e028daf2c.tar.bz2
bpo-37834: Normalise handling of reparse points on Windows (GH-15231)
bpo-37834: Normalise handling of reparse points on Windows * ntpath.realpath() and nt.stat() will traverse all supported reparse points (previously was mixed) * nt.lstat() will let the OS traverse reparse points that are not name surrogates (previously would not traverse any reparse point) * nt.[l]stat() will only set S_IFLNK for symlinks (previous behaviour) * nt.readlink() will read destinations for symlinks and junction points only bpo-1311: os.path.exists('nul') now returns True on Windows * nt.stat('nul').st_mode is now S_IFCHR (previously was an error)
Diffstat (limited to 'Lib/shutil.py')
-rw-r--r--Lib/shutil.py48
1 files changed, 41 insertions, 7 deletions
diff --git a/Lib/shutil.py b/Lib/shutil.py
index ab1a7d6..39f793b 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -452,7 +452,14 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
dstname = os.path.join(dst, srcentry.name)
srcobj = srcentry if use_srcentry else srcname
try:
- if srcentry.is_symlink():
+ is_symlink = srcentry.is_symlink()
+ if is_symlink and os.name == 'nt':
+ # Special check for directory junctions, which appear as
+ # symlinks but we want to recurse.
+ lstat = srcentry.stat(follow_symlinks=False)
+ if lstat.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT:
+ is_symlink = False
+ if is_symlink:
linkto = os.readlink(srcname)
if symlinks:
# We can't just leave it to `copy_function` because legacy
@@ -537,6 +544,37 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
ignore_dangling_symlinks=ignore_dangling_symlinks,
dirs_exist_ok=dirs_exist_ok)
+if hasattr(stat, 'FILE_ATTRIBUTE_REPARSE_POINT'):
+ # Special handling for directory junctions to make them behave like
+ # symlinks for shutil.rmtree, since in general they do not appear as
+ # regular links.
+ def _rmtree_isdir(entry):
+ try:
+ st = entry.stat(follow_symlinks=False)
+ return (stat.S_ISDIR(st.st_mode) and not
+ (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT
+ and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT))
+ except OSError:
+ return False
+
+ def _rmtree_islink(path):
+ try:
+ st = os.lstat(path)
+ return (stat.S_ISLNK(st.st_mode) or
+ (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT
+ and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT))
+ except OSError:
+ return False
+else:
+ def _rmtree_isdir(entry):
+ try:
+ return entry.is_dir(follow_symlinks=False)
+ except OSError:
+ return False
+
+ def _rmtree_islink(path):
+ return os.path.islink(path)
+
# version vulnerable to race conditions
def _rmtree_unsafe(path, onerror):
try:
@@ -547,11 +585,7 @@ def _rmtree_unsafe(path, onerror):
entries = []
for entry in entries:
fullname = entry.path
- try:
- is_dir = entry.is_dir(follow_symlinks=False)
- except OSError:
- is_dir = False
- if is_dir:
+ if _rmtree_isdir(entry):
try:
if entry.is_symlink():
# This can only happen if someone replaces
@@ -681,7 +715,7 @@ def rmtree(path, ignore_errors=False, onerror=None):
os.close(fd)
else:
try:
- if os.path.islink(path):
+ if _rmtree_islink(path):
# symlinks to directories are forbidden, see bug #1669
raise OSError("Cannot call rmtree on a symbolic link")
except OSError: