8000 bpo-46785: Fix race condition between `os.stat()` and unlink by itaisteinherz · Pull Request #31858 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-46785: Fix race condition between os.stat() and unlink #31858

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
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
44 changes: 44 additions & 0 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import sys
import sysconfig
import tempfile
import textwrap
import time
import types
import unittest
Expand Down Expand Up @@ -2878,6 +2879,49 @@ def test_getfinalpathname_handles(self):

self.assertEqual(0, handle_delta)

@support.requires_subprocess()
def test_stat_unlink_race(self):
# bpo-46785: the implementation of os.stat() falls back to reading
# the parent directory if CreateFileW() fails with a permission
# error. If reading the parent directory fails because the file or
# directory are subsequently unlinked, or because the volume or
# share are no longer available, then the original permission error
# should not be restored.
filename = os_helper.TESTFN
self.addCleanup(os_helper.unlink, filename)
deadline = time.time() + 5
command = textwrap.dedent("""\
import os
import sys
import time

filename = sys.argv[1]
deadline = float(sys.argv[2])

while time.time() < deadline:
try:
with open(filename, "w") as f:
pass
except OSError:
pass
try:
os.remove(filename)
except OSError:
pass
""")

with subprocess.Popen([sys.executable, '-c', command, filename, str(deadline)]) as proc:
while time.time() < deadline:
try:
os.stat(filename)
except FileNotFoundError as e:
assert e.winerror == 2 # ERROR_FILE_NOT_FOUND
try:
proc.wait(1)
except subprocess.TimeoutExpired:
proc.terminate()


@os_helper.skip_unless_symlink
class NonLocalSymlinkTests(unittest.TestCase):

Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,7 @@ Anthony Starks
David Steele
Oliver Steele
Greg Stein
Itai Steinherz
Marek Stepniowski
Baruch Sterin
Chris Stern
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix race condition between :func:`os.stat` and unlinking a file on Windows, by using errors codes returned by ``FindFirstFileW()`` when appropriate in ``win32_xstat_impl``.
12 changes: 11 additions & 1 deletion Modules/posixmodule.c
5BED
Original file line number Diff line number Diff line change
Expand Up @@ -1888,7 +1888,17 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
/* Try reading the parent directory. */
if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
/* Cannot read the parent directory. */
SetLastError(error);
switch (GetLastError()) {
case ERROR_FILE_NOT_FOUND: /* File cannot be found */
case ERROR_PATH_NOT_FOUND: /* File parent directory cannot be found */
case ERROR_NOT_READY: /* Drive exists but unavailable */
case ERROR_BAD_NET_NAME: /* Remote drive unavailable */
break;
/* Restore the error from CreateFileW(). */
default:
SetLastError(error);
}

return -1;
}
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
Expand Down
0