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:
Jeffrey Kintscher 2023-12-05 01:33:51 -08:00 committed by GitHub
parent b31232ddf7
commit 268415bbb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 97 additions and 11 deletions

View file

@ -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):