@@ -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
0 commit comments