mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
gh-81441: shutil.rmtree() FileNotFoundError race condition (GH-14064)
Ignore missing files and directories while enumerating directory entries in shutil.rmtree(). Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
b31232ddf7
commit
268415bbb3
4 changed files with 97 additions and 11 deletions
|
@ -590,23 +590,21 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
|
|||
dirs_exist_ok=dirs_exist_ok)
|
||||
|
||||
if hasattr(os.stat_result, 'st_file_attributes'):
|
||||
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
|
||||
def _rmtree_islink(st):
|
||||
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))
|
||||
else:
|
||||
def _rmtree_islink(path):
|
||||
return os.path.islink(path)
|
||||
def _rmtree_islink(st):
|
||||
return stat.S_ISLNK(st.st_mode)
|
||||
|
||||
# version vulnerable to race conditions
|
||||
def _rmtree_unsafe(path, onexc):
|
||||
try:
|
||||
with os.scandir(path) as scandir_it:
|
||||
entries = list(scandir_it)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
except OSError as err:
|
||||
onexc(os.scandir, path, err)
|
||||
entries = []
|
||||
|
@ -614,6 +612,8 @@ def _rmtree_unsafe(path, onexc):
|
|||
fullname = entry.path
|
||||
try:
|
||||
is_dir = entry.is_dir(follow_symlinks=False)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError:
|
||||
is_dir = False
|
||||
|
||||
|
@ -624,6 +624,8 @@ def _rmtree_unsafe(path, onexc):
|
|||
# a directory with a symlink after the call to
|
||||
# os.scandir or entry.is_dir above.
|
||||
raise OSError("Cannot call rmtree on a symbolic link")
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError as err:
|
||||
onexc(os.path.islink, fullname, err)
|
||||
continue
|
||||
|
@ -631,10 +633,14 @@ def _rmtree_unsafe(path, onexc):
|
|||
else:
|
||||
try:
|
||||
os.unlink(fullname)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError as err:
|
||||
onexc(os.unlink, fullname, err)
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError as err:
|
||||
onexc(os.rmdir, path, err)
|
||||
|
||||
|
@ -643,6 +649,8 @@ def _rmtree_safe_fd(topfd, path, onexc):
|
|||
try:
|
||||
with os.scandir(topfd) as scandir_it:
|
||||
entries = list(scandir_it)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
except OSError as err:
|
||||
err.filename = path
|
||||
onexc(os.scandir, path, err)
|
||||
|
@ -651,6 +659,8 @@ def _rmtree_safe_fd(topfd, path, onexc):
|
|||
fullname = os.path.join(path, entry.name)
|
||||
try:
|
||||
is_dir = entry.is_dir(follow_symlinks=False)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError:
|
||||
is_dir = False
|
||||
else:
|
||||
|
@ -658,6 +668,8 @@ def _rmtree_safe_fd(topfd, path, onexc):
|
|||
try:
|
||||
orig_st = entry.stat(follow_symlinks=False)
|
||||
is_dir = stat.S_ISDIR(orig_st.st_mode)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError as err:
|
||||
onexc(os.lstat, fullname, err)
|
||||
continue
|
||||
|
@ -665,6 +677,8 @@ def _rmtree_safe_fd(topfd, path, onexc):
|
|||
try:
|
||||
dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd)
|
||||
dirfd_closed = False
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError as err:
|
||||
onexc(os.open, fullname, err)
|
||||
else:
|
||||
|
@ -675,6 +689,8 @@ def _rmtree_safe_fd(topfd, path, onexc):
|
|||
os.close(dirfd)
|
||||
dirfd_closed = True
|
||||
os.rmdir(entry.name, dir_fd=topfd)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError as err:
|
||||
onexc(os.rmdir, fullname, err)
|
||||
else:
|
||||
|
@ -692,6 +708,8 @@ def _rmtree_safe_fd(topfd, path, onexc):
|
|||
else:
|
||||
try:
|
||||
os.unlink(entry.name, dir_fd=topfd)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError as err:
|
||||
onexc(os.unlink, fullname, err)
|
||||
|
||||
|
@ -781,7 +799,12 @@ def rmtree(path, ignore_errors=False, onerror=None, *, onexc=None, dir_fd=None):
|
|||
if dir_fd is not None:
|
||||
raise NotImplementedError("dir_fd unavailable on this platform")
|
||||
try:
|
||||
if _rmtree_islink(path):
|
||||
st = os.lstat(path)
|
||||
except OSError as err:
|
||||
onexc(os.lstat, path, err)
|
||||
return
|
||||
try:
|
||||
if _rmtree_islink(st):
|
||||
# symlinks to directories are forbidden, see bug #1669
|
||||
raise OSError("Cannot call rmtree on a symbolic link")
|
||||
except OSError as err:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue