win,fs: get (most) fstat when no permission (#4566)
Replaces: https://github.com/libuv/libuv/pull/4504 Fixes: https://github.com/libuv/libuv/issues/1980 Fixes: https://github.com/libuv/libuv/issues/3267 Co-authored-by: Hüseyin Açacak <huseyin@janeasystems.com>
This commit is contained in:
parent
16e6e84dcc
commit
72d9abccd7
205
src/win/fs.c
205
src/win/fs.c
@ -58,6 +58,19 @@
|
||||
#define FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE 0x0010
|
||||
#endif /* FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE */
|
||||
|
||||
NTSTATUS uv__RtlUnicodeStringInit(
|
||||
PUNICODE_STRING DestinationString,
|
||||
PWSTR SourceString,
|
||||
size_t SourceStringLen
|
||||
) {
|
||||
if (SourceStringLen > 0x7FFF)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
DestinationString->MaximumLength = DestinationString->Length =
|
||||
SourceStringLen * sizeof(SourceString[0]);
|
||||
DestinationString->Buffer = SourceString;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#define INIT(subtype) \
|
||||
do { \
|
||||
if (req == NULL) \
|
||||
@ -1689,12 +1702,12 @@ INLINE static fs__stat_path_return_t fs__stat_path(WCHAR* path,
|
||||
uv_stat_t* statbuf, int do_lstat) {
|
||||
FILE_STAT_BASIC_INFORMATION stat_info;
|
||||
|
||||
// Check if the new fast API is available.
|
||||
/* Check if the new fast API is available. */
|
||||
if (!pGetFileInformationByName) {
|
||||
return FS__STAT_PATH_TRY_SLOW;
|
||||
}
|
||||
|
||||
// Check if the API call fails.
|
||||
/* Check if the API call fails. */
|
||||
if (!pGetFileInformationByName(path, FileStatBasicByNameInfo, &stat_info,
|
||||
sizeof(stat_info))) {
|
||||
switch(GetLastError()) {
|
||||
@ -1708,7 +1721,7 @@ INLINE static fs__stat_path_return_t fs__stat_path(WCHAR* path,
|
||||
return FS__STAT_PATH_TRY_SLOW;
|
||||
}
|
||||
|
||||
// A file handle is needed to get st_size for links.
|
||||
/* A file handle is needed to get st_size for links. */
|
||||
if ((stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
|
||||
return FS__STAT_PATH_TRY_SLOW;
|
||||
}
|
||||
@ -1802,7 +1815,6 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
|
||||
* detect this failure and retry without do_lstat if appropriate.
|
||||
*/
|
||||
if (fs__readlink_handle(handle, NULL, &target_length) != 0) {
|
||||
fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
|
||||
return -1;
|
||||
}
|
||||
stat_info.EndOfFile.QuadPart = target_length;
|
||||
@ -1941,6 +1953,179 @@ INLINE static void fs__stat_prepare_path(WCHAR* pathw) {
|
||||
}
|
||||
}
|
||||
|
||||
INLINE static DWORD fs__stat_directory(WCHAR* path, uv_stat_t* statbuf,
|
||||
int do_lstat, DWORD ret_error) {
|
||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||
FILE_STAT_BASIC_INFORMATION stat_info;
|
||||
FILE_ID_FULL_DIR_INFORMATION dir_info;
|
||||
FILE_FS_VOLUME_INFORMATION volume_info;
|
||||
FILE_FS_DEVICE_INFORMATION device_info;
|
||||
IO_STATUS_BLOCK io_status;
|
||||
NTSTATUS nt_status;
|
||||
WCHAR* path_dirpath = NULL;
|
||||
WCHAR* path_filename = NULL;
|
||||
UNICODE_STRING FileMask;
|
||||
size_t len;
|
||||
size_t split;
|
||||
WCHAR splitchar;
|
||||
int includes_name;
|
||||
|
||||
/* AKA strtok or wcscspn, in reverse. */
|
||||
len = wcslen(path);
|
||||
split = len;
|
||||
|
||||
includes_name = 0;
|
||||
while (split > 0 && path[split - 1] != L'\\' && path[split - 1] != L'/' &&
|
||||
path[split - 1] != L':') {
|
||||
/* check if the path contains a character other than /,\,:,. */
|
||||
if (path[split-1] != '.') {
|
||||
includes_name = 1;
|
||||
}
|
||||
split--;
|
||||
}
|
||||
/* If the path is a relative path with a file name or a folder name */
|
||||
if (split == 0 && includes_name) {
|
||||
path_dirpath = L".";
|
||||
/* If there is a slash or a backslash */
|
||||
} else if (path[split - 1] == L'\\' || path[split - 1] == L'/') {
|
||||
path_dirpath = path;
|
||||
/* If there is no filename, consider it as a relative folder path */
|
||||
if (!includes_name) {
|
||||
split = len;
|
||||
/* Else, split it */
|
||||
} else {
|
||||
splitchar = path[split - 1];
|
||||
path[split - 1] = L'\0';
|
||||
}
|
||||
/* e.g. "..", "c:" */
|
||||
} else {
|
||||
path_dirpath = path;
|
||||
split = len;
|
||||
}
|
||||
path_filename = &path[split];
|
||||
|
||||
len = 0;
|
||||
while (1) {
|
||||
if (path_filename[len] == L'\0')
|
||||
break;
|
||||
if (path_filename[len] == L'*' || path_filename[len] == L'?' ||
|
||||
path_filename[len] == L'>' || path_filename[len] == L'<' ||
|
||||
path_filename[len] == L'"') {
|
||||
ret_error = ERROR_INVALID_NAME;
|
||||
goto cleanup;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
|
||||
/* Get directory handle */
|
||||
handle = CreateFileW(path_dirpath,
|
||||
FILE_LIST_DIRECTORY,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS,
|
||||
NULL);
|
||||
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
ret_error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Get files in the directory */
|
||||
nt_status = uv__RtlUnicodeStringInit(&FileMask, path_filename, len);
|
||||
if (!NT_SUCCESS(nt_status)) {
|
||||
ret_error = pRtlNtStatusToDosError(nt_status);
|
||||
goto cleanup;
|
||||
}
|
||||
nt_status = pNtQueryDirectoryFile(handle,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&io_status,
|
||||
&dir_info,
|
||||
sizeof(dir_info),
|
||||
FileIdFullDirectoryInformation,
|
||||
TRUE,
|
||||
&FileMask,
|
||||
TRUE);
|
||||
|
||||
/* Buffer overflow (a warning status code) is expected here since there isn't
|
||||
* enough space to store the FileName, and actually indicates success. */
|
||||
if (!NT_SUCCESS(nt_status) && nt_status != STATUS_BUFFER_OVERFLOW) {
|
||||
if (nt_status == STATUS_NO_MORE_FILES)
|
||||
ret_error = ERROR_PATH_NOT_FOUND;
|
||||
else
|
||||
ret_error = pRtlNtStatusToDosError(nt_status);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Assign values to stat_info */
|
||||
memset(&stat_info, 0, sizeof(FILE_STAT_BASIC_INFORMATION));
|
||||
stat_info.FileAttributes = dir_info.FileAttributes;
|
||||
stat_info.CreationTime.QuadPart = dir_info.CreationTime.QuadPart;
|
||||
stat_info.LastAccessTime.QuadPart = dir_info.LastAccessTime.QuadPart;
|
||||
stat_info.LastWriteTime.QuadPart = dir_info.LastWriteTime.QuadPart;
|
||||
if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
/* A file handle is needed to get st_size for the link (from
|
||||
* FSCTL_GET_REPARSE_POINT), which is required by posix, but we are here
|
||||
* because getting the file handle failed. We could get just the
|
||||
* ReparsePointTag by querying FILE_ID_EXTD_DIR_INFORMATION instead to make
|
||||
* sure this really is a link before giving up here on the uv_fs_stat call,
|
||||
* but that doesn't seem essential. */
|
||||
if (!do_lstat)
|
||||
goto cleanup;
|
||||
stat_info.EndOfFile.QuadPart = 0;
|
||||
stat_info.AllocationSize.QuadPart = 0;
|
||||
} else {
|
||||
stat_info.EndOfFile.QuadPart = dir_info.EndOfFile.QuadPart;
|
||||
stat_info.AllocationSize.QuadPart = dir_info.AllocationSize.QuadPart;
|
||||
}
|
||||
stat_info.ChangeTime.QuadPart = dir_info.ChangeTime.QuadPart;
|
||||
stat_info.FileId.QuadPart = dir_info.FileId.QuadPart;
|
||||
|
||||
/* Finish up by getting device info from the directory handle,
|
||||
* since files presumably must live on their device. */
|
||||
nt_status = pNtQueryVolumeInformationFile(handle,
|
||||
&io_status,
|
||||
&volume_info,
|
||||
sizeof volume_info,
|
||||
FileFsVolumeInformation);
|
||||
|
||||
/* Buffer overflow (a warning status code) is expected here. */
|
||||
if (io_status.Status == STATUS_NOT_IMPLEMENTED) {
|
||||
stat_info.VolumeSerialNumber.QuadPart = 0;
|
||||
} else if (NT_ERROR(nt_status)) {
|
||||
ret_error = pRtlNtStatusToDosError(nt_status);
|
||||
goto cleanup;
|
||||
} else {
|
||||
stat_info.VolumeSerialNumber.QuadPart = volume_info.VolumeSerialNumber;
|
||||
}
|
||||
|
||||
nt_status = pNtQueryVolumeInformationFile(handle,
|
||||
&io_status,
|
||||
&device_info,
|
||||
sizeof device_info,
|
||||
FileFsDeviceInformation);
|
||||
|
||||
/* Buffer overflow (a warning status code) is expected here. */
|
||||
if (NT_ERROR(nt_status)) {
|
||||
ret_error = pRtlNtStatusToDosError(nt_status);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
stat_info.DeviceType = device_info.DeviceType;
|
||||
stat_info.NumberOfLinks = 1; /* No way to recover this info. */
|
||||
|
||||
fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
|
||||
ret_error = 0;
|
||||
|
||||
cleanup:
|
||||
if (split != 0)
|
||||
path[split - 1] = splitchar;
|
||||
if (handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(handle);
|
||||
return ret_error;
|
||||
}
|
||||
|
||||
INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
||||
int do_lstat,
|
||||
@ -1949,7 +2134,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
||||
DWORD flags;
|
||||
DWORD ret;
|
||||
|
||||
// If new API exists, try to use it.
|
||||
/* If new API exists, try to use it. */
|
||||
switch (fs__stat_path(path, statbuf, do_lstat)) {
|
||||
case FS__STAT_PATH_SUCCESS:
|
||||
return 0;
|
||||
@ -1959,7 +2144,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
||||
break;
|
||||
}
|
||||
|
||||
// If the new API does not exist, use the old API.
|
||||
/* If the new API does not exist, use the old API. */
|
||||
flags = FILE_FLAG_BACKUP_SEMANTICS;
|
||||
if (do_lstat)
|
||||
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
|
||||
@ -1972,8 +2157,12 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
||||
flags,
|
||||
NULL);
|
||||
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
return GetLastError();
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
ret = GetLastError();
|
||||
if (ret != ERROR_ACCESS_DENIED && ret != ERROR_SHARING_VIOLATION)
|
||||
return ret;
|
||||
return fs__stat_directory(path, statbuf, do_lstat, ret);
|
||||
}
|
||||
|
||||
if (fs__stat_handle(handle, statbuf, do_lstat) != 0)
|
||||
ret = GetLastError();
|
||||
|
||||
@ -4287,6 +4287,22 @@ typedef struct _FILE_BOTH_DIR_INFORMATION {
|
||||
WCHAR FileName[1];
|
||||
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
|
||||
|
||||
typedef struct _FILE_ID_FULL_DIR_INFORMATION {
|
||||
ULONG NextEntryOffset;
|
||||
ULONG FileIndex;
|
||||
LARGE_INTEGER CreationTime;
|
||||
LARGE_INTEGER LastAccessTime;
|
||||
LARGE_INTEGER LastWriteTime;
|
||||
LARGE_INTEGER ChangeTime;
|
||||
LARGE_INTEGER EndOfFile;
|
||||
LARGE_INTEGER AllocationSize;
|
||||
ULONG FileAttributes;
|
||||
ULONG FileNameLength;
|
||||
ULONG EaSize;
|
||||
LARGE_INTEGER FileId;
|
||||
WCHAR FileName[1];
|
||||
} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION;
|
||||
|
||||
typedef struct _FILE_BASIC_INFORMATION {
|
||||
LARGE_INTEGER CreationTime;
|
||||
LARGE_INTEGER LastAccessTime;
|
||||
|
||||
@ -4507,6 +4507,60 @@ TEST_IMPL(fs_open_readonly_acl) {
|
||||
MAKE_VALGRIND_HAPPY(loop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_IMPL(fs_stat_no_permission) {
|
||||
uv_passwd_t pwd;
|
||||
uv_fs_t req;
|
||||
int r;
|
||||
char* filename = "test_file_no_permission.txt";
|
||||
|
||||
/* Setup - clear the ACL and remove the file */
|
||||
loop = uv_default_loop();
|
||||
r = uv_os_get_passwd(&pwd);
|
||||
ASSERT_OK(r);
|
||||
call_icacls("icacls %s /remove *S-1-1-0:(F)", filename);
|
||||
unlink(filename);
|
||||
|
||||
/* Create the file */
|
||||
r = uv_fs_open(loop,
|
||||
&open_req1,
|
||||
filename,
|
||||
UV_FS_O_RDONLY | UV_FS_O_CREAT,
|
||||
S_IRUSR,
|
||||
NULL);
|
||||
ASSERT_GE(r, 0);
|
||||
ASSERT_GE(open_req1.result, 0);
|
||||
uv_fs_req_cleanup(&open_req1);
|
||||
r = uv_fs_close(NULL, &close_req, open_req1.result, NULL);
|
||||
ASSERT_OK(r);
|
||||
ASSERT_OK(close_req.result);
|
||||
uv_fs_req_cleanup(&close_req);
|
||||
|
||||
/* Set up ACL */
|
||||
r = call_icacls("icacls %s /deny *S-1-1-0:(F)", filename);
|
||||
if (r != 0) {
|
||||
goto acl_cleanup;
|
||||
}
|
||||
|
||||
/* Read file stats */
|
||||
r = uv_fs_stat(NULL, &req, filename, NULL);
|
||||
if (r != 0) {
|
||||
goto acl_cleanup;
|
||||
}
|
||||
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
acl_cleanup:
|
||||
/* Cleanup */
|
||||
call_icacls("icacls %s /reset", filename);
|
||||
uv_fs_unlink(NULL, &unlink_req, filename, NULL);
|
||||
uv_fs_req_cleanup(&unlink_req);
|
||||
unlink(filename);
|
||||
uv_os_free_passwd(&pwd);
|
||||
ASSERT_OK(r);
|
||||
MAKE_VALGRIND_HAPPY(loop);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
@ -514,6 +514,7 @@ TEST_DECLARE (environment_creation)
|
||||
TEST_DECLARE (listen_with_simultaneous_accepts)
|
||||
TEST_DECLARE (listen_no_simultaneous_accepts)
|
||||
TEST_DECLARE (fs_stat_root)
|
||||
TEST_DECLARE (fs_stat_no_permission)
|
||||
TEST_DECLARE (spawn_with_an_odd_path)
|
||||
TEST_DECLARE (spawn_no_path)
|
||||
TEST_DECLARE (spawn_no_ext)
|
||||
@ -1043,6 +1044,7 @@ TASK_LIST_START
|
||||
TEST_ENTRY (listen_with_simultaneous_accepts)
|
||||
TEST_ENTRY (listen_no_simultaneous_accepts)
|
||||
TEST_ENTRY (fs_stat_root)
|
||||
TEST_ENTRY (fs_stat_no_permission)
|
||||
TEST_ENTRY (spawn_with_an_odd_path)
|
||||
TEST_ENTRY (spawn_no_path)
|
||||
TEST_ENTRY (spawn_no_ext)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user