https://github.com/python/cpython/commit/ed066481c76c6888ff5709f5b9f93b92c232a4a6
commit: ed066481c76c6888ff5709f5b9f93b92c232a4a6
branch: main
author: Steve Dower <[email protected]>
committer: zooba <[email protected]>
date: 2024-01-12T15:27:56Z
summary:
gh-111877: Fixes stat() handling for inaccessible files on Windows (GH-113716)
files:
A Misc/NEWS.d/next/Windows/2024-01-04-21-16-31.gh-issue-111877.fR-B4c.rst
M Lib/test/test_os.py
M Modules/posixmodule.c
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index bff6e604cccdd6..98b30d2108a1a1 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -3085,6 +3085,66 @@ def test_stat_unlink_race(self):
except subprocess.TimeoutExpired:
proc.terminate()
+ @support.requires_subprocess()
+ def test_stat_inaccessible_file(self):
+ filename = os_helper.TESTFN
+ ICACLS = os.path.expandvars(r"%SystemRoot%\System32\icacls.exe")
+
+ with open(filename, "wb") as f:
+ f.write(b'Test data')
+
+ stat1 = os.stat(filename)
+
+ try:
+ # Remove all permissions from the file
+ subprocess.check_output([ICACLS, filename, "/inheritance:r"],
+ stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as ex:
+ if support.verbose:
+ print(ICACLS, filename, "/inheritance:r", "failed.")
+ print(ex.stdout.decode("oem", "replace").rstrip())
+ try:
+ os.unlink(filename)
+ except OSError:
+ pass
+ self.skipTest("Unable to create inaccessible file")
+
+ def cleanup():
+ # Give delete permission. We are the file owner, so we can do this
+ # even though we removed all permissions earlier.
+ subprocess.check_output([ICACLS, filename, "/grant",
"Everyone:(D)"],
+ stderr=subprocess.STDOUT)
+ os.unlink(filename)
+
+ self.addCleanup(cleanup)
+
+ if support.verbose:
+ print("File:", filename)
+ print("stat with access:", stat1)
+
+ # First test - we shouldn't raise here, because we still have access to
+ # the directory and can extract enough information from its metadata.
+ stat2 = os.stat(filename)
+
+ if support.verbose:
+ print(" without access:", stat2)
+
+ # We cannot get st_dev/st_ino, so ensure those are 0 or else our test
+ # is not set up correctly
+ self.assertEqual(0, stat2.st_dev)
+ self.assertEqual(0, stat2.st_ino)
+
+ # st_mode and st_size should match (for a normal file, at least)
+ self.assertEqual(stat1.st_mode, stat2.st_mode)
+ self.assertEqual(stat1.st_size, stat2.st_size)
+
+ # st_ctime and st_mtime should be the same
+ self.assertEqual(stat1.st_ctime, stat2.st_ctime)
+ self.assertEqual(stat1.st_mtime, stat2.st_mtime)
+
+ # st_atime should be the same or later
+ self.assertGreaterEqual(stat1.st_atime, stat2.st_atime)
+
@os_helper.skip_unless_symlink
class NonLocalSymlinkTests(unittest.TestCase):
diff --git
a/Misc/NEWS.d/next/Windows/2024-01-04-21-16-31.gh-issue-111877.fR-B4c.rst
b/Misc/NEWS.d/next/Windows/2024-01-04-21-16-31.gh-issue-111877.fR-B4c.rst
new file mode 100644
index 00000000000000..99ed8d34af7cc2
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2024-01-04-21-16-31.gh-issue-111877.fR-B4c.rst
@@ -0,0 +1,2 @@
+:func:`os.stat` calls were returning incorrect time values for files that
+could not be accessed directly.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 179497a21b5a95..007fc1cb116f84 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -1886,8 +1886,9 @@ win32_xstat_slow_impl(const wchar_t *path, struct
_Py_stat_struct *result,
HANDLE hFile;
BY_HANDLE_FILE_INFORMATION fileInfo;
FILE_BASIC_INFO basicInfo;
+ FILE_BASIC_INFO *pBasicInfo = NULL;
FILE_ID_INFO idInfo;
- FILE_ID_INFO *pIdInfo = &idInfo;
+ FILE_ID_INFO *pIdInfo = NULL;
FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };
DWORD fileType, error;
BOOL isUnhandledTag = FALSE;
@@ -2038,14 +2039,17 @@ win32_xstat_slow_impl(const wchar_t *path, struct
_Py_stat_struct *result,
retval = -1;
goto cleanup;
}
- }
- if (!GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo,
sizeof(idInfo))) {
- /* Failed to get FileIdInfo, so do not pass it along */
- pIdInfo = NULL;
+ /* Successfully got FileBasicInfo, so we'll pass it along */
+ pBasicInfo = &basicInfo;
+
+ if (GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo,
sizeof(idInfo))) {
+ /* Successfully got FileIdInfo, so pass it along */
+ pIdInfo = &idInfo;
+ }
}
- _Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, &basicInfo,
pIdInfo, result);
+ _Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, pBasicInfo,
pIdInfo, result);
update_st_mode_from_path(path, fileInfo.dwFileAttributes, result);
cleanup:
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]