mirror of https://github.com/python/cpython
bpo-46245: Add optional parameter dir_fd in shutil.rmtree() (GH-30365)
This commit is contained in:
parent
5eb03b1b51
commit
02fbaf4887
|
@ -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
|
||||
|
|
|
@ -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
|
||||
------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add optional parameter *dir_fd* in :func:`shutil.rmtree`.
|
Loading…
Reference in New Issue