@@ -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 |
|