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
|
#define FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE 0x0010
|
||||||
#endif /* FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE */
|
#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) \
|
#define INIT(subtype) \
|
||||||
do { \
|
do { \
|
||||||
if (req == NULL) \
|
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) {
|
uv_stat_t* statbuf, int do_lstat) {
|
||||||
FILE_STAT_BASIC_INFORMATION stat_info;
|
FILE_STAT_BASIC_INFORMATION stat_info;
|
||||||
|
|
||||||
// Check if the new fast API is available.
|
/* Check if the new fast API is available. */
|
||||||
if (!pGetFileInformationByName) {
|
if (!pGetFileInformationByName) {
|
||||||
return FS__STAT_PATH_TRY_SLOW;
|
return FS__STAT_PATH_TRY_SLOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the API call fails.
|
/* Check if the API call fails. */
|
||||||
if (!pGetFileInformationByName(path, FileStatBasicByNameInfo, &stat_info,
|
if (!pGetFileInformationByName(path, FileStatBasicByNameInfo, &stat_info,
|
||||||
sizeof(stat_info))) {
|
sizeof(stat_info))) {
|
||||||
switch(GetLastError()) {
|
switch(GetLastError()) {
|
||||||
@ -1708,7 +1721,7 @@ INLINE static fs__stat_path_return_t fs__stat_path(WCHAR* path,
|
|||||||
return FS__STAT_PATH_TRY_SLOW;
|
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)) {
|
if ((stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
|
||||||
return FS__STAT_PATH_TRY_SLOW;
|
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.
|
* detect this failure and retry without do_lstat if appropriate.
|
||||||
*/
|
*/
|
||||||
if (fs__readlink_handle(handle, NULL, &target_length) != 0) {
|
if (fs__readlink_handle(handle, NULL, &target_length) != 0) {
|
||||||
fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
stat_info.EndOfFile.QuadPart = target_length;
|
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,
|
INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
||||||
int do_lstat,
|
int do_lstat,
|
||||||
@ -1949,7 +2134,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
|||||||
DWORD flags;
|
DWORD flags;
|
||||||
DWORD ret;
|
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)) {
|
switch (fs__stat_path(path, statbuf, do_lstat)) {
|
||||||
case FS__STAT_PATH_SUCCESS:
|
case FS__STAT_PATH_SUCCESS:
|
||||||
return 0;
|
return 0;
|
||||||
@ -1959,7 +2144,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
|||||||
break;
|
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;
|
flags = FILE_FLAG_BACKUP_SEMANTICS;
|
||||||
if (do_lstat)
|
if (do_lstat)
|
||||||
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
|
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
|
||||||
@ -1972,8 +2157,12 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
|
|||||||
flags,
|
flags,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
if (handle == INVALID_HANDLE_VALUE)
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
return GetLastError();
|
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)
|
if (fs__stat_handle(handle, statbuf, do_lstat) != 0)
|
||||||
ret = GetLastError();
|
ret = GetLastError();
|
||||||
|
|||||||
@ -4287,6 +4287,22 @@ typedef struct _FILE_BOTH_DIR_INFORMATION {
|
|||||||
WCHAR FileName[1];
|
WCHAR FileName[1];
|
||||||
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
|
} 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 {
|
typedef struct _FILE_BASIC_INFORMATION {
|
||||||
LARGE_INTEGER CreationTime;
|
LARGE_INTEGER CreationTime;
|
||||||
LARGE_INTEGER LastAccessTime;
|
LARGE_INTEGER LastAccessTime;
|
||||||
|
|||||||
@ -4507,6 +4507,60 @@ TEST_IMPL(fs_open_readonly_acl) {
|
|||||||
MAKE_VALGRIND_HAPPY(loop);
|
MAKE_VALGRIND_HAPPY(loop);
|
||||||
return 0;
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|||||||
@ -514,6 +514,7 @@ TEST_DECLARE (environment_creation)
|
|||||||
TEST_DECLARE (listen_with_simultaneous_accepts)
|
TEST_DECLARE (listen_with_simultaneous_accepts)
|
||||||
TEST_DECLARE (listen_no_simultaneous_accepts)
|
TEST_DECLARE (listen_no_simultaneous_accepts)
|
||||||
TEST_DECLARE (fs_stat_root)
|
TEST_DECLARE (fs_stat_root)
|
||||||
|
TEST_DECLARE (fs_stat_no_permission)
|
||||||
TEST_DECLARE (spawn_with_an_odd_path)
|
TEST_DECLARE (spawn_with_an_odd_path)
|
||||||
TEST_DECLARE (spawn_no_path)
|
TEST_DECLARE (spawn_no_path)
|
||||||
TEST_DECLARE (spawn_no_ext)
|
TEST_DECLARE (spawn_no_ext)
|
||||||
@ -1043,6 +1044,7 @@ TASK_LIST_START
|
|||||||
TEST_ENTRY (listen_with_simultaneous_accepts)
|
TEST_ENTRY (listen_with_simultaneous_accepts)
|
||||||
TEST_ENTRY (listen_no_simultaneous_accepts)
|
TEST_ENTRY (listen_no_simultaneous_accepts)
|
||||||
TEST_ENTRY (fs_stat_root)
|
TEST_ENTRY (fs_stat_root)
|
||||||
|
TEST_ENTRY (fs_stat_no_permission)
|
||||||
TEST_ENTRY (spawn_with_an_odd_path)
|
TEST_ENTRY (spawn_with_an_odd_path)
|
||||||
TEST_ENTRY (spawn_no_path)
|
TEST_ENTRY (spawn_no_path)
|
||||||
TEST_ENTRY (spawn_no_ext)
|
TEST_ENTRY (spawn_no_ext)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user