diff options
author | Steve Dower <steve.dower@python.org> | 2019-08-21 22:27:33 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-21 22:27:33 (GMT) |
commit | df2d4a6f3d5da2839c4fc11d31511c8e028daf2c (patch) | |
tree | e60154de9e835976aed87ab51ac3d5d9fda7f45f /Lib/shutil.py | |
parent | bcc446f525421156fe693139140e7051d000592e (diff) | |
download | cpython-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.py | 48 |
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: |