@@ -605,7 +605,22 @@ def _rmtree_islink(st):
605605 return stat .S_ISLNK (st .st_mode )
606606
607607# 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
609624 def onerror (err ):
610625 if not isinstance (err , FileNotFoundError ):
611626 onexc (os .scandir , err .filename , err )
@@ -635,7 +650,26 @@ def onerror(err):
635650 onexc (os .rmdir , path , err )
636651
637652# 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 ):
639673 # Each stack item has four elements:
640674 # * func: The first operation to perform: os.lstat, os.close or os.rmdir.
641675 # Walking a directory starts with an os.lstat() to detect symlinks; in
@@ -710,6 +744,7 @@ def _rmtree_safe_fd(stack, onexc):
710744 os .supports_dir_fd and
711745 os .scandir in os .supports_fd and
712746 os .stat in os .supports_follow_symlinks )
747+ _rmtree_impl = _rmtree_safe_fd if _use_fd_functions else _rmtree_unsafe
713748
714749def rmtree (path , ignore_errors = False , onerror = None , * , onexc = None , dir_fd = None ):
715750 """Recursively delete a directory tree.
@@ -753,41 +788,7 @@ def onexc(*args):
753788 exc_info = type (exc ), exc , exc .__traceback__
754789 return onerror (func , path , exc_info )
755790
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 )
791792
792793# Allow introspection of whether or not the hardening against symlink
793794# attacks is supported on the current platform
0 commit comments