bpo-28564: Use os.scandir() in shutil.rmtree(). (#4085) · python/cpython@d4d79bc (original) (raw)
`@@ -362,25 +362,27 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
`
362
362
`# version vulnerable to race conditions
`
363
363
`def _rmtree_unsafe(path, onerror):
`
364
364
`try:
`
365
``
`-
if os.path.islink(path):
`
366
``
`-
symlinks to directories are forbidden, see bug #1669
`
367
``
`-
raise OSError("Cannot call rmtree on a symbolic link")
`
``
365
`+
with os.scandir(path) as scandir_it:
`
``
366
`+
entries = list(scandir_it)
`
368
367
`except OSError:
`
369
``
`-
onerror(os.path.islink, path, sys.exc_info())
`
370
``
`-
can't continue even if onerror hook returns
`
371
``
`-
return
`
372
``
`-
names = []
`
373
``
`-
try:
`
374
``
`-
names = os.listdir(path)
`
375
``
`-
except OSError:
`
376
``
`-
onerror(os.listdir, path, sys.exc_info())
`
377
``
`-
for name in names:
`
378
``
`-
fullname = os.path.join(path, name)
`
``
368
`+
onerror(os.scandir, path, sys.exc_info())
`
``
369
`+
entries = []
`
``
370
`+
for entry in entries:
`
``
371
`+
fullname = entry.path
`
379
372
`try:
`
380
``
`-
mode = os.lstat(fullname).st_mode
`
``
373
`+
is_dir = entry.is_dir(follow_symlinks=False)
`
381
374
`except OSError:
`
382
``
`-
mode = 0
`
383
``
`-
if stat.S_ISDIR(mode):
`
``
375
`+
is_dir = False
`
``
376
`+
if is_dir:
`
``
377
`+
try:
`
``
378
`+
if entry.is_symlink():
`
``
379
`+
This can only happen if someone replaces
`
``
380
`+
a directory with a symlink after the call to
`
``
381
`+
os.scandir or entry.is_dir above.
`
``
382
`+
raise OSError("Cannot call rmtree on a symbolic link")
`
``
383
`+
except OSError:
`
``
384
`+
onerror(os.path.islink, fullname, sys.exc_info())
`
``
385
`+
continue
`
384
386
`_rmtree_unsafe(fullname, onerror)
`
385
387
`else:
`
386
388
`try:
`
`@@ -394,37 +396,40 @@ def _rmtree_unsafe(path, onerror):
`
394
396
``
395
397
`# Version using fd-based APIs to protect against races
`
396
398
`def _rmtree_safe_fd(topfd, path, onerror):
`
397
``
`-
names = []
`
398
399
`try:
`
399
``
`-
names = os.listdir(topfd)
`
``
400
`+
with os.scandir(topfd) as scandir_it:
`
``
401
`+
entries = list(scandir_it)
`
400
402
`except OSError as err:
`
401
403
`err.filename = path
`
402
``
`-
onerror(os.listdir, path, sys.exc_info())
`
403
``
`-
for name in names:
`
404
``
`-
fullname = os.path.join(path, name)
`
``
404
`+
onerror(os.scandir, path, sys.exc_info())
`
``
405
`+
return
`
``
406
`+
for entry in entries:
`
``
407
`+
fullname = os.path.join(path, entry.name)
`
405
408
`try:
`
406
``
`-
orig_st = os.stat(name, dir_fd=topfd, follow_symlinks=False)
`
407
``
`-
mode = orig_st.st_mode
`
``
409
`+
is_dir = entry.is_dir(follow_symlinks=False)
`
``
410
`+
if is_dir:
`
``
411
`+
orig_st = entry.stat(follow_symlinks=False)
`
``
412
`+
is_dir = stat.S_ISDIR(orig_st.st_mode)
`
408
413
`except OSError:
`
409
``
`-
mode = 0
`
410
``
`-
if stat.S_ISDIR(mode):
`
``
414
`+
is_dir = False
`
``
415
`+
if is_dir:
`
411
416
`try:
`
412
``
`-
dirfd = os.open(name, os.O_RDONLY, dir_fd=topfd)
`
``
417
`+
dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd)
`
413
418
`except OSError:
`
414
419
`onerror(os.open, fullname, sys.exc_info())
`
415
420
`else:
`
416
421
`try:
`
417
422
`if os.path.samestat(orig_st, os.fstat(dirfd)):
`
418
423
`_rmtree_safe_fd(dirfd, fullname, onerror)
`
419
424
`try:
`
420
``
`-
os.rmdir(name, dir_fd=topfd)
`
``
425
`+
os.rmdir(entry.name, dir_fd=topfd)
`
421
426
`except OSError:
`
422
427
`onerror(os.rmdir, fullname, sys.exc_info())
`
423
428
`else:
`
424
429
`try:
`
425
430
`# This can only happen if someone replaces
`
426
431
`# a directory with a symlink after the call to
`
427
``
`-
stat.S_ISDIR above.
`
``
432
`+
os.scandir or stat.S_ISDIR above.
`
428
433
`raise OSError("Cannot call rmtree on a symbolic "
`
429
434
`"link")
`
430
435
`except OSError:
`
`@@ -433,13 +438,13 @@ def _rmtree_safe_fd(topfd, path, onerror):
`
433
438
`os.close(dirfd)
`
434
439
`else:
`
435
440
`try:
`
436
``
`-
os.unlink(name, dir_fd=topfd)
`
``
441
`+
os.unlink(entry.name, dir_fd=topfd)
`
437
442
`except OSError:
`
438
443
`onerror(os.unlink, fullname, sys.exc_info())
`
439
444
``
440
445
`_use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <=
`
441
446
`os.supports_dir_fd and
`
442
``
`-
os.listdir in os.supports_fd and
`
``
447
`+
os.scandir in os.supports_fd and
`
443
448
`os.stat in os.supports_follow_symlinks)
`
444
449
``
445
450
`def rmtree(path, ignore_errors=False, onerror=None):
`
`@@ -491,6 +496,14 @@ def onerror(*args):
`
491
496
`finally:
`
492
497
`os.close(fd)
`
493
498
`else:
`
``
499
`+
try:
`
``
500
`+
if os.path.islink(path):
`
``
501
`+
symlinks to directories are forbidden, see bug #1669
`
``
502
`+
raise OSError("Cannot call rmtree on a symbolic link")
`
``
503
`+
except OSError:
`
``
504
`+
onerror(os.path.islink, path, sys.exc_info())
`
``
505
`+
can't continue even if onerror hook returns
`
``
506
`+
return
`
494
507
`return _rmtree_unsafe(path, onerror)
`
495
508
``
496
509
`# Allow introspection of whether or not the hardening against symlink
`