diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/shutil.py | 17 | ||||
-rw-r--r-- | Lib/test/test_shutil.py | 21 |
2 files changed, 33 insertions, 5 deletions
diff --git a/Lib/shutil.py b/Lib/shutil.py index eb768f9..22bd86d 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -684,9 +684,14 @@ _use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <= os.scandir in os.supports_fd and os.stat in os.supports_follow_symlinks) -def rmtree(path, ignore_errors=False, onerror=None): +def rmtree(path, ignore_errors=False, onerror=None, *, dir_fd=None): """Recursively delete a directory tree. + If dir_fd is not None, it should be a file descriptor open to a directory; + path will then be relative to that directory. + dir_fd may not be implemented on your platform. + If it is unavailable, using it will raise a NotImplementedError. + If ignore_errors is set, errors are ignored; otherwise, if onerror is set, it is called to handle the error with arguments (func, path, exc_info) where func is platform and implementation dependent; @@ -695,7 +700,7 @@ def rmtree(path, ignore_errors=False, onerror=None): is false and onerror is None, an exception is raised. """ - sys.audit("shutil.rmtree", path) + sys.audit("shutil.rmtree", path, dir_fd) if ignore_errors: def onerror(*args): pass @@ -709,12 +714,12 @@ def rmtree(path, ignore_errors=False, onerror=None): # Note: To guard against symlink races, we use the standard # lstat()/open()/fstat() trick. try: - orig_st = os.lstat(path) + orig_st = os.lstat(path, dir_fd=dir_fd) except Exception: onerror(os.lstat, path, sys.exc_info()) return try: - fd = os.open(path, os.O_RDONLY) + fd = os.open(path, os.O_RDONLY, dir_fd=dir_fd) fd_closed = False except Exception: onerror(os.open, path, sys.exc_info()) @@ -725,7 +730,7 @@ def rmtree(path, ignore_errors=False, onerror=None): try: os.close(fd) fd_closed = True - os.rmdir(path) + os.rmdir(path, dir_fd=dir_fd) except OSError: onerror(os.rmdir, path, sys.exc_info()) else: @@ -738,6 +743,8 @@ def rmtree(path, ignore_errors=False, onerror=None): if not fd_closed: os.close(fd) else: + if dir_fd is not None: + raise NotImplementedError("dir_fd unavailable on this platform") try: if _rmtree_islink(path): # symlinks to directories are forbidden, see bug #1669 diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7669b94..7003386 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -405,6 +405,27 @@ class TestRmTree(BaseTest, unittest.TestCase): self.assertFalse(shutil._use_fd_functions) self.assertFalse(shutil.rmtree.avoids_symlink_attacks) + @unittest.skipUnless(shutil._use_fd_functions, "dir_fd is not supported") + def test_rmtree_with_dir_fd(self): + tmp_dir = self.mkdtemp() + victim = 'killme' + fullname = os.path.join(tmp_dir, victim) + dir_fd = os.open(tmp_dir, os.O_RDONLY) + self.addCleanup(os.close, dir_fd) + os.mkdir(fullname) + os.mkdir(os.path.join(fullname, 'subdir')) + write_file(os.path.join(fullname, 'subdir', 'somefile'), 'foo') + self.assertTrue(os.path.exists(fullname)) + shutil.rmtree(victim, dir_fd=dir_fd) + self.assertFalse(os.path.exists(fullname)) + + @unittest.skipIf(shutil._use_fd_functions, "dir_fd is supported") + def test_rmtree_with_dir_fd_unsupported(self): + tmp_dir = self.mkdtemp() + with self.assertRaises(NotImplementedError): + shutil.rmtree(tmp_dir, dir_fd=0) + self.assertTrue(os.path.exists(tmp_dir)) + def test_rmtree_dont_delete_file(self): # When called on a file instead of a directory, don't delete it. handle, path = tempfile.mkstemp(dir=self.mkdtemp()) |