]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-46245: Add optional parameter dir_fd in shutil.rmtree() (GH-30365)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 9 Mar 2022 12:29:33 +0000 (14:29 +0200)
committerGitHub <noreply@github.com>
Wed, 9 Mar 2022 12:29:33 +0000 (14:29 +0200)
Doc/library/shutil.rst
Doc/whatsnew/3.11.rst
Lib/shutil.py
Lib/test/test_shutil.py
Misc/NEWS.d/next/Library/2022-01-03-20-12-14.bpo-46245.3w4RlA.rst [new file with mode: 0644]

index 22d6dba9e1a9c61afa6294b869acd7269f718359..16b8d3cdeebc8489711578dd4968bf8e28ddbe95 100644 (file)
@@ -286,7 +286,7 @@ Directory and files operations
    .. versionadded:: 3.8
       The *dirs_exist_ok* parameter.
 
-.. function:: rmtree(path, ignore_errors=False, onerror=None)
+.. function:: rmtree(path, ignore_errors=False, onerror=None, *, dir_fd=None)
 
    .. index:: single: directory; deleting
 
@@ -296,6 +296,9 @@ Directory and files operations
    handled by calling a handler specified by *onerror* or, if that is omitted,
    they raise an exception.
 
+   This function can support :ref:`paths relative to directory descriptors
+   <dir_fd>`.
+
    .. note::
 
       On platforms that support the necessary fd-based functions a symlink
@@ -315,7 +318,7 @@ Directory and files operations
    *excinfo*, will be the exception information returned by
    :func:`sys.exc_info`.  Exceptions raised by *onerror* will not be caught.
 
-   .. audit-event:: shutil.rmtree path shutil.rmtree
+   .. audit-event:: shutil.rmtree path,dir_fd shutil.rmtree
 
    .. versionchanged:: 3.3
       Added a symlink attack resistant version that is used automatically
@@ -325,6 +328,9 @@ Directory and files operations
       On Windows, will no longer delete the contents of a directory junction
       before removing the junction.
 
+   .. versionchanged:: 3.11
+      The *dir_fd* parameter.
+
    .. attribute:: rmtree.avoids_symlink_attacks
 
       Indicates whether the current platform and implementation provides a
index d9e5d0646836fbe087ccafe67f931aa9ec1e7452..628d4c0aaa1e6771e44f77b6a7c02ec6442bf77b 100644 (file)
@@ -283,6 +283,13 @@ os
   (Contributed by Dong-hee Na in :issue:`44611`.)
 
 
+shutil
+------
+
+* Add optional parameter *dir_fd* in :func:`shutil.rmtree`.
+  (Contributed by Serhiy Storchaka in :issue:`46245`.)
+
+
 socket
 ------
 
index eb768f9e03b7d79bee5a43ab6feae3cdaeb35af4..22bd86d569e7ede9bedfd55af69c3f06613cce58 100644 (file)
@@ -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
index 7669b94ac35a688e3244641ea8449253865ea9f9..700338634528058f50891afe0b09fa411c2580ea 100644 (file)
@@ -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())
diff --git a/Misc/NEWS.d/next/Library/2022-01-03-20-12-14.bpo-46245.3w4RlA.rst b/Misc/NEWS.d/next/Library/2022-01-03-20-12-14.bpo-46245.3w4RlA.rst
new file mode 100644 (file)
index 0000000..43e8660
--- /dev/null
@@ -0,0 +1 @@
+Add optional parameter *dir_fd* in :func:`shutil.rmtree`.