@@ -605,7 +605,22 @@ def _rmtree_islink(st):
605
605
return stat .S_ISLNK (st .st_mode )
606
606
607
607
# version vulnerable to race conditions
608
- def _rmtree_unsafe (path , onexc ):
608
+ def _rmtree_unsafe (path , dir_fd , onexc ):
609
+ if dir_fd is not None :
610
+ raise NotImplementedError ("dir_fd unavailable on this platform" )
611
+ try :
612
+ st = os .lstat (path )
613
+ except OSError as err :
614
+ onexc (os .lstat , path , err )
615
+ return
616
+ try :
617
+ if _rmtree_islink (st ):
618
+ # symlinks to directories are forbidden, see bug #1669
619
+ raise OSError ("Cannot call rmtree on a symbolic link" )
620
+ except OSError as err :
621
+ onexc (os .path .islink , path , err )
622
+ # can't continue even if onexc hook returns
623
+ return
609
624
def onerror (err ):
610
625
if not isinstance (err , FileNotFoundError ):
611
626
onexc (os .scandir , err .filename , err )
@@ -635,7 +650,26 @@ def onerror(err):
635
650
onexc (os .rmdir , path , err )
636
651
637
652
# Version using fd-based APIs to protect against races
638
- def _rmtree_safe_fd (stack , onexc ):
653
+ def _rmtree_safe_fd (path , dir_fd , onexc ):
654
+ # While the unsafe rmtree works fine on bytes, the fd based does not.
655
+ if isinstance (path , bytes ):
656
+ path = os .fsdecode (path )
657
+ stack = [(os .lstat , dir_fd , path , None )]
658
+ try :
659
+ while stack :
660
+ _rmtree_safe_fd_step (stack , onexc )
661
+ finally :
662
+ # Close any file descriptors still on the stack.
663
+ while stack :
664
+ func , fd , path , entry = stack .pop ()
665
+ if func is not os .close :
666
+ continue
667
+ try :
668
+ os .close (fd )
669
+ except OSError as err :
670
+ onexc (os .close , path , err )
671
+
672
+ def _rmtree_safe_fd_step (stack , onexc ):
639
673
# Each stack item has four elements:
640
674
# * func: The first operation to perform: os.lstat, os.close or os.rmdir.
641
675
# Walking a directory starts with an os.lstat() to detect symlinks; in
@@ -710,6 +744,7 @@ def _rmtree_safe_fd(stack, onexc):
710
744
os .supports_dir_fd and
711
745
os .scandir in os .supports_fd and
712
746
os .stat in os .supports_follow_symlinks )
747
+ _rmtree_impl = _rmtree_safe_fd if _use_fd_functions else _rmtree_unsafe
713
748
714
749
def rmtree (path , ignore_errors = False , onerror = None , * , onexc = None , dir_fd = None ):
715
750
"""Recursively delete a directory tree.
@@ -753,41 +788,7 @@ def onexc(*args):
753
788
exc_info = type (exc ), exc , exc .__traceback__
754
789
return onerror (func , path , exc_info )
755
790
756
- if _use_fd_functions :
757
- # While the unsafe rmtree works fine on bytes, the fd based does not.
758
- if isinstance (path , bytes ):
759
- path = os .fsdecode (path )
760
- stack = [(os .lstat , dir_fd , path , None )]
761
- try :
762
- while stack :
763
- _rmtree_safe_fd (stack , onexc )
764
- finally :
765
- # Close any file descriptors still on the stack.
766
- while stack :
767
- func , fd , path , entry = stack .pop ()
768
- if func is not os .close :
769
- continue
770
- try :
771
- os .close (fd )
772
- except OSError as err :
773
- onexc (os .close , path , err )
774
- else :
775
- if dir_fd is not None :
776
- raise NotImplementedError ("dir_fd unavailable on this platform" )
777
- try :
778
- st = os .lstat (path )
779
- except OSError as err :
780
- onexc (os .lstat , path , err )
781
- return
782
- try :
783
- if _rmtree_islink (st ):
784
- # symlinks to directories are forbidden, see bug #1669
785
- raise OSError ("Cannot call rmtree on a symbolic link" )
786
- except OSError as err :
787
- onexc (os .path .islink , path , err )
788
- # can't continue even if onexc hook returns
789
- return
790
- return _rmtree_unsafe (path , onexc )
791
+ _rmtree_impl (path , dir_fd , onexc )
791
792
792
793
# Allow introspection of whether or not the hardening against symlink
793
794
# attacks is supported on the current platform
0 commit comments