__all__.extend(["makedirs", "removedirs", "renames"])
+# Private sentinel that makes walk() classify all symlinks and junctions as
+# regular files.
+_walk_symlinks_as_files = object()
+
def walk(top, topdown=True, onerror=None, followlinks=False):
"""Directory tree generator.
break
try:
- is_dir = entry.is_dir()
+ if followlinks is _walk_symlinks_as_files:
+ is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction()
+ else:
+ is_dir = entry.is_dir()
except OSError:
# If is_dir() raises an OSError, consider the entry not to
# be a directory, same behaviour as os.path.isdir().
# 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 = []
- for entry in entries:
- fullname = entry.path
- try:
- is_dir = entry.is_dir(follow_symlinks=False)
- except FileNotFoundError:
- continue
- except OSError:
- is_dir = False
-
- if is_dir and not entry.is_junction():
+ def onerror(err):
+ if not isinstance(err, FileNotFoundError):
+ 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 FileNotFoundError:
continue
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 FileNotFoundError:
shutil.rmtree(TESTFN)
raise
+ @unittest.skipIf(shutil._use_fd_functions, "fd-based functions remain unfixed (GH-89727)")
+ def test_rmtree_above_recursion_limit(self):
+ recursion_limit = 40
+ # directory_depth > recursion_limit
+ directory_depth = recursion_limit + 10
+ base = os.path.join(TESTFN, *(['d'] * directory_depth))
+ os.makedirs(base)
+
+ with support.infinite_recursion(recursion_limit):
+ shutil.rmtree(TESTFN)
+
class TestCopyTree(BaseTest, unittest.TestCase):