From f30e015b85fc43dbfd65cbe910ee78e3fbe87b2e Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Wed, 12 Jun 2024 14:26:50 -0700 Subject: [PATCH 1/9] Initial Linux implementation of uv_fs_openat This commit adds the initial Linux implementation of `uv_fs_openat`. Windows support using `NtCreateFile` will come in a later commit. fixes #4167 Signed-off-by: Yage Hu --- include/uv.h | 10 ++++++++- src/unix/fs.c | 47 +++++++++++++++++++++++++++++++++++++++ src/unix/internal.h | 2 ++ src/unix/linux.c | 22 ++++++++++++++++++ test/test-fs.c | 54 +++++++++++++++++++++++++++++++++++++++++++++ test/test-list.h | 2 ++ 6 files changed, 136 insertions(+), 1 deletion(-) diff --git a/include/uv.h b/include/uv.h index 3b6b7224..b5744a8d 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1398,7 +1398,8 @@ typedef enum { UV_FS_CLOSEDIR, UV_FS_STATFS, UV_FS_MKSTEMP, - UV_FS_LUTIME + UV_FS_LUTIME, + UV_FS_OPENAT } uv_fs_type; struct uv_dir_s { @@ -1439,6 +1440,13 @@ UV_EXTERN int uv_fs_open(uv_loop_t* loop, int flags, int mode, uv_fs_cb cb); +UV_EXTERN int uv_fs_openat(uv_loop_t* loop, + uv_fs_t* req, + uv_os_fd_t file, + const char* path, + int flags, + int mode, + uv_fs_cb cb); UV_EXTERN int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file, diff --git a/src/unix/fs.c b/src/unix/fs.c index c4eadd63..40b57465 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -504,6 +504,34 @@ static ssize_t uv__pwritev(int fd, return uv__preadv_or_pwritev(fd, bufs, nbufs, off, &cache, /*is_pread*/0); } +static ssize_t uv__fs_openat(uv_fs_t* req) { +#ifdef O_CLOEXEC + return openat(req->file, req->path, req->flags | O_CLOEXEC, req->mode); +#else /* O_CLOEXEC */ + int r; + + if (req->cb != NULL) + uv_rwlock_rdlock(&req->loop->cloexec_lock); + + r = openat(req->file, req->path, req->flags, req->mode); + + /* In case of failure `uv__cloexec` will leave error in `errno`, + * so it is enough to just set `r` to `-1`. + */ + if (r >= 0 && uv__cloexec(r, 1) != 0) { + r = uv__close(r); + if (r != 0) + abort(); + r = -1; + } + + if (req->cb != NULL) + uv_rwlock_rdunlock(&req->loop->cloexec_lock); + + return r; +#endif /* O_CLOEXEC */ +} + static ssize_t uv__fs_read(uv_fs_t* req) { const struct iovec* bufs; @@ -1718,6 +1746,7 @@ static void uv__fs_work(struct uv__work* w) { X(MKDTEMP, uv__fs_mkdtemp(req)); X(MKSTEMP, uv__fs_mkstemp(req)); X(OPEN, uv__fs_open(req)); + X(OPENAT, uv__fs_openat(req)); X(READ, uv__fs_read(req)); X(SCANDIR, uv__fs_scandir(req)); X(OPENDIR, uv__fs_opendir(req)); @@ -2013,6 +2042,24 @@ int uv_fs_open(uv_loop_t* loop, POST; } +int uv_fs_openat(uv_loop_t* loop, + uv_fs_t* req, + uv_os_fd_t file, + const char* path, + int flags, + int mode, + uv_fs_cb cb) { + INIT(OPENAT); + PATH; + req->file = file; + req->flags = flags; + req->mode = mode; + if (cb != NULL) + if (uv__iou_fs_openat(loop, req)) + return 0; + POST; +} + int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file, diff --git a/src/unix/internal.h b/src/unix/internal.h index 3ad37052..b0617d57 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -338,6 +338,7 @@ int uv__iou_fs_fsync_or_fdatasync(uv_loop_t* loop, int uv__iou_fs_link(uv_loop_t* loop, uv_fs_t* req); int uv__iou_fs_mkdir(uv_loop_t* loop, uv_fs_t* req); int uv__iou_fs_open(uv_loop_t* loop, uv_fs_t* req); +int uv__iou_fs_openat(uv_loop_t* loop, uv_fs_t* req); int uv__iou_fs_read_or_write(uv_loop_t* loop, uv_fs_t* req, int is_read); @@ -354,6 +355,7 @@ int uv__iou_fs_unlink(uv_loop_t* loop, uv_fs_t* req); #define uv__iou_fs_link(loop, req) 0 #define uv__iou_fs_mkdir(loop, req) 0 #define uv__iou_fs_open(loop, req) 0 +#define uv__iou_fs_openat(loop, req) 0 #define uv__iou_fs_read_or_write(loop, req, is_read) 0 #define uv__iou_fs_rename(loop, req) 0 #define uv__iou_fs_statx(loop, req, is_fstat, is_lstat) 0 diff --git a/src/unix/linux.c b/src/unix/linux.c index 8fdcb12c..46bea209 100644 --- a/src/unix/linux.c +++ b/src/unix/linux.c @@ -948,6 +948,28 @@ int uv__iou_fs_open(uv_loop_t* loop, uv_fs_t* req) { } +int uv__iou_fs_openat(uv_loop_t* loop, uv_fs_t* req) { + struct uv__io_uring_sqe* sqe; + struct uv__iou* iou; + + iou = &uv__get_internal_fields(loop)->iou; + + sqe = uv__iou_get_sqe(iou, loop, req); + if (sqe == NULL) + return 0; + + sqe->addr = (uintptr_t) req->path; + sqe->fd = req->file; + sqe->len = req->mode; + sqe->opcode = UV__IORING_OP_OPENAT; + sqe->open_flags = req->flags | O_CLOEXEC; + + uv__iou_submit(iou); + + return 1; +} + + int uv__iou_fs_rename(uv_loop_t* loop, uv_fs_t* req) { struct uv__io_uring_sqe* sqe; struct uv__iou* iou; diff --git a/test/test-fs.c b/test/test-fs.c index fe78117b..c2828aa7 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -3006,6 +3006,60 @@ TEST_IMPL(fs_scandir_early_exit) { } +TEST_IMPL(fs_openat) { + uv_fs_t req; + int r; + uv_os_fd_t fd; + uv_os_fd_t dirfd; + + /* Setup. */ + unlink("test/fixtures/test_dir/test_file"); + rmdir("test/fixtures/test_dir"); + + loop = uv_default_loop(); + + r = uv_fs_mkdir(NULL, &req, "test/fixtures/test_dir", 0755, NULL); + ASSERT_OK(r); + + r = uv_fs_open(NULL, + &req, + "test/fixtures/test_dir", + UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, + 0, + NULL); + ASSERT_GE(r, 0); + uv_fs_req_cleanup(&req); + + dirfd = (uv_os_fd_t) req.result; + + r = uv_fs_openat(NULL, + &req, + dirfd, + "test_file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_GE(r, 0); + uv_fs_req_cleanup(&req); + + fd = (uv_os_fd_t) req.result; + + r = uv_fs_close(NULL, &req, dirfd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + + /* Cleanup */ + unlink("test/fixtures/test_dir/test_file"); + rmdir("test/fixtures/test_dir"); + + MAKE_VALGRIND_HAPPY(loop); + return 0; +} + + TEST_IMPL(fs_open_dir) { const char* path; uv_fs_t req; diff --git a/test/test-list.h b/test/test-list.h index ad4593d8..394c805b 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -420,6 +420,7 @@ TEST_DECLARE (fs_scandir_empty_dir) TEST_DECLARE (fs_scandir_non_existent_dir) TEST_DECLARE (fs_scandir_file) TEST_DECLARE (fs_scandir_early_exit) +TEST_DECLARE (fs_openat) TEST_DECLARE (fs_open_dir) TEST_DECLARE (fs_readdir_empty_dir) TEST_DECLARE (fs_readdir_file) @@ -1127,6 +1128,7 @@ TASK_LIST_START TEST_ENTRY (fs_scandir_non_existent_dir) TEST_ENTRY (fs_scandir_file) TEST_ENTRY (fs_scandir_early_exit) + TEST_ENTRY (fs_openat) TEST_ENTRY (fs_open_dir) TEST_ENTRY (fs_readdir_empty_dir) TEST_ENTRY (fs_readdir_file) From d0f64f293fca06f4f7226a0d6e1d7f220979526a Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Wed, 12 Jun 2024 16:01:49 -0700 Subject: [PATCH 2/9] Initial Windows openat impl using NtCreateFile Signed-off-by: Yage Hu --- CMakeLists.txt | 1 + src/win/fs.c | 281 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce086f4a..f40d04ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -483,6 +483,7 @@ endif() target_link_libraries(uv_a ${uv_libraries}) set_target_properties(uv_a PROPERTIES OUTPUT_NAME "uv") if(WIN32) + target_link_libraries(uv_a ntdll) set_target_properties(uv_a PROPERTIES PREFIX "lib") endif() diff --git a/src/win/fs.c b/src/win/fs.c index b73c17d8..b1379084 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -35,6 +35,7 @@ /* requires , included via "uv.h" above, but needs to be included before our "winapi.h", included via "internal.h" below. */ #include +#include #include "internal.h" #include "req-inl.h" @@ -630,6 +631,263 @@ void fs__open(uv_fs_t* req) { SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); } + +void fs__openat(uv_fs_t* req) { + DWORD access; + DWORD share; + DWORD disposition; + DWORD attributes = 0; + ULONG options = 0; + HANDLE file; + UNICODE_STRING str; + IO_STATUS_BLOCK isb; + OBJECT_ATTRIBUTES obj; + int current_umask; + int flags = req->fs.info.file_flags; + struct uv__fd_info_s fd_info; + + /* Adjust flags to be compatible with the memory file mapping. Save the + * original flags to emulate the correct behavior. */ + if (flags & UV_FS_O_FILEMAP) { + fd_info.flags = flags; + fd_info.current_pos.QuadPart = 0; + + if ((flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) == + UV_FS_O_WRONLY) { + /* CreateFileMapping always needs read access */ + flags = (flags & ~UV_FS_O_WRONLY) | UV_FS_O_RDWR; + } + + if (flags & UV_FS_O_APPEND) { + /* Clear the append flag and ensure RDRW mode */ + flags &= ~UV_FS_O_APPEND; + flags &= ~(UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR); + flags |= UV_FS_O_RDWR; + } + } + + /* Obtain the active umask. umask() never fails and returns the previous + * umask. */ + current_umask = _umask(0); + _umask(current_umask); + + /* convert flags and mode to CreateFile parameters */ + switch (flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) { + case UV_FS_O_RDONLY: + access = FILE_GENERIC_READ; + break; + case UV_FS_O_WRONLY: + access = FILE_GENERIC_WRITE; + break; + case UV_FS_O_RDWR: + access = FILE_GENERIC_READ | FILE_GENERIC_WRITE; + break; + default: + goto einval; + } + + if (flags & UV_FS_O_APPEND) { + access &= ~FILE_WRITE_DATA; + access |= FILE_APPEND_DATA; + } + + /* + * Here is where we deviate significantly from what CRT's _open() + * does. We indiscriminately use all the sharing modes, to match + * UNIX semantics. In particular, this ensures that the file can + * be deleted even whilst it's open, fixing issue + * https://github.com/nodejs/node-v0.x-archive/issues/1449. + * We still support exclusive sharing mode, since it is necessary + * for opening raw block devices, otherwise Windows will prevent + * any attempt to write past the master boot record. + */ + if (flags & UV_FS_O_EXLOCK) { + share = 0; + } else { + share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + } + + switch (flags & (UV_FS_O_CREAT | UV_FS_O_EXCL | UV_FS_O_TRUNC)) { + case 0: + case UV_FS_O_EXCL: + disposition = OPEN_EXISTING; + break; + case UV_FS_O_CREAT: + disposition = OPEN_ALWAYS; + break; + case UV_FS_O_CREAT | UV_FS_O_EXCL: + case UV_FS_O_CREAT | UV_FS_O_TRUNC | UV_FS_O_EXCL: + disposition = CREATE_NEW; + break; + case UV_FS_O_TRUNC: + case UV_FS_O_TRUNC | UV_FS_O_EXCL: + disposition = TRUNCATE_EXISTING; + break; + case UV_FS_O_CREAT | UV_FS_O_TRUNC: + disposition = CREATE_ALWAYS; + break; + default: + goto einval; + } + + attributes |= FILE_ATTRIBUTE_NORMAL; + if (flags & UV_FS_O_CREAT) { + if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) { + attributes |= FILE_ATTRIBUTE_READONLY; + } + } + + if (flags & UV_FS_O_TEMPORARY ) { + attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY; + access |= DELETE; + } + + if (flags & UV_FS_O_SHORT_LIVED) { + attributes |= FILE_ATTRIBUTE_TEMPORARY; + } + + switch (flags & (UV_FS_O_SEQUENTIAL | UV_FS_O_RANDOM)) { + case 0: + break; + case UV_FS_O_SEQUENTIAL: + attributes |= FILE_FLAG_SEQUENTIAL_SCAN; + break; + case UV_FS_O_RANDOM: + attributes |= FILE_FLAG_RANDOM_ACCESS; + break; + default: + goto einval; + } + + if (flags & UV_FS_O_DIRECT) { + /* + * FILE_APPEND_DATA and FILE_FLAG_NO_BUFFERING are mutually exclusive. + * Windows returns 87, ERROR_INVALID_PARAMETER if these are combined. + * + * FILE_APPEND_DATA is included in FILE_GENERIC_WRITE: + * + * FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | + * FILE_WRITE_DATA | + * FILE_WRITE_ATTRIBUTES | + * FILE_WRITE_EA | + * FILE_APPEND_DATA | + * SYNCHRONIZE + * + * Note: Appends are also permitted by FILE_WRITE_DATA. + * + * In order for direct writes and direct appends to succeed, we therefore + * exclude FILE_APPEND_DATA if FILE_WRITE_DATA is specified, and otherwise + * fail if the user's sole permission is a direct append, since this + * particular combination is invalid. + */ + if (access & FILE_APPEND_DATA) { + if (access & FILE_WRITE_DATA) { + access &= ~FILE_APPEND_DATA; + } else { + goto einval; + } + } + attributes |= FILE_FLAG_NO_BUFFERING; + } + + switch (flags & (UV_FS_O_DSYNC | UV_FS_O_SYNC)) { + case 0: + break; + case UV_FS_O_DSYNC: + case UV_FS_O_SYNC: + attributes |= FILE_FLAG_WRITE_THROUGH; + break; + default: + goto einval; + } + + /* Setting this flag makes it possible to open a directory. */ + attributes |= FILE_FLAG_BACKUP_SEMANTICS; + + if (flags & UV_FS_O_DIRECTORY) { + options |= FILE_DIRECTORY_FILE; + } + + RtlInitUnicodeString(&str, req->file.pathw); + InitializeObjectAttributes(&obj, &str, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS status = NtCreateFile(&file, + access, + &obj, + &isb, + 0, + attributes, + share, + disposition, + options, + NULL, + 0); + if (!NT_SUCCESS(status)) { + if (file == INVALID_HANDLE_VALUE) { + ULONG error = RtlNtStatusToDosError(status); + if ((isb.Information & FILE_EXISTS != 0) && (flags & UV_FS_O_CREAT) && + !(flags & UV_FS_O_EXCL)) { + /* Special case: when FILE_EXISTS happens and UV_FS_O_CREAT was + * specified, it means the path referred to a directory. */ + SET_REQ_UV_ERROR(req, UV_EISDIR, error); + } else { + SET_REQ_WIN32_ERROR(req, error); + } + return; + } + + if (flags & UV_FS_O_FILEMAP) { + FILE_STANDARD_INFO file_info; + if (!GetFileInformationByHandleEx(file, + FileStandardInfo, + &file_info, + sizeof file_info)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(file); + return; + } + fd_info.is_directory = file_info.Directory; + + if (fd_info.is_directory) { + fd_info.size.QuadPart = 0; + fd_info.mapping = INVALID_HANDLE_VALUE; + } else { + if (!GetFileSizeEx(file, &fd_info.size)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(file); + return; + } + + if (fd_info.size.QuadPart == 0) { + fd_info.mapping = INVALID_HANDLE_VALUE; + } else { + DWORD flProtect = (fd_info.flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | + UV_FS_O_RDWR)) == UV_FS_O_RDONLY ? PAGE_READONLY : PAGE_READWRITE; + fd_info.mapping = CreateFileMapping(file, + NULL, + flProtect, + fd_info.size.HighPart, + fd_info.size.LowPart, + NULL); + if (fd_info.mapping == NULL) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + CloseHandle(file); + return; + } + } + } + + uv__fd_hash_add(file, &fd_info); + } + + SET_REQ_RESULT(req, (uintptr_t)file); + return; + + einval: + SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); +} + + void fs__close(uv_fs_t* req) { int fd = req->file.fd; int result; @@ -2890,6 +3148,29 @@ int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, } +int uv_fs_openat(uv_loop_t* loop, + uv_fs_t* req, + uv_os_fd_t handle, + const char* path, + int flags, + int mode, + uv_fs_cb cb) { + int err; + + INIT(UV_FS_OPENAT); + err = fs__capture_path(req, path, NULL, cb != NULL); + if (err) { + SET_REQ_WIN32_ERROR(req, err); + return req->result; + } + + req->file.hFile = handle; + req->fs.info.file_flags = flags; + req->fs.info.mode = mode; + POST0; +} + + int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) { INIT(UV_FS_CLOSE); req->file.fd = fd; From 90ef4ecdb1734aece09ccc36a59d06131be0564d Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Thu, 13 Jun 2024 00:13:56 -0700 Subject: [PATCH 3/9] Initial happy path Windows openat impl Signed-off-by: Yage Hu --- CMakeLists.txt | 1 - src/win/fs.c | 75 ++++++++++++++++++++--------------- src/win/winapi.h | 100 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 139 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f40d04ab..ce086f4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -483,7 +483,6 @@ endif() target_link_libraries(uv_a ${uv_libraries}) set_target_properties(uv_a PROPERTIES OUTPUT_NAME "uv") if(WIN32) - target_link_libraries(uv_a ntdll) set_target_properties(uv_a PROPERTIES PREFIX "lib") endif() diff --git a/src/win/fs.c b/src/win/fs.c index b1379084..2f85ca2c 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -35,7 +35,6 @@ /* requires , included via "uv.h" above, but needs to be included before our "winapi.h", included via "internal.h" below. */ #include -#include #include "internal.h" #include "req-inl.h" @@ -674,13 +673,13 @@ void fs__openat(uv_fs_t* req) { /* convert flags and mode to CreateFile parameters */ switch (flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) { case UV_FS_O_RDONLY: - access = FILE_GENERIC_READ; + access = GENERIC_READ; break; case UV_FS_O_WRONLY: - access = FILE_GENERIC_WRITE; + access = GENERIC_WRITE; break; case UV_FS_O_RDWR: - access = FILE_GENERIC_READ | FILE_GENERIC_WRITE; + access = GENERIC_READ | GENERIC_WRITE; break; default: goto einval; @@ -710,21 +709,21 @@ void fs__openat(uv_fs_t* req) { switch (flags & (UV_FS_O_CREAT | UV_FS_O_EXCL | UV_FS_O_TRUNC)) { case 0: case UV_FS_O_EXCL: - disposition = OPEN_EXISTING; + disposition = FILE_OPEN; break; case UV_FS_O_CREAT: - disposition = OPEN_ALWAYS; + disposition = FILE_OPEN_IF; break; case UV_FS_O_CREAT | UV_FS_O_EXCL: case UV_FS_O_CREAT | UV_FS_O_TRUNC | UV_FS_O_EXCL: - disposition = CREATE_NEW; + disposition = FILE_CREATE; break; case UV_FS_O_TRUNC: case UV_FS_O_TRUNC | UV_FS_O_EXCL: - disposition = TRUNCATE_EXISTING; + disposition = FILE_OVERWRITE; break; case UV_FS_O_CREAT | UV_FS_O_TRUNC: - disposition = CREATE_ALWAYS; + disposition = FILE_SUPERSEDE; break; default: goto einval; @@ -738,7 +737,8 @@ void fs__openat(uv_fs_t* req) { } if (flags & UV_FS_O_TEMPORARY ) { - attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY; + options |= FILE_DELETE_ON_CLOSE; + attributes |= FILE_ATTRIBUTE_TEMPORARY; access |= DELETE; } @@ -750,10 +750,10 @@ void fs__openat(uv_fs_t* req) { case 0: break; case UV_FS_O_SEQUENTIAL: - attributes |= FILE_FLAG_SEQUENTIAL_SCAN; + options |= FILE_SEQUENTIAL_ONLY; break; case UV_FS_O_RANDOM: - attributes |= FILE_FLAG_RANDOM_ACCESS; + options |= FILE_RANDOM_ACCESS; break; default: goto einval; @@ -787,7 +787,7 @@ void fs__openat(uv_fs_t* req) { goto einval; } } - attributes |= FILE_FLAG_NO_BUFFERING; + options |= FILE_NO_INTERMEDIATE_BUFFERING; } switch (flags & (UV_FS_O_DSYNC | UV_FS_O_SYNC)) { @@ -795,36 +795,46 @@ void fs__openat(uv_fs_t* req) { break; case UV_FS_O_DSYNC: case UV_FS_O_SYNC: - attributes |= FILE_FLAG_WRITE_THROUGH; + options |= FILE_WRITE_THROUGH; break; default: goto einval; } - /* Setting this flag makes it possible to open a directory. */ - attributes |= FILE_FLAG_BACKUP_SEMANTICS; if (flags & UV_FS_O_DIRECTORY) { + /* Setting this flag makes it possible to open a directory. */ + options |= FILE_OPEN_FOR_BACKUP_INTENT; options |= FILE_DIRECTORY_FILE; } - RtlInitUnicodeString(&str, req->file.pathw); - InitializeObjectAttributes(&obj, &str, OBJ_CASE_INSENSITIVE, NULL, NULL); + HMODULE ntdll = GetModuleHandle("ntdll.dll"); + RtlInitUnicodeString _RtlInitUnicodeString = + (RtlInitUnicodeString) GetProcAddress(ntdll, "RtlInitUnicodeString"); + NtCreateFile _NtCreateFile = + (NtCreateFile) GetProcAddress(ntdll, "NtCreateFile"); - NTSTATUS status = NtCreateFile(&file, - access, - &obj, - &isb, - 0, - attributes, - share, - disposition, - options, - NULL, - 0); + _RtlInitUnicodeString(&str, req->file.pathw); + InitializeObjectAttributes(&obj, + &str, + OBJ_CASE_INSENSITIVE, + req->fs.info.hFile_out, + NULL); + + NTSTATUS status = _NtCreateFile(&file, + access, + &obj, + &isb, + 0, + attributes, + share, + disposition, + options, + NULL, + 0); if (!NT_SUCCESS(status)) { - if (file == INVALID_HANDLE_VALUE) { - ULONG error = RtlNtStatusToDosError(status); + ULONG error = pRtlNtStatusToDosError(status); + if ((isb.Information & FILE_EXISTS != 0) && (flags & UV_FS_O_CREAT) && !(flags & UV_FS_O_EXCL)) { /* Special case: when FILE_EXISTS happens and UV_FS_O_CREAT was @@ -3043,6 +3053,7 @@ static void uv__fs_work(struct uv__work* w) { #define XX(uc, lc) case UV_FS_##uc: fs__##lc(req); break; switch (req->fs_type) { XX(OPEN, open) + XX(OPENAT, openat) XX(CLOSE, close) XX(READ, read) XX(WRITE, write) @@ -3164,7 +3175,7 @@ int uv_fs_openat(uv_loop_t* loop, return req->result; } - req->file.hFile = handle; + req->fs.info.hFile_out = handle; req->fs.info.file_flags = flags; req->fs.info.mode = mode; POST0; diff --git a/src/win/winapi.h b/src/win/winapi.h index d380bda4..3aab5c7d 100644 --- a/src/win/winapi.h +++ b/src/win/winapi.h @@ -4108,10 +4108,7 @@ # define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x00000002 #endif -/* from winternl.h */ -#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32__) -#define __UNICODE_STRING_DEFINED -#endif +/* from ntdef.h */ typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; @@ -4119,6 +4116,52 @@ typedef struct _UNICODE_STRING { } UNICODE_STRING, *PUNICODE_STRING; typedef const UNICODE_STRING *PCUNICODE_STRING; +#ifndef _OBJECT_ATTRIBUTES +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; +#endif + + +#ifndef FILE_DIRECTORY_FILE +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_FOR_RECOVERY 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 +#endif + +#ifndef OBJ_CASE_INSENSITIVE +#define OBJ_CASE_INSENSITIVE 0x00000040 +#endif + +#ifndef FILE_EXISTS +#define FILE_EXISTS 0x00000004 +#endif + +#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32__) +#define __UNICODE_STRING_DEFINED +#endif /* from ntifs.h */ #ifndef DEVICE_TYPE @@ -4168,6 +4211,7 @@ typedef struct _IO_STATUS_BLOCK { ULONG_PTR Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + typedef enum _FILE_INFORMATION_CLASS { FileDirectoryInformation = 1, FileFullDirectoryInformation, @@ -4707,6 +4751,14 @@ typedef DWORD (WINAPI *sPowerRegisterSuspendResumeNotification) HANDLE Recipient, _PHPOWERNOTIFY RegistrationHandle); + +/* from wdm.h */ +typedef VOID (NTAPI *RtlInitUnicodeString)( + PUNICODE_STRING DestinationString, + __drv_aliasesMem PCWSTR SourceString +); + + /* from Winuser.h */ typedef VOID (CALLBACK* WINEVENTPROC) (HWINEVENTHOOK hWinEventHook, @@ -4766,4 +4818,44 @@ typedef int (WINAPI *uv_sGetHostNameW) int); extern uv_sGetHostNameW pGetHostNameW; + +/* from winternl.h */ +typedef NTSTATUS (__stdcall *NtCreateFile)( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PLARGE_INTEGER AllocationSize OPTIONAL, + IN ULONG FileAttributes, + IN ULONG ShareAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions, + IN PVOID EaBuffer OPTIONAL, + IN ULONG EaLength +); + +/* from ntdef.h */ +#ifndef InitializeObjectAttributes +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ + } +#endif + +// NtCreateFile CreateDisposition +#ifndef FILE_SUPERSEDE +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 +#endif + + #endif /* UV_WIN_WINAPI_H_ */ From b4431748813516086b0f684e12a16c425e826134 Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Thu, 13 Jun 2024 09:07:56 -0700 Subject: [PATCH 4/9] Add async and other test cases Signed-off-by: Yage Hu --- src/win/winapi.h | 11 ++-- test/test-fs.c | 139 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 130 insertions(+), 20 deletions(-) diff --git a/src/win/winapi.h b/src/win/winapi.h index 3aab5c7d..07767014 100644 --- a/src/win/winapi.h +++ b/src/win/winapi.h @@ -4108,7 +4108,11 @@ # define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x00000002 #endif -/* from ntdef.h */ +/* from winternl.h */ +#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32__) +#define __UNICODE_STRING_DEFINED +#endif + typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; @@ -4159,10 +4163,6 @@ typedef struct _OBJECT_ATTRIBUTES { #define FILE_EXISTS 0x00000004 #endif -#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32__) -#define __UNICODE_STRING_DEFINED -#endif - /* from ntifs.h */ #ifndef DEVICE_TYPE # define DEVICE_TYPE DWORD @@ -4211,7 +4211,6 @@ typedef struct _IO_STATUS_BLOCK { ULONG_PTR Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; - typedef enum _FILE_INFORMATION_CLASS { FileDirectoryInformation = 1, FileFullDirectoryInformation, diff --git a/test/test-fs.c b/test/test-fs.c index c2828aa7..601ac141 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -70,6 +70,7 @@ static int dummy_cb_count; static int close_cb_count; static int create_cb_count; static int open_cb_count; +static int openat_cb_count; static int read_cb_count; static int write_cb_count; static int unlink_cb_count; @@ -447,6 +448,18 @@ static void open_cb_simple(uv_fs_t* req) { } +static void openat_cb_simple(uv_fs_t* req) { + ASSERT_EQ(req->fs_type, UV_FS_OPENAT); + if (req->result < 0) { + fprintf(stderr, "async openat error: %d\n", (int) req->result); + ASSERT(0); + } + openat_cb_count++; + ASSERT(req->path); + uv_fs_req_cleanup(req); +} + + static void fsync_cb(uv_fs_t* req) { int r; ASSERT_PTR_EQ(req, &fsync_req); @@ -3007,12 +3020,13 @@ TEST_IMPL(fs_scandir_early_exit) { TEST_IMPL(fs_openat) { - uv_fs_t req; int r; + uv_fs_t req; uv_os_fd_t fd; uv_os_fd_t dirfd; /* Setup. */ + unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); rmdir("test/fixtures/test_dir"); @@ -3032,26 +3046,123 @@ TEST_IMPL(fs_openat) { dirfd = (uv_os_fd_t) req.result; - r = uv_fs_openat(NULL, - &req, - dirfd, - "test_file", - UV_FS_O_RDWR | UV_FS_O_CREAT, - S_IWUSR | S_IRUSR, - NULL); - ASSERT_GE(r, 0); - uv_fs_req_cleanup(&req); - - fd = (uv_os_fd_t) req.result; - - r = uv_fs_close(NULL, &req, dirfd, NULL); + r = uv_fs_open(NULL, + &req, + "test/fixtures/test_dir/test_file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); + fd = (uv_os_fd_t) req.result; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); + // Open an existing file + { + r = uv_fs_openat(NULL, + &req, + dirfd, + "test_file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + fd = (uv_os_fd_t) req.result; + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Open an existing file async + { + r = uv_fs_openat(loop, + &req, + dirfd, + "test_file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + openat_cb_simple); + ASSERT_OK(r); + + ASSERT_OK(openat_cb_count); + uv_run(loop, UV_RUN_DEFAULT); + ASSERT_EQ(1, openat_cb_count); + uv_fs_req_cleanup(&req); + + fd = (uv_os_fd_t) req.result; + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Create a new file + { + r = uv_fs_openat(NULL, + &req, + dirfd, + "test_file_not_exist", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + fd = (uv_os_fd_t) req.result; + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Exclusively create an existing file. + { + r = uv_fs_openat(NULL, + &req, + dirfd, + "test_file", + UV_FS_O_RDWR | UV_FS_O_CREAT | UV_FS_O_EXCL, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_EQ(r, UV_EEXIST); + uv_fs_req_cleanup(&req); + } + + // Open a file read-only and try to write to it + { + r = uv_fs_openat(NULL, + &req, + dirfd, + "test_file", + UV_FS_O_RDONLY, + 0, + NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + + fd = (uv_os_fd_t) req.result; + + iov = uv_buf_init(test_buf, sizeof(test_buf)); + r = uv_fs_write(NULL, + &req, + fd, + &iov, + 1, + -1, + NULL); + ASSERT_EQ(r, UV_EBADF); + + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + r = uv_fs_close(NULL, &req, dirfd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + /* Cleanup */ + unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); rmdir("test/fixtures/test_dir"); From d643706b02d8ee84c434ca683b0304eff6777731 Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Thu, 13 Jun 2024 11:02:17 -0700 Subject: [PATCH 5/9] Add another test case Signed-off-by: Yage Hu --- src/win/fs.c | 10 ++-------- src/win/winapi.c | 13 +++++++++++++ src/win/winapi.h | 42 ++++++++++++++++++++---------------------- test/test-fs.c | 23 +++++++++++++++++++++++ 4 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/win/fs.c b/src/win/fs.c index 2f85ca2c..ebcc2921 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -808,20 +808,14 @@ void fs__openat(uv_fs_t* req) { options |= FILE_DIRECTORY_FILE; } - HMODULE ntdll = GetModuleHandle("ntdll.dll"); - RtlInitUnicodeString _RtlInitUnicodeString = - (RtlInitUnicodeString) GetProcAddress(ntdll, "RtlInitUnicodeString"); - NtCreateFile _NtCreateFile = - (NtCreateFile) GetProcAddress(ntdll, "NtCreateFile"); - - _RtlInitUnicodeString(&str, req->file.pathw); + pRtlInitUnicodeString(&str, req->file.pathw); InitializeObjectAttributes(&obj, &str, OBJ_CASE_INSENSITIVE, req->fs.info.hFile_out, NULL); - NTSTATUS status = _NtCreateFile(&file, + NTSTATUS status = pNtCreateFile(&file, access, &obj, &isb, diff --git a/src/win/winapi.c b/src/win/winapi.c index 53147b82..4b4fbe43 100644 --- a/src/win/winapi.c +++ b/src/win/winapi.c @@ -27,7 +27,9 @@ /* Ntdll function pointers */ sRtlGetVersion pRtlGetVersion; +sRtlInitUnicodeString pRtlInitUnicodeString; sRtlNtStatusToDosError pRtlNtStatusToDosError; +sNtCreateFile pNtCreateFile; sNtDeviceIoControlFile pNtDeviceIoControlFile; sNtQueryInformationFile pNtQueryInformationFile; sNtSetInformationFile pNtSetInformationFile; @@ -70,6 +72,17 @@ void uv__winapi_init(void) { uv_fatal_error(GetLastError(), "GetProcAddress"); } + pRtlInitUnicodeString = + (sRtlInitUnicodeString) GetProcAddress(ntdll_module, "RtlInitUnicodeString"); + if (pRtlInitUnicodeString == NULL) { + uv_fatal_error(GetLastError(), "GetProcAddress"); + } + + pNtCreateFile = (sNtCreateFile) GetProcAddress(ntdll_module, "NtCreateFile"); + if (pNtCreateFile == NULL) { + uv_fatal_error(GetLastError(), "GetProcAddress"); + } + pNtDeviceIoControlFile = (sNtDeviceIoControlFile) GetProcAddress( ntdll_module, "NtDeviceIoControlFile"); diff --git a/src/win/winapi.h b/src/win/winapi.h index 07767014..86c36c5c 100644 --- a/src/win/winapi.h +++ b/src/win/winapi.h @@ -4576,9 +4576,27 @@ typedef VOID (NTAPI *PIO_APC_ROUTINE) typedef NTSTATUS (NTAPI *sRtlGetVersion) (PRTL_OSVERSIONINFOW lpVersionInformation); +typedef VOID (NTAPI *sRtlInitUnicodeString)( + PUNICODE_STRING DestinationString, + __drv_aliasesMem PCWSTR SourceString +); + typedef ULONG (NTAPI *sRtlNtStatusToDosError) (NTSTATUS Status); +typedef NTSTATUS (NTAPI *sNtCreateFile) + (PHANDLE FileHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + PLARGE_INTEGER AllocationSize, + ULONG FileAttributes, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + PVOID EaBuffer, + ULONG EaLength); + typedef NTSTATUS (NTAPI *sNtDeviceIoControlFile) (HANDLE FileHandle, HANDLE Event, @@ -4751,13 +4769,6 @@ typedef DWORD (WINAPI *sPowerRegisterSuspendResumeNotification) _PHPOWERNOTIFY RegistrationHandle); -/* from wdm.h */ -typedef VOID (NTAPI *RtlInitUnicodeString)( - PUNICODE_STRING DestinationString, - __drv_aliasesMem PCWSTR SourceString -); - - /* from Winuser.h */ typedef VOID (CALLBACK* WINEVENTPROC) (HWINEVENTHOOK hWinEventHook, @@ -4792,7 +4803,9 @@ typedef struct _TCP_INITIAL_RTO_PARAMETERS { /* Ntdll function pointers */ extern sRtlGetVersion pRtlGetVersion; +extern sRtlInitUnicodeString pRtlInitUnicodeString; extern sRtlNtStatusToDosError pRtlNtStatusToDosError; +extern sNtCreateFile pNtCreateFile; extern sNtDeviceIoControlFile pNtDeviceIoControlFile; extern sNtQueryInformationFile pNtQueryInformationFile; extern sNtSetInformationFile pNtSetInformationFile; @@ -4818,21 +4831,6 @@ typedef int (WINAPI *uv_sGetHostNameW) extern uv_sGetHostNameW pGetHostNameW; -/* from winternl.h */ -typedef NTSTATUS (__stdcall *NtCreateFile)( - OUT PHANDLE FileHandle, - IN ACCESS_MASK DesiredAccess, - IN POBJECT_ATTRIBUTES ObjectAttributes, - OUT PIO_STATUS_BLOCK IoStatusBlock, - IN PLARGE_INTEGER AllocationSize OPTIONAL, - IN ULONG FileAttributes, - IN ULONG ShareAccess, - IN ULONG CreateDisposition, - IN ULONG CreateOptions, - IN PVOID EaBuffer OPTIONAL, - IN ULONG EaLength -); - /* from ntdef.h */ #ifndef InitializeObjectAttributes #define InitializeObjectAttributes( p, n, a, r, s ) { \ diff --git a/test/test-fs.c b/test/test-fs.c index 601ac141..6213912d 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -3028,12 +3028,17 @@ TEST_IMPL(fs_openat) { /* Setup. */ unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); + rmdir("test/fixtures/test_dir/nested_dir"); rmdir("test/fixtures/test_dir"); loop = uv_default_loop(); r = uv_fs_mkdir(NULL, &req, "test/fixtures/test_dir", 0755, NULL); ASSERT_OK(r); + uv_fs_req_cleanup(&req); + r = uv_fs_mkdir(NULL, &req, "test/fixtures/test_dir/nested_dir", 0755, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); r = uv_fs_open(NULL, &req, @@ -3098,6 +3103,23 @@ TEST_IMPL(fs_openat) { uv_fs_req_cleanup(&req); } + // Open a nested dir + { + r = uv_fs_openat(NULL, + &req, + dirfd, + "nested_dir", + UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + fd = (uv_os_fd_t) req.result; + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + // Create a new file { r = uv_fs_openat(NULL, @@ -3164,6 +3186,7 @@ TEST_IMPL(fs_openat) { /* Cleanup */ unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); + rmdir("test/fixtures/test_dir/nested_dir"); rmdir("test/fixtures/test_dir"); MAKE_VALGRIND_HAPPY(loop); From baecd3b243a66fd54779c9ce170e47b40e2dbddb Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Thu, 13 Jun 2024 11:24:41 -0700 Subject: [PATCH 6/9] Add docs for uv_fs_openat Signed-off-by: Yage Hu --- docs/src/fs.rst | 12 +++++++++++- include/uv.h | 2 +- src/unix/fs.c | 2 +- src/win/fs.c | 40 ++++++++++++++++++++++++++++++++-------- test/test-fs.c | 30 +++++++++++++++--------------- 5 files changed, 60 insertions(+), 26 deletions(-) diff --git a/docs/src/fs.rst b/docs/src/fs.rst index 891ee74c..659cc380 100644 --- a/docs/src/fs.rst +++ b/docs/src/fs.rst @@ -108,7 +108,8 @@ Data types UV_FS_READDIR, UV_FS_CLOSEDIR, UV_FS_MKSTEMP, - UV_FS_LUTIME + UV_FS_LUTIME, + UV_FS_OPENAT } uv_fs_type; .. c:type:: uv_statfs_t @@ -227,6 +228,15 @@ API in binary mode. Because of this the O_BINARY and O_TEXT flags are not supported. +.. c:function:: int uv_fs_openat(uv_loop_t* loop, uv_fs_t* req, uv_file file, const char* path, int flags, int mode, uv_fs_cb cb) + + Equivalent to :man:`openat(2)`. + + .. note:: + On Windows libuv uses `NtCreateFile` and thus the file is always opened + in binary mode. Because of this the O_BINARY and O_TEXT flags are not + supported. + .. c:function:: int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file, const uv_buf_t bufs[], unsigned int nbufs, int64_t offset, uv_fs_cb cb) Equivalent to :man:`preadv(2)`. If the `offset` argument is `-1`, then diff --git a/include/uv.h b/include/uv.h index b5744a8d..8883f158 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1442,7 +1442,7 @@ UV_EXTERN int uv_fs_open(uv_loop_t* loop, uv_fs_cb cb); UV_EXTERN int uv_fs_openat(uv_loop_t* loop, uv_fs_t* req, - uv_os_fd_t file, + uv_file file, const char* path, int flags, int mode, diff --git a/src/unix/fs.c b/src/unix/fs.c index 40b57465..e58a275f 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -2044,7 +2044,7 @@ int uv_fs_open(uv_loop_t* loop, int uv_fs_openat(uv_loop_t* loop, uv_fs_t* req, - uv_os_fd_t file, + uv_file file, const char* path, int flags, int mode, diff --git a/src/win/fs.c b/src/win/fs.c index ebcc2921..31a3fce3 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -641,7 +641,7 @@ void fs__openat(uv_fs_t* req) { UNICODE_STRING str; IO_STATUS_BLOCK isb; OBJECT_ATTRIBUTES obj; - int current_umask; + int fd, current_umask; int flags = req->fs.info.file_flags; struct uv__fd_info_s fd_info; @@ -808,11 +808,19 @@ void fs__openat(uv_fs_t* req) { options |= FILE_DIRECTORY_FILE; } + HANDLE dir = (HANDLE) _get_osfhandle(req->fs.info.fd_out); + if (dir == INVALID_HANDLE_VALUE) { + fprintf(stderr, "get_osfhandle\n"); + SET_REQ_WIN32_ERROR(req, (DWORD) UV_EBADF); + return; + } + + pRtlInitUnicodeString(&str, req->file.pathw); InitializeObjectAttributes(&obj, &str, OBJ_CASE_INSENSITIVE, - req->fs.info.hFile_out, + dir, NULL); NTSTATUS status = pNtCreateFile(&file, @@ -829,7 +837,7 @@ void fs__openat(uv_fs_t* req) { if (!NT_SUCCESS(status)) { ULONG error = pRtlNtStatusToDosError(status); - if ((isb.Information & FILE_EXISTS != 0) && (flags & UV_FS_O_CREAT) && + if (((isb.Information & FILE_EXISTS) != 0) && (flags & UV_FS_O_CREAT) && !(flags & UV_FS_O_EXCL)) { /* Special case: when FILE_EXISTS happens and UV_FS_O_CREAT was * specified, it means the path referred to a directory. */ @@ -840,6 +848,22 @@ void fs__openat(uv_fs_t* req) { return; } + fd = _open_osfhandle((intptr_t) file, flags); + if (fd < 0) { + /* The only known failure mode for _open_osfhandle() is EMFILE, in which + * case GetLastError() will return zero. However we'll try to handle other + * errors as well, should they ever occur. + */ + if (errno == EMFILE) + SET_REQ_UV_ERROR(req, UV_EMFILE, ERROR_TOO_MANY_OPEN_FILES); + else if (GetLastError() != ERROR_SUCCESS) + SET_REQ_WIN32_ERROR(req, GetLastError()); + else + SET_REQ_WIN32_ERROR(req, (DWORD) UV_UNKNOWN); + CloseHandle(file); + return; + } + if (flags & UV_FS_O_FILEMAP) { FILE_STANDARD_INFO file_info; if (!GetFileInformationByHandleEx(file, @@ -881,10 +905,10 @@ void fs__openat(uv_fs_t* req) { } } - uv__fd_hash_add(file, &fd_info); + uv__fd_hash_add(fd, &fd_info); } - SET_REQ_RESULT(req, (uintptr_t)file); + SET_REQ_RESULT(req, fd); return; einval: @@ -3155,7 +3179,7 @@ int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int uv_fs_openat(uv_loop_t* loop, uv_fs_t* req, - uv_os_fd_t handle, + uv_file handle, const char* path, int flags, int mode, @@ -3169,10 +3193,10 @@ int uv_fs_openat(uv_loop_t* loop, return req->result; } - req->fs.info.hFile_out = handle; + req->fs.info.fd_out = handle; req->fs.info.file_flags = flags; req->fs.info.mode = mode; - POST0; + POST; } diff --git a/test/test-fs.c b/test/test-fs.c index 6213912d..4aaff2f2 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -3022,8 +3022,8 @@ TEST_IMPL(fs_scandir_early_exit) { TEST_IMPL(fs_openat) { int r; uv_fs_t req; - uv_os_fd_t fd; - uv_os_fd_t dirfd; + uv_file fd; + uv_file dirfd; /* Setup. */ unlink("test/fixtures/test_dir/test_file_not_exist"); @@ -3049,7 +3049,7 @@ TEST_IMPL(fs_openat) { ASSERT_GE(r, 0); uv_fs_req_cleanup(&req); - dirfd = (uv_os_fd_t) req.result; + dirfd = (uv_file) r; r = uv_fs_open(NULL, &req, @@ -3057,9 +3057,9 @@ TEST_IMPL(fs_openat) { UV_FS_O_RDWR | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); - ASSERT_OK(r); + ASSERT_GE(r, 0); uv_fs_req_cleanup(&req); - fd = (uv_os_fd_t) req.result; + fd = r; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3073,9 +3073,9 @@ TEST_IMPL(fs_openat) { UV_FS_O_RDWR | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); - ASSERT_OK(r); + ASSERT_GE(r, 0); uv_fs_req_cleanup(&req); - fd = (uv_os_fd_t) req.result; + fd = (uv_file) r; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3090,14 +3090,14 @@ TEST_IMPL(fs_openat) { UV_FS_O_RDWR | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, openat_cb_simple); - ASSERT_OK(r); + ASSERT_GE(r, 0); ASSERT_OK(openat_cb_count); uv_run(loop, UV_RUN_DEFAULT); ASSERT_EQ(1, openat_cb_count); uv_fs_req_cleanup(&req); - fd = (uv_os_fd_t) req.result; + fd = (uv_file) req.result; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3112,9 +3112,9 @@ TEST_IMPL(fs_openat) { UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, S_IWUSR | S_IRUSR, NULL); - ASSERT_OK(r); + ASSERT_GE(r, 0); uv_fs_req_cleanup(&req); - fd = (uv_os_fd_t) req.result; + fd = (uv_file) req.result; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3129,9 +3129,9 @@ TEST_IMPL(fs_openat) { UV_FS_O_RDWR | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); - ASSERT_OK(r); + ASSERT_GE(r, 0); uv_fs_req_cleanup(&req); - fd = (uv_os_fd_t) req.result; + fd = (uv_file) req.result; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3159,10 +3159,10 @@ TEST_IMPL(fs_openat) { UV_FS_O_RDONLY, 0, NULL); - ASSERT_OK(r); + ASSERT_GE(r, 0); uv_fs_req_cleanup(&req); - fd = (uv_os_fd_t) req.result; + fd = (uv_file) req.result; iov = uv_buf_init(test_buf, sizeof(test_buf)); r = uv_fs_write(NULL, From a7dbd8260dbb352cdc03650f57f07aa7a8125df0 Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Tue, 18 Jun 2024 14:16:57 -0700 Subject: [PATCH 7/9] Support NOFOLLOW on Windows Signed-off-by: Yage Hu --- include/uv/win.h | 2 +- src/win/fs.c | 207 ++++++++++++++++++++++++++++++++++++++++++----- test/test-fs.c | 127 ++++++++++++++++++++++++----- 3 files changed, 294 insertions(+), 42 deletions(-) diff --git a/include/uv/win.h b/include/uv/win.h index 12ac53b4..367edfc7 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -697,7 +697,7 @@ typedef struct { #define UV_FS_O_EXLOCK 0x10000000 /* EXCLUSIVE SHARING MODE */ #define UV_FS_O_NOATIME 0 #define UV_FS_O_NOCTTY 0 -#define UV_FS_O_NOFOLLOW 0 +#define UV_FS_O_NOFOLLOW 0x40000000 /* Not mapped but handled as a special case in openat */ #define UV_FS_O_NONBLOCK 0 #define UV_FS_O_SYMLINK 0 #define UV_FS_O_SYNC 0x08000000 /* FILE_FLAG_WRITE_THROUGH */ diff --git a/src/win/fs.c b/src/win/fs.c index 31a3fce3..e1536ed4 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -630,6 +630,91 @@ void fs__open(uv_fs_t* req) { SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); } +struct path { + WCHAR * buf; + + // Capacity of the path buffer minus the terminating null in WCHARs. + size_t cap; + + // Length of the path string without the terminating null in WCHARs. + size_t len; +}; + +// Must be freed by `uv__path_free`. +int uv__path_init(struct path * p) { + WCHAR * buf = uv__malloc(1); + if (buf == NULL) return UV_ENOMEM; + + p->buf = buf; + p->buf[0] = L'\0'; + p->cap = 0; + p->len = 0; + + return 0; +} + +void uv__path_free(struct path * p) { + uv__free(p->buf); +} + +int uv__path_grow_until(struct path * p, size_t cap) { + if (cap <= p->cap) { + return 0; + } + + WCHAR * buf = uv__realloc(p->buf, (cap + 1) * sizeof(WCHAR)); + if (buf == NULL) return ENOMEM; + + p->buf = buf; + p->cap = cap; + + return 0; +} + +int uv__path_set(struct path * p, WCHAR * source) { + size_t len = wcslen(source); + + int err = uv__path_grow_until(p, len); + if (err) return err; + + memcpy(p->buf, source, len * sizeof(WCHAR)); + p->len = len; + p->buf[p->len] = L'\0'; + + return 0; +} + +int uv__path_push(struct path * p, WCHAR * component) { + size_t len = wcslen(component); + + int err = uv__path_grow_until(p, p->len + len + 1); + if (err) return err; + + if (p->len > 0 && p->buf[p->len - 1] != L'\\') { + p->buf[p->len] = L'\\'; + p->len += 1; + } + + memcpy(p->buf + p->len, component, len * sizeof(WCHAR)); + p->len += len; + p->buf[p->len] = L'\0'; + + return 0; +} + +int uv__path_pop(struct path * p) { + if (p->len == 0) return 1; + + for (int i = p->len - 1; i >= 0; i--) { + if (p->buf[i] == L'\\' || i == 0) { + p->buf[i] = L'\0'; + p->len = i; + break; + } + } + + return 0; +} void fs__openat(uv_fs_t* req) { DWORD access; @@ -644,6 +729,79 @@ void fs__openat(uv_fs_t* req) { int fd, current_umask; int flags = req->fs.info.file_flags; struct uv__fd_info_s fd_info; + WCHAR * path = req->file.pathw; + struct path rebuilt_path; + const size_t path_len = wcslen(path); + + // NtCreateFile doesn't recognize forward slashes, only back slashes. + for (int i = 0; path[i] != 0; i++) + if (path[i] == L'/') + path[i] = L'\\'; + + HANDLE dir_handle = (HANDLE) _get_osfhandle(req->fs.info.fd_out); + HANDLE root_dir_handle = dir_handle; + if (dir_handle == INVALID_HANDLE_VALUE) { + SET_REQ_WIN32_ERROR(req, (DWORD) UV_EBADF); + return; + } + + uv__path_init(&rebuilt_path); + + WCHAR * next_token = NULL; + WCHAR * token = wcstok_s(path, L"\\", &next_token); + + while (token != NULL) { + if (!wcscmp(L".", token) || wcslen(token) == 0) { + // Do nothing. + } else if (!wcscmp(L"..", token)) { + // If rebuilt_path is empty, set it to the path of the parent direcotry. + if (rebuilt_path.len == 0) { + DWORD dir_path_len = GetFinalPathNameByHandleW(dir_handle, NULL, 0, VOLUME_NAME_DOS); + if (dir_path_len == 0) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + WCHAR * dir_path_buf = uv__malloc((dir_path_len + 1) * sizeof(WCHAR)); + if (dir_path_buf == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; + } + + if ( + GetFinalPathNameByHandleW( + dir_handle, + dir_path_buf, + dir_path_len, + VOLUME_NAME_DOS + ) == 0 + ) { + uv__free(dir_path_buf); + SET_REQ_UV_ERROR(req, UV_EBADF, ERROR_INVALID_HANDLE); + return -1; + } + + // We'll call `NtCreateFile` with an absolute path, set root dir handle + // to the null handle. + root_dir_handle = 0; + + // The path we get has a prefix of `\\?\`. + // But we need an NT object directory prefix of `\??\`. + WCHAR * dir_path_without_extended_prefix = dir_path_buf + 4; + + uv__path_set(&rebuilt_path, L"\\??"); + uv__path_push(&rebuilt_path, dir_path_without_extended_prefix); + uv__free(dir_path_buf); + } + + // Then pop the last component. + uv__path_pop(&rebuilt_path); + } else { + uv__path_push(&rebuilt_path, token); + } + + token = wcstok_s(NULL, L"\\", &next_token); + } /* Adjust flags to be compatible with the memory file mapping. Save the * original flags to emulate the correct behavior. */ @@ -732,7 +890,7 @@ void fs__openat(uv_fs_t* req) { attributes |= FILE_ATTRIBUTE_NORMAL; if (flags & UV_FS_O_CREAT) { if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) { - attributes |= FILE_ATTRIBUTE_READONLY; + // attributes |= FILE_ATTRIBUTE_READONLY; } } @@ -801,26 +959,14 @@ void fs__openat(uv_fs_t* req) { goto einval; } + /* Setting this flag makes it possible to open a directory. */ + options |= FILE_OPEN_FOR_BACKUP_INTENT; - if (flags & UV_FS_O_DIRECTORY) { - /* Setting this flag makes it possible to open a directory. */ - options |= FILE_OPEN_FOR_BACKUP_INTENT; - options |= FILE_DIRECTORY_FILE; - } - - HANDLE dir = (HANDLE) _get_osfhandle(req->fs.info.fd_out); - if (dir == INVALID_HANDLE_VALUE) { - fprintf(stderr, "get_osfhandle\n"); - SET_REQ_WIN32_ERROR(req, (DWORD) UV_EBADF); - return; - } - - - pRtlInitUnicodeString(&str, req->file.pathw); + pRtlInitUnicodeString(&str, rebuilt_path.buf); InitializeObjectAttributes(&obj, &str, OBJ_CASE_INSENSITIVE, - dir, + root_dir_handle, NULL); NTSTATUS status = pNtCreateFile(&file, @@ -834,6 +980,7 @@ void fs__openat(uv_fs_t* req) { options, NULL, 0); + uv__path_free(&rebuilt_path); if (!NT_SUCCESS(status)) { ULONG error = pRtlNtStatusToDosError(status); @@ -848,6 +995,28 @@ void fs__openat(uv_fs_t* req) { return; } + if (flags & UV_FS_O_NOFOLLOW) { + // Emulate O_NOFOLLOW. + + IO_STATUS_BLOCK io_status; + FILE_BASIC_INFORMATION basic_info; + + status = pNtQueryInformationFile(file, + &io_status, + &basic_info, + sizeof(basic_info), + FileBasicInformation); + if (!NT_SUCCESS(status)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + if (basic_info.FileAttributes & FILE_ATTRIBUTE_ARCHIVE) { + SET_REQ_WIN32_ERROR(req, (DWORD) UV_ELOOP); + return; + } + } + fd = _open_osfhandle((intptr_t) file, flags); if (fd < 0) { /* The only known failure mode for _open_osfhandle() is EMFILE, in which @@ -912,6 +1081,7 @@ void fs__openat(uv_fs_t* req) { return; einval: + uv__path_free(&rebuilt_path); SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); } @@ -2831,12 +3001,11 @@ static void fs__symlink(uv_fs_t* req) { flags = SYMBOLIC_LINK_FLAG_DIRECTORY | uv__file_symlink_usermode_flag; else flags = uv__file_symlink_usermode_flag; - + if (CreateSymbolicLinkW(new_pathw, pathw, flags)) { SET_REQ_RESULT(req, 0); return; } - /* Something went wrong. We will test if it is because of user-mode * symlinks. */ diff --git a/test/test-fs.c b/test/test-fs.c index 4aaff2f2..72c55db1 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -450,13 +450,10 @@ static void open_cb_simple(uv_fs_t* req) { static void openat_cb_simple(uv_fs_t* req) { ASSERT_EQ(req->fs_type, UV_FS_OPENAT); - if (req->result < 0) { - fprintf(stderr, "async openat error: %d\n", (int) req->result); - ASSERT(0); - } - openat_cb_count++; + ASSERT_GE(req->result, 0); ASSERT(req->path); uv_fs_req_cleanup(req); + openat_cb_count++; } @@ -3023,11 +3020,14 @@ TEST_IMPL(fs_openat) { int r; uv_fs_t req; uv_file fd; - uv_file dirfd; + uv_file dir; + uv_file nested_dir; /* Setup. */ unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); + unlink("test/fixtures/test_dir/link"); + unlink("test/fixtures/test_dir/nested_dir/file"); rmdir("test/fixtures/test_dir/nested_dir"); rmdir("test/fixtures/test_dir"); @@ -3047,9 +3047,18 @@ TEST_IMPL(fs_openat) { 0, NULL); ASSERT_GE(r, 0); + dir = (uv_file) req.result; uv_fs_req_cleanup(&req); - dirfd = (uv_file) r; + r = uv_fs_open(NULL, + &req, + "test/fixtures/test_dir/nested_dir", + UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, + 0, + NULL); + ASSERT_GE(r, 0); + nested_dir = (uv_file) req.result; + uv_fs_req_cleanup(&req); r = uv_fs_open(NULL, &req, @@ -3058,24 +3067,28 @@ TEST_IMPL(fs_openat) { S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); + fd = (uv_file) req.result; uv_fs_req_cleanup(&req); - fd = r; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); + r = uv_fs_symlink(NULL, &req, "test_file", "test/fixtures/test_dir/link", 0, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + // Open an existing file { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file", - UV_FS_O_RDWR | UV_FS_O_CREAT, + UV_FS_O_RDWR, S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); + fd = (uv_file) req.result; uv_fs_req_cleanup(&req); - fd = (uv_file) r; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3085,9 +3098,9 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(loop, &req, - dirfd, + dir, "test_file", - UV_FS_O_RDWR | UV_FS_O_CREAT, + UV_FS_O_RDWR, S_IWUSR | S_IRUSR, openat_cb_simple); ASSERT_GE(r, 0); @@ -3107,14 +3120,65 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "nested_dir", UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); - uv_fs_req_cleanup(&req); fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Open a file in a nested dir + { + r = uv_fs_openat(NULL, + &req, + dir, + "nested_dir/file", + UV_FS_O_RDWR | UV_FS_O_CREAT | UV_FS_O_EXCL, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_GE(r, 0); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Open a file in the parent dir + { + r = uv_fs_openat(NULL, + &req, + dir, + "../empty_file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_GE(r, 0); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Resolve multiple dot dots + { + r = uv_fs_openat(NULL, + &req, + dir, + "../test_dir/nested_dir/././../../empty_file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_GE(r, 0); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3124,14 +3188,14 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file_not_exist", UV_FS_O_RDWR | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); - uv_fs_req_cleanup(&req); fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3141,7 +3205,7 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file", UV_FS_O_RDWR | UV_FS_O_CREAT | UV_FS_O_EXCL, S_IWUSR | S_IRUSR, @@ -3154,15 +3218,14 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file", UV_FS_O_RDONLY, 0, NULL); ASSERT_GE(r, 0); - uv_fs_req_cleanup(&req); - fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); iov = uv_buf_init(test_buf, sizeof(test_buf)); r = uv_fs_write(NULL, @@ -3179,13 +3242,33 @@ TEST_IMPL(fs_openat) { uv_fs_req_cleanup(&req); } - r = uv_fs_close(NULL, &req, dirfd, NULL); + // Open a symlink without following + { + r = uv_fs_openat(NULL, + &req, + dir, + "link", + UV_FS_O_RDONLY | UV_FS_O_NOFOLLOW, + 0, + NULL); + ASSERT_EQ(r, UV_ELOOP); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + } + + r = uv_fs_close(NULL, &req, dir, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + + r = uv_fs_close(NULL, &req, nested_dir, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); /* Cleanup */ unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); + unlink("test/fixtures/test_dir/link"); + unlink("test/fixtures/test_dir/nested_dir/file"); rmdir("test/fixtures/test_dir/nested_dir"); rmdir("test/fixtures/test_dir"); From 8e91f4974c9681fd1aaf7b42fcb493a3b587ac2f Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Tue, 18 Jun 2024 14:16:57 -0700 Subject: [PATCH 8/9] Support NOFOLLOW on Windows Signed-off-by: Yage Hu --- include/uv/win.h | 2 +- src/win/fs.c | 215 ++++++++++++++++++++++++++++++++++++++++++----- test/test-fs.c | 129 +++++++++++++++++++++++----- 3 files changed, 301 insertions(+), 45 deletions(-) diff --git a/include/uv/win.h b/include/uv/win.h index 12ac53b4..367edfc7 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -697,7 +697,7 @@ typedef struct { #define UV_FS_O_EXLOCK 0x10000000 /* EXCLUSIVE SHARING MODE */ #define UV_FS_O_NOATIME 0 #define UV_FS_O_NOCTTY 0 -#define UV_FS_O_NOFOLLOW 0 +#define UV_FS_O_NOFOLLOW 0x40000000 /* Not mapped but handled as a special case in openat */ #define UV_FS_O_NONBLOCK 0 #define UV_FS_O_SYMLINK 0 #define UV_FS_O_SYNC 0x08000000 /* FILE_FLAG_WRITE_THROUGH */ diff --git a/src/win/fs.c b/src/win/fs.c index 31a3fce3..60f49997 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -630,6 +630,93 @@ void fs__open(uv_fs_t* req) { SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); } +struct path { + WCHAR * buf; + + // Capacity of the path buffer minus the terminating null in WCHARs. + size_t cap; + + // Length of the path string without the terminating null in WCHARs. + size_t len; +}; + +// Must be freed by `uv__path_free`. +int uv__path_init(struct path * p) { + WCHAR * buf = uv__malloc(sizeof(WCHAR)); + if (buf == NULL) return UV_ENOMEM; + + p->buf = buf; + p->buf[0] = L'\0'; + p->cap = 0; + p->len = 0; + + return 0; +} + +void uv__path_free(struct path * p) { + uv__free(p->buf); +} + +int uv__path_grow_until(struct path * p, size_t cap) { + if (cap <= p->cap) { + return 0; + } + + WCHAR * buf = uv__realloc(p->buf, (cap + 1) * sizeof(WCHAR)); + if (buf == NULL) return ENOMEM; + + p->buf = buf; + p->cap = cap; + + return 0; +} + +int uv__path_set(struct path * p, WCHAR * source) { + size_t len = wcslen(source); + + int err = uv__path_grow_until(p, len); + if (err) return err; + + memcpy(p->buf, source, len * sizeof(WCHAR)); + p->len = len; + p->buf[p->len] = L'\0'; + + return 0; +} + +int uv__path_push(struct path * p, WCHAR * component) { + size_t len = wcslen(component); + + int err = uv__path_grow_until(p, p->len + len + 1); + if (err) return err; + + if (p->len > 0 && p->buf[p->len - 1] != L'\\') { + p->buf[p->len] = L'\\'; + p->len += 1; + } + + memcpy(p->buf + p->len, component, len * sizeof(WCHAR)); + p->len += len; + p->buf[p->len] = L'\0'; + + return 0; +} + +int uv__path_pop(struct path * p) { + int i; + + if (p->len == 0) return 1; + + for (i = p->len - 1; i >= 0; i--) { + if (p->buf[i] == L'\\' || i == 0) { + p->buf[i] = L'\0'; + p->len = i; + break; + } + } + + return 0; +} void fs__openat(uv_fs_t* req) { DWORD access; @@ -641,9 +728,82 @@ void fs__openat(uv_fs_t* req) { UNICODE_STRING str; IO_STATUS_BLOCK isb; OBJECT_ATTRIBUTES obj; - int fd, current_umask; + int fd, current_umask, temp_umask = 0; int flags = req->fs.info.file_flags; struct uv__fd_info_s fd_info; + WCHAR * path = req->file.pathw; + struct path rebuilt_path; + int i; + + // NtCreateFile doesn't recognize forward slashes, only back slashes. + for (i = 0; path[i] != L'\0'; i++) + if (path[i] == L'/') + path[i] = L'\\'; + + HANDLE dir_handle = (HANDLE) _get_osfhandle(req->fs.info.fd_out); + HANDLE root_dir_handle = dir_handle; + if (dir_handle == INVALID_HANDLE_VALUE) { + SET_REQ_WIN32_ERROR(req, (DWORD) UV_EBADF); + return; + } + + uv__path_init(&rebuilt_path); + + WCHAR * next_token = NULL; + WCHAR * token = wcstok_s(path, L"\\", &next_token); + + while (token != NULL) { + if (!wcscmp(L".", token) || wcslen(token) == 0) { + // Do nothing. + } else if (!wcscmp(L"..", token)) { + // If rebuilt_path is empty, set it to the path of the parent direcotry. + if (rebuilt_path.len == 0) { + DWORD dir_path_len = GetFinalPathNameByHandleW(dir_handle, NULL, 0, VOLUME_NAME_DOS); + if (dir_path_len == 0) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + WCHAR * dir_path_buf = uv__malloc((dir_path_len + 1) * sizeof(WCHAR)); + if (dir_path_buf == NULL) { + SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY); + return; + } + + if ( + GetFinalPathNameByHandleW( + dir_handle, + dir_path_buf, + dir_path_len, + VOLUME_NAME_DOS + ) == 0 + ) { + uv__free(dir_path_buf); + SET_REQ_UV_ERROR(req, UV_EBADF, ERROR_INVALID_HANDLE); + return; + } + + // We'll call `NtCreateFile` with an absolute path, set root dir handle + // to the null handle. + root_dir_handle = 0; + + // The path we get has a prefix of `\\?\`. + // But we need an NT object directory prefix of `\??\`. + WCHAR * dir_path_without_extended_prefix = dir_path_buf + 4; + + uv__path_set(&rebuilt_path, L"\\??"); + uv__path_push(&rebuilt_path, dir_path_without_extended_prefix); + uv__free(dir_path_buf); + } + + // Then pop the last component. + uv__path_pop(&rebuilt_path); + } else { + uv__path_push(&rebuilt_path, token); + } + + token = wcstok_s(NULL, L"\\", &next_token); + } /* Adjust flags to be compatible with the memory file mapping. Save the * original flags to emulate the correct behavior. */ @@ -667,8 +827,8 @@ void fs__openat(uv_fs_t* req) { /* Obtain the active umask. umask() never fails and returns the previous * umask. */ - current_umask = _umask(0); - _umask(current_umask); + _umask_s(temp_umask, ¤t_umask); + _umask_s(current_umask, &temp_umask); /* convert flags and mode to CreateFile parameters */ switch (flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) { @@ -732,7 +892,7 @@ void fs__openat(uv_fs_t* req) { attributes |= FILE_ATTRIBUTE_NORMAL; if (flags & UV_FS_O_CREAT) { if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) { - attributes |= FILE_ATTRIBUTE_READONLY; + // attributes |= FILE_ATTRIBUTE_READONLY; } } @@ -801,26 +961,14 @@ void fs__openat(uv_fs_t* req) { goto einval; } + /* Setting this flag makes it possible to open a directory. */ + options |= FILE_OPEN_FOR_BACKUP_INTENT; - if (flags & UV_FS_O_DIRECTORY) { - /* Setting this flag makes it possible to open a directory. */ - options |= FILE_OPEN_FOR_BACKUP_INTENT; - options |= FILE_DIRECTORY_FILE; - } - - HANDLE dir = (HANDLE) _get_osfhandle(req->fs.info.fd_out); - if (dir == INVALID_HANDLE_VALUE) { - fprintf(stderr, "get_osfhandle\n"); - SET_REQ_WIN32_ERROR(req, (DWORD) UV_EBADF); - return; - } - - - pRtlInitUnicodeString(&str, req->file.pathw); + pRtlInitUnicodeString(&str, rebuilt_path.buf); InitializeObjectAttributes(&obj, &str, OBJ_CASE_INSENSITIVE, - dir, + root_dir_handle, NULL); NTSTATUS status = pNtCreateFile(&file, @@ -834,6 +982,7 @@ void fs__openat(uv_fs_t* req) { options, NULL, 0); + uv__path_free(&rebuilt_path); if (!NT_SUCCESS(status)) { ULONG error = pRtlNtStatusToDosError(status); @@ -848,6 +997,28 @@ void fs__openat(uv_fs_t* req) { return; } + if (flags & UV_FS_O_NOFOLLOW) { + // Emulate O_NOFOLLOW. + + IO_STATUS_BLOCK io_status; + FILE_BASIC_INFORMATION basic_info; + + status = pNtQueryInformationFile(file, + &io_status, + &basic_info, + sizeof(basic_info), + FileBasicInformation); + if (!NT_SUCCESS(status)) { + SET_REQ_WIN32_ERROR(req, GetLastError()); + return; + } + + if (basic_info.FileAttributes & FILE_ATTRIBUTE_ARCHIVE) { + SET_REQ_WIN32_ERROR(req, (DWORD) UV_ELOOP); + return; + } + } + fd = _open_osfhandle((intptr_t) file, flags); if (fd < 0) { /* The only known failure mode for _open_osfhandle() is EMFILE, in which @@ -912,6 +1083,7 @@ void fs__openat(uv_fs_t* req) { return; einval: + uv__path_free(&rebuilt_path); SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER); } @@ -2831,12 +3003,11 @@ static void fs__symlink(uv_fs_t* req) { flags = SYMBOLIC_LINK_FLAG_DIRECTORY | uv__file_symlink_usermode_flag; else flags = uv__file_symlink_usermode_flag; - + if (CreateSymbolicLinkW(new_pathw, pathw, flags)) { SET_REQ_RESULT(req, 0); return; } - /* Something went wrong. We will test if it is because of user-mode * symlinks. */ diff --git a/test/test-fs.c b/test/test-fs.c index 4aaff2f2..f9da96e6 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -450,13 +450,10 @@ static void open_cb_simple(uv_fs_t* req) { static void openat_cb_simple(uv_fs_t* req) { ASSERT_EQ(req->fs_type, UV_FS_OPENAT); - if (req->result < 0) { - fprintf(stderr, "async openat error: %d\n", (int) req->result); - ASSERT(0); - } - openat_cb_count++; + ASSERT_GE(req->result, 0); ASSERT(req->path); uv_fs_req_cleanup(req); + openat_cb_count++; } @@ -3023,11 +3020,15 @@ TEST_IMPL(fs_openat) { int r; uv_fs_t req; uv_file fd; - uv_file dirfd; + uv_file dir; + uv_file nested_dir; /* Setup. */ unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); + unlink("test/fixtures/test_dir/link"); + unlink("test/fixtures/test_dir/nested_dir/file"); + unlink("test/fixtures/file"); rmdir("test/fixtures/test_dir/nested_dir"); rmdir("test/fixtures/test_dir"); @@ -3047,9 +3048,18 @@ TEST_IMPL(fs_openat) { 0, NULL); ASSERT_GE(r, 0); + dir = (uv_file) req.result; uv_fs_req_cleanup(&req); - dirfd = (uv_file) r; + r = uv_fs_open(NULL, + &req, + "test/fixtures/test_dir/nested_dir", + UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, + 0, + NULL); + ASSERT_GE(r, 0); + nested_dir = (uv_file) req.result; + uv_fs_req_cleanup(&req); r = uv_fs_open(NULL, &req, @@ -3058,24 +3068,28 @@ TEST_IMPL(fs_openat) { S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); + fd = (uv_file) req.result; uv_fs_req_cleanup(&req); - fd = r; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); + r = uv_fs_symlink(NULL, &req, "test_file", "test/fixtures/test_dir/link", 0, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + // Open an existing file { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file", - UV_FS_O_RDWR | UV_FS_O_CREAT, + UV_FS_O_RDWR, S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); + fd = (uv_file) req.result; uv_fs_req_cleanup(&req); - fd = (uv_file) r; r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3085,9 +3099,9 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(loop, &req, - dirfd, + dir, "test_file", - UV_FS_O_RDWR | UV_FS_O_CREAT, + UV_FS_O_RDWR, S_IWUSR | S_IRUSR, openat_cb_simple); ASSERT_GE(r, 0); @@ -3107,14 +3121,65 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "nested_dir", UV_FS_O_RDONLY | UV_FS_O_DIRECTORY, S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); - uv_fs_req_cleanup(&req); fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Open a file in a nested dir + { + r = uv_fs_openat(NULL, + &req, + dir, + "nested_dir/file", + UV_FS_O_RDWR | UV_FS_O_CREAT | UV_FS_O_EXCL, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_GE(r, 0); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Open a file in the parent dir + { + r = uv_fs_openat(NULL, + &req, + dir, + "../file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_GE(r, 0); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + + // Resolve multiple dot dots + { + r = uv_fs_openat(NULL, + &req, + dir, + "../test_dir/nested_dir/././../../file", + UV_FS_O_RDWR | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT_GE(r, 0); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3124,14 +3189,14 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file_not_exist", UV_FS_O_RDWR | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); ASSERT_GE(r, 0); - uv_fs_req_cleanup(&req); fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); r = uv_fs_close(NULL, &req, fd, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3141,7 +3206,7 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file", UV_FS_O_RDWR | UV_FS_O_CREAT | UV_FS_O_EXCL, S_IWUSR | S_IRUSR, @@ -3154,15 +3219,14 @@ TEST_IMPL(fs_openat) { { r = uv_fs_openat(NULL, &req, - dirfd, + dir, "test_file", UV_FS_O_RDONLY, 0, NULL); ASSERT_GE(r, 0); - uv_fs_req_cleanup(&req); - fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); iov = uv_buf_init(test_buf, sizeof(test_buf)); r = uv_fs_write(NULL, @@ -3179,13 +3243,34 @@ TEST_IMPL(fs_openat) { uv_fs_req_cleanup(&req); } - r = uv_fs_close(NULL, &req, dirfd, NULL); + // Open a symlink without following + { + r = uv_fs_openat(NULL, + &req, + dir, + "link", + UV_FS_O_RDONLY | UV_FS_O_NOFOLLOW, + 0, + NULL); + ASSERT_EQ(r, UV_ELOOP); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + } + + r = uv_fs_close(NULL, &req, dir, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + + r = uv_fs_close(NULL, &req, nested_dir, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); /* Cleanup */ unlink("test/fixtures/test_dir/test_file_not_exist"); unlink("test/fixtures/test_dir/test_file"); + unlink("test/fixtures/test_dir/link"); + unlink("test/fixtures/test_dir/nested_dir/file"); + unlink("test/fixtures/file"); rmdir("test/fixtures/test_dir/nested_dir"); rmdir("test/fixtures/test_dir"); From 1dc6e1d5d336da291359bfc82c111d3761b870f4 Mon Sep 17 00:00:00 2001 From: Yage Hu Date: Tue, 18 Jun 2024 16:19:07 -0700 Subject: [PATCH 9/9] Support absolute Windows path Signed-off-by: Yage Hu --- src/win/fs.c | 40 +++++++++++++++++++++++++++++++++------- test/test-fs.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/win/fs.c b/src/win/fs.c index 60f49997..1efd04be 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -35,6 +35,7 @@ /* requires , included via "uv.h" above, but needs to be included before our "winapi.h", included via "internal.h" below. */ #include +#include #include "internal.h" #include "req-inl.h" @@ -732,6 +733,7 @@ void fs__openat(uv_fs_t* req) { int flags = req->fs.info.file_flags; struct uv__fd_info_s fd_info; WCHAR * path = req->file.pathw; + size_t path_len = wcslen(path); struct path rebuilt_path; int i; @@ -740,11 +742,23 @@ void fs__openat(uv_fs_t* req) { if (path[i] == L'/') path[i] = L'\\'; - HANDLE dir_handle = (HANDLE) _get_osfhandle(req->fs.info.fd_out); - HANDLE root_dir_handle = dir_handle; - if (dir_handle == INVALID_HANDLE_VALUE) { - SET_REQ_WIN32_ERROR(req, (DWORD) UV_EBADF); - return; + HANDLE root_dir_handle = 0; + HANDLE dir_handle; + BOOL is_absolute = FALSE; + + if ( + (path_len > 0 && path[0] == L'\\') || + (path_len > 2 && path[1] == L':' && path[2] == L'\\') + ) is_absolute = TRUE; + + if (!is_absolute) { + dir_handle = (HANDLE) _get_osfhandle(req->fs.info.fd_out); + if (dir_handle == INVALID_HANDLE_VALUE) { + SET_REQ_WIN32_ERROR(req, (DWORD) UV_EBADF); + return; + } + + root_dir_handle = dir_handle; } uv__path_init(&rebuilt_path); @@ -757,7 +771,7 @@ void fs__openat(uv_fs_t* req) { // Do nothing. } else if (!wcscmp(L"..", token)) { // If rebuilt_path is empty, set it to the path of the parent direcotry. - if (rebuilt_path.len == 0) { + if (rebuilt_path.len == 0 && !is_absolute) { DWORD dir_path_len = GetFinalPathNameByHandleW(dir_handle, NULL, 0, VOLUME_NAME_DOS); if (dir_path_len == 0) { SET_REQ_WIN32_ERROR(req, GetLastError()); @@ -797,7 +811,7 @@ void fs__openat(uv_fs_t* req) { } // Then pop the last component. - uv__path_pop(&rebuilt_path); + if (rebuilt_path.len > 0) uv__path_pop(&rebuilt_path); } else { uv__path_push(&rebuilt_path, token); } @@ -805,6 +819,18 @@ void fs__openat(uv_fs_t* req) { token = wcstok_s(NULL, L"\\", &next_token); } + if (is_absolute) { + // Prepend the path with the NT object directory prefix. + + WCHAR * buf = uv__malloc((rebuilt_path.len + 1) * sizeof(WCHAR)); + memcpy(buf, rebuilt_path.buf, (rebuilt_path.len + 1) * sizeof(WCHAR)); + uv__path_set(&rebuilt_path, L"\\??"); + uv__path_push(&rebuilt_path, buf); + uv__free(buf); + } + + wprintf(L"%d %s\n", root_dir_handle, rebuilt_path.buf); + /* Adjust flags to be compatible with the memory file mapping. Save the * original flags to emulate the correct behavior. */ if (flags & UV_FS_O_FILEMAP) { diff --git a/test/test-fs.c b/test/test-fs.c index b45a453d..b27f9cf0 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -3257,6 +3257,32 @@ TEST_IMPL(fs_openat) { uv_fs_req_cleanup(&req); } + { + r = uv_fs_realpath(NULL, &req, "test/fixtures/file", NULL); + ASSERT_OK(r); + + size_t len = strlen(req.ptr); + char * abs_path = malloc(len + 1); + memcpy(abs_path, req.ptr, len + 1); + uv_fs_req_cleanup(&req); + + r = uv_fs_openat(NULL, + &req, + dir, + abs_path, + UV_FS_O_RDONLY, + 0, + NULL); + ASSERT_GE(r, 0); + free(abs_path); + fd = (uv_file) req.result; + uv_fs_req_cleanup(&req); + + r = uv_fs_close(NULL, &req, fd, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); + } + r = uv_fs_close(NULL, &req, dir, NULL); ASSERT_OK(r); uv_fs_req_cleanup(&req); @@ -3270,10 +3296,7 @@ TEST_IMPL(fs_openat) { unlink("test/fixtures/test_dir/test_file"); unlink("test/fixtures/test_dir/link"); unlink("test/fixtures/test_dir/nested_dir/file"); -<<<<<<< HEAD -======= unlink("test/fixtures/file"); ->>>>>>> 8e91f4974c9681fd1aaf7b42fcb493a3b587ac2f rmdir("test/fixtures/test_dir/nested_dir"); rmdir("test/fixtures/test_dir");