Eryk Sun <[email protected]> added the comment:
Here's the requested overview for the case where name-surrogate reparse points
are handled as winlinks (Windows links), but S_IFLNK is reserved for
IO_REPARSE_TAG_SYMLINK. I took the time this afternoon to write it up in C,
which hopefully is clearer than my prose. It handles all CreateFileW failures
inline, but uses a recursive call to traverse a non-link. No reparse tag values
are hard coded in win32_xstat_impl.
I extended it to support devices that aren't file systems, such as "con", disk
volumes, and raw disks. This enhancement was requested on another issue, but it
may as well get updated in this issue if win32_xstat_impl is getting
overhauled. Some of these devices are already supported by fstat(). The latter
can be extended similarly to support volume devices and raw disk devices.
I opted to fail the call in the unhandled-tag case if it opens a link that
should be traversed. Either it's an unhandled link, which is an unacceptable
condition (i.e. it should be a sink, not a link), or it's a link or sequence of
links to an unhandled reparse point. Returning the link reparse-point data is
not what the caller wants from a successful stat().
---
static int
win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
BOOL traverse)
{
HANDLE hFile;
BY_HANDLE_FILE_INFORMATION fileInfo;
FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };
DWORD fileType, error;
BOOL isUnhandledTag = FALSE;
int retval = 0;
DWORD access = FILE_READ_ATTRIBUTES;
DWORD flags = FILE_FLAG_BACKUP_SEMANTICS; /* Allow opening directories. */
if (!traverse) {
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
hFile = CreateFileW(path, access, 0, NULL, OPEN_EXISTING, flags, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
/* Either the path doesn't exist, or the caller lacks access. */
error = GetLastError();
switch (error) {
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)) {
/* Cannot read the parent directory. */
SetLastError(error);
return -1;
}
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
if (traverse ||
!IsReparseTagNameSurrogate(tagInfo.ReparseTag)) {
/* The stat call has to traverse but cannot, so fail. */
SetLastError(error);
return -1;
}
}
break;
case ERROR_INVALID_PARAMETER:
/* \\.\con requires read or write access. */
hFile = CreateFileW(path, access | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, flags, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
SetLastError(error);
return -1;
}
break;
case ERROR_CANT_ACCESS_FILE:
/* bpo37834: open unhandled reparse points if traverse fails. */
if (traverse) {
traverse = FALSE;
isUnhandledTag = TRUE;
hFile = CreateFileW(path, access, 0, NULL, OPEN_EXISTING,
flags | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
}
if (hFile == INVALID_HANDLE_VALUE) {
SetLastError(error);
return -1;
}
break;
default:
return -1;
}
}
if (hFile != INVALID_HANDLE_VALUE) {
/* Query the reparse tag, and traverse a non-link. */
if (!traverse) {
if (!GetFileInformationByHandleEx(hFile, FileAttributeTagInfo,
&tagInfo, sizeof(tagInfo))) {
/* Allow devices that do no support FileAttributeTagInfo. */
error = GetLastError() ;
if (error == ERROR_INVALID_PARAMETER ||
error == ERROR_INVALID_FUNCTION ||
error == ERROR_NOT_SUPPORTED) {
tagInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
tagInfo.ReparseTag = 0;
} else {
retval = -1;
goto cleanup;
}
} else if (tagInfo.FileAttributes &
FILE_ATTRIBUTE_REPARSE_POINT) {
if (IsReparseTagNameSurrogate(tagInfo.ReparseTag)) {
if (isUnhandledTag) {
/* Traversing previously failed for either this link
or its target. */
SetLastError(ERROR_CANT_ACCESS_FILE);
retval = -1;
goto cleanup;
}
/* Traverse a non-link, but not if traversing already failed
for an unhandled tag. */
} else if (!isUnhandledTag) {
CloseHandle(hFile);
return win32_xstat_impl(path, result, TRUE);
}
}
}
fileType = GetFileType(hFile);
if (fileType != FILE_TYPE_DISK) {
if (fileType == FILE_TYPE_UNKNOWN && GetLastError() != 0) {
retval = -1;
goto cleanup;
}
DWORD fileAttributes = GetFileAttributesW(path);
memset(result, 0, sizeof(*result));
if (fileAttributes != INVALID_FILE_ATTRIBUTES &&
fileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
/* \\.\pipe\ or \\.\mailslot\ */
result->st_mode = _S_IFDIR;
} else if (fileType == FILE_TYPE_CHAR) {
/* \\.\nul */
result->st_mode = _S_IFCHR;
} else if (fileType == FILE_TYPE_PIPE) {
/* \\.\pipe\spam */
result->st_mode = _S_IFIFO;
}
/* FILE_TYPE_UNKNOWN, e.g. \\.\mailslot\waitfor.exe\spam */
goto cleanup;
}
if (!GetFileInformationByHandle(hFile, &fileInfo)) {
error = GetLastError();
if (error != ERROR_INVALID_PARAMETER &&
error != ERROR_INVALID_FUNCTION &&
error != ERROR_NOT_SUPPORTED) {
retval = -1;
goto cleanup;
}
/* 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;
}
}
_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, result);
if (!(fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
/* 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
GetSecurityInfo, OpenThreadToken/OpenProcessToken, and
AccessCheck to check for generic read, write, and execute
access. */
const wchar_t *fileExtension = wcsrchr(path, '.');
if (fileExtension) {
if (_wcsicmp(fileExtension, L".exe") == 0 ||
_wcsicmp(fileExtension, L".bat") == 0 ||
_wcsicmp(fileExtension, L".cmd") == 0 ||
_wcsicmp(fileExtension, L".com") == 0) {
result->st_mode |= 0111;
}
}
}
cleanup:
if (hFile != INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
}
return retval;
}
----------
_______________________________________
Python tracker <[email protected]>
<https://bugs.python.org/issue37834>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com