Skip to content

Commit

Permalink
bpo-46785: Fix race condition between os.stat() and unlink on Windows (
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
itaisteinherz committed May 9, 2022
1 parent 249be82 commit 1fb25a9
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 1 deletion.
43 changes: 43 additions & 0 deletions Lib/test/test_os.py
Expand Up @@ -23,6 +23,7 @@
import sys
import sysconfig
import tempfile
import textwrap
import threading
import time
import types
Expand Down Expand Up @@ -2714,6 +2715,48 @@ def test_getfinalpathname_handles(self):

self.assertEqual(0, handle_delta)

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 = support.TESTFN
self.addCleanup(support.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()


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

Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Expand Up @@ -1664,6 +1664,7 @@ Anthony Starks
David Steele
Oliver Steele
Greg Stein
Itai Steinherz
Marek Stepniowski
Baruch Sterin
Chris Stern
Expand Down
@@ -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
Expand Up @@ -1853,7 +1853,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 comments on commit 1fb25a9

Please sign in to comment.