Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 17 additions & 62 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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 */
Expand All @@ -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. */
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
114 changes: 75 additions & 39 deletions Python/fileutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand Down
Loading