diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 57db175336702e..5d0f2bf9108569 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -828,12 +828,15 @@ 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 *, - struct _Py_stat_struct *); -void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *, - struct _Py_stat_struct *); +void _Py_time_t_to_FILE_TIME(time_t, int, FILETIME*); +void _Py_attribute_data_to_stat(FILE_BASIC_INFO* basic_info, + FILE_STANDARD_INFO* standard_info, + FILE_ID_INFO* id_info, + ULONG reparse_tag, + struct _Py_stat_struct* result); +void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION*, struct _Py_stat_struct*); +void _Py_find_data_to_stat(WIN32_FIND_DATAW*, struct _Py_stat_struct*); +int _Py_stat_from_file_handle(HANDLE h, struct _Py_stat_struct* result, ULONG reparse_tag, BOOL set_ino); #endif @@ -2023,27 +2026,8 @@ win32_wchdir(LPCWSTR path) #define HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES 1 #define HAVE_STRUCT_STAT_ST_REPARSE_TAG 1 -static void -find_data_to_file_info(WIN32_FIND_DATAW *pFileData, - BY_HANDLE_FILE_INFORMATION *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; */ - if (pFileData->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) - *reparse_tag = pFileData->dwReserved0; - else - *reparse_tag = 0; -} - static BOOL -attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag) +attributes_from_dir(LPCWSTR pszFile, struct _Py_stat_struct* result, ULONG *reparse_tag) { HANDLE hFindFile; WIN32_FIND_DATAW FileData; @@ -2074,7 +2058,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); + _Py_find_data_to_stat(&FileData, result); return TRUE; } @@ -2083,7 +2067,7 @@ static void update_st_mode_from_path(const wchar_t *path, DWORD attr, struct _Py_stat_struct *result) { - if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) { + if (!(attr & _S_IFDIR)) { /* Fix the file execute permissions. This hack sets S_IEXEC if the filename has an extension that is commonly used by files that CreateProcessW can execute. A real implementation calls @@ -2108,11 +2092,6 @@ 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; - FILE_ID_INFO *pIdInfo = NULL; FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 }; DWORD fileType, error; BOOL isUnhandledTag = FALSE; @@ -2132,7 +2111,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, result, &tagInfo.ReparseTag)) { /* Cannot read the parent directory. */ switch (GetLastError()) { case ERROR_FILE_NOT_FOUND: /* File cannot be found */ @@ -2147,7 +2126,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, return -1; } - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + if (result->st_file_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { if (traverse || !IsReparseTagNameSurrogate(tagInfo.ReparseTag)) { /* The stat call has to traverse but cannot, so fail. */ @@ -2247,34 +2226,13 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, } } - if (!GetFileInformationByHandle(hFile, &fileInfo) || - !GetFileInformationByHandleEx(hFile, FileBasicInfo, - &basicInfo, sizeof(basicInfo))) { - switch (GetLastError()) { - case ERROR_INVALID_PARAMETER: - case ERROR_INVALID_FUNCTION: - case ERROR_NOT_SUPPORTED: - /* Volumes and physical disks are block devices, e.g. - \\.\C: and \\.\PhysicalDrive0. */ - memset(result, 0, sizeof(*result)); - result->st_mode = 0x6000; /* S_IFBLK */ - goto cleanup; - } + if (_Py_stat_from_file_handle(hFile, result, tagInfo.ReparseTag, FALSE)) { retval = -1; 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); + update_st_mode_from_path(path, result->st_mode, result); cleanup: if (hFile != INVALID_HANDLE_VALUE) { @@ -16667,8 +16625,6 @@ 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; @@ -16705,8 +16661,7 @@ 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, reparse_tag, NULL, NULL, &entry->win32_lstat); + _Py_find_data_to_stat(dataW, &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 98ed66eff94ee1..9c862bdeb35c58 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1103,29 +1103,26 @@ 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, - struct _Py_stat_struct *result) +_Py_attribute_data_to_stat(FILE_BASIC_INFO* basic_info, + FILE_STANDARD_INFO* standard_info, + FILE_ID_INFO* id_info, + ULONG reparse_tag, + 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_mode = attributes_to_mode(basic_info->FileAttributes); + result->st_size = standard_info->EndOfFile.QuadPart; result->st_rdev = 0; + /* 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); - } - result->st_nlink = info->nNumberOfLinks; + 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; 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; @@ -1134,19 +1131,19 @@ _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; + 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 (info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && + 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 = info->dwFileAttributes; + result->st_file_attributes = basic_info->FileAttributes; } void @@ -1213,6 +1210,63 @@ _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 && + 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; +} + +int +_Py_stat_from_file_handle(HANDLE h, struct _Py_stat_struct* result, ULONG reparse_tag, BOOL set_ino) +{ + FILE_BASIC_INFO basic_info = {0}; + FILE_STANDARD_INFO standard_info = {0}; + FILE_ID_INFO idInfo = {0}; + FILE_ID_INFO* pIdInfo = &idInfo; + if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic_info, sizeof(basic_info)) || + !GetFileInformationByHandleEx(h, FileStandardInfo, &standard_info, sizeof(standard_info))) { + /* The Win32 error is already set, but we also set errno for + callers who expect it */ + switch (GetLastError()) { + case ERROR_INVALID_PARAMETER: + case ERROR_INVALID_FUNCTION: + case ERROR_NOT_SUPPORTED: + /* Volumes and physical disks are block devices, e.g. + \\.\C: and \\.\PhysicalDrive0. */ + memset(result, 0, sizeof(*result)); + result->st_mode = 0x6000; /* S_IFBLK */ + } + PyErr_SetFromWindowsErr(0); + errno = winerror_to_errno(GetLastError()); + return -1; + } + + if (!GetFileInformationByHandleEx(h, FileIdInfo, &idInfo, sizeof(idInfo))) { + /* Failed to get FileIdInfo, so do not pass it along */ + pIdInfo = NULL; + } + + _Py_attribute_data_to_stat(&basic_info, &standard_info, pIdInfo, reparse_tag, result); + /* specific to fstat() */ + if (set_ino) { + result->st_ino = basic_info.CreationTime.QuadPart; + } + return 0; +} #endif /* Return information about a file. @@ -1231,10 +1285,6 @@ 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; - FILE_ID_INFO *pIdInfo = &idInfo; HANDLE h; int type; @@ -1266,21 +1316,7 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status) return 0; } - if (!GetFileInformationByHandle(h, &info) || - !GetFileInformationByHandleEx(h, FileBasicInfo, &basicInfo, sizeof(basicInfo))) { - /* The Win32 error is already set, but we also set errno for - callers who expect it */ - errno = winerror_to_errno(GetLastError()); - return -1; - } - - if (!GetFileInformationByHandleEx(h, FileIdInfo, &idInfo, sizeof(idInfo))) { - /* Failed to get FileIdInfo, so do not pass it along */ - pIdInfo = NULL; - } - - _Py_attribute_data_to_stat(&info, 0, &basicInfo, pIdInfo, status); - return 0; + return _Py_stat_from_file_handle(h, status, 0, TRUE); #else return fstat(fd, status); #endif