Skip to content

Commit 7b7cf75

Browse files
gh-111877: Fixes stat() handling for inaccessible files on Windows (GH-113716)
(cherry picked from commit ed06648) Co-authored-by: Steve Dower <[email protected]>
1 parent eb22afb commit 7b7cf75

File tree

3 files changed

+72
-6
lines changed

3 files changed

+72
-6
lines changed

Lib/test/test_os.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3077,6 +3077,66 @@ def test_stat_unlink_race(self):
30773077
except subprocess.TimeoutExpired:
30783078
proc.terminate()
30793079

3080+
@support.requires_subprocess()
3081+
def test_stat_inaccessible_file(self):
3082+
filename = os_helper.TESTFN
3083+
ICACLS = os.path.expandvars(r"%SystemRoot%\System32\icacls.exe")
3084+
3085+
with open(filename, "wb") as f:
3086+
f.write(b'Test data')
3087+
3088+
stat1 = os.stat(filename)
3089+
3090+
try:
3091+
# Remove all permissions from the file
3092+
subprocess.check_output([ICACLS, filename, "/inheritance:r"],
3093+
stderr=subprocess.STDOUT)
3094+
except subprocess.CalledProcessError as ex:
3095+
if support.verbose:
3096+
print(ICACLS, filename, "/inheritance:r", "failed.")
3097+
print(ex.stdout.decode("oem", "replace").rstrip())
3098+
try:
3099+
os.unlink(filename)
3100+
except OSError:
3101+
pass
3102+
self.skipTest("Unable to create inaccessible file")
3103+
3104+
def cleanup():
3105+
# Give delete permission. We are the file owner, so we can do this
3106+
# even though we removed all permissions earlier.
3107+
subprocess.check_output([ICACLS, filename, "/grant", "Everyone:(D)"],
3108+
stderr=subprocess.STDOUT)
3109+
os.unlink(filename)
3110+
3111+
self.addCleanup(cleanup)
3112+
3113+
if support.verbose:
3114+
print("File:", filename)
3115+
print("stat with access:", stat1)
3116+
3117+
# First test - we shouldn't raise here, because we still have access to
3118+
# the directory and can extract enough information from its metadata.
3119+
stat2 = os.stat(filename)
3120+
3121+
if support.verbose:
3122+
print(" without access:", stat2)
3123+
3124+
# We cannot get st_dev/st_ino, so ensure those are 0 or else our test
3125+
# is not set up correctly
3126+
self.assertEqual(0, stat2.st_dev)
3127+
self.assertEqual(0, stat2.st_ino)
3128+
3129+
# st_mode and st_size should match (for a normal file, at least)
3130+
self.assertEqual(stat1.st_mode, stat2.st_mode)
3131+
self.assertEqual(stat1.st_size, stat2.st_size)
3132+
3133+
# st_ctime and st_mtime should be the same
3134+
self.assertEqual(stat1.st_ctime, stat2.st_ctime)
3135+
self.assertEqual(stat1.st_mtime, stat2.st_mtime)
3136+
3137+
# st_atime should be the same or later
3138+
self.assertGreaterEqual(stat1.st_atime, stat2.st_atime)
3139+
30803140

30813141
@os_helper.skip_unless_symlink
30823142
class NonLocalSymlinkTests(unittest.TestCase):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`os.stat` calls were returning incorrect time values for files that
2+
could not be accessed directly.

Modules/posixmodule.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,8 +1864,9 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
18641864
HANDLE hFile;
18651865
BY_HANDLE_FILE_INFORMATION fileInfo;
18661866
FILE_BASIC_INFO basicInfo;
1867+
FILE_BASIC_INFO *pBasicInfo = NULL;
18671868
FILE_ID_INFO idInfo;
1868-
FILE_ID_INFO *pIdInfo = &idInfo;
1869+
FILE_ID_INFO *pIdInfo = NULL;
18691870
FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };
18701871
DWORD fileType, error;
18711872
BOOL isUnhandledTag = FALSE;
@@ -2016,14 +2017,17 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
20162017
retval = -1;
20172018
goto cleanup;
20182019
}
2019-
}
20202020

2021-
if (!GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo, sizeof(idInfo))) {
2022-
/* Failed to get FileIdInfo, so do not pass it along */
2023-
pIdInfo = NULL;
2021+
/* Successfully got FileBasicInfo, so we'll pass it along */
2022+
pBasicInfo = &basicInfo;
2023+
2024+
if (GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo, sizeof(idInfo))) {
2025+
/* Successfully got FileIdInfo, so pass it along */
2026+
pIdInfo = &idInfo;
2027+
}
20242028
}
20252029

2026-
_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result);
2030+
_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, pBasicInfo, pIdInfo, result);
20272031
update_st_mode_from_path(path, fileInfo.dwFileAttributes, result);
20282032

20292033
cleanup:

0 commit comments

Comments
 (0)