Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-46245: Add optional parameter dir_fd in shutil.rmtree() #30365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Doc/library/shutil.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,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
------

Expand Down
17 changes: 12 additions & 5 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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())
Expand All @@ -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:
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add optional parameter *dir_fd* in :func:`shutil.rmtree`.