[3.12] GH-89727: Partially fix shutil.rmtree() recursion error on deep trees (GH-119634) (#119749)

* GH-89727: Partially fix `shutil.rmtree()` recursion error on deep trees (#119634)

Make `shutil._rmtree_unsafe()` call `os.walk()`, which is implemented
without recursion.

`shutil._rmtree_safe_fd()` is not affected and can still raise a recursion
error.

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
(cherry picked from commit a150679f90)
This commit is contained in:
Barney Gale 2024-05-29 21:53:08 +01:00 committed by GitHub
parent e902503980
commit 45587df8d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 32 additions and 24 deletions

View file

@ -617,31 +617,18 @@ else:
# version vulnerable to race conditions
def _rmtree_unsafe(path, onexc):
try:
with os.scandir(path) as scandir_it:
entries = list(scandir_it)
except OSError as err:
onexc(os.scandir, path, err)
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 and not entry.is_junction():
def onerror(err):
onexc(os.scandir, err.filename, err)
results = os.walk(path, topdown=False, onerror=onerror, followlinks=os._walk_symlinks_as_files)
for dirpath, dirnames, filenames in results:
for name in dirnames:
fullname = os.path.join(dirpath, name)
try:
if entry.is_symlink():
# This can only happen if someone replaces
# 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")
os.rmdir(fullname)
except OSError as err:
onexc(os.path.islink, fullname, err)
continue
_rmtree_unsafe(fullname, onexc)
else:
onexc(os.rmdir, fullname, err)
for name in filenames:
fullname = os.path.join(dirpath, name)
try:
os.unlink(fullname)
except OSError as err: