mirror of
https://github.com/python/cpython.git
synced 2025-08-17 07:11:51 +00:00
bpo-37834: Normalise handling of reparse points on Windows (GH-15370)
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)
This commit is contained in:
parent
c30c869e8d
commit
9eb3d54639
16 changed files with 477 additions and 240 deletions
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue