Skip to content

Commit 72d9abc

Browse files
win,fs: get (most) fstat when no permission (libuv#4566)
Replaces: libuv#4504 Fixes: libuv#1980 Fixes: libuv#3267 Co-authored-by: Hüseyin Açacak <huseyin@janeasystems.com>
1 parent 16e6e84 commit 72d9abc

File tree

4 files changed

+269
-8
lines changed

4 files changed

+269
-8
lines changed

src/win/fs.c

Lines changed: 197 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@
5858
#define FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE 0x0010
5959
#endif /* FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE */
6060

61+
NTSTATUS uv__RtlUnicodeStringInit(
62+
PUNICODE_STRING DestinationString,
63+
PWSTR SourceString,
64+
size_t SourceStringLen
65+
) {
66+
if (SourceStringLen > 0x7FFF)
67+
return STATUS_INVALID_PARAMETER;
68+
DestinationString->MaximumLength = DestinationString->Length =
69+
SourceStringLen * sizeof(SourceString[0]);
70+
DestinationString->Buffer = SourceString;
71+
return STATUS_SUCCESS;
72+
}
73+
6174
#define INIT(subtype) \
6275
do { \
6376
if (req == NULL) \
@@ -1689,12 +1702,12 @@ INLINE static fs__stat_path_return_t fs__stat_path(WCHAR* path,
16891702
uv_stat_t* statbuf, int do_lstat) {
16901703
FILE_STAT_BASIC_INFORMATION stat_info;
16911704

1692-
// Check if the new fast API is available.
1705+
/* Check if the new fast API is available. */
16931706
if (!pGetFileInformationByName) {
16941707
return FS__STAT_PATH_TRY_SLOW;
16951708
}
16961709

1697-
// Check if the API call fails.
1710+
/* Check if the API call fails. */
16981711
if (!pGetFileInformationByName(path, FileStatBasicByNameInfo, &stat_info,
16991712
sizeof(stat_info))) {
17001713
switch(GetLastError()) {
@@ -1708,7 +1721,7 @@ INLINE static fs__stat_path_return_t fs__stat_path(WCHAR* path,
17081721
return FS__STAT_PATH_TRY_SLOW;
17091722
}
17101723

1711-
// A file handle is needed to get st_size for links.
1724+
/* A file handle is needed to get st_size for links. */
17121725
if ((stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
17131726
return FS__STAT_PATH_TRY_SLOW;
17141727
}
@@ -1802,7 +1815,6 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
18021815
* detect this failure and retry without do_lstat if appropriate.
18031816
*/
18041817
if (fs__readlink_handle(handle, NULL, &target_length) != 0) {
1805-
fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
18061818
return -1;
18071819
}
18081820
stat_info.EndOfFile.QuadPart = target_length;
@@ -1941,6 +1953,179 @@ INLINE static void fs__stat_prepare_path(WCHAR* pathw) {
19411953
}
19421954
}
19431955

1956+
INLINE static DWORD fs__stat_directory(WCHAR* path, uv_stat_t* statbuf,
1957+
int do_lstat, DWORD ret_error) {
1958+
HANDLE handle = INVALID_HANDLE_VALUE;
1959+
FILE_STAT_BASIC_INFORMATION stat_info;
1960+
FILE_ID_FULL_DIR_INFORMATION dir_info;
1961+
FILE_FS_VOLUME_INFORMATION volume_info;
1962+
FILE_FS_DEVICE_INFORMATION device_info;
1963+
IO_STATUS_BLOCK io_status;
1964+
NTSTATUS nt_status;
1965+
WCHAR* path_dirpath = NULL;
1966+
WCHAR* path_filename = NULL;
1967+
UNICODE_STRING FileMask;
1968+
size_t len;
1969+
size_t split;
1970+
WCHAR splitchar;
1971+
int includes_name;
1972+
1973+
/* AKA strtok or wcscspn, in reverse. */
1974+
len = wcslen(path);
1975+
split = len;
1976+
1977+
includes_name = 0;
1978+
while (split > 0 && path[split - 1] != L'\\' && path[split - 1] != L'/' &&
1979+
path[split - 1] != L':') {
1980+
/* check if the path contains a character other than /,\,:,. */
1981+
if (path[split-1] != '.') {
1982+
includes_name = 1;
1983+
}
1984+
split--;
1985+
}
1986+
/* If the path is a relative path with a file name or a folder name */
1987+
if (split == 0 && includes_name) {
1988+
path_dirpath = L".";
1989+
/* If there is a slash or a backslash */
1990+
} else if (path[split - 1] == L'\\' || path[split - 1] == L'/') {
1991+
path_dirpath = path;
1992+
/* If there is no filename, consider it as a relative folder path */
1993+
if (!includes_name) {
1994+
split = len;
1995+
/* Else, split it */
1996+
} else {
1997+
splitchar = path[split - 1];
1998+
path[split - 1] = L'\0';
1999+
}
2000+
/* e.g. "..", "c:" */
2001+
} else {
2002+
path_dirpath = path;
2003+
split = len;
2004+
}
2005+
path_filename = &path[split];
2006+
2007+
len = 0;
2008+
while (1) {
2009+
if (path_filename[len] == L'\0')
2010+
break;
2011+
if (path_filename[len] == L'*' || path_filename[len] == L'?' ||
2012+
path_filename[len] == L'>' || path_filename[len] == L'<' ||
2013+
path_filename[len] == L'"') {
2014+
ret_error = ERROR_INVALID_NAME;
2015+
goto cleanup;
2016+
}
2017+
len++;
2018+
}
2019+
2020+
/* Get directory handle */
2021+
handle = CreateFileW(path_dirpath,
2022+
FILE_LIST_DIRECTORY,
2023+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
2024+
NULL,
2025+
OPEN_EXISTING,
2026+
FILE_FLAG_BACKUP_SEMANTICS,
2027+
NULL);
2028+
2029+
if (handle == INVALID_HANDLE_VALUE) {
2030+
ret_error = GetLastError();
2031+
goto cleanup;
2032+
}
2033+
2034+
/* Get files in the directory */
2035+
nt_status = uv__RtlUnicodeStringInit(&FileMask, path_filename, len);
2036+
if (!NT_SUCCESS(nt_status)) {
2037+
ret_error = pRtlNtStatusToDosError(nt_status);
2038+
goto cleanup;
2039+
}
2040+
nt_status = pNtQueryDirectoryFile(handle,
2041+
NULL,
2042+
NULL,
2043+
NULL,
2044+
&io_status,
2045+
&dir_info,
2046+
sizeof(dir_info),
2047+
FileIdFullDirectoryInformation,
2048+
TRUE,
2049+
&FileMask,
2050+
TRUE);
2051+
2052+
/* Buffer overflow (a warning status code) is expected here since there isn't
2053+
* enough space to store the FileName, and actually indicates success. */
2054+
if (!NT_SUCCESS(nt_status) && nt_status != STATUS_BUFFER_OVERFLOW) {
2055+
if (nt_status == STATUS_NO_MORE_FILES)
2056+
ret_error = ERROR_PATH_NOT_FOUND;
2057+
else
2058+
ret_error = pRtlNtStatusToDosError(nt_status);
2059+
goto cleanup;
2060+
}
2061+
2062+
/* Assign values to stat_info */
2063+
memset(&stat_info, 0, sizeof(FILE_STAT_BASIC_INFORMATION));
2064+
stat_info.FileAttributes = dir_info.FileAttributes;
2065+
stat_info.CreationTime.QuadPart = dir_info.CreationTime.QuadPart;
2066+
stat_info.LastAccessTime.QuadPart = dir_info.LastAccessTime.QuadPart;
2067+
stat_info.LastWriteTime.QuadPart = dir_info.LastWriteTime.QuadPart;
2068+
if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
2069+
/* A file handle is needed to get st_size for the link (from
2070+
* FSCTL_GET_REPARSE_POINT), which is required by posix, but we are here
2071+
* because getting the file handle failed. We could get just the
2072+
* ReparsePointTag by querying FILE_ID_EXTD_DIR_INFORMATION instead to make
2073+
* sure this really is a link before giving up here on the uv_fs_stat call,
2074+
* but that doesn't seem essential. */
2075+
if (!do_lstat)
2076+
goto cleanup;
2077+
stat_info.EndOfFile.QuadPart = 0;
2078+
stat_info.AllocationSize.QuadPart = 0;
2079+
} else {
2080+
stat_info.EndOfFile.QuadPart = dir_info.EndOfFile.QuadPart;
2081+
stat_info.AllocationSize.QuadPart = dir_info.AllocationSize.QuadPart;
2082+
}
2083+
stat_info.ChangeTime.QuadPart = dir_info.ChangeTime.QuadPart;
2084+
stat_info.FileId.QuadPart = dir_info.FileId.QuadPart;
2085+
2086+
/* Finish up by getting device info from the directory handle,
2087+
* since files presumably must live on their device. */
2088+
nt_status = pNtQueryVolumeInformationFile(handle,
2089+
&io_status,
2090+
&volume_info,
2091+
sizeof volume_info,
2092+
FileFsVolumeInformation);
2093+
2094+
/* Buffer overflow (a warning status code) is expected here. */
2095+
if (io_status.Status == STATUS_NOT_IMPLEMENTED) {
2096+
stat_info.VolumeSerialNumber.QuadPart = 0;
2097+
} else if (NT_ERROR(nt_status)) {
2098+
ret_error = pRtlNtStatusToDosError(nt_status);
2099+
goto cleanup;
2100+
} else {
2101+
stat_info.VolumeSerialNumber.QuadPart = volume_info.VolumeSerialNumber;
2102+
}
2103+
2104+
nt_status = pNtQueryVolumeInformationFile(handle,
2105+
&io_status,
2106+
&device_info,
2107+
sizeof device_info,
2108+
FileFsDeviceInformation);
2109+
2110+
/* Buffer overflow (a warning status code) is expected here. */
2111+
if (NT_ERROR(nt_status)) {
2112+
ret_error = pRtlNtStatusToDosError(nt_status);
2113+
goto cleanup;
2114+
}
2115+
2116+
stat_info.DeviceType = device_info.DeviceType;
2117+
stat_info.NumberOfLinks = 1; /* No way to recover this info. */
2118+
2119+
fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
2120+
ret_error = 0;
2121+
2122+
cleanup:
2123+
if (split != 0)
2124+
path[split - 1] = splitchar;
2125+
if (handle != INVALID_HANDLE_VALUE)
2126+
CloseHandle(handle);
2127+
return ret_error;
2128+
}
19442129

19452130
INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
19462131
int do_lstat,
@@ -1949,7 +2134,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
19492134
DWORD flags;
19502135
DWORD ret;
19512136

1952-
// If new API exists, try to use it.
2137+
/* If new API exists, try to use it. */
19532138
switch (fs__stat_path(path, statbuf, do_lstat)) {
19542139
case FS__STAT_PATH_SUCCESS:
19552140
return 0;
@@ -1959,7 +2144,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
19592144
break;
19602145
}
19612146

1962-
// If the new API does not exist, use the old API.
2147+
/* If the new API does not exist, use the old API. */
19632148
flags = FILE_FLAG_BACKUP_SEMANTICS;
19642149
if (do_lstat)
19652150
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
@@ -1972,8 +2157,12 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
19722157
flags,
19732158
NULL);
19742159

1975-
if (handle == INVALID_HANDLE_VALUE)
1976-
return GetLastError();
2160+
if (handle == INVALID_HANDLE_VALUE) {
2161+
ret = GetLastError();
2162+
if (ret != ERROR_ACCESS_DENIED && ret != ERROR_SHARING_VIOLATION)
2163+
return ret;
2164+
return fs__stat_directory(path, statbuf, do_lstat, ret);
2165+
}
19772166

19782167
if (fs__stat_handle(handle, statbuf, do_lstat) != 0)
19792168
ret = GetLastError();

src/win/winapi.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4287,6 +4287,22 @@ typedef struct _FILE_BOTH_DIR_INFORMATION {
42874287
WCHAR FileName[1];
42884288
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
42894289

4290+
typedef struct _FILE_ID_FULL_DIR_INFORMATION {
4291+
ULONG NextEntryOffset;
4292+
ULONG FileIndex;
4293+
LARGE_INTEGER CreationTime;
4294+
LARGE_INTEGER LastAccessTime;
4295+
LARGE_INTEGER LastWriteTime;
4296+
LARGE_INTEGER ChangeTime;
4297+
LARGE_INTEGER EndOfFile;
4298+
LARGE_INTEGER AllocationSize;
4299+
ULONG FileAttributes;
4300+
ULONG FileNameLength;
4301+
ULONG EaSize;
4302+
LARGE_INTEGER FileId;
4303+
WCHAR FileName[1];
4304+
} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION;
4305+
42904306
typedef struct _FILE_BASIC_INFORMATION {
42914307
LARGE_INTEGER CreationTime;
42924308
LARGE_INTEGER LastAccessTime;

test/test-fs.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4507,6 +4507,60 @@ TEST_IMPL(fs_open_readonly_acl) {
45074507
MAKE_VALGRIND_HAPPY(loop);
45084508
return 0;
45094509
}
4510+
4511+
TEST_IMPL(fs_stat_no_permission) {
4512+
uv_passwd_t pwd;
4513+
uv_fs_t req;
4514+
int r;
4515+
char* filename = "test_file_no_permission.txt";
4516+
4517+
/* Setup - clear the ACL and remove the file */
4518+
loop = uv_default_loop();
4519+
r = uv_os_get_passwd(&pwd);
4520+
ASSERT_OK(r);
4521+
call_icacls("icacls %s /remove *S-1-1-0:(F)", filename);
4522+
unlink(filename);
4523+
4524+
/* Create the file */
4525+
r = uv_fs_open(loop,
4526+
&open_req1,
4527+
filename,
4528+
UV_FS_O_RDONLY | UV_FS_O_CREAT,
4529+
S_IRUSR,
4530+
NULL);
4531+
ASSERT_GE(r, 0);
4532+
ASSERT_GE(open_req1.result, 0);
4533+
uv_fs_req_cleanup(&open_req1);
4534+
r = uv_fs_close(NULL, &close_req, open_req1.result, NULL);
4535+
ASSERT_OK(r);
4536+
ASSERT_OK(close_req.result);
4537+
uv_fs_req_cleanup(&close_req);
4538+
4539+
/* Set up ACL */
4540+
r = call_icacls("icacls %s /deny *S-1-1-0:(F)", filename);
4541+
if (r != 0) {
4542+
goto acl_cleanup;
4543+
}
4544+
4545+
/* Read file stats */
4546+
r = uv_fs_stat(NULL, &req, filename, NULL);
4547+
if (r != 0) {
4548+
goto acl_cleanup;
4549+
}
4550+
4551+
uv_fs_req_cleanup(&req);
4552+
4553+
acl_cleanup:
4554+
/* Cleanup */
4555+
call_icacls("icacls %s /reset", filename);
4556+
uv_fs_unlink(NULL, &unlink_req, filename, NULL);
4557+
uv_fs_req_cleanup(&unlink_req);
4558+
unlink(filename);
4559+
uv_os_free_passwd(&pwd);
4560+
ASSERT_OK(r);
4561+
MAKE_VALGRIND_HAPPY(loop);
4562+
return 0;
4563+
}
45104564
#endif
45114565

45124566
#ifdef _WIN32

test/test-list.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ TEST_DECLARE (environment_creation)
514514
TEST_DECLARE (listen_with_simultaneous_accepts)
515515
TEST_DECLARE (listen_no_simultaneous_accepts)
516516
TEST_DECLARE (fs_stat_root)
517+
TEST_DECLARE (fs_stat_no_permission)
517518
TEST_DECLARE (spawn_with_an_odd_path)
518519
TEST_DECLARE (spawn_no_path)
519520
TEST_DECLARE (spawn_no_ext)
@@ -1043,6 +1044,7 @@ TASK_LIST_START
10431044
TEST_ENTRY (listen_with_simultaneous_accepts)
10441045
TEST_ENTRY (listen_no_simultaneous_accepts)
10451046
TEST_ENTRY (fs_stat_root)
1047+
TEST_ENTRY (fs_stat_no_permission)
10461048
TEST_ENTRY (spawn_with_an_odd_path)
10471049
TEST_ENTRY (spawn_no_path)
10481050
TEST_ENTRY (spawn_no_ext)

0 commit comments

Comments
 (0)