From 190b3f218b8ad645cba181fbc464751c3a5cd8fa Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:38:58 +0200 Subject: [PATCH 01/12] gh-152433: Windows: modernize fileutils to allow build for Windows UWP --- ...-07-17-16-21-17.gh-issue-152433.45rR2A.rst | 1 + Modules/posixmodule.c | 59 ++++++++++------ Python/fileutils.c | 69 ++++++++++++++++++- 3 files changed, 106 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst diff --git a/Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst b/Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst new file mode 100644 index 00000000000000..a4c382562f518f --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst @@ -0,0 +1 @@ +Modernize fileutils to allow build for Windows UWP. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 36debd6fe7aa15..a21315ae66bd95 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -834,6 +834,9 @@ void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *, ULONG, struct _Py_stat_struct *); void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *, struct _Py_stat_struct *); +void _Py_attribute_data_to_stat_UWP(FILE_STANDARD_INFO*, ULONG, + FILE_BASIC_INFO*, struct _Py_stat_struct*); +void _Py_find_data_to_stat(WIN32_FIND_DATAW* find_data, struct _Py_stat_struct* result); #endif @@ -2043,7 +2046,7 @@ find_data_to_file_info(WIN32_FIND_DATAW *pFileData, } static BOOL -attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag) +attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION* info, ULONG *reparse_tag, struct _Py_stat_struct* result) { HANDLE hFindFile; WIN32_FIND_DATAW FileData; @@ -2074,7 +2077,12 @@ attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *re return FALSE; } FindClose(hFindFile); + +#ifdef MS_WINDOWS_DESKTOP find_data_to_file_info(&FileData, info, reparse_tag); +#else + _Py_find_data_to_stat(&FileData, result); +#endif return TRUE; } @@ -2108,10 +2116,10 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, BOOL traverse) { HANDLE hFile; - BY_HANDLE_FILE_INFORMATION fileInfo; - FILE_BASIC_INFO basicInfo; - FILE_BASIC_INFO *pBasicInfo = NULL; - FILE_ID_INFO idInfo; + BY_HANDLE_FILE_INFORMATION fileInfo = {0}; + FILE_STANDARD_INFO standardInfo = {0}; + FILE_BASIC_INFO basicInfo = {0}; + FILE_ID_INFO idInfo = {0}; FILE_ID_INFO *pIdInfo = NULL; FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 }; DWORD fileType, error; @@ -2132,7 +2140,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, case ERROR_ACCESS_DENIED: /* Cannot sync or read attributes. */ case ERROR_SHARING_VIOLATION: /* It's a paging file. */ /* Try reading the parent directory. */ - if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) { + if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag, result)) { /* Cannot read the parent directory. */ switch (GetLastError()) { case ERROR_FILE_NOT_FOUND: /* File cannot be found */ @@ -2147,7 +2155,12 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, return -1; } + +#ifdef MS_WINDOWS_DESKTOP if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { +#else + if (result->st_file_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { +#endif if (traverse || !IsReparseTagNameSurrogate(tagInfo.ReparseTag)) { /* The stat call has to traverse but cannot, so fail. */ @@ -2247,9 +2260,13 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, } } +#ifdef MS_WINDOWS_DESKTOP if (!GetFileInformationByHandle(hFile, &fileInfo) || - !GetFileInformationByHandleEx(hFile, FileBasicInfo, - &basicInfo, sizeof(basicInfo))) { + !GetFileInformationByHandleEx(hFile, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { +#else + if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo, sizeof(standardInfo)) || + !GetFileInformationByHandleEx(hFile, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { +#endif switch (GetLastError()) { case ERROR_INVALID_PARAMETER: case ERROR_INVALID_FUNCTION: @@ -2264,17 +2281,18 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, goto cleanup; } - /* 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, pBasicInfo, pIdInfo, result); - update_st_mode_from_path(path, fileInfo.dwFileAttributes, result); +#ifdef MS_WINDOWS_DESKTOP + _Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); +#else + _Py_attribute_data_to_stat_UWP(&standardInfo, tagInfo.ReparseTag, &basicInfo, result); +#endif + update_st_mode_from_path(path, basicInfo.FileAttributes, result); cleanup: if (hFile != INVALID_HANDLE_VALUE) { @@ -16669,13 +16687,8 @@ join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename, static PyObject * DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) { - DirEntry *entry; - BY_HANDLE_FILE_INFORMATION file_info; - ULONG reparse_tag; - wchar_t *joined_path; - PyObject *DirEntryType = get_posix_state(module)->DirEntryType; - entry = PyObject_New(DirEntry, (PyTypeObject *)DirEntryType); + DirEntry* entry = PyObject_New(DirEntry, (PyTypeObject *)DirEntryType); if (!entry) return NULL; entry->name = NULL; @@ -16694,7 +16707,7 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) goto error; } - joined_path = join_path_filenameW(path->wide, dataW->cFileName, 0); + wchar_t* joined_path = join_path_filenameW(path->wide, dataW->cFileName, 0); if (!joined_path) goto error; @@ -16708,8 +16721,14 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) goto error; } +#ifdef MS_WINDOWS_DESKTOP + BY_HANDLE_FILE_INFORMATION file_info; + ULONG reparse_tag; find_data_to_file_info(dataW, &file_info, &reparse_tag); _Py_attribute_data_to_stat(&file_info, reparse_tag, NULL, NULL, &entry->win32_lstat); +#else + _Py_find_data_to_stat(dataW, &entry->win32_lstat); +#endif /* ctime is only deprecated from 3.12, so we copy birthtime across */ entry->win32_lstat.st_ctime = entry->win32_lstat.st_birthtime; diff --git a/Python/fileutils.c b/Python/fileutils.c index 98ed66eff94ee1..22a0f8ca39043a 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1149,6 +1149,36 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, result->st_file_attributes = info->dwFileAttributes; } +void +_Py_attribute_data_to_stat_UWP(FILE_STANDARD_INFO* standard_info, ULONG reparse_tag, + FILE_BASIC_INFO* basic_info, struct _Py_stat_struct* result) +{ + memset(result, 0, sizeof(*result)); + result->st_mode = attributes_to_mode(basic_info->FileAttributes); + result->st_size = standard_info->EndOfFile.QuadPart; + result->st_dev = 1; + + /* st_ctime is deprecated, but we preserve the legacy value in our caller, not here */ + LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec); + LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec); + LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec); + LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec); + + result->st_nlink = standard_info->NumberOfLinks; + result->st_ino = basic_info->CreationTime.QuadPart; + + /* bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will + open other name surrogate reparse points without traversing them. To + detect/handle these, check st_file_attributes and st_reparse_tag. */ + result->st_reparse_tag = reparse_tag; + if (basic_info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && + reparse_tag == IO_REPARSE_TAG_SYMLINK) { + /* set the bits that make this a symlink */ + result->st_mode = (result->st_mode & ~S_IFMT) | S_IFLNK; + } + result->st_file_attributes = basic_info->FileAttributes; +} + void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *info, struct _Py_stat_struct *result) @@ -1213,6 +1243,29 @@ _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *info, } } +void +_Py_find_data_to_stat(WIN32_FIND_DATAW* find_data, struct _Py_stat_struct* result) +{ + memset(result, 0, sizeof(*result)); + result->st_mode = attributes_to_mode(find_data->dwFileAttributes); + FILE_TIME_to_time_t_nsec(&find_data->ftCreationTime, &result->st_ctime, &result->st_ctime_nsec); + FILE_TIME_to_time_t_nsec(&find_data->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec); + FILE_TIME_to_time_t_nsec(&find_data->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec); + result->st_size = (((long long)find_data->nFileSizeHigh) << 32) | find_data->nFileSizeLow; + + if (find_data->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + result->st_reparse_tag = find_data->dwReserved0; + + if (find_data->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && + find_data->dwReserved0 == IO_REPARSE_TAG_SYMLINK) { + /* first clear the S_IFMT bits */ + result->st_mode ^= (result->st_mode & S_IFMT); + /* now set the bits that make this a symlink */ + result->st_mode |= S_IFLNK; + } + result->st_file_attributes = find_data->dwFileAttributes; +} + #endif /* Return information about a file. @@ -1231,9 +1284,10 @@ int _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) { #ifdef MS_WINDOWS - BY_HANDLE_FILE_INFORMATION info; - FILE_BASIC_INFO basicInfo; - FILE_ID_INFO idInfo; + BY_HANDLE_FILE_INFORMATION info = {0}; + FILE_STANDARD_INFO standardInfo = {0}; + FILE_BASIC_INFO basicInfo = {0}; + FILE_ID_INFO idInfo = {0}; FILE_ID_INFO *pIdInfo = &idInfo; HANDLE h; int type; @@ -1266,8 +1320,13 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) return 0; } +#ifdef MS_WINDOWS_DESKTOP if (!GetFileInformationByHandle(h, &info) || !GetFileInformationByHandleEx(h, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { +#else + if (!GetFileInformationByHandleEx(h,FileStandardInfo, &standardInfo, sizeof(standardInfo)) || + !GetFileInformationByHandleEx(h, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { +#endif /* The Win32 error is already set, but we also set errno for callers who expect it */ errno = winerror_to_errno(GetLastError()); @@ -1279,7 +1338,11 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) pIdInfo = NULL; } +#ifdef MS_WINDOWS_DESKTOP _Py_attribute_data_to_stat(&info, 0, &basicInfo, pIdInfo, status); +#else + _Py_attribute_data_to_stat_UWP(&standardInfo, 0, &basicInfo, status); +#endif return 0; #else return fstat(fd, status); From 5dabc7c9e5836b7acde451405993d54831d35ceb Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:04:58 +0200 Subject: [PATCH 02/12] Simplification 1 --- Modules/posixmodule.c | 17 ++--------------- Python/fileutils.c | 23 ----------------------- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a21315ae66bd95..acc9c558ecd243 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2046,7 +2046,7 @@ find_data_to_file_info(WIN32_FIND_DATAW *pFileData, } static BOOL -attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION* info, ULONG *reparse_tag, struct _Py_stat_struct* result) +attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag) { HANDLE hFindFile; WIN32_FIND_DATAW FileData; @@ -2077,12 +2077,7 @@ attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION* info, ULONG *re return FALSE; } FindClose(hFindFile); - -#ifdef MS_WINDOWS_DESKTOP find_data_to_file_info(&FileData, info, reparse_tag); -#else - _Py_find_data_to_stat(&FileData, result); -#endif return TRUE; } @@ -2140,7 +2135,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, case ERROR_ACCESS_DENIED: /* Cannot sync or read attributes. */ case ERROR_SHARING_VIOLATION: /* It's a paging file. */ /* Try reading the parent directory. */ - if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag, result)) { + if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) { /* Cannot read the parent directory. */ switch (GetLastError()) { case ERROR_FILE_NOT_FOUND: /* File cannot be found */ @@ -2156,11 +2151,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, return -1; } -#ifdef MS_WINDOWS_DESKTOP if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { -#else - if (result->st_file_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { -#endif if (traverse || !IsReparseTagNameSurrogate(tagInfo.ReparseTag)) { /* The stat call has to traverse but cannot, so fail. */ @@ -16721,14 +16712,10 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) goto error; } -#ifdef MS_WINDOWS_DESKTOP BY_HANDLE_FILE_INFORMATION file_info; ULONG reparse_tag; find_data_to_file_info(dataW, &file_info, &reparse_tag); _Py_attribute_data_to_stat(&file_info, reparse_tag, NULL, NULL, &entry->win32_lstat); -#else - _Py_find_data_to_stat(dataW, &entry->win32_lstat); -#endif /* ctime is only deprecated from 3.12, so we copy birthtime across */ entry->win32_lstat.st_ctime = entry->win32_lstat.st_birthtime; diff --git a/Python/fileutils.c b/Python/fileutils.c index 22a0f8ca39043a..b7328e6a02f1e7 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1243,29 +1243,6 @@ _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *info, } } -void -_Py_find_data_to_stat(WIN32_FIND_DATAW* find_data, struct _Py_stat_struct* result) -{ - memset(result, 0, sizeof(*result)); - result->st_mode = attributes_to_mode(find_data->dwFileAttributes); - FILE_TIME_to_time_t_nsec(&find_data->ftCreationTime, &result->st_ctime, &result->st_ctime_nsec); - FILE_TIME_to_time_t_nsec(&find_data->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec); - FILE_TIME_to_time_t_nsec(&find_data->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec); - result->st_size = (((long long)find_data->nFileSizeHigh) << 32) | find_data->nFileSizeLow; - - if (find_data->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) - result->st_reparse_tag = find_data->dwReserved0; - - if (find_data->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && - find_data->dwReserved0 == IO_REPARSE_TAG_SYMLINK) { - /* first clear the S_IFMT bits */ - result->st_mode ^= (result->st_mode & S_IFMT); - /* now set the bits that make this a symlink */ - result->st_mode |= S_IFLNK; - } - result->st_file_attributes = find_data->dwFileAttributes; -} - #endif /* Return information about a file. From 43d4fc2c69fd4125e358e283c0900f85e123b2c2 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:03:40 +0200 Subject: [PATCH 03/12] Full remove of _Py_find_data_to_stat --- Modules/posixmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index acc9c558ecd243..485e9373967452 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -836,7 +836,6 @@ void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *, struct _Py_stat_struct *); void _Py_attribute_data_to_stat_UWP(FILE_STANDARD_INFO*, ULONG, FILE_BASIC_INFO*, struct _Py_stat_struct*); -void _Py_find_data_to_stat(WIN32_FIND_DATAW* find_data, struct _Py_stat_struct* result); #endif From 003af986907f41354f44aa76b5cdfcca735a4622 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:37:34 +0200 Subject: [PATCH 04/12] Simplification 2 --- Modules/posixmodule.c | 12 ++++---- Python/fileutils.c | 68 ++++++++++++++++++------------------------- 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 485e9373967452..b7917490e8ad92 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -829,13 +829,11 @@ PyOS_AfterFork(void) #ifdef MS_WINDOWS /* defined in fileutils.c */ void _Py_time_t_to_FILE_TIME(time_t, int, FILETIME *); -void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *, ULONG, - FILE_BASIC_INFO *, FILE_ID_INFO *, +void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *, FILE_STANDARD_INFO*, + ULONG, FILE_BASIC_INFO *, FILE_ID_INFO *, struct _Py_stat_struct *); void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *, struct _Py_stat_struct *); -void _Py_attribute_data_to_stat_UWP(FILE_STANDARD_INFO*, ULONG, - FILE_BASIC_INFO*, struct _Py_stat_struct*); #endif @@ -2278,9 +2276,9 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, } #ifdef MS_WINDOWS_DESKTOP - _Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); + _Py_attribute_data_to_stat(&fileInfo, NULL, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); #else - _Py_attribute_data_to_stat_UWP(&standardInfo, tagInfo.ReparseTag, &basicInfo, result); + _Py_attribute_data_to_stat(NULL, &standardInfo, tagInfo.ReparseTag, &basicInfo, NULL, result); #endif update_st_mode_from_path(path, basicInfo.FileAttributes, result); @@ -16714,7 +16712,7 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) BY_HANDLE_FILE_INFORMATION file_info; ULONG reparse_tag; find_data_to_file_info(dataW, &file_info, &reparse_tag); - _Py_attribute_data_to_stat(&file_info, reparse_tag, NULL, NULL, &entry->win32_lstat); + _Py_attribute_data_to_stat(&file_info, NULL, reparse_tag, NULL, NULL, &entry->win32_lstat); /* ctime is only deprecated from 3.12, so we copy birthtime across */ entry->win32_lstat.st_ctime = entry->win32_lstat.st_birthtime; diff --git a/Python/fileutils.c b/Python/fileutils.c index b7328e6a02f1e7..ff89aa612071ad 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1103,15 +1103,22 @@ typedef union { void -_Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, - FILE_BASIC_INFO *basic_info, FILE_ID_INFO *id_info, +_Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, FILE_STANDARD_INFO* standard_info, + ULONG reparse_tag, FILE_BASIC_INFO *basic_info, FILE_ID_INFO *id_info, struct _Py_stat_struct *result) { memset(result, 0, sizeof(*result)); - result->st_mode = attributes_to_mode(info->dwFileAttributes); - result->st_size = (((__int64)info->nFileSizeHigh)<<32) + info->nFileSizeLow; - result->st_dev = id_info ? id_info->VolumeSerialNumber : info->dwVolumeSerialNumber; - result->st_rdev = 0; + + if (info) { + result->st_size = (((__int64)info->nFileSizeHigh) << 32) + info->nFileSizeLow; + result->st_nlink = info->nNumberOfLinks; + result->st_dev = id_info ? id_info->VolumeSerialNumber : info->dwVolumeSerialNumber; + } + if (standard_info && !info) { + result->st_size = standard_info->EndOfFile.QuadPart; + result->st_nlink = standard_info->NumberOfLinks; + } + /* st_ctime is deprecated, but we preserve the legacy value in our caller, not here */ if (basic_info) { LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec); @@ -1123,7 +1130,6 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, FILE_TIME_to_time_t_nsec(&info->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec); FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec); } - result->st_nlink = info->nNumberOfLinks; if (id_info) { id_128_to_ino file_id; @@ -1134,49 +1140,31 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, if (!result->st_ino && !result->st_ino_high) { /* should only occur for DirEntry_from_find_data, in which case the index is likely to be zero anyway. */ - result->st_ino = (((uint64_t)info->nFileIndexHigh) << 32) + info->nFileIndexLow; + if (info) + result->st_ino = (((uint64_t)info->nFileIndexHigh) << 32) + info->nFileIndexLow; } - /* bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will - open other name surrogate reparse points without traversing them. To - detect/handle these, check st_file_attributes and st_reparse_tag. */ - result->st_reparse_tag = reparse_tag; - if (info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && - reparse_tag == IO_REPARSE_TAG_SYMLINK) { - /* set the bits that make this a symlink */ - result->st_mode = (result->st_mode & ~S_IFMT) | S_IFLNK; - } - result->st_file_attributes = info->dwFileAttributes; -} + const DWORD fileAttributes = basic_info ? basic_info->FileAttributes : info->dwFileAttributes; -void -_Py_attribute_data_to_stat_UWP(FILE_STANDARD_INFO* standard_info, ULONG reparse_tag, - FILE_BASIC_INFO* basic_info, struct _Py_stat_struct* result) -{ - memset(result, 0, sizeof(*result)); - result->st_mode = attributes_to_mode(basic_info->FileAttributes); - result->st_size = standard_info->EndOfFile.QuadPart; - result->st_dev = 1; - - /* st_ctime is deprecated, but we preserve the legacy value in our caller, not here */ - LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec); - LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec); - LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec); - LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec); - - result->st_nlink = standard_info->NumberOfLinks; - result->st_ino = basic_info->CreationTime.QuadPart; + result->st_file_attributes = fileAttributes; + result->st_mode = attributes_to_mode(fileAttributes); /* bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will open other name surrogate reparse points without traversing them. To detect/handle these, check st_file_attributes and st_reparse_tag. */ result->st_reparse_tag = reparse_tag; - if (basic_info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && + if (result->st_file_attributes & FILE_ATTRIBUTE_REPARSE_POINT && reparse_tag == IO_REPARSE_TAG_SYMLINK) { /* set the bits that make this a symlink */ result->st_mode = (result->st_mode & ~S_IFMT) | S_IFLNK; } - result->st_file_attributes = basic_info->FileAttributes; + + // For UWP compatibility since is not possible obtain the VolumeSerialNumber + // and FileId due security restriction and App isolation +#ifndef MS_WINDOWS_DESKTOP + result->st_dev = 1; + basic_info->CreationTime.QuadPart; +#endif } void @@ -1316,9 +1304,9 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) } #ifdef MS_WINDOWS_DESKTOP - _Py_attribute_data_to_stat(&info, 0, &basicInfo, pIdInfo, status); + _Py_attribute_data_to_stat(&info, NULL, 0, &basicInfo, pIdInfo, status); #else - _Py_attribute_data_to_stat_UWP(&standardInfo, 0, &basicInfo, status); + _Py_attribute_data_to_stat(NULL, &standardInfo, 0, &basicInfo, NULL, status); #endif return 0; #else From a0f6bb73a33599c54b49d56a0ee40f45780815b2 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:45:54 +0200 Subject: [PATCH 05/12] Fix typo --- Python/fileutils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/fileutils.c b/Python/fileutils.c index ff89aa612071ad..0bd41f8e2c416d 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1163,7 +1163,7 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, FILE_STANDARD_INFO* // and FileId due security restriction and App isolation #ifndef MS_WINDOWS_DESKTOP result->st_dev = 1; - basic_info->CreationTime.QuadPart; + result->st_ino = basic_info->CreationTime.QuadPart; #endif } From 9a694a39eb6918b87b73d94520314d543f9435b3 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:36:50 +0200 Subject: [PATCH 06/12] Simplification 3: Remove ``GetFileInformationByHandle`` on Desktop Use same code for UWP and Desktop. --- Modules/posixmodule.c | 11 +---------- Python/fileutils.c | 16 ++++------------ 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b7917490e8ad92..da626e6cc14f04 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2248,13 +2248,8 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, } } -#ifdef MS_WINDOWS_DESKTOP - if (!GetFileInformationByHandle(hFile, &fileInfo) || - !GetFileInformationByHandleEx(hFile, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { -#else if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo, sizeof(standardInfo)) || !GetFileInformationByHandleEx(hFile, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { -#endif switch (GetLastError()) { case ERROR_INVALID_PARAMETER: case ERROR_INVALID_FUNCTION: @@ -2275,11 +2270,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, } } -#ifdef MS_WINDOWS_DESKTOP - _Py_attribute_data_to_stat(&fileInfo, NULL, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); -#else - _Py_attribute_data_to_stat(NULL, &standardInfo, tagInfo.ReparseTag, &basicInfo, NULL, result); -#endif + _Py_attribute_data_to_stat(NULL, &standardInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); update_st_mode_from_path(path, basicInfo.FileAttributes, result); cleanup: diff --git a/Python/fileutils.c b/Python/fileutils.c index 0bd41f8e2c416d..bedcb2c5685157 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1112,7 +1112,8 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, FILE_STANDARD_INFO* if (info) { result->st_size = (((__int64)info->nFileSizeHigh) << 32) + info->nFileSizeLow; result->st_nlink = info->nNumberOfLinks; - result->st_dev = id_info ? id_info->VolumeSerialNumber : info->dwVolumeSerialNumber; + if (!id_info) + result->st_dev = info->dwVolumeSerialNumber; } if (standard_info && !info) { result->st_size = standard_info->EndOfFile.QuadPart; @@ -1132,6 +1133,7 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, FILE_STANDARD_INFO* } if (id_info) { + result->st_dev = id_info->VolumeSerialNumber; id_128_to_ino file_id; file_id.id = id_info->FileId; result->st_ino = file_id.st_ino; @@ -1249,7 +1251,6 @@ int _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) { #ifdef MS_WINDOWS - BY_HANDLE_FILE_INFORMATION info = {0}; FILE_STANDARD_INFO standardInfo = {0}; FILE_BASIC_INFO basicInfo = {0}; FILE_ID_INFO idInfo = {0}; @@ -1285,13 +1286,8 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) return 0; } -#ifdef MS_WINDOWS_DESKTOP - if (!GetFileInformationByHandle(h, &info) || - !GetFileInformationByHandleEx(h, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { -#else if (!GetFileInformationByHandleEx(h,FileStandardInfo, &standardInfo, sizeof(standardInfo)) || !GetFileInformationByHandleEx(h, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { -#endif /* The Win32 error is already set, but we also set errno for callers who expect it */ errno = winerror_to_errno(GetLastError()); @@ -1303,11 +1299,7 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) pIdInfo = NULL; } -#ifdef MS_WINDOWS_DESKTOP - _Py_attribute_data_to_stat(&info, NULL, 0, &basicInfo, pIdInfo, status); -#else - _Py_attribute_data_to_stat(NULL, &standardInfo, 0, &basicInfo, NULL, status); -#endif + _Py_attribute_data_to_stat(NULL, &standardInfo, 0, &basicInfo, pIdInfo, status); return 0; #else return fstat(fd, status); From b81a7d150032e98146105e0bbc677aac5025de12 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:45:57 +0200 Subject: [PATCH 07/12] Remove unnecessary/accidental changes --- Modules/posixmodule.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index da626e6cc14f04..2eaf26c0249fb6 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2147,7 +2147,6 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, return -1; } - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { if (traverse || !IsReparseTagNameSurrogate(tagInfo.ReparseTag)) { @@ -16666,8 +16665,13 @@ join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename, static PyObject * DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) { + DirEntry* entry; + BY_HANDLE_FILE_INFORMATION file_info; + ULONG reparse_tag; + wchar_t* joined_path; + PyObject *DirEntryType = get_posix_state(module)->DirEntryType; - DirEntry* entry = PyObject_New(DirEntry, (PyTypeObject *)DirEntryType); + entry = PyObject_New(DirEntry, (PyTypeObject *)DirEntryType); if (!entry) return NULL; entry->name = NULL; @@ -16686,7 +16690,7 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) goto error; } - wchar_t* joined_path = join_path_filenameW(path->wide, dataW->cFileName, 0); + joined_path = join_path_filenameW(path->wide, dataW->cFileName, 0); if (!joined_path) goto error; @@ -16700,8 +16704,6 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) goto error; } - BY_HANDLE_FILE_INFORMATION file_info; - ULONG reparse_tag; find_data_to_file_info(dataW, &file_info, &reparse_tag); _Py_attribute_data_to_stat(&file_info, NULL, reparse_tag, NULL, NULL, &entry->win32_lstat); From 24a0f478f009404e56fa8345e4e24f010ea0a1d2 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:14:20 +0200 Subject: [PATCH 08/12] Preserve old code format --- Modules/posixmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 2eaf26c0249fb6..065359c0d3f4d0 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -16665,10 +16665,10 @@ join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename, static PyObject * DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) { - DirEntry* entry; + DirEntry *entry; BY_HANDLE_FILE_INFORMATION file_info; ULONG reparse_tag; - wchar_t* joined_path; + wchar_t *joined_path; PyObject *DirEntryType = get_posix_state(module)->DirEntryType; entry = PyObject_New(DirEntry, (PyTypeObject *)DirEntryType); From 4c180914c214e333fe8565981e9a4aca58fbc4cf Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:47:12 +0200 Subject: [PATCH 09/12] Use BASIC_INFO and STANDARD_INFO in ``find_data_to_file_info`` --- Modules/posixmodule.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 065359c0d3f4d0..3960f192730a7e 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2025,17 +2025,23 @@ win32_wchdir(LPCWSTR path) static void find_data_to_file_info(WIN32_FIND_DATAW *pFileData, - BY_HANDLE_FILE_INFORMATION *info, + FILE_BASIC_INFO* basic_info, + FILE_STANDARD_INFO* standard_info, ULONG *reparse_tag) { - memset(info, 0, sizeof(*info)); - info->dwFileAttributes = pFileData->dwFileAttributes; - info->ftCreationTime = pFileData->ftCreationTime; - info->ftLastAccessTime = pFileData->ftLastAccessTime; - info->ftLastWriteTime = pFileData->ftLastWriteTime; - info->nFileSizeHigh = pFileData->nFileSizeHigh; - info->nFileSizeLow = pFileData->nFileSizeLow; -/* info->nNumberOfLinks = 1; */ + memset(basic_info, 0, sizeof(*basic_info)); + memset(standard_info, 0, sizeof(*standard_info)); + + basic_info->FileAttributes = pFileData->dwFileAttributes; + basic_info->CreationTime.HighPart = pFileData->ftCreationTime.dwHighDateTime; + basic_info->CreationTime.LowPart = pFileData->ftCreationTime.dwLowDateTime; + basic_info->LastAccessTime.HighPart = pFileData->ftLastAccessTime.dwHighDateTime; + basic_info->LastAccessTime.LowPart = pFileData->ftLastAccessTime.dwLowDateTime; + basic_info->LastWriteTime.HighPart = pFileData->ftLastWriteTime.dwHighDateTime; + basic_info->LastWriteTime.LowPart = pFileData->ftLastWriteTime.dwLowDateTime; + standard_info->EndOfFile.HighPart = pFileData->nFileSizeHigh; + standard_info->EndOfFile.LowPart = pFileData->nFileSizeLow; + if (pFileData->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) *reparse_tag = pFileData->dwReserved0; else @@ -2043,7 +2049,7 @@ find_data_to_file_info(WIN32_FIND_DATAW *pFileData, } static BOOL -attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag) +attributes_from_dir(LPCWSTR pszFile, FILE_BASIC_INFO *basic_info, FILE_STANDARD_INFO* standard_info, ULONG *reparse_tag) { HANDLE hFindFile; WIN32_FIND_DATAW FileData; @@ -2074,7 +2080,7 @@ attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *re return FALSE; } FindClose(hFindFile); - find_data_to_file_info(&FileData, info, reparse_tag); + find_data_to_file_info(&FileData, basic_info, standard_info, reparse_tag); return TRUE; } @@ -2108,7 +2114,6 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, BOOL traverse) { HANDLE hFile; - BY_HANDLE_FILE_INFORMATION fileInfo = {0}; FILE_STANDARD_INFO standardInfo = {0}; FILE_BASIC_INFO basicInfo = {0}; FILE_ID_INFO idInfo = {0}; @@ -2132,7 +2137,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, case ERROR_ACCESS_DENIED: /* Cannot sync or read attributes. */ case ERROR_SHARING_VIOLATION: /* It's a paging file. */ /* Try reading the parent directory. */ - if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) { + if (!attributes_from_dir(path, &basicInfo, &standardInfo, &tagInfo.ReparseTag)) { /* Cannot read the parent directory. */ switch (GetLastError()) { case ERROR_FILE_NOT_FOUND: /* File cannot be found */ @@ -16666,7 +16671,8 @@ static PyObject * DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) { DirEntry *entry; - BY_HANDLE_FILE_INFORMATION file_info; + FILE_BASIC_INFO basic_info; + FILE_STANDARD_INFO standard_info; ULONG reparse_tag; wchar_t *joined_path; @@ -16704,8 +16710,8 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) goto error; } - find_data_to_file_info(dataW, &file_info, &reparse_tag); - _Py_attribute_data_to_stat(&file_info, NULL, reparse_tag, NULL, NULL, &entry->win32_lstat); + find_data_to_file_info(dataW, &basic_info, &standard_info, &reparse_tag); + _Py_attribute_data_to_stat(NULL, &standard_info, reparse_tag, &basic_info, NULL, &entry->win32_lstat); /* ctime is only deprecated from 3.12, so we copy birthtime across */ entry->win32_lstat.st_ctime = entry->win32_lstat.st_birthtime; From 7221f0c2887b10503a45ce6e7b24358b05d42944 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:15:31 +0200 Subject: [PATCH 10/12] Full remove of BY_HANDLE_FILE_INFORMATION parameter --- Modules/posixmodule.c | 12 +++++----- Python/fileutils.c | 56 +++++++++++++------------------------------ 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3960f192730a7e..3ba58630619e23 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -829,9 +829,9 @@ PyOS_AfterFork(void) #ifdef MS_WINDOWS /* defined in fileutils.c */ void _Py_time_t_to_FILE_TIME(time_t, int, FILETIME *); -void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *, FILE_STANDARD_INFO*, - ULONG, FILE_BASIC_INFO *, FILE_ID_INFO *, - struct _Py_stat_struct *); +void _Py_attribute_data_to_stat(FILE_STANDARD_INFO*, ULONG, + FILE_BASIC_INFO*, FILE_ID_INFO*, + struct _Py_stat_struct*); void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *, struct _Py_stat_struct *); #endif @@ -2152,7 +2152,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, return -1; } - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + if (basicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { if (traverse || !IsReparseTagNameSurrogate(tagInfo.ReparseTag)) { /* The stat call has to traverse but cannot, so fail. */ @@ -2274,7 +2274,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, } } - _Py_attribute_data_to_stat(NULL, &standardInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); + _Py_attribute_data_to_stat(&standardInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); update_st_mode_from_path(path, basicInfo.FileAttributes, result); cleanup: @@ -16711,7 +16711,7 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW) } find_data_to_file_info(dataW, &basic_info, &standard_info, &reparse_tag); - _Py_attribute_data_to_stat(NULL, &standard_info, reparse_tag, &basic_info, NULL, &entry->win32_lstat); + _Py_attribute_data_to_stat(&standard_info, reparse_tag, &basic_info, NULL, &entry->win32_lstat); /* ctime is only deprecated from 3.12, so we copy birthtime across */ entry->win32_lstat.st_ctime = entry->win32_lstat.st_birthtime; diff --git a/Python/fileutils.c b/Python/fileutils.c index bedcb2c5685157..21cad4b1f56c08 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1103,34 +1103,20 @@ typedef union { void -_Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, FILE_STANDARD_INFO* standard_info, - ULONG reparse_tag, FILE_BASIC_INFO *basic_info, FILE_ID_INFO *id_info, +_Py_attribute_data_to_stat(FILE_STANDARD_INFO* standard_info, ULONG reparse_tag, + FILE_BASIC_INFO *basic_info, FILE_ID_INFO *id_info, struct _Py_stat_struct *result) { memset(result, 0, sizeof(*result)); - if (info) { - result->st_size = (((__int64)info->nFileSizeHigh) << 32) + info->nFileSizeLow; - result->st_nlink = info->nNumberOfLinks; - if (!id_info) - result->st_dev = info->dwVolumeSerialNumber; - } - if (standard_info && !info) { - result->st_size = standard_info->EndOfFile.QuadPart; - result->st_nlink = standard_info->NumberOfLinks; - } + result->st_size = standard_info->EndOfFile.QuadPart; + result->st_nlink = standard_info->NumberOfLinks; /* st_ctime is deprecated, but we preserve the legacy value in our caller, not here */ - if (basic_info) { - LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec); - LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec); - LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec); - LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec); - } else { - FILE_TIME_to_time_t_nsec(&info->ftCreationTime, &result->st_birthtime, &result->st_birthtime_nsec); - FILE_TIME_to_time_t_nsec(&info->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec); - FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec); - } + LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec); + LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec); + LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec); + LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec); if (id_info) { result->st_dev = id_info->VolumeSerialNumber; @@ -1138,18 +1124,15 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, FILE_STANDARD_INFO* file_id.id = id_info->FileId; result->st_ino = file_id.st_ino; result->st_ino_high = file_id.st_ino_high; + } else { + // use these fallback values for systems where it's not possible + // to obtain VolumeSerialNumber / FileId (e.g. Windows UWP) + result->st_dev = 1; + result->st_ino = basic_info->CreationTime.QuadPart; } - if (!result->st_ino && !result->st_ino_high) { - /* should only occur for DirEntry_from_find_data, in which case the - index is likely to be zero anyway. */ - if (info) - result->st_ino = (((uint64_t)info->nFileIndexHigh) << 32) + info->nFileIndexLow; - } - - const DWORD fileAttributes = basic_info ? basic_info->FileAttributes : info->dwFileAttributes; - result->st_file_attributes = fileAttributes; - result->st_mode = attributes_to_mode(fileAttributes); + result->st_file_attributes = basic_info->FileAttributes; + result->st_mode = attributes_to_mode(result->st_file_attributes); /* bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will open other name surrogate reparse points without traversing them. To @@ -1160,13 +1143,6 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, FILE_STANDARD_INFO* /* set the bits that make this a symlink */ result->st_mode = (result->st_mode & ~S_IFMT) | S_IFLNK; } - - // For UWP compatibility since is not possible obtain the VolumeSerialNumber - // and FileId due security restriction and App isolation -#ifndef MS_WINDOWS_DESKTOP - result->st_dev = 1; - result->st_ino = basic_info->CreationTime.QuadPart; -#endif } void @@ -1299,7 +1275,7 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) pIdInfo = NULL; } - _Py_attribute_data_to_stat(NULL, &standardInfo, 0, &basicInfo, pIdInfo, status); + _Py_attribute_data_to_stat(&standardInfo, 0, &basicInfo, pIdInfo, status); return 0; #else return fstat(fd, status); From 9c7338a7caf53aa5f79ae230f4d0b395aebd63ba Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:44:31 +0200 Subject: [PATCH 11/12] Improve NEWS and cosmetics --- .../Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst | 3 ++- Modules/posixmodule.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst b/Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst index a4c382562f518f..eb45c1cbd0e9ea 100644 --- a/Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst +++ b/Misc/NEWS.d/next/Windows/2026-07-17-16-21-17.gh-issue-152433.45rR2A.rst @@ -1 +1,2 @@ -Modernize fileutils to allow build for Windows UWP. +Modernize fileutils removing GetFileInformationByHandle API calls and allow +build for Universal Windows Platform. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3ba58630619e23..f113e26b339675 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2049,7 +2049,7 @@ find_data_to_file_info(WIN32_FIND_DATAW *pFileData, } static BOOL -attributes_from_dir(LPCWSTR pszFile, FILE_BASIC_INFO *basic_info, FILE_STANDARD_INFO* standard_info, ULONG *reparse_tag) +attributes_from_dir(LPCWSTR pszFile, FILE_BASIC_INFO* basic_info, FILE_STANDARD_INFO* standard_info, ULONG* reparse_tag) { HANDLE hFindFile; WIN32_FIND_DATAW FileData; From e4e4d5d55c5daf7f6778b4142f57f150e89fea47 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:06:17 +0200 Subject: [PATCH 12/12] Remove st_dev and st_ino fallback values --- Python/fileutils.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Python/fileutils.c b/Python/fileutils.c index 21cad4b1f56c08..5cbfd8e6ce4fa0 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1124,11 +1124,6 @@ _Py_attribute_data_to_stat(FILE_STANDARD_INFO* standard_info, ULONG reparse_tag, file_id.id = id_info->FileId; result->st_ino = file_id.st_ino; result->st_ino_high = file_id.st_ino_high; - } else { - // use these fallback values for systems where it's not possible - // to obtain VolumeSerialNumber / FileId (e.g. Windows UWP) - result->st_dev = 1; - result->st_ino = basic_info->CreationTime.QuadPart; } result->st_file_attributes = basic_info->FileAttributes;