From c17bd99f1ccf236698e5bda3edbbe82843710ef9 Mon Sep 17 00:00:00 2001 From: Stefan Stojanovic Date: Tue, 29 Nov 2022 23:46:09 +0100 Subject: [PATCH] win: fix fstat for pipes and character files (#3811) Calling uv_fs_fstat for file types other then disk type was resulting in error on Windows while it was retrieving data on Linux. This change enables getting fstat for pipes and character files on Windows with data fetched being as reasonable as possible. A simple test is also added to check this behavior on all platforms. It uses stdin, stdout and stderr. uv_fs_fstat needs to pass with disk files pipes and character files (eg. console). Refs: https://github.com/nodejs/node/issues/40006 Co-authored-by: Jameson Nash --- include/uv/win.h | 5 ++++ src/win/fs.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++- test/test-fs.c | 37 +++++++++++++++++++++++++++++ test/test-list.h | 2 ++ 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/include/uv/win.h b/include/uv/win.h index 5d08d637..1c69c022 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -70,6 +70,11 @@ typedef struct pollfd { # define S_IFLNK 0xA000 #endif +// Define missing in Windows Kit Include\{VERSION}\ucrt\sys\stat.h +#if defined(_CRT_INTERNAL_NONSTDC_NAMES) && _CRT_INTERNAL_NONSTDC_NAMES && !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + /* Additional signals supported by uv_signal and or uv_kill. The CRT defines * the following signals already: * diff --git a/src/win/fs.c b/src/win/fs.c index 79230799..efc23934 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -36,6 +36,8 @@ #include "handle-inl.h" #include "fs-fd-hash-inl.h" +#include + #define UV_FS_FREE_PATHS 0x0002 #define UV_FS_FREE_PTR 0x0008 @@ -1706,11 +1708,36 @@ void fs__closedir(uv_fs_t* req) { INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf, int do_lstat) { + FILE_FS_DEVICE_INFORMATION device_info; FILE_ALL_INFORMATION file_info; FILE_FS_VOLUME_INFORMATION volume_info; NTSTATUS nt_status; IO_STATUS_BLOCK io_status; + 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)) { + SetLastError(pRtlNtStatusToDosError(nt_status)); + return -1; + } + + /* 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; + return 0; + } + nt_status = pNtQueryInformationFile(handle, &io_status, &file_info, @@ -1915,6 +1942,37 @@ INLINE static void fs__stat_impl(uv_fs_t* req, int do_lstat) { } +INLINE static int fs__fstat_handle(int fd, HANDLE handle, uv_stat_t* statbuf) { + DWORD file_type; + + /* Each file type is processed differently. */ + file_type = uv_guess_handle(fd); + switch (file_type) { + /* Disk files use the existing logic from fs__stat_handle. */ + case UV_FILE: + return fs__stat_handle(handle, statbuf, 0); + + /* Devices and pipes are processed identically. There is no more information + * for them from any API. Fields are set as reasonably as possible and the + * function returns. */ + case UV_TTY: + case UV_NAMED_PIPE: + memset(statbuf, 0, sizeof(uv_stat_t)); + statbuf->st_mode = file_type == UV_TTY ? _S_IFCHR : _S_IFIFO; + statbuf->st_nlink = 1; + statbuf->st_rdev = (file_type == UV_TTY ? FILE_DEVICE_CONSOLE : FILE_DEVICE_NAMED_PIPE) << 16; + statbuf->st_ino = (uint64_t) handle; + return 0; + + /* If file type is unknown it is an error. */ + case UV_UNKNOWN_HANDLE: + default: + SetLastError(ERROR_INVALID_HANDLE); + return -1; + } +} + + static void fs__stat(uv_fs_t* req) { fs__stat_prepare_path(req->file.pathw); fs__stat_impl(req, 0); @@ -1940,7 +1998,7 @@ static void fs__fstat(uv_fs_t* req) { return; } - if (fs__stat_handle(handle, &req->statbuf, 0) != 0) { + if (fs__fstat_handle(fd, handle, &req->statbuf) != 0) { SET_REQ_WIN32_ERROR(req, GetLastError()); return; } diff --git a/test/test-fs.c b/test/test-fs.c index 78ee4999..ebf41050 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -1539,6 +1539,43 @@ TEST_IMPL(fs_fstat) { } +TEST_IMPL(fs_fstat_stdio) { + int fd; + int res; + uv_fs_t req; +#ifdef _WIN32 + uv_stat_t* st; + DWORD ft; +#endif + + for (fd = 0; fd <= 2; ++fd) { + res = uv_fs_fstat(NULL, &req, fd, NULL); + ASSERT(res == 0); + ASSERT(req.result == 0); + +#ifdef _WIN32 + st = req.ptr; + ft = uv_guess_handle(fd); + switch (ft) { + case UV_TTY: + case UV_NAMED_PIPE: + ASSERT(st->st_mode == ft == UV_TTY ? S_IFCHR : S_IFIFO); + ASSERT(st->st_nlink == 1); + ASSERT(st->st_rdev == (ft == UV_TTY ? FILE_DEVICE_CONSOLE : FILE_DEVICE_NAMED_PIPE) << 16); + break; + default: + break; + } +#endif + + uv_fs_req_cleanup(&req); + } + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + TEST_IMPL(fs_access) { int r; uv_fs_t req; diff --git a/test/test-list.h b/test/test-list.h index 45261be2..ec23b344 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -347,6 +347,7 @@ TEST_DECLARE (fs_async_sendfile_nodata) TEST_DECLARE (fs_mkdtemp) TEST_DECLARE (fs_mkstemp) TEST_DECLARE (fs_fstat) +TEST_DECLARE (fs_fstat_stdio) TEST_DECLARE (fs_access) TEST_DECLARE (fs_chmod) TEST_DECLARE (fs_copyfile) @@ -1024,6 +1025,7 @@ TASK_LIST_START TEST_ENTRY (fs_mkdtemp) TEST_ENTRY (fs_mkstemp) TEST_ENTRY (fs_fstat) + TEST_ENTRY (fs_fstat_stdio) TEST_ENTRY (fs_access) TEST_ENTRY (fs_chmod) TEST_ENTRY (fs_copyfile)