@@ -639,69 +639,68 @@ def onerror(err):
639
639
onexc (os .rmdir , path , err )
640
640
641
641
# Version using fd-based APIs to protect against races
642
- def _rmtree_safe_fd (topfd , path , onexc ):
642
+ def _rmtree_safe_fd (stack , onexc ):
643
+ # Each stack item has four elements:
644
+ # * func: The first operation to perform: os.lstat, os.close or os.rmdir.
645
+ # Walking a directory starts with an os.lstat() to detect symlinks; in
646
+ # this case, func is updated before subsequent operations and passed to
647
+ # onexc() if an error occurs.
648
+ # * dirfd: Open file descriptor, or None if we're processing the top-level
649
+ # directory given to rmtree() and the user didn't supply dir_fd.
650
+ # * path: Path of file to operate upon. This is passed to onexc() if an
651
+ # error occurs.
652
+ # * orig_entry: os.DirEntry, or None if we're processing the top-level
653
+ # directory given to rmtree(). We used the cached stat() of the entry to
654
+ # save a call to os.lstat() when walking subdirectories.
655
+ func , dirfd , path , orig_entry = stack .pop ()
656
+ name = path if orig_entry is None else orig_entry .name
643
657
try :
658
+ if func is os .close :
659
+ os .close (dirfd )
660
+ return
661
+ if func is os .rmdir :
662
+ os .rmdir (name , dir_fd = dirfd )
663
+ return
664
+
665
+ # Note: To guard against symlink races, we use the standard
666
+ # lstat()/open()/fstat() trick.
667
+ assert func is os .lstat
668
+ if orig_entry is None :
669
+ orig_st = os .lstat (name , dir_fd = dirfd )
670
+ else :
671
+ orig_st = orig_entry .stat (follow_symlinks = False )
672
+
673
+ func = os .open # For error reporting.
674
+ topfd = os .open (name , os .O_RDONLY | os .O_NONBLOCK , dir_fd = dirfd )
675
+
676
+ func = os .path .islink # For error reporting.
677
+ try :
678
+ if not os .path .samestat (orig_st , os .fstat (topfd )):
679
+ # Symlinks to directories are forbidden, see GH-46010.
680
+ raise OSError ("Cannot call rmtree on a symbolic link" )
681
+ stack .append ((os .rmdir , dirfd , path , orig_entry ))
682
+ finally :
683
+ stack .append ((os .close , topfd , path , orig_entry ))
684
+
685
+ func = os .scandir # For error reporting.
644
686
with os .scandir (topfd ) as scandir_it :
645
687
entries = list (scandir_it )
646
- except OSError as err :
647
- err .filename = path
648
- onexc (os .scandir , path , err )
649
- return
650
- for entry in entries :
651
- fullname = os .path .join (path , entry .name )
652
- try :
653
- is_dir = entry .is_dir (follow_symlinks = False )
654
- except OSError :
655
- is_dir = False
656
- else :
657
- if is_dir :
658
- try :
659
- orig_st = entry .stat (follow_symlinks = False )
660
- is_dir = stat .S_ISDIR (orig_st .st_mode )
661
- except OSError as err :
662
- onexc (os .lstat , fullname , err )
663
- continue
664
- if is_dir :
688
+ for entry in entries :
689
+ fullname = os .path .join (path , entry .name )
665
690
try :
666
- dirfd = os .open (entry .name , os .O_RDONLY | os .O_NONBLOCK , dir_fd = topfd )
667
- dirfd_closed = False
668
- except OSError as err :
669
- onexc (os .open , fullname , err )
670
- else :
671
- try :
672
- if os .path .samestat (orig_st , os .fstat (dirfd )):
673
- _rmtree_safe_fd (dirfd , fullname , onexc )
674
- try :
675
- os .close (dirfd )
676
- except OSError as err :
677
- # close() should not be retried after an error.
678
- dirfd_closed = True
679
- onexc (os .close , fullname , err )
680
- dirfd_closed = True
681
- try :
682
- os .rmdir (entry .name , dir_fd = topfd )
683
- except OSError as err :
684
- onexc (os .rmdir , fullname , err )
685
- else :
686
- try :
687
- # This can only happen if someone replaces
688
- # a directory with a symlink after the call to
689
- # os.scandir or stat.S_ISDIR above.
690
- raise OSError ("Cannot call rmtree on a symbolic "
691
- "link" )
692
- except OSError as err :
693
- onexc (os .path .islink , fullname , err )
694
- finally :
695
- if not dirfd_closed :
696
- try :
697
- os .close (dirfd )
698
- except OSError as err :
699
- onexc (os .close , fullname , err )
700
- else :
691
+ if entry .is_dir (follow_symlinks = False ):
692
+ # Traverse into sub-directory.
693
+ stack .append ((os .lstat , topfd , fullname , entry ))
694
+ continue
695
+ except OSError :
696
+ pass
701
697
try :
702
698
os .unlink (entry .name , dir_fd = topfd )
703
699
except OSError as err :
704
700
onexc (os .unlink , fullname , err )
701
+ except OSError as err :
702
+ err .filename = path
703
+ onexc (func , path , err )
705
704
706
705
_use_fd_functions = ({os .open , os .stat , os .unlink , os .rmdir } <=
707
706
os .supports_dir_fd and
@@ -754,41 +753,16 @@ def onexc(*args):
754
753
# While the unsafe rmtree works fine on bytes, the fd based does not.
755
754
if isinstance (path , bytes ):
756
755
path = os .fsdecode (path )
757
- # Note: To guard against symlink races, we use the standard
758
- # lstat()/open()/fstat() trick.
759
- try :
760
- orig_st = os .lstat (path , dir_fd = dir_fd )
761
- except Exception as err :
762
- onexc (os .lstat , path , err )
763
- return
756
+ stack = [(os .lstat , dir_fd , path , None )]
764
757
try :
765
- fd = os .open (path , os .O_RDONLY | os .O_NONBLOCK , dir_fd = dir_fd )
766
- fd_closed = False
767
- except Exception as err :
768
- onexc (os .open , path , err )
769
- return
770
- try :
771
- if os .path .samestat (orig_st , os .fstat (fd )):
772
- _rmtree_safe_fd (fd , path , onexc )
773
- try :
774
- os .close (fd )
775
- except OSError as err :
776
- # close() should not be retried after an error.
777
- fd_closed = True
778
- onexc (os .close , path , err )
779
- fd_closed = True
780
- try :
781
- os .rmdir (path , dir_fd = dir_fd )
782
- except OSError as err :
783
- onexc (os .rmdir , path , err )
784
- else :
785
- try :
786
- # symlinks to directories are forbidden, see bug #1669
787
- raise OSError ("Cannot call rmtree on a symbolic link" )
788
- except OSError as err :
789
- onexc (os .path .islink , path , err )
758
+ while stack :
759
+ _rmtree_safe_fd (stack , onexc )
790
760
finally :
791
- if not fd_closed :
761
+ # Close any file descriptors still on the stack.
762
+ while stack :
763
+ func , fd , path , entry = stack .pop ()
764
+ if func is not os .close :
765
+ continue
792
766
try :
793
767
os .close (fd )
794
768
except OSError as err :
0 commit comments