@@ -575,12 +575,12 @@ def _rmtree_islink(path):
575575 return os .path .islink (path )
576576
577577# version vulnerable to race conditions
578- def _rmtree_unsafe (path , onerror ):
578+ def _rmtree_unsafe (path , onexc ):
579579 try :
580580 with os .scandir (path ) as scandir_it :
581581 entries = list (scandir_it )
582- except OSError :
583- onerror (os .scandir , path , sys . exc_info () )
582+ except OSError as err :
583+ onexc (os .scandir , path , err )
584584 entries = []
585585 for entry in entries :
586586 fullname = entry .path
@@ -596,28 +596,28 @@ def _rmtree_unsafe(path, onerror):
596596 # a directory with a symlink after the call to
597597 # os.scandir or entry.is_dir above.
598598 raise OSError ("Cannot call rmtree on a symbolic link" )
599- except OSError :
600- onerror (os .path .islink , fullname , sys . exc_info () )
599+ except OSError as err :
600+ onexc (os .path .islink , fullname , err )
601601 continue
602- _rmtree_unsafe (fullname , onerror )
602+ _rmtree_unsafe (fullname , onexc )
603603 else :
604604 try :
605605 os .unlink (fullname )
606- except OSError :
607- onerror (os .unlink , fullname , sys . exc_info () )
606+ except OSError as err :
607+ onexc (os .unlink , fullname , err )
608608 try :
609609 os .rmdir (path )
610- except OSError :
611- onerror (os .rmdir , path , sys . exc_info () )
610+ except OSError as err :
611+ onexc (os .rmdir , path , err )
612612
613613# Version using fd-based APIs to protect against races
614- def _rmtree_safe_fd (topfd , path , onerror ):
614+ def _rmtree_safe_fd (topfd , path , onexc ):
615615 try :
616616 with os .scandir (topfd ) as scandir_it :
617617 entries = list (scandir_it )
618618 except OSError as err :
619619 err .filename = path
620- onerror (os .scandir , path , sys . exc_info () )
620+ onexc (os .scandir , path , err )
621621 return
622622 for entry in entries :
623623 fullname = os .path .join (path , entry .name )
@@ -630,71 +630,89 @@ def _rmtree_safe_fd(topfd, path, onerror):
630630 try :
631631 orig_st = entry .stat (follow_symlinks = False )
632632 is_dir = stat .S_ISDIR (orig_st .st_mode )
633- except OSError :
634- onerror (os .lstat , fullname , sys . exc_info () )
633+ except OSError as err :
634+ onexc (os .lstat , fullname , err )
635635 continue
636636 if is_dir :
637637 try :
638638 dirfd = os .open (entry .name , os .O_RDONLY , dir_fd = topfd )
639639 dirfd_closed = False
640- except OSError :
641- onerror (os .open , fullname , sys . exc_info () )
640+ except OSError as err :
641+ onexc (os .open , fullname , err )
642642 else :
643643 try :
644644 if os .path .samestat (orig_st , os .fstat (dirfd )):
645- _rmtree_safe_fd (dirfd , fullname , onerror )
645+ _rmtree_safe_fd (dirfd , fullname , onexc )
646646 try :
647647 os .close (dirfd )
648648 dirfd_closed = True
649649 os .rmdir (entry .name , dir_fd = topfd )
650- except OSError :
651- onerror (os .rmdir , fullname , sys . exc_info () )
650+ except OSError as err :
651+ onexc (os .rmdir , fullname , err )
652652 else :
653653 try :
654654 # This can only happen if someone replaces
655655 # a directory with a symlink after the call to
656656 # os.scandir or stat.S_ISDIR above.
657657 raise OSError ("Cannot call rmtree on a symbolic "
658658 "link" )
659- except OSError :
660- onerror (os .path .islink , fullname , sys . exc_info () )
659+ except OSError as err :
660+ onexc (os .path .islink , fullname , err )
661661 finally :
662662 if not dirfd_closed :
663663 os .close (dirfd )
664664 else :
665665 try :
666666 os .unlink (entry .name , dir_fd = topfd )
667- except OSError :
668- onerror (os .unlink , fullname , sys . exc_info () )
667+ except OSError as err :
668+ onexc (os .unlink , fullname , err )
669669
670670_use_fd_functions = ({os .open , os .stat , os .unlink , os .rmdir } <=
671671 os .supports_dir_fd and
672672 os .scandir in os .supports_fd and
673673 os .stat in os .supports_follow_symlinks )
674674
675- def rmtree (path , ignore_errors = False , onerror = None , * , dir_fd = None ):
675+ def rmtree (path , ignore_errors = False , onerror = None , * , onexc = None , dir_fd = None ):
676676 """Recursively delete a directory tree.
677677
678678 If dir_fd is not None, it should be a file descriptor open to a directory;
679679 path will then be relative to that directory.
680680 dir_fd may not be implemented on your platform.
681681 If it is unavailable, using it will raise a NotImplementedError.
682682
683- If ignore_errors is set, errors are ignored; otherwise, if onerror
684- is set, it is called to handle the error with arguments (func,
683+ If ignore_errors is set, errors are ignored; otherwise, if onexc or
684+ onerror is set, it is called to handle the error with arguments (func,
685685 path, exc_info) where func is platform and implementation dependent;
686686 path is the argument to that function that caused it to fail; and
687- exc_info is a tuple returned by sys.exc_info(). If ignore_errors
688- is false and onerror is None, an exception is raised.
687+ the value of exc_info describes the exception. For onexc it is the
688+ exception instance, and for onerror it is a tuple as returned by
689+ sys.exc_info(). If ignore_errors is false and both onexc and
690+ onerror are None, the exception is reraised.
689691
692+ onerror is deprecated and only remains for backwards compatibility.
693+ If both onerror and onexc are set, onerror is ignored and onexc is used.
690694 """
691695 sys .audit ("shutil.rmtree" , path , dir_fd )
692696 if ignore_errors :
693- def onerror (* args ):
697+ def onexc (* args ):
694698 pass
695- elif onerror is None :
696- def onerror (* args ):
699+ elif onerror is None and onexc is None :
700+ def onexc (* args ):
697701 raise
702+ elif onexc is None :
703+ if onerror is None :
704+ def onexc (* args ):
705+ raise
706+ else :
707+ # delegate to onerror
708+ def onexc (* args ):
709+ func , path , exc = args
710+ if exc is None :
711+ exc_info = None , None , None
712+ else :
713+ exc_info = type (exc ), exc , exc .__traceback__
714+ return onerror (func , path , exc_info )
715+
698716 if _use_fd_functions :
699717 # While the unsafe rmtree works fine on bytes, the fd based does not.
700718 if isinstance (path , bytes ):
@@ -703,30 +721,30 @@ def onerror(*args):
703721 # lstat()/open()/fstat() trick.
704722 try :
705723 orig_st = os .lstat (path , dir_fd = dir_fd )
706- except Exception :
707- onerror (os .lstat , path , sys . exc_info () )
724+ except Exception as err :
725+ onexc (os .lstat , path , err )
708726 return
709727 try :
710728 fd = os .open (path , os .O_RDONLY , dir_fd = dir_fd )
711729 fd_closed = False
712- except Exception :
713- onerror (os .open , path , sys . exc_info () )
730+ except Exception as err :
731+ onexc (os .open , path , err )
714732 return
715733 try :
716734 if os .path .samestat (orig_st , os .fstat (fd )):
717- _rmtree_safe_fd (fd , path , onerror )
735+ _rmtree_safe_fd (fd , path , onexc )
718736 try :
719737 os .close (fd )
720738 fd_closed = True
721739 os .rmdir (path , dir_fd = dir_fd )
722- except OSError :
723- onerror (os .rmdir , path , sys . exc_info () )
740+ except OSError as err :
741+ onexc (os .rmdir , path , err )
724742 else :
725743 try :
726744 # symlinks to directories are forbidden, see bug #1669
727745 raise OSError ("Cannot call rmtree on a symbolic link" )
728- except OSError :
729- onerror (os .path .islink , path , sys . exc_info () )
746+ except OSError as err :
747+ onexc (os .path .islink , path , err )
730748 finally :
731749 if not fd_closed :
732750 os .close (fd )
@@ -737,11 +755,11 @@ def onerror(*args):
737755 if _rmtree_islink (path ):
738756 # symlinks to directories are forbidden, see bug #1669
739757 raise OSError ("Cannot call rmtree on a symbolic link" )
740- except OSError :
741- onerror (os .path .islink , path , sys . exc_info () )
742- # can't continue even if onerror hook returns
758+ except OSError as err :
759+ onexc (os .path .islink , path , err )
760+ # can't continue even if onexc hook returns
743761 return
744- return _rmtree_unsafe (path , onerror )
762+ return _rmtree_unsafe (path , onexc )
745763
746764# Allow introspection of whether or not the hardening against symlink
747765# attacks is supported on the current platform
0 commit comments