aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHüseyin Açacak <110401522+huseyinacacak-janea@users.noreply.github.com>2024-07-30 15:58:41 +0300
committerGitHub <noreply@github.com>2024-07-30 08:58:41 -0400
commit4e310d0f90af29e699e2dedad5fa0dcee181a7cc (patch)
treec58cdf3ede7eebe4c7b43710f0e88dd11464b5ed /src
parentf23037fe21ef8e7d36ebeaf5c8a589bd8e56d3dd (diff)
downloadlibuv-4e310d0f90af29e699e2dedad5fa0dcee181a7cc.tar.gz
libuv-4e310d0f90af29e699e2dedad5fa0dcee181a7cc.zip
win,fs: use the new Windows fast stat API (#4327)
Windows added a new API for file information, which doesn't have to open the file thus greatly improving performance: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfileinformationbyname The stat functions are already covered by tests, so no test was added here. I considered comparing the result of old and new code, but that would require exposing internal fs functions, and we would be testing Windows functionality, not libuv.
Diffstat (limited to 'src')
-rw-r--r--src/win/fs.c155
-rw-r--r--src/win/winapi.c10
-rw-r--r--src/win/winapi.h36
3 files changed, 171 insertions, 30 deletions
diff --git a/src/win/fs.c b/src/win/fs.c
index 8414f778..4b29e597 100644
--- a/src/win/fs.c
+++ b/src/win/fs.c
@@ -147,6 +147,16 @@ static int uv__file_symlink_usermode_flag = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGE
static DWORD uv__allocation_granularity;
+typedef enum {
+ FS__STAT_PATH_SUCCESS,
+ FS__STAT_PATH_ERROR,
+ FS__STAT_PATH_TRY_SLOW
+} fs__stat_path_return_t;
+
+INLINE static void fs__stat_assign_statbuf_null(uv_stat_t* statbuf);
+INLINE static void fs__stat_assign_statbuf(uv_stat_t* statbuf,
+ FILE_STAT_BASIC_INFORMATION stat_info, int do_lstat);
+
void uv__fs_init(void) {
SYSTEM_INFO system_info;
@@ -1673,6 +1683,43 @@ void fs__closedir(uv_fs_t* req) {
SET_REQ_RESULT(req, 0);
}
+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.
+ if (!pGetFileInformationByName) {
+ return FS__STAT_PATH_TRY_SLOW;
+ }
+
+ // Check if the API call fails.
+ if (!pGetFileInformationByName(path, FileStatBasicByNameInfo, &stat_info,
+ sizeof(stat_info))) {
+ switch(GetLastError()) {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_NOT_READY:
+ case ERROR_BAD_NET_NAME:
+ /* These errors aren't worth retrying with the slow path. */
+ return FS__STAT_PATH_ERROR;
+ }
+ return FS__STAT_PATH_TRY_SLOW;
+ }
+
+ // 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;
+ }
+
+ if (stat_info.DeviceType == FILE_DEVICE_NULL) {
+ fs__stat_assign_statbuf_null(statbuf);
+ return FS__STAT_PATH_SUCCESS;
+ }
+
+ fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
+ return FS__STAT_PATH_SUCCESS;
+}
+
INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
int do_lstat) {
size_t target_length = 0;
@@ -1681,6 +1728,7 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
FILE_FS_VOLUME_INFORMATION volume_info;
NTSTATUS nt_status;
IO_STATUS_BLOCK io_status;
+ FILE_STAT_BASIC_INFORMATION stat_info;
nt_status = pNtQueryVolumeInformationFile(handle,
&io_status,
@@ -1696,13 +1744,7 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
/* If it's NUL device set fields as reasonable as possible and return. */
if (device_info.DeviceType == FILE_DEVICE_NULL) {
- memset(statbuf, 0, sizeof(uv_stat_t));
- statbuf->st_mode = _S_IFCHR;
- statbuf->st_mode |= (_S_IREAD | _S_IWRITE) | ((_S_IREAD | _S_IWRITE) >> 3) |
- ((_S_IREAD | _S_IWRITE) >> 6);
- statbuf->st_nlink = 1;
- statbuf->st_blksize = 4096;
- statbuf->st_rdev = FILE_DEVICE_NULL << 16;
+ fs__stat_assign_statbuf_null(statbuf);
return 0;
}
@@ -1726,14 +1768,65 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
/* Buffer overflow (a warning status code) is expected here. */
if (io_status.Status == STATUS_NOT_IMPLEMENTED) {
- statbuf->st_dev = 0;
+ stat_info.VolumeSerialNumber.QuadPart = 0;
} else if (NT_ERROR(nt_status)) {
SetLastError(pRtlNtStatusToDosError(nt_status));
return -1;
} else {
- statbuf->st_dev = volume_info.VolumeSerialNumber;
+ stat_info.VolumeSerialNumber.QuadPart = volume_info.VolumeSerialNumber;
+ }
+
+ stat_info.DeviceType = device_info.DeviceType;
+ stat_info.FileAttributes = file_info.BasicInformation.FileAttributes;
+ stat_info.NumberOfLinks = file_info.StandardInformation.NumberOfLinks;
+ stat_info.FileId.QuadPart =
+ file_info.InternalInformation.IndexNumber.QuadPart;
+ stat_info.ChangeTime.QuadPart =
+ file_info.BasicInformation.ChangeTime.QuadPart;
+ stat_info.CreationTime.QuadPart =
+ file_info.BasicInformation.CreationTime.QuadPart;
+ stat_info.LastAccessTime.QuadPart =
+ file_info.BasicInformation.LastAccessTime.QuadPart;
+ stat_info.LastWriteTime.QuadPart =
+ file_info.BasicInformation.LastWriteTime.QuadPart;
+ stat_info.AllocationSize.QuadPart =
+ file_info.StandardInformation.AllocationSize.QuadPart;
+
+ if (do_lstat &&
+ (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ /*
+ * If reading the link fails, the reparse point is not a symlink and needs
+ * to be treated as a regular file. The higher level lstat function will
+ * 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;
+ } else {
+ stat_info.EndOfFile.QuadPart =
+ file_info.StandardInformation.EndOfFile.QuadPart;
}
+ fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
+ return 0;
+}
+
+INLINE static void fs__stat_assign_statbuf_null(uv_stat_t* statbuf) {
+ memset(statbuf, 0, sizeof(uv_stat_t));
+ statbuf->st_mode = _S_IFCHR;
+ statbuf->st_mode |= (_S_IREAD | _S_IWRITE) | ((_S_IREAD | _S_IWRITE) >> 3) |
+ ((_S_IREAD | _S_IWRITE) >> 6);
+ statbuf->st_nlink = 1;
+ statbuf->st_blksize = 4096;
+ statbuf->st_rdev = FILE_DEVICE_NULL << 16;
+}
+
+INLINE static void fs__stat_assign_statbuf(uv_stat_t* statbuf,
+ FILE_STAT_BASIC_INFORMATION stat_info, int do_lstat) {
+ statbuf->st_dev = stat_info.VolumeSerialNumber.QuadPart;
+
/* Todo: st_mode should probably always be 0666 for everyone. We might also
* want to report 0777 if the file is a .exe or a directory.
*
@@ -1765,50 +1858,43 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
* target. Otherwise, reparse points must be treated as regular files.
*/
if (do_lstat &&
- (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
- /*
- * If reading the link fails, the reparse point is not a symlink and needs
- * to be treated as a regular file. The higher level lstat function will
- * detect this failure and retry without do_lstat if appropriate.
- */
- if (fs__readlink_handle(handle, NULL, &target_length) != 0)
- return -1;
+ (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
statbuf->st_mode |= S_IFLNK;
- statbuf->st_size = target_length;
+ statbuf->st_size = stat_info.EndOfFile.QuadPart;
}
if (statbuf->st_mode == 0) {
- if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if (stat_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
statbuf->st_mode |= _S_IFDIR;
statbuf->st_size = 0;
} else {
statbuf->st_mode |= _S_IFREG;
- statbuf->st_size = file_info.StandardInformation.EndOfFile.QuadPart;
+ statbuf->st_size = stat_info.EndOfFile.QuadPart;
}
}
- if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_READONLY)
+ if (stat_info.FileAttributes & FILE_ATTRIBUTE_READONLY)
statbuf->st_mode |= _S_IREAD | (_S_IREAD >> 3) | (_S_IREAD >> 6);
else
statbuf->st_mode |= (_S_IREAD | _S_IWRITE) | ((_S_IREAD | _S_IWRITE) >> 3) |
((_S_IREAD | _S_IWRITE) >> 6);
uv__filetime_to_timespec(&statbuf->st_atim,
- file_info.BasicInformation.LastAccessTime.QuadPart);
+ stat_info.LastAccessTime.QuadPart);
uv__filetime_to_timespec(&statbuf->st_ctim,
- file_info.BasicInformation.ChangeTime.QuadPart);
+ stat_info.ChangeTime.QuadPart);
uv__filetime_to_timespec(&statbuf->st_mtim,
- file_info.BasicInformation.LastWriteTime.QuadPart);
+ stat_info.LastWriteTime.QuadPart);
uv__filetime_to_timespec(&statbuf->st_birthtim,
- file_info.BasicInformation.CreationTime.QuadPart);
+ stat_info.CreationTime.QuadPart);
- statbuf->st_ino = file_info.InternalInformation.IndexNumber.QuadPart;
+ statbuf->st_ino = stat_info.FileId.QuadPart;
/* st_blocks contains the on-disk allocation size in 512-byte units. */
statbuf->st_blocks =
- (uint64_t) file_info.StandardInformation.AllocationSize.QuadPart >> 9;
+ (uint64_t) stat_info.AllocationSize.QuadPart >> 9;
- statbuf->st_nlink = file_info.StandardInformation.NumberOfLinks;
+ statbuf->st_nlink = stat_info.NumberOfLinks;
/* The st_blksize is supposed to be the 'optimal' number of bytes for reading
* and writing to the disk. That is, for any definition of 'optimal' - it's
@@ -1840,8 +1926,6 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
statbuf->st_uid = 0;
statbuf->st_rdev = 0;
statbuf->st_gen = 0;
-
- return 0;
}
@@ -1863,6 +1947,17 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
DWORD flags;
DWORD ret;
+ // If new API exists, try to use it.
+ switch (fs__stat_path(path, statbuf, do_lstat)) {
+ case FS__STAT_PATH_SUCCESS:
+ return 0;
+ case FS__STAT_PATH_ERROR:
+ return GetLastError();
+ case FS__STAT_PATH_TRY_SLOW:
+ break;
+ }
+
+ // 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;
diff --git a/src/win/winapi.c b/src/win/winapi.c
index 53147b82..4add0e27 100644
--- a/src/win/winapi.c
+++ b/src/win/winapi.c
@@ -48,12 +48,16 @@ sSetWinEventHook pSetWinEventHook;
/* ws2_32.dll function pointer */
uv_sGetHostNameW pGetHostNameW;
+/* api-ms-win-core-file-l2-1-4.dll function pointer */
+sGetFileInformationByName pGetFileInformationByName;
+
void uv__winapi_init(void) {
HMODULE ntdll_module;
HMODULE powrprof_module;
HMODULE user32_module;
HMODULE kernel32_module;
HMODULE ws2_32_module;
+ HMODULE api_win_core_file_module;
ntdll_module = GetModuleHandleA("ntdll.dll");
if (ntdll_module == NULL) {
@@ -144,4 +148,10 @@ void uv__winapi_init(void) {
ws2_32_module,
"GetHostNameW");
}
+
+ api_win_core_file_module = GetModuleHandleA("api-ms-win-core-file-l2-1-4.dll");
+ if (api_win_core_file_module != NULL) {
+ pGetFileInformationByName = (sGetFileInformationByName)GetProcAddress(
+ api_win_core_file_module, "GetFileInformationByName");
+ }
}
diff --git a/src/win/winapi.h b/src/win/winapi.h
index d3449c18..6d948236 100644
--- a/src/win/winapi.h
+++ b/src/win/winapi.h
@@ -4125,6 +4125,24 @@ typedef const UNICODE_STRING *PCUNICODE_STRING;
# define DEVICE_TYPE DWORD
#endif
+typedef struct _FILE_STAT_BASIC_INFORMATION {
+ LARGE_INTEGER FileId;
+ LARGE_INTEGER CreationTime;
+ LARGE_INTEGER LastAccessTime;
+ LARGE_INTEGER LastWriteTime;
+ LARGE_INTEGER ChangeTime;
+ LARGE_INTEGER AllocationSize;
+ LARGE_INTEGER EndOfFile;
+ ULONG FileAttributes;
+ ULONG ReparseTag;
+ ULONG NumberOfLinks;
+ ULONG DeviceType;
+ ULONG DeviceCharacteristics;
+ ULONG Reserved;
+ FILE_ID_128 FileId128;
+ LARGE_INTEGER VolumeSerialNumber;
+} FILE_STAT_BASIC_INFORMATION;
+
/* MinGW already has a definition for REPARSE_DATA_BUFFER, but mingw-w64 does
* not.
*/
@@ -4752,6 +4770,21 @@ typedef struct _TCP_INITIAL_RTO_PARAMETERS {
# define SIO_TCP_INITIAL_RTO _WSAIOW(IOC_VENDOR,17)
#endif
+/* from winnt.h */
+typedef enum _FILE_INFO_BY_NAME_CLASS {
+ FileStatByNameInfo,
+ FileStatLxByNameInfo,
+ FileCaseSensitiveByNameInfo,
+ FileStatBasicByNameInfo,
+ MaximumFileInfoByNameClass
+} FILE_INFO_BY_NAME_CLASS;
+
+typedef BOOL(WINAPI* sGetFileInformationByName)(
+ PCWSTR FileName,
+ FILE_INFO_BY_NAME_CLASS FileInformationClass,
+ PVOID FileInfoBuffer,
+ ULONG FileInfoBufferSize);
+
/* Ntdll function pointers */
extern sRtlGetVersion pRtlGetVersion;
extern sRtlNtStatusToDosError pRtlNtStatusToDosError;
@@ -4772,6 +4805,9 @@ extern sPowerRegisterSuspendResumeNotification pPowerRegisterSuspendResumeNotifi
/* User32.dll function pointer */
extern sSetWinEventHook pSetWinEventHook;
+/* api-ms-win-core-file-l2-1-4.dll function pointers */
+extern sGetFileInformationByName pGetFileInformationByName;
+
/* ws2_32.dll function pointer */
/* mingw doesn't have this definition, so let's declare it here locally */
typedef int (WINAPI *uv_sGetHostNameW)