Skip to content
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
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