Skip to content

Commit 39e6b8a

Browse files
bpo-46785: Fix race condition between os.stat() and unlink on Windows (GH-31858)
1 parent ebb8b51 commit 39e6b8a

File tree

4 files changed

+57
-1
lines changed

4 files changed

+57
-1
lines changed

Lib/test/test_os.py

+44
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import sys
2525
import sysconfig
2626
import tempfile
27+
import textwrap
2728
import time
2829
import types
2930
import unittest
@@ -2883,6 +2884,49 @@ def test_getfinalpathname_handles(self):
28832884

28842885
self.assertEqual(0, handle_delta)
28852886

2887+
@support.requires_subprocess()
2888+
def test_stat_unlink_race(self):
2889+
# bpo-46785: the implementation of os.stat() falls back to reading
2890+
# the parent directory if CreateFileW() fails with a permission
2891+
# error. If reading the parent directory fails because the file or
2892+
# directory are subsequently unlinked, or because the volume or
2893+
# share are no longer available, then the original permission error
2894+
# should not be restored.
2895+
filename = os_helper.TESTFN
2896+
self.addCleanup(os_helper.unlink, filename)
2897+
deadline = time.time() + 5
2898+
command = textwrap.dedent("""\
2899+
import os
2900+
import sys
2901+
import time
2902+
2903+
filename = sys.argv[1]
2904+
deadline = float(sys.argv[2])
2905+
2906+
while time.time() < deadline:
2907+
try:
2908+
with open(filename, "w") as f:
2909+
pass
2910+
except OSError:
2911+
pass
2912+
try:
2913+
os.remove(filename)
2914+
except OSError:
2915+
pass
2916+
""")
2917+
2918+
with subprocess.Popen([sys.executable, '-c', command, filename, str(deadline)]) as proc:
2919+
while time.time() < deadline:
2920+
try:
2921+
os.stat(filename)
2922+
except FileNotFoundError as e:
2923+
assert e.winerror == 2 # ERROR_FILE_NOT_FOUND
2924+
try:
2925+
proc.wait(1)
2926+
except subprocess.TimeoutExpired:
2927+
proc.terminate()
2928+
2929+
28862930
@os_helper.skip_unless_symlink
28872931
class NonLocalSymlinkTests(unittest.TestCase):
28882932

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,7 @@ Anthony Starks
17051705
David Steele
17061706
Oliver Steele
17071707
Greg Stein
1708+
Itai Steinherz
17081709
Marek Stepniowski
17091710
Baruch Sterin
17101711
Chris Stern
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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``.

Modules/posixmodule.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -1890,7 +1890,17 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
18901890
/* Try reading the parent directory. */
18911891
if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
18921892
/* Cannot read the parent directory. */
1893-
SetLastError(error);
1893+
switch (GetLastError()) {
1894+
case ERROR_FILE_NOT_FOUND: /* File cannot be found */
1895+
case ERROR_PATH_NOT_FOUND: /* File parent directory cannot be found */
1896+
case ERROR_NOT_READY: /* Drive exists but unavailable */
1897+
case ERROR_BAD_NET_NAME: /* Remote drive unavailable */
1898+
break;
1899+
/* Restore the error from CreateFileW(). */
1900+
default:
1901+
SetLastError(error);
1902+
}
1903+
18941904
return -1;
18951905
}
18961906
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {

0 commit comments

Comments
 (0)