diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2017-11-04 12:16:35 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-04 12:16:35 (GMT) |
commit | d4d79bc1ff91b04625c312f0219c89aabcd19ce4 (patch) | |
tree | ba24dc24106347ce69d12d5e27532ccfb929e50e /Lib | |
parent | 82cd3cede804ca694fb0657fd985d5eff84a414f (diff) | |
download | cpython-d4d79bc1ff91b04625c312f0219c89aabcd19ce4.zip cpython-d4d79bc1ff91b04625c312f0219c89aabcd19ce4.tar.gz cpython-d4d79bc1ff91b04625c312f0219c89aabcd19ce4.tar.bz2 |
bpo-28564: Use os.scandir() in shutil.rmtree(). (#4085)
This speeds up it to 20-40%.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/shutil.py | 73 | ||||
-rw-r--r-- | Lib/test/test_shutil.py | 2 |
2 files changed, 44 insertions, 31 deletions
diff --git a/Lib/shutil.py b/Lib/shutil.py index 464ee91..3c02776 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -362,25 +362,27 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, # version vulnerable to race conditions def _rmtree_unsafe(path, onerror): try: - if os.path.islink(path): - # symlinks to directories are forbidden, see bug #1669 - raise OSError("Cannot call rmtree on a symbolic link") + with os.scandir(path) as scandir_it: + entries = list(scandir_it) except OSError: - onerror(os.path.islink, path, sys.exc_info()) - # can't continue even if onerror hook returns - return - names = [] - try: - names = os.listdir(path) - except OSError: - onerror(os.listdir, path, sys.exc_info()) - for name in names: - fullname = os.path.join(path, name) + onerror(os.scandir, path, sys.exc_info()) + entries = [] + for entry in entries: + fullname = entry.path try: - mode = os.lstat(fullname).st_mode + is_dir = entry.is_dir(follow_symlinks=False) except OSError: - mode = 0 - if stat.S_ISDIR(mode): + is_dir = False + if is_dir: + try: + if entry.is_symlink(): + # This can only happen if someone replaces + # a directory with a symlink after the call to + # os.scandir or entry.is_dir above. + raise OSError("Cannot call rmtree on a symbolic link") + except OSError: + onerror(os.path.islink, fullname, sys.exc_info()) + continue _rmtree_unsafe(fullname, onerror) else: try: @@ -394,22 +396,25 @@ def _rmtree_unsafe(path, onerror): # Version using fd-based APIs to protect against races def _rmtree_safe_fd(topfd, path, onerror): - names = [] try: - names = os.listdir(topfd) + with os.scandir(topfd) as scandir_it: + entries = list(scandir_it) except OSError as err: err.filename = path - onerror(os.listdir, path, sys.exc_info()) - for name in names: - fullname = os.path.join(path, name) + onerror(os.scandir, path, sys.exc_info()) + return + for entry in entries: + fullname = os.path.join(path, entry.name) try: - orig_st = os.stat(name, dir_fd=topfd, follow_symlinks=False) - mode = orig_st.st_mode + is_dir = entry.is_dir(follow_symlinks=False) + if is_dir: + orig_st = entry.stat(follow_symlinks=False) + is_dir = stat.S_ISDIR(orig_st.st_mode) except OSError: - mode = 0 - if stat.S_ISDIR(mode): + is_dir = False + if is_dir: try: - dirfd = os.open(name, os.O_RDONLY, dir_fd=topfd) + dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd) except OSError: onerror(os.open, fullname, sys.exc_info()) else: @@ -417,14 +422,14 @@ def _rmtree_safe_fd(topfd, path, onerror): if os.path.samestat(orig_st, os.fstat(dirfd)): _rmtree_safe_fd(dirfd, fullname, onerror) try: - os.rmdir(name, dir_fd=topfd) + os.rmdir(entry.name, dir_fd=topfd) except OSError: onerror(os.rmdir, fullname, sys.exc_info()) else: try: # This can only happen if someone replaces # a directory with a symlink after the call to - # stat.S_ISDIR above. + # os.scandir or stat.S_ISDIR above. raise OSError("Cannot call rmtree on a symbolic " "link") except OSError: @@ -433,13 +438,13 @@ def _rmtree_safe_fd(topfd, path, onerror): os.close(dirfd) else: try: - os.unlink(name, dir_fd=topfd) + os.unlink(entry.name, dir_fd=topfd) except OSError: onerror(os.unlink, fullname, sys.exc_info()) _use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <= os.supports_dir_fd and - os.listdir in os.supports_fd and + os.scandir in os.supports_fd and os.stat in os.supports_follow_symlinks) def rmtree(path, ignore_errors=False, onerror=None): @@ -491,6 +496,14 @@ def rmtree(path, ignore_errors=False, onerror=None): finally: os.close(fd) else: + try: + if os.path.islink(path): + # symlinks to directories are forbidden, see bug #1669 + raise OSError("Cannot call rmtree on a symbolic link") + except OSError: + onerror(os.path.islink, path, sys.exc_info()) + # can't continue even if onerror hook returns + return return _rmtree_unsafe(path, onerror) # Allow introspection of whether or not the hardening against symlink diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 3b4891d..4a72b4a 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -183,7 +183,7 @@ class TestShutil(unittest.TestCase): errors.append(args) shutil.rmtree(filename, onerror=onerror) self.assertEqual(len(errors), 2) - self.assertIs(errors[0][0], os.listdir) + self.assertIs(errors[0][0], os.scandir) self.assertEqual(errors[0][1], filename) self.assertIsInstance(errors[0][2][1], NotADirectoryError) self.assertIn(errors[0][2][1].filename, possible_args) |