8000 bpo-46785: Fix race condition between os.stat() and unlink on Windows… · python/cpython@1fb25a9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1fb25a9

Browse files
bpo-46785: Fix race condition between os.stat() and unlink on Windows (GH-31858)
* [3.9] bpo-46785: Fix race condition between os.stat() and unlink on Windows (GH-31858). (cherry picked from commit 39e6b8a) Co-authored-by: Itai Steinherz <itaisteinherz@gmail.com>
1 parent 249be82 commit 1fb25a9

File tree

4 files changed

+56
-1
lines changed

4 files changed

+56
-1
lines changed

Lib/test/test_os.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import sys
2424
import sysconfig
2525
import tempfile
26+
import textwrap
2627
import threading
2728
import time
2829
import types
@@ -2714,6 +2715,48 @@ def test_getfinalpathname_handles(self):
27142715

27152716
self.assertEqual(0, handle_delta)
27162717

2718+
def test_stat_unlink_race(self):
2719+
# bpo-46785: the implementation of os.stat() falls back to reading
2720+
# the parent directory if CreateFileW() fails with a permission
2721+
# error. If reading the parent directory fails because the file or
2722+
# directory are subsequently unlinked, or because the volume or
2723+
# share are no longer available, then the original permission error
2724+
# should not be restored.
2725+
filename = support.TESTFN
2726+
self.addCleanup(support.unlink, filename)
2727+
deadline = time.time() + 5
2728+
command = textwrap.dedent("""\
2729+
import os
2730+
import sys
2731+
import time
2732+
2733+
filename = sys.argv[1]
2734+
deadline = float(sys.argv[2])
2735+
2736+
while time.time() < deadline:
2737+
try:
2738+
with open(filename, "w") as f:
2739+
pass
2740+
except OSError:
2741+
pass
2742+
try:
2743+
os.remove(filename)
2744+
except OSError:
2745+
pass
2746+
""")
2747+
2748+
with subprocess.Popen([sys.executable, '-c', command, filename, str(deadline)]) as proc:
2749+
while time.time() < deadline:
2750+
try:
2751+
os.stat(filename)
2752+
except FileNotFoundError as e:
2753+
assert e.winerror == 2 # ERROR_FILE_NOT_FOUND
2754+
try:
2755+
proc.wait(1)
2756+
except subprocess.TimeoutExpired:
2757+
proc.terminate()
2758+
2759+
27172760
@support.skip_unless_symlink
27182761
class NonLocalSymlinkTests(unittest.TestCase):
27192762

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,7 @@ Anthony Starks
16641664
David Steele
16651665
Oliver Steele
16661666
Greg Stein
1667+
Itai Steinherz
16671668
Marek Stepniowski
16681669
Baruch Sterin
16691670
Chris Stern
Lines changed: 1 addition & 0 deletions
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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,17 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
18531853
/* Try reading the parent directory. */
18541854
if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
18551855
/* Cannot read the parent directory. */
1856-
SetLastError(error);
1856+
switch (GetLastError()) {
1857+
case ERROR_FILE_NOT_FOUND: /* File cannot be found */
1858+
case ERROR_PATH_NOT_FOUND: /* File parent directory cannot be found */
1859+
case ERROR_NOT_READY: /* Drive exists but unavailable */
1860+
case ERROR_BAD_NET_NAME: /* Remote drive unavailable */
1861+
break;
1862+
/* Restore the error from CreateFileW(). */
1863+
default:
1864+
SetLastError(error);
1865+
}
1866+
18571867
return -1;
18581868
}
18591869
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {

0 commit comments

Comments
 (0)
0