diff --git a/include/uv/win.h b/include/uv/win.h index a88bf29e..79c2eb99 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -692,7 +692,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 03fb55ac..b9b9f3a3 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -665,6 +665,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; @@ -679,6 +764,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. */ @@ -767,7 +925,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; } } @@ -836,26 +994,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, @@ -869,6 +1015,7 @@ void fs__openat(uv_fs_t* req) { options, NULL, 0); + uv__path_free(&rebuilt_path); if (!NT_SUCCESS(status)) { ULONG error = pRtlNtStatusToDosError(status); @@ -883,6 +1030,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 @@ -947,6 +1116,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); } @@ -3179,12 +3349,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 f4601659..e0549940 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -463,13 +463,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++; } @@ -3249,11 +3246,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"); @@ -3273,9 +3273,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, @@ -3284,24 +3293,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); @@ -3311,9 +3324,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); @@ -3333,14 +3346,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); @@ -3350,14 +3414,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); @@ -3367,7 +3431,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, @@ -3380,15 +3444,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, @@ -3405,13 +3468,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");