[Python-checkins] bpo-46245: Add optional parameter dir_fd in shutil.rmtree() (GH-30365)
serhiy-storchaka
webhook-mailer at python.org
Wed Mar 9 07:29:39 EST 2022
https://github.com/python/cpython/commit/02fbaf4887deaf0207a5805d3736e0124a694c14
commit: 02fbaf4887deaf0207a5805d3736e0124a694c14
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2022-03-09T14:29:33+02:00
summary:
bpo-46245: Add optional parameter dir_fd in shutil.rmtree() (GH-30365)
files:
A Misc/NEWS.d/next/Library/2022-01-03-20-12-14.bpo-46245.3w4RlA.rst
M Doc/library/shutil.rst
M Doc/whatsnew/3.11.rst
M Lib/shutil.py
M Lib/test/test_shutil.py
diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst
index 22d6dba9e1a9c..16b8d3cdeebc8 100644
--- a/Doc/library/shutil.rst
+++ b/Doc/library/shutil.rst
@@ -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
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index d9e5d0646836f..628d4c0aaa1e6 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -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
------
diff --git a/Lib/shutil.py b/Lib/shutil.py
index eb768f9e03b7d..22bd86d569e7e 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -684,9 +684,14 @@ def _rmtree_safe_fd(topfd, path, onerror):
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 onerror(*args):
# 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 onerror(*args):
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 onerror(*args):
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 7669b94ac35a6..7003386345280 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -405,6 +405,27 @@ def _raiser(*args, **kwargs):
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
index 0000000000000..43e8660b2a3d3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-01-03-20-12-14.bpo-46245.3w4RlA.rst
@@ -0,0 +1 @@
+Add optional parameter *dir_fd* in :func:`shutil.rmtree`.
More information about the Python-checkins
mailing list