mirror of
https://github.com/python/cpython.git
synced 2025-08-30 13:38:43 +00:00
gh-81441: shutil.rmtree() FileNotFoundError race condition (GH-14064)
Ignore missing files and directories while enumerating directory entries in shutil.rmtree(). Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
b31232ddf7
commit
268415bbb3
4 changed files with 97 additions and 11 deletions
|
@ -633,6 +633,63 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||
finally:
|
||||
shutil.rmtree(TESTFN, ignore_errors=True)
|
||||
|
||||
@unittest.skipIf(sys.platform[:6] == 'cygwin',
|
||||
"This test can't be run on Cygwin (issue #1071513).")
|
||||
@os_helper.skip_if_dac_override
|
||||
@os_helper.skip_unless_working_chmod
|
||||
def test_rmtree_deleted_race_condition(self):
|
||||
# bpo-37260
|
||||
#
|
||||
# Test that a file or a directory deleted after it is enumerated
|
||||
# by scandir() but before unlink() or rmdr() is called doesn't
|
||||
# generate any errors.
|
||||
def _onexc(fn, path, exc):
|
||||
assert fn in (os.rmdir, os.unlink)
|
||||
if not isinstance(exc, PermissionError):
|
||||
raise
|
||||
# Make the parent and the children writeable.
|
||||
for p, mode in zip(paths, old_modes):
|
||||
os.chmod(p, mode)
|
||||
# Remove other dirs except one.
|
||||
keep = next(p for p in dirs if p != path)
|
||||
for p in dirs:
|
||||
if p != keep:
|
||||
os.rmdir(p)
|
||||
# Remove other files except one.
|
||||
keep = next(p for p in files if p != path)
|
||||
for p in files:
|
||||
if p != keep:
|
||||
os.unlink(p)
|
||||
|
||||
os.mkdir(TESTFN)
|
||||
paths = [TESTFN] + [os.path.join(TESTFN, f'child{i}')
|
||||
for i in range(6)]
|
||||
dirs = paths[1::2]
|
||||
files = paths[2::2]
|
||||
for path in dirs:
|
||||
os.mkdir(path)
|
||||
for path in files:
|
||||
write_file(path, '')
|
||||
|
||||
old_modes = [os.stat(path).st_mode for path in paths]
|
||||
|
||||
# Make the parent and the children non-writeable.
|
||||
new_mode = stat.S_IREAD|stat.S_IEXEC
|
||||
for path in reversed(paths):
|
||||
os.chmod(path, new_mode)
|
||||
|
||||
try:
|
||||
shutil.rmtree(TESTFN, onexc=_onexc)
|
||||
except:
|
||||
# Test failed, so cleanup artifacts.
|
||||
for path, mode in zip(paths, old_modes):
|
||||
try:
|
||||
os.chmod(path, mode)
|
||||
except OSError:
|
||||
pass
|
||||
shutil.rmtree(TESTFN)
|
||||
raise
|
||||
|
||||
|
||||
class TestCopyTree(BaseTest, unittest.TestCase):
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue