summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_os.py43
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst1
-rw-r--r--Modules/posixmodule.c12
4 files changed, 56 insertions, 1 deletions
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 7f7d14e..1243b57 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -23,6 +23,7 @@ import subprocess
import sys
import sysconfig
import tempfile
+import textwrap
import threading
import time
import types
@@ -2859,6 +2860,48 @@ class Win32NtTests(unittest.TestCase):
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 = 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):
diff --git a/Misc/ACKS b/Misc/ACKS
index c6abfac..889bf8a 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1694,6 +1694,7 @@ Anthony Starks
David Steele
Oliver Steele
Greg Stein
+Itai Steinherz
Marek Stepniowski
Baruch Sterin
Chris Stern
diff --git a/Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst b/Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst
new file mode 100644
index 0000000..0a87abd
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst
@@ -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``.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 03de470..26c8e7b 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -1905,7 +1905,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) {