libuv/src/win/fs.c
Ben Noordhuis d03abfd400 win: work around sharepoint scandir bug
It has been reported that for SharePoint connections mapped as a drive,
uv_fs_scandir() returns "." and ".." entries when the expectation is
that they should be filtered out.

After some investigation it looks like the driver returns ".\0" and
"..\0" for those entries, that is, it includes the zero byte in the
filename length.  Rewrite the filter to catch those entries as well.

Fixes: https://github.com/nodejs/node/issues/4002
PR-URL: https://github.com/libuv/libuv/pull/636
Reviewed-By: Alexis Campailla <orangemocha@nodejs.org>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
2016-04-15 17:11:53 +02:00

2480 lines
63 KiB
C

/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h>
#include <stdlib.h>
#include <direct.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/utime.h>
#include <stdio.h>
#include "uv.h"
#include "internal.h"
#include "req-inl.h"
#include "handle-inl.h"
#include <wincrypt.h>
#define UV_FS_FREE_PATHS 0x0002
#define UV_FS_FREE_PTR 0x0008
#define UV_FS_CLEANEDUP 0x0010
#define QUEUE_FS_TP_JOB(loop, req) \
do { \
uv__req_register(loop, req); \
uv__work_submit((loop), &(req)->work_req, uv__fs_work, uv__fs_done); \
} while (0)
#define SET_REQ_RESULT(req, result_value) \
do { \
req->result = (result_value); \
if (req->result == -1) { \
req->sys_errno_ = _doserrno; \
req->result = uv_translate_sys_error(req->sys_errno_); \
} \
} while (0)
#define SET_REQ_WIN32_ERROR(req, sys_errno) \
do { \
req->sys_errno_ = (sys_errno); \
req->result = uv_translate_sys_error(req->sys_errno_); \
} while (0)
#define SET_REQ_UV_ERROR(req, uv_errno, sys_errno) \
do { \
req->result = (uv_errno); \
req->sys_errno_ = (sys_errno); \
} while (0)
#define VERIFY_FD(fd, req) \
if (fd == -1) { \
req->result = UV_EBADF; \
req->sys_errno_ = ERROR_INVALID_HANDLE; \
return; \
}
#define FILETIME_TO_UINT(filetime) \
(*((uint64_t*) &(filetime)) - 116444736000000000ULL)
#define FILETIME_TO_TIME_T(filetime) \
(FILETIME_TO_UINT(filetime) / 10000000ULL)
#define FILETIME_TO_TIME_NS(filetime, secs) \
((FILETIME_TO_UINT(filetime) - (secs * 10000000ULL)) * 100)
#define FILETIME_TO_TIMESPEC(ts, filetime) \
do { \
(ts).tv_sec = (long) FILETIME_TO_TIME_T(filetime); \
(ts).tv_nsec = (long) FILETIME_TO_TIME_NS(filetime, (ts).tv_sec); \
} while(0)
#define TIME_T_TO_FILETIME(time, filetime_ptr) \
do { \
uint64_t bigtime = ((int64_t) (time) * 10000000LL) + \
116444736000000000ULL; \
(filetime_ptr)->dwLowDateTime = bigtime & 0xFFFFFFFF; \
(filetime_ptr)->dwHighDateTime = bigtime >> 32; \
} while(0)
#define IS_SLASH(c) ((c) == L'\\' || (c) == L'/')
#define IS_LETTER(c) (((c) >= L'a' && (c) <= L'z') || \
((c) >= L'A' && (c) <= L'Z'))
const WCHAR JUNCTION_PREFIX[] = L"\\??\\";
const WCHAR JUNCTION_PREFIX_LEN = 4;
const WCHAR LONG_PATH_PREFIX[] = L"\\\\?\\";
const WCHAR LONG_PATH_PREFIX_LEN = 4;
const WCHAR UNC_PATH_PREFIX[] = L"\\\\?\\UNC\\";
const WCHAR UNC_PATH_PREFIX_LEN = 8;
void uv_fs_init() {
_fmode = _O_BINARY;
}
INLINE static int fs__capture_path(uv_fs_t* req, const char* path,
const char* new_path, const int copy_path) {
char* buf;
char* pos;
ssize_t buf_sz = 0, path_len, pathw_len = 0, new_pathw_len = 0;
/* new_path can only be set if path is also set. */
assert(new_path == NULL || path != NULL);
if (path != NULL) {
pathw_len = MultiByteToWideChar(CP_UTF8,
0,
path,
-1,
NULL,
0);
if (pathw_len == 0) {
return GetLastError();
}
buf_sz += pathw_len * sizeof(WCHAR);
}
if (path != NULL && copy_path) {
path_len = 1 + strlen(path);
buf_sz += path_len;
}
if (new_path != NULL) {
new_pathw_len = MultiByteToWideChar(CP_UTF8,
0,
new_path,
-1,
NULL,
0);
if (new_pathw_len == 0) {
return GetLastError();
}
buf_sz += new_pathw_len * sizeof(WCHAR);
}
if (buf_sz == 0) {
req->file.pathw = NULL;
req->fs.info.new_pathw = NULL;
req->path = NULL;
return 0;
}
buf = (char*) uv__malloc(buf_sz);
if (buf == NULL) {
return ERROR_OUTOFMEMORY;
}
pos = buf;
if (path != NULL) {
DWORD r = MultiByteToWideChar(CP_UTF8,
0,
path,
-1,
(WCHAR*) pos,
pathw_len);
assert(r == (DWORD) pathw_len);
req->file.pathw = (WCHAR*) pos;
pos += r * sizeof(WCHAR);
} else {
req->file.pathw = NULL;
}
if (new_path != NULL) {
DWORD r = MultiByteToWideChar(CP_UTF8,
0,
new_path,
-1,
(WCHAR*) pos,
new_pathw_len);
assert(r == (DWORD) new_pathw_len);
req->fs.info.new_pathw = (WCHAR*) pos;
pos += r * sizeof(WCHAR);
} else {
req->fs.info.new_pathw = NULL;
}
if (!copy_path) {
req->path = path;
} else if (path) {
memcpy(pos, path, path_len);
assert(path_len == buf_sz - (pos - buf));
req->path = pos;
} else {
req->path = NULL;
}
req->flags |= UV_FS_FREE_PATHS;
return 0;
}
INLINE static void uv_fs_req_init(uv_loop_t* loop, uv_fs_t* req,
uv_fs_type fs_type, const uv_fs_cb cb) {
uv_req_init(loop, (uv_req_t*) req);
req->type = UV_FS;
req->loop = loop;
req->flags = 0;
req->fs_type = fs_type;
req->result = 0;
req->ptr = NULL;
req->path = NULL;
req->cb = cb;
}
static int fs__wide_to_utf8(WCHAR* w_source_ptr,
DWORD w_source_len,
char** target_ptr,
uint64_t* target_len_ptr) {
int r;
int target_len;
char* target;
target_len = WideCharToMultiByte(CP_UTF8,
0,
w_source_ptr,
w_source_len,
NULL,
0,
NULL,
NULL);
if (target_len == 0) {
return -1;
}
if (target_len_ptr != NULL) {
*target_len_ptr = target_len;
}
if (target_ptr == NULL) {
return 0;
}
target = uv__malloc(target_len + 1);
if (target == NULL) {
SetLastError(ERROR_OUTOFMEMORY);
return -1;
}
r = WideCharToMultiByte(CP_UTF8,
0,
w_source_ptr,
w_source_len,
target,
target_len,
NULL,
NULL);
assert(r == target_len);
target[target_len] = '\0';
*target_ptr = target;
return 0;
}
INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr,
uint64_t* target_len_ptr) {
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
REPARSE_DATA_BUFFER* reparse_data = (REPARSE_DATA_BUFFER*) buffer;
WCHAR* w_target;
DWORD w_target_len;
DWORD bytes;
if (!DeviceIoControl(handle,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
buffer,
sizeof buffer,
&bytes,
NULL)) {
return -1;
}
if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
/* Real symlink */
w_target = reparse_data->SymbolicLinkReparseBuffer.PathBuffer +
(reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset /
sizeof(WCHAR));
w_target_len =
reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
sizeof(WCHAR);
/* Real symlinks can contain pretty much everything, but the only thing */
/* we really care about is undoing the implicit conversion to an NT */
/* namespaced path that CreateSymbolicLink will perform on absolute */
/* paths. If the path is win32-namespaced then the user must have */
/* explicitly made it so, and we better just return the unmodified */
/* reparse data. */
if (w_target_len >= 4 &&
w_target[0] == L'\\' &&
w_target[1] == L'?' &&
w_target[2] == L'?' &&
w_target[3] == L'\\') {
/* Starts with \??\ */
if (w_target_len >= 6 &&
((w_target[4] >= L'A' && w_target[4] <= L'Z') ||
(w_target[4] >= L'a' && w_target[4] <= L'z')) &&
w_target[5] == L':' &&
(w_target_len == 6 || w_target[6] == L'\\')) {
/* \??\<drive>:\ */
w_target += 4;
w_target_len -= 4;
} else if (w_target_len >= 8 &&
(w_target[4] == L'U' || w_target[4] == L'u') &&
(w_target[5] == L'N' || w_target[5] == L'n') &&
(w_target[6] == L'C' || w_target[6] == L'c') &&
w_target[7] == L'\\') {
/* \??\UNC\<server>\<share>\ - make sure the final path looks like */
/* \\<server>\<share>\ */
w_target += 6;
w_target[0] = L'\\';
w_target_len -= 6;
}
}
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
/* Junction. */
w_target = reparse_data->MountPointReparseBuffer.PathBuffer +
(reparse_data->MountPointReparseBuffer.SubstituteNameOffset /
sizeof(WCHAR));
w_target_len = reparse_data->MountPointReparseBuffer.SubstituteNameLength /
sizeof(WCHAR);
/* Only treat junctions that look like \??\<drive>:\ as symlink. */
/* Junctions can also be used as mount points, like \??\Volume{<guid>}, */
/* but that's confusing for programs since they wouldn't be able to */
/* actually understand such a path when returned by uv_readlink(). */
/* UNC paths are never valid for junctions so we don't care about them. */
if (!(w_target_len >= 6 &&
w_target[0] == L'\\' &&
w_target[1] == L'?' &&
w_target[2] == L'?' &&
w_target[3] == L'\\' &&
((w_target[4] >= L'A' && w_target[4] <= L'Z') ||
(w_target[4] >= L'a' && w_target[4] <= L'z')) &&
w_target[5] == L':' &&
(w_target_len == 6 || w_target[6] == L'\\'))) {
SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
return -1;
}
/* Remove leading \??\ */
w_target += 4;
w_target_len -= 4;
} else {
/* Reparse tag does not indicate a symlink. */
SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
return -1;
}
return fs__wide_to_utf8(w_target, w_target_len, target_ptr, target_len_ptr);
}
void fs__open(uv_fs_t* req) {
DWORD access;
DWORD share;
DWORD disposition;
DWORD attributes = 0;
HANDLE file;
int fd, current_umask;
int flags = req->fs.info.file_flags;
/* 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 & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
case _O_RDONLY:
access = FILE_GENERIC_READ;
attributes |= FILE_FLAG_BACKUP_SEMANTICS;
break;
case _O_WRONLY:
access = FILE_GENERIC_WRITE;
break;
case _O_RDWR:
access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
break;
default:
goto einval;
}
if (flags & _O_APPEND) {
access &= ~FILE_WRITE_DATA;
access |= FILE_APPEND_DATA;
attributes &= ~FILE_FLAG_BACKUP_SEMANTICS;
}
/*
* 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 #1449.
*/
share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
case 0:
case _O_EXCL:
disposition = OPEN_EXISTING;
break;
case _O_CREAT:
disposition = OPEN_ALWAYS;
break;
case _O_CREAT | _O_EXCL:
case _O_CREAT | _O_TRUNC | _O_EXCL:
disposition = CREATE_NEW;
break;
case _O_TRUNC:
case _O_TRUNC | _O_EXCL:
disposition = TRUNCATE_EXISTING;
break;
case _O_CREAT | _O_TRUNC:
disposition = CREATE_ALWAYS;
break;
default:
goto einval;
}
attributes |= FILE_ATTRIBUTE_NORMAL;
if (flags & _O_CREAT) {
if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) {
attributes |= FILE_ATTRIBUTE_READONLY;
}
}
if (flags & _O_TEMPORARY ) {
attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
access |= DELETE;
}
if (flags & _O_SHORT_LIVED) {
attributes |= FILE_ATTRIBUTE_TEMPORARY;
}
switch (flags & (_O_SEQUENTIAL | _O_RANDOM)) {
case 0:
break;
case _O_SEQUENTIAL:
attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
break;
case _O_RANDOM:
attributes |= FILE_FLAG_RANDOM_ACCESS;
break;
default:
goto einval;
}
/* Setting this flag makes it possible to open a directory. */
attributes |= FILE_FLAG_BACKUP_SEMANTICS;
file = CreateFileW(req->file.pathw,
access,
share,
NULL,
disposition,
attributes,
NULL);
if (file == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
if (error == ERROR_FILE_EXISTS && (flags & _O_CREAT) &&
!(flags & _O_EXCL)) {
/* Special case: when ERROR_FILE_EXISTS happens and 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, GetLastError());
}
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, UV_UNKNOWN);
CloseHandle(file);
return;
}
SET_REQ_RESULT(req, fd);
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;
VERIFY_FD(fd, req);
if (fd > 2)
result = _close(fd);
else
result = 0;
/* _close doesn't set _doserrno on failure, but it does always set errno
* to EBADF on failure.
*/
if (result == -1) {
assert(errno == EBADF);
SET_REQ_UV_ERROR(req, UV_EBADF, ERROR_INVALID_HANDLE);
} else {
req->result = 0;
}
}
void fs__read(uv_fs_t* req) {
int fd = req->file.fd;
int64_t offset = req->fs.info.offset;
HANDLE handle;
OVERLAPPED overlapped, *overlapped_ptr;
LARGE_INTEGER offset_;
DWORD bytes;
DWORD error;
int result;
unsigned int index;
VERIFY_FD(fd, req);
handle = uv__get_osfhandle(fd);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
return;
}
if (offset != -1) {
memset(&overlapped, 0, sizeof overlapped);
overlapped_ptr = &overlapped;
} else {
overlapped_ptr = NULL;
}
index = 0;
bytes = 0;
do {
DWORD incremental_bytes;
if (offset != -1) {
offset_.QuadPart = offset + bytes;
overlapped.Offset = offset_.LowPart;
overlapped.OffsetHigh = offset_.HighPart;
}
result = ReadFile(handle,
req->fs.info.bufs[index].base,
req->fs.info.bufs[index].len,
&incremental_bytes,
overlapped_ptr);
bytes += incremental_bytes;
++index;
} while (result && index < req->fs.info.nbufs);
if (result || bytes > 0) {
SET_REQ_RESULT(req, bytes);
} else {
error = GetLastError();
if (error == ERROR_HANDLE_EOF) {
SET_REQ_RESULT(req, bytes);
} else {
SET_REQ_WIN32_ERROR(req, error);
}
}
}
void fs__write(uv_fs_t* req) {
int fd = req->file.fd;
int64_t offset = req->fs.info.offset;
HANDLE handle;
OVERLAPPED overlapped, *overlapped_ptr;
LARGE_INTEGER offset_;
DWORD bytes;
int result;
unsigned int index;
VERIFY_FD(fd, req);
handle = uv__get_osfhandle(fd);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
return;
}
if (offset != -1) {
memset(&overlapped, 0, sizeof overlapped);
overlapped_ptr = &overlapped;
} else {
overlapped_ptr = NULL;
}
index = 0;
bytes = 0;
do {
DWORD incremental_bytes;
if (offset != -1) {
offset_.QuadPart = offset + bytes;
overlapped.Offset = offset_.LowPart;
overlapped.OffsetHigh = offset_.HighPart;
}
result = WriteFile(handle,
req->fs.info.bufs[index].base,
req->fs.info.bufs[index].len,
&incremental_bytes,
overlapped_ptr);
bytes += incremental_bytes;
++index;
} while (result && index < req->fs.info.nbufs);
if (result || bytes > 0) {
SET_REQ_RESULT(req, bytes);
} else {
SET_REQ_WIN32_ERROR(req, GetLastError());
}
}
void fs__rmdir(uv_fs_t* req) {
int result = _wrmdir(req->file.pathw);
SET_REQ_RESULT(req, result);
}
void fs__unlink(uv_fs_t* req) {
const WCHAR* pathw = req->file.pathw;
HANDLE handle;
BY_HANDLE_FILE_INFORMATION info;
FILE_DISPOSITION_INFORMATION disposition;
IO_STATUS_BLOCK iosb;
NTSTATUS status;
handle = CreateFileW(pathw,
FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | DELETE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
if (!GetFileInformationByHandle(handle, &info)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
CloseHandle(handle);
return;
}
if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
/* Do not allow deletion of directories, unless it is a symlink. When */
/* the path refers to a non-symlink directory, report EPERM as mandated */
/* by POSIX.1. */
/* Check if it is a reparse point. If it's not, it's a normal directory. */
if (!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
SET_REQ_WIN32_ERROR(req, ERROR_ACCESS_DENIED);
CloseHandle(handle);
return;
}
/* Read the reparse point and check if it is a valid symlink. */
/* If not, don't unlink. */
if (fs__readlink_handle(handle, NULL, NULL) < 0) {
DWORD error = GetLastError();
if (error == ERROR_SYMLINK_NOT_SUPPORTED)
error = ERROR_ACCESS_DENIED;
SET_REQ_WIN32_ERROR(req, error);
CloseHandle(handle);
return;
}
}
if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
/* Remove read-only attribute */
FILE_BASIC_INFORMATION basic = { 0 };
basic.FileAttributes = info.dwFileAttributes & ~(FILE_ATTRIBUTE_READONLY);
status = pNtSetInformationFile(handle,
&iosb,
&basic,
sizeof basic,
FileBasicInformation);
if (!NT_SUCCESS(status)) {
SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
CloseHandle(handle);
return;
}
}
/* Try to set the delete flag. */
disposition.DeleteFile = TRUE;
status = pNtSetInformationFile(handle,
&iosb,
&disposition,
sizeof disposition,
FileDispositionInformation);
if (NT_SUCCESS(status)) {
SET_REQ_SUCCESS(req);
} else {
SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
}
CloseHandle(handle);
}
void fs__mkdir(uv_fs_t* req) {
/* TODO: use req->mode. */
int result = _wmkdir(req->file.pathw);
SET_REQ_RESULT(req, result);
}
/* OpenBSD original: lib/libc/stdio/mktemp.c */
void fs__mkdtemp(uv_fs_t* req) {
static const WCHAR *tempchars =
L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static const size_t num_chars = 62;
static const size_t num_x = 6;
WCHAR *cp, *ep;
unsigned int tries, i;
size_t len;
HCRYPTPROV h_crypt_prov;
uint64_t v;
BOOL released;
len = wcslen(req->file.pathw);
ep = req->file.pathw + len;
if (len < num_x || wcsncmp(ep - num_x, L"XXXXXX", num_x)) {
SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER);
return;
}
if (!CryptAcquireContext(&h_crypt_prov, NULL, NULL, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
tries = TMP_MAX;
do {
if (!CryptGenRandom(h_crypt_prov, sizeof(v), (BYTE*) &v)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
break;
}
cp = ep - num_x;
for (i = 0; i < num_x; i++) {
*cp++ = tempchars[v % num_chars];
v /= num_chars;
}
if (_wmkdir(req->file.pathw) == 0) {
len = strlen(req->path);
wcstombs((char*) req->path + len - num_x, ep - num_x, num_x);
SET_REQ_RESULT(req, 0);
break;
} else if (errno != EEXIST) {
SET_REQ_RESULT(req, -1);
break;
}
} while (--tries);
released = CryptReleaseContext(h_crypt_prov, 0);
assert(released);
if (tries == 0) {
SET_REQ_RESULT(req, -1);
}
}
void fs__scandir(uv_fs_t* req) {
static const size_t dirents_initial_size = 32;
HANDLE dir_handle = INVALID_HANDLE_VALUE;
uv__dirent_t** dirents = NULL;
size_t dirents_size = 0;
size_t dirents_used = 0;
IO_STATUS_BLOCK iosb;
NTSTATUS status;
/* Buffer to hold directory entries returned by NtQueryDirectoryFile.
* It's important that this buffer can hold at least one entry, regardless
* of the length of the file names present in the enumerated directory.
* A file name is at most 256 WCHARs long.
* According to MSDN, the buffer must be aligned at an 8-byte boundary.
*/
#if _MSC_VER
__declspec(align(8)) char buffer[8192];
#else
__attribute__ ((aligned (8))) char buffer[8192];
#endif
STATIC_ASSERT(sizeof buffer >=
sizeof(FILE_DIRECTORY_INFORMATION) + 256 * sizeof(WCHAR));
/* Open the directory. */
dir_handle =
CreateFileW(req->file.pathw,
FILE_LIST_DIRECTORY | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (dir_handle == INVALID_HANDLE_VALUE)
goto win32_error;
/* Read the first chunk. */
status = pNtQueryDirectoryFile(dir_handle,
NULL,
NULL,
NULL,
&iosb,
&buffer,
sizeof buffer,
FileDirectoryInformation,
FALSE,
NULL,
TRUE);
/* If the handle is not a directory, we'll get STATUS_INVALID_PARAMETER.
* This should be reported back as UV_ENOTDIR.
*/
if (status == STATUS_INVALID_PARAMETER)
goto not_a_directory_error;
while (NT_SUCCESS(status)) {
char* position = buffer;
size_t next_entry_offset = 0;
do {
FILE_DIRECTORY_INFORMATION* info;
uv__dirent_t* dirent;
size_t wchar_len;
size_t utf8_len;
/* Obtain a pointer to the current directory entry. */
position += next_entry_offset;
info = (FILE_DIRECTORY_INFORMATION*) position;
/* Fetch the offset to the next directory entry. */
next_entry_offset = info->NextEntryOffset;
/* Compute the length of the filename in WCHARs. */
wchar_len = info->FileNameLength / sizeof info->FileName[0];
/* Skip over '.' and '..' entries. It has been reported that
* the SharePoint driver includes the terminating zero byte in
* the filename length. Strip those first.
*/
while (wchar_len > 0 && info->FileName[wchar_len - 1] == L'\0')
wchar_len -= 1;
if (wchar_len == 0)
continue;
if (wchar_len == 1 && info->FileName[0] == L'.')
continue;
if (wchar_len == 2 && info->FileName[0] == L'.' &&
info->FileName[1] == L'.')
continue;
/* Compute the space required to store the filename as UTF-8. */
utf8_len = WideCharToMultiByte(
CP_UTF8, 0, &info->FileName[0], wchar_len, NULL, 0, NULL, NULL);
if (utf8_len == 0)
goto win32_error;
/* Resize the dirent array if needed. */
if (dirents_used >= dirents_size) {
size_t new_dirents_size =
dirents_size == 0 ? dirents_initial_size : dirents_size << 1;
uv__dirent_t** new_dirents =
uv__realloc(dirents, new_dirents_size * sizeof *dirents);
if (new_dirents == NULL)
goto out_of_memory_error;
dirents_size = new_dirents_size;
dirents = new_dirents;
}
/* Allocate space for the uv dirent structure. The dirent structure
* includes room for the first character of the filename, but `utf8_len`
* doesn't count the NULL terminator at this point.
*/
dirent = uv__malloc(sizeof *dirent + utf8_len);
if (dirent == NULL)
goto out_of_memory_error;
dirents[dirents_used++] = dirent;
/* Convert file name to UTF-8. */
if (WideCharToMultiByte(CP_UTF8,
0,
&info->FileName[0],
wchar_len,
&dirent->d_name[0],
utf8_len,
NULL,
NULL) == 0)
goto win32_error;
/* Add a null terminator to the filename. */
dirent->d_name[utf8_len] = '\0';
/* Fill out the type field. */
if (info->FileAttributes & FILE_ATTRIBUTE_DEVICE)
dirent->d_type = UV__DT_CHAR;
else if (info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
dirent->d_type = UV__DT_LINK;
else if (info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
dirent->d_type = UV__DT_DIR;
else
dirent->d_type = UV__DT_FILE;
} while (next_entry_offset != 0);
/* Read the next chunk. */
status = pNtQueryDirectoryFile(dir_handle,
NULL,
NULL,
NULL,
&iosb,
&buffer,
sizeof buffer,
FileDirectoryInformation,
FALSE,
NULL,
FALSE);
/* After the first pNtQueryDirectoryFile call, the function may return
* STATUS_SUCCESS even if the buffer was too small to hold at least one
* directory entry.
*/
if (status == STATUS_SUCCESS && iosb.Information == 0)
status = STATUS_BUFFER_OVERFLOW;
}
if (status != STATUS_NO_MORE_FILES)
goto nt_error;
CloseHandle(dir_handle);
/* Store the result in the request object. */
req->ptr = dirents;
if (dirents != NULL)
req->flags |= UV_FS_FREE_PTR;
SET_REQ_RESULT(req, dirents_used);
/* `nbufs` will be used as index by uv_fs_scandir_next. */
req->fs.info.nbufs = 0;
return;
nt_error:
SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
goto cleanup;
win32_error:
SET_REQ_WIN32_ERROR(req, GetLastError());
goto cleanup;
not_a_directory_error:
SET_REQ_UV_ERROR(req, UV_ENOTDIR, ERROR_DIRECTORY);
goto cleanup;
out_of_memory_error:
SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY);
goto cleanup;
cleanup:
if (dir_handle != INVALID_HANDLE_VALUE)
CloseHandle(dir_handle);
while (dirents_used > 0)
uv__free(dirents[--dirents_used]);
if (dirents != NULL)
uv__free(dirents);
}
INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf) {
FILE_ALL_INFORMATION file_info;
FILE_FS_VOLUME_INFORMATION volume_info;
NTSTATUS nt_status;
IO_STATUS_BLOCK io_status;
nt_status = pNtQueryInformationFile(handle,
&io_status,
&file_info,
sizeof file_info,
FileAllInformation);
/* Buffer overflow (a warning status code) is expected here. */
if (NT_ERROR(nt_status)) {
SetLastError(pRtlNtStatusToDosError(nt_status));
return -1;
}
nt_status = pNtQueryVolumeInformationFile(handle,
&io_status,
&volume_info,
sizeof volume_info,
FileFsVolumeInformation);
/* Buffer overflow (a warning status code) is expected here. */
if (io_status.Status == STATUS_NOT_IMPLEMENTED) {
statbuf->st_dev = 0;
} else if (NT_ERROR(nt_status)) {
SetLastError(pRtlNtStatusToDosError(nt_status));
return -1;
} else {
statbuf->st_dev = volume_info.VolumeSerialNumber;
}
/* 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.
*
* Currently it's based on whether the 'readonly' attribute is set, which
* makes little sense because the semantics are so different: the 'read-only'
* flag is just a way for a user to protect against accidental deletion, and
* serves no security purpose. Windows uses ACLs for that.
*
* Also people now use uv_fs_chmod() to take away the writable bit for good
* reasons. Windows however just makes the file read-only, which makes it
* impossible to delete the file afterwards, since read-only files can't be
* deleted.
*
* IOW it's all just a clusterfuck and we should think of something that
* makes slightly more sense.
*
* And uv_fs_chmod should probably just fail on windows or be a total no-op.
* There's nothing sensible it can do anyway.
*/
statbuf->st_mode = 0;
if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
statbuf->st_mode |= S_IFLNK;
if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0)
return -1;
} else if (file_info.BasicInformation.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;
}
if (file_info.BasicInformation.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);
FILETIME_TO_TIMESPEC(statbuf->st_atim, file_info.BasicInformation.LastAccessTime);
FILETIME_TO_TIMESPEC(statbuf->st_ctim, file_info.BasicInformation.ChangeTime);
FILETIME_TO_TIMESPEC(statbuf->st_mtim, file_info.BasicInformation.LastWriteTime);
FILETIME_TO_TIMESPEC(statbuf->st_birthtim, file_info.BasicInformation.CreationTime);
statbuf->st_ino = file_info.InternalInformation.IndexNumber.QuadPart;
/* st_blocks contains the on-disk allocation size in 512-byte units. */
statbuf->st_blocks =
file_info.StandardInformation.AllocationSize.QuadPart >> 9ULL;
statbuf->st_nlink = file_info.StandardInformation.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
* supposed to at least avoid read-update-write behavior when writing to the
* disk.
*
* However nobody knows this and even fewer people actually use this value,
* and in order to fill it out we'd have to make another syscall to query the
* volume for FILE_FS_SECTOR_SIZE_INFORMATION.
*
* Therefore we'll just report a sensible value that's quite commonly okay
* on modern hardware.
*/
statbuf->st_blksize = 2048;
/* Todo: set st_flags to something meaningful. Also provide a wrapper for
* chattr(2).
*/
statbuf->st_flags = 0;
/* Windows has nothing sensible to say about these values, so they'll just
* remain empty.
*/
statbuf->st_gid = 0;
statbuf->st_uid = 0;
statbuf->st_rdev = 0;
statbuf->st_gen = 0;
return 0;
}
INLINE static void fs__stat_prepare_path(WCHAR* pathw) {
size_t len = wcslen(pathw);
/* TODO: ignore namespaced paths. */
if (len > 1 && pathw[len - 2] != L':' &&
(pathw[len - 1] == L'\\' || pathw[len - 1] == L'/')) {
pathw[len - 1] = '\0';
}
}
INLINE static void fs__stat_impl(uv_fs_t* req, int do_lstat) {
HANDLE handle;
DWORD flags;
flags = FILE_FLAG_BACKUP_SEMANTICS;
if (do_lstat) {
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
handle = CreateFileW(req->file.pathw,
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
flags,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
if (fs__stat_handle(handle, &req->statbuf) != 0) {
DWORD error = GetLastError();
if (do_lstat && error == ERROR_SYMLINK_NOT_SUPPORTED) {
/* We opened a reparse point but it was not a symlink. Try again. */
fs__stat_impl(req, 0);
} else {
/* Stat failed. */
SET_REQ_WIN32_ERROR(req, GetLastError());
}
CloseHandle(handle);
return;
}
req->ptr = &req->statbuf;
req->result = 0;
CloseHandle(handle);
}
static void fs__stat(uv_fs_t* req) {
fs__stat_prepare_path(req->file.pathw);
fs__stat_impl(req, 0);
}
static void fs__lstat(uv_fs_t* req) {
fs__stat_prepare_path(req->file.pathw);
fs__stat_impl(req, 1);
}
static void fs__fstat(uv_fs_t* req) {
int fd = req->file.fd;
HANDLE handle;
VERIFY_FD(fd, req);
handle = uv__get_osfhandle(fd);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
return;
}
if (fs__stat_handle(handle, &req->statbuf) != 0) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
req->ptr = &req->statbuf;
req->result = 0;
}
static void fs__rename(uv_fs_t* req) {
if (!MoveFileExW(req->file.pathw, req->fs.info.new_pathw, MOVEFILE_REPLACE_EXISTING)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
SET_REQ_RESULT(req, 0);
}
INLINE static void fs__sync_impl(uv_fs_t* req) {
int fd = req->file.fd;
int result;
VERIFY_FD(fd, req);
result = FlushFileBuffers(uv__get_osfhandle(fd)) ? 0 : -1;
if (result == -1) {
SET_REQ_WIN32_ERROR(req, GetLastError());
} else {
SET_REQ_RESULT(req, result);
}
}
static void fs__fsync(uv_fs_t* req) {
fs__sync_impl(req);
}
static void fs__fdatasync(uv_fs_t* req) {
fs__sync_impl(req);
}
static void fs__ftruncate(uv_fs_t* req) {
int fd = req->file.fd;
HANDLE handle;
NTSTATUS status;
IO_STATUS_BLOCK io_status;
FILE_END_OF_FILE_INFORMATION eof_info;
VERIFY_FD(fd, req);
handle = uv__get_osfhandle(fd);
eof_info.EndOfFile.QuadPart = req->fs.info.offset;
status = pNtSetInformationFile(handle,
&io_status,
&eof_info,
sizeof eof_info,
FileEndOfFileInformation);
if (NT_SUCCESS(status)) {
SET_REQ_RESULT(req, 0);
} else {
SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
}
}
static void fs__sendfile(uv_fs_t* req) {
int fd_in = req->file.fd, fd_out = req->fs.info.fd_out;
size_t length = req->fs.info.bufsml[0].len;
int64_t offset = req->fs.info.offset;
const size_t max_buf_size = 65536;
size_t buf_size = length < max_buf_size ? length : max_buf_size;
int n, result = 0;
int64_t result_offset = 0;
char* buf = (char*) uv__malloc(buf_size);
if (!buf) {
uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
}
if (offset != -1) {
result_offset = _lseeki64(fd_in, offset, SEEK_SET);
}
if (result_offset == -1) {
result = -1;
} else {
while (length > 0) {
n = _read(fd_in, buf, length < buf_size ? length : buf_size);
if (n == 0) {
break;
} else if (n == -1) {
result = -1;
break;
}
length -= n;
n = _write(fd_out, buf, n);
if (n == -1) {
result = -1;
break;
}
result += n;
}
}
uv__free(buf);
SET_REQ_RESULT(req, result);
}
static void fs__access(uv_fs_t* req) {
DWORD attr = GetFileAttributesW(req->file.pathw);
if (attr == INVALID_FILE_ATTRIBUTES) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
/*
* Access is possible if
* - write access wasn't requested,
* - or the file isn't read-only,
* - or it's a directory.
* (Directories cannot be read-only on Windows.)
*/
if (!(req->flags & W_OK) ||
!(attr & FILE_ATTRIBUTE_READONLY) ||
(attr & FILE_ATTRIBUTE_DIRECTORY)) {
SET_REQ_RESULT(req, 0);
} else {
SET_REQ_WIN32_ERROR(req, UV_EPERM);
}
}
static void fs__chmod(uv_fs_t* req) {
int result = _wchmod(req->file.pathw, req->fs.info.mode);
SET_REQ_RESULT(req, result);
}
static void fs__fchmod(uv_fs_t* req) {
int fd = req->file.fd;
HANDLE handle;
NTSTATUS nt_status;
IO_STATUS_BLOCK io_status;
FILE_BASIC_INFORMATION file_info;
VERIFY_FD(fd, req);
handle = uv__get_osfhandle(fd);
nt_status = pNtQueryInformationFile(handle,
&io_status,
&file_info,
sizeof file_info,
FileBasicInformation);
if (!NT_SUCCESS(nt_status)) {
SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status));
return;
}
if (req->fs.info.mode & _S_IWRITE) {
file_info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
} else {
file_info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
}
nt_status = pNtSetInformationFile(handle,
&io_status,
&file_info,
sizeof file_info,
FileBasicInformation);
if (!NT_SUCCESS(nt_status)) {
SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status));
return;
}
SET_REQ_SUCCESS(req);
}
INLINE static int fs__utime_handle(HANDLE handle, double atime, double mtime) {
FILETIME filetime_a, filetime_m;
TIME_T_TO_FILETIME((time_t) atime, &filetime_a);
TIME_T_TO_FILETIME((time_t) mtime, &filetime_m);
if (!SetFileTime(handle, NULL, &filetime_a, &filetime_m)) {
return -1;
}
return 0;
}
static void fs__utime(uv_fs_t* req) {
HANDLE handle;
handle = CreateFileW(req->file.pathw,
FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
if (fs__utime_handle(handle, req->fs.time.atime, req->fs.time.mtime) != 0) {
SET_REQ_WIN32_ERROR(req, GetLastError());
CloseHandle(handle);
return;
}
CloseHandle(handle);
req->result = 0;
}
static void fs__futime(uv_fs_t* req) {
int fd = req->file.fd;
HANDLE handle;
VERIFY_FD(fd, req);
handle = uv__get_osfhandle(fd);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
return;
}
if (fs__utime_handle(handle, req->fs.time.atime, req->fs.time.mtime) != 0) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
req->result = 0;
}
static void fs__link(uv_fs_t* req) {
DWORD r = CreateHardLinkW(req->fs.info.new_pathw, req->file.pathw, NULL);
if (r == 0) {
SET_REQ_WIN32_ERROR(req, GetLastError());
} else {
req->result = 0;
}
}
static void fs__create_junction(uv_fs_t* req, const WCHAR* path,
const WCHAR* new_path) {
HANDLE handle = INVALID_HANDLE_VALUE;
REPARSE_DATA_BUFFER *buffer = NULL;
int created = 0;
int target_len;
int is_absolute, is_long_path;
int needed_buf_size, used_buf_size, used_data_size, path_buf_len;
int start, len, i;
int add_slash;
DWORD bytes;
WCHAR* path_buf;
target_len = wcslen(path);
is_long_path = wcsncmp(path, LONG_PATH_PREFIX, LONG_PATH_PREFIX_LEN) == 0;
if (is_long_path) {
is_absolute = 1;
} else {
is_absolute = target_len >= 3 && IS_LETTER(path[0]) &&
path[1] == L':' && IS_SLASH(path[2]);
}
if (!is_absolute) {
/* Not supporting relative paths */
SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_NOT_SUPPORTED);
return;
}
/* Do a pessimistic calculation of the required buffer size */
needed_buf_size =
FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) +
JUNCTION_PREFIX_LEN * sizeof(WCHAR) +
2 * (target_len + 2) * sizeof(WCHAR);
/* Allocate the buffer */
buffer = (REPARSE_DATA_BUFFER*)uv__malloc(needed_buf_size);
if (!buffer) {
uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
}
/* Grab a pointer to the part of the buffer where filenames go */
path_buf = (WCHAR*)&(buffer->MountPointReparseBuffer.PathBuffer);
path_buf_len = 0;
/* Copy the substitute (internal) target path */
start = path_buf_len;
wcsncpy((WCHAR*)&path_buf[path_buf_len], JUNCTION_PREFIX,
JUNCTION_PREFIX_LEN);
path_buf_len += JUNCTION_PREFIX_LEN;
add_slash = 0;
for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
if (IS_SLASH(path[i])) {
add_slash = 1;
continue;
}
if (add_slash) {
path_buf[path_buf_len++] = L'\\';
add_slash = 0;
}
path_buf[path_buf_len++] = path[i];
}
path_buf[path_buf_len++] = L'\\';
len = path_buf_len - start;
/* Set the info about the substitute name */
buffer->MountPointReparseBuffer.SubstituteNameOffset = start * sizeof(WCHAR);
buffer->MountPointReparseBuffer.SubstituteNameLength = len * sizeof(WCHAR);
/* Insert null terminator */
path_buf[path_buf_len++] = L'\0';
/* Copy the print name of the target path */
start = path_buf_len;
add_slash = 0;
for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
if (IS_SLASH(path[i])) {
add_slash = 1;
continue;
}
if (add_slash) {
path_buf[path_buf_len++] = L'\\';
add_slash = 0;
}
path_buf[path_buf_len++] = path[i];
}
len = path_buf_len - start;
if (len == 2) {
path_buf[path_buf_len++] = L'\\';
len++;
}
/* Set the info about the print name */
buffer->MountPointReparseBuffer.PrintNameOffset = start * sizeof(WCHAR);
buffer->MountPointReparseBuffer.PrintNameLength = len * sizeof(WCHAR);
/* Insert another null terminator */
path_buf[path_buf_len++] = L'\0';
/* Calculate how much buffer space was actually used */
used_buf_size = FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) +
path_buf_len * sizeof(WCHAR);
used_data_size = used_buf_size -
FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer);
/* Put general info in the data buffer */
buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buffer->ReparseDataLength = used_data_size;
buffer->Reserved = 0;
/* Create a new directory */
if (!CreateDirectoryW(new_path, NULL)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto error;
}
created = 1;
/* Open the directory */
handle = CreateFileW(new_path,
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_OPEN_REPARSE_POINT,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto error;
}
/* Create the actual reparse point */
if (!DeviceIoControl(handle,
FSCTL_SET_REPARSE_POINT,
buffer,
used_buf_size,
NULL,
0,
&bytes,
NULL)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto error;
}
/* Clean up */
CloseHandle(handle);
uv__free(buffer);
SET_REQ_RESULT(req, 0);
return;
error:
uv__free(buffer);
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
if (created) {
RemoveDirectoryW(new_path);
}
}
static void fs__symlink(uv_fs_t* req) {
WCHAR* pathw = req->file.pathw;
WCHAR* new_pathw = req->fs.info.new_pathw;
int flags = req->fs.info.file_flags;
int result;
if (flags & UV_FS_SYMLINK_JUNCTION) {
fs__create_junction(req, pathw, new_pathw);
} else if (pCreateSymbolicLinkW) {
result = pCreateSymbolicLinkW(new_pathw,
pathw,
flags & UV_FS_SYMLINK_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) ? 0 : -1;
if (result == -1) {
SET_REQ_WIN32_ERROR(req, GetLastError());
} else {
SET_REQ_RESULT(req, result);
}
} else {
SET_REQ_UV_ERROR(req, UV_ENOSYS, ERROR_NOT_SUPPORTED);
}
}
static void fs__readlink(uv_fs_t* req) {
HANDLE handle;
handle = CreateFileW(req->file.pathw,
0,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
if (fs__readlink_handle(handle, (char**) &req->ptr, NULL) != 0) {
SET_REQ_WIN32_ERROR(req, GetLastError());
CloseHandle(handle);
return;
}
req->flags |= UV_FS_FREE_PTR;
SET_REQ_RESULT(req, 0);
CloseHandle(handle);
}
static size_t fs__realpath_handle(HANDLE handle, char** realpath_ptr) {
int r;
DWORD w_realpath_len;
WCHAR* w_realpath_ptr = NULL;
WCHAR* w_realpath_buf;
w_realpath_len = pGetFinalPathNameByHandleW(handle, NULL, 0, VOLUME_NAME_DOS);
if (w_realpath_len == 0) {
return -1;
}
w_realpath_buf = uv__malloc((w_realpath_len + 1) * sizeof(WCHAR));
if (w_realpath_buf == NULL) {
SetLastError(ERROR_OUTOFMEMORY);
return -1;
}
w_realpath_ptr = w_realpath_buf;
if (pGetFinalPathNameByHandleW(handle,
w_realpath_ptr,
w_realpath_len,
VOLUME_NAME_DOS) == 0) {
uv__free(w_realpath_buf);
SetLastError(ERROR_INVALID_HANDLE);
return -1;
}
/* convert UNC path to long path */
if (wcsncmp(w_realpath_ptr,
UNC_PATH_PREFIX,
UNC_PATH_PREFIX_LEN) == 0) {
w_realpath_ptr += 6;
*w_realpath_ptr = L'\\';
w_realpath_len -= 6;
} else if (wcsncmp(w_realpath_ptr,
LONG_PATH_PREFIX,
LONG_PATH_PREFIX_LEN) == 0) {
w_realpath_ptr += 4;
w_realpath_len -= 4;
} else {
uv__free(w_realpath_buf);
SetLastError(ERROR_INVALID_HANDLE);
return -1;
}
r = fs__wide_to_utf8(w_realpath_ptr, w_realpath_len, realpath_ptr, NULL);
uv__free(w_realpath_buf);
return r;
}
static void fs__realpath(uv_fs_t* req) {
HANDLE handle;
if (!pGetFinalPathNameByHandleW) {
SET_REQ_UV_ERROR(req, UV_ENOSYS, ERROR_NOT_SUPPORTED);
return;
}
handle = CreateFileW(req->file.pathw,
0,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
if (fs__realpath_handle(handle, (char**) &req->ptr) == -1) {
CloseHandle(handle);
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
CloseHandle(handle);
req->flags |= UV_FS_FREE_PTR;
SET_REQ_RESULT(req, 0);
}
static void fs__chown(uv_fs_t* req) {
req->result = 0;
}
static void fs__fchown(uv_fs_t* req) {
req->result = 0;
}
static void uv__fs_work(struct uv__work* w) {
uv_fs_t* req;
req = container_of(w, uv_fs_t, work_req);
assert(req->type == UV_FS);
#define XX(uc, lc) case UV_FS_##uc: fs__##lc(req); break;
switch (req->fs_type) {
XX(OPEN, open)
XX(CLOSE, close)
XX(READ, read)
XX(WRITE, write)
XX(SENDFILE, sendfile)
XX(STAT, stat)
XX(LSTAT, lstat)
XX(FSTAT, fstat)
XX(FTRUNCATE, ftruncate)
XX(UTIME, utime)
XX(FUTIME, futime)
XX(ACCESS, access)
XX(CHMOD, chmod)
XX(FCHMOD, fchmod)
XX(FSYNC, fsync)
XX(FDATASYNC, fdatasync)
XX(UNLINK, unlink)
XX(RMDIR, rmdir)
XX(MKDIR, mkdir)
XX(MKDTEMP, mkdtemp)
XX(RENAME, rename)
XX(SCANDIR, scandir)
XX(LINK, link)
XX(SYMLINK, symlink)
XX(READLINK, readlink)
XX(REALPATH, realpath)
XX(CHOWN, chown)
XX(FCHOWN, fchown);
default:
assert(!"bad uv_fs_type");
}
}
static void uv__fs_done(struct uv__work* w, int status) {
uv_fs_t* req;
req = container_of(w, uv_fs_t, work_req);
uv__req_unregister(req->loop, req);
if (status == UV_ECANCELED) {
assert(req->result == 0);
req->result = UV_ECANCELED;
}
req->cb(req);
}
void uv_fs_req_cleanup(uv_fs_t* req) {
if (req->flags & UV_FS_CLEANEDUP)
return;
if (req->flags & UV_FS_FREE_PATHS)
uv__free(req->file.pathw);
if (req->flags & UV_FS_FREE_PTR)
uv__free(req->ptr);
req->path = NULL;
req->file.pathw = NULL;
req->fs.info.new_pathw = NULL;
req->ptr = NULL;
req->flags |= UV_FS_CLEANEDUP;
}
int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
int mode, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_OPEN, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
req->fs.info.file_flags = flags;
req->fs.info.mode = mode;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__open(req);
return req->result;
}
}
int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_CLOSE, cb);
req->file.fd = fd;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__close(req);
return req->result;
}
}
int uv_fs_read(uv_loop_t* loop,
uv_fs_t* req,
uv_file fd,
const uv_buf_t bufs[],
unsigned int nbufs,
int64_t offset,
uv_fs_cb cb) {
if (bufs == NULL || nbufs == 0)
return UV_EINVAL;
uv_fs_req_init(loop, req, UV_FS_READ, cb);
req->file.fd = fd;
req->fs.info.nbufs = nbufs;
req->fs.info.bufs = req->fs.info.bufsml;
if (nbufs > ARRAY_SIZE(req->fs.info.bufsml))
req->fs.info.bufs = uv__malloc(nbufs * sizeof(*bufs));
if (req->fs.info.bufs == NULL)
return UV_ENOMEM;
memcpy(req->fs.info.bufs, bufs, nbufs * sizeof(*bufs));
req->fs.info.offset = offset;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__read(req);
return req->result;
}
}
int uv_fs_write(uv_loop_t* loop,
uv_fs_t* req,
uv_file fd,
const uv_buf_t bufs[],
unsigned int nbufs,
int64_t offset,
uv_fs_cb cb) {
if (bufs == NULL || nbufs == 0)
return UV_EINVAL;
uv_fs_req_init(loop, req, UV_FS_WRITE, cb);
req->file.fd = fd;
req->fs.info.nbufs = nbufs;
req->fs.info.bufs = req->fs.info.bufsml;
if (nbufs > ARRAY_SIZE(req->fs.info.bufsml))
req->fs.info.bufs = uv__malloc(nbufs * sizeof(*bufs));
if (req->fs.info.bufs == NULL)
return UV_ENOMEM;
memcpy(req->fs.info.bufs, bufs, nbufs * sizeof(*bufs));
req->fs.info.offset = offset;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__write(req);
return req->result;
}
}
int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_UNLINK, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__unlink(req);
return req->result;
}
}
int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_MKDIR, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
req->fs.info.mode = mode;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__mkdir(req);
return req->result;
}
}
int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl,
uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_MKDTEMP, cb);
err = fs__capture_path(req, tpl, NULL, TRUE);
if (err)
return uv_translate_sys_error(err);
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__mkdtemp(req);
return req->result;
}
}
int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_RMDIR, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__rmdir(req);
return req->result;
}
}
int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_SCANDIR, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
req->fs.info.file_flags = flags;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__scandir(req);
return req->result;
}
}
int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path,
const char* new_path, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_LINK, cb);
err = fs__capture_path(req, path, new_path, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__link(req);
return req->result;
}
}
int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
const char* new_path, int flags, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_SYMLINK, cb);
err = fs__capture_path(req, path, new_path, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
req->fs.info.file_flags = flags;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__symlink(req);
return req->result;
}
}
int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_READLINK, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__readlink(req);
return req->result;
}
}
int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path,
uv_fs_cb cb) {
int err;
if (!req || !path) {
return UV_EINVAL;
}
uv_fs_req_init(loop, req, UV_FS_REALPATH, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__realpath(req);
return req->result;
}
}
int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid,
uv_gid_t gid, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_CHOWN, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__chown(req);
return req->result;
}
}
int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_uid_t uid,
uv_gid_t gid, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_FCHOWN, cb);
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__fchown(req);
return req->result;
}
}
int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_STAT, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__stat(req);
return req->result;
}
}
int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_LSTAT, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__lstat(req);
return req->result;
}
}
int uv_fs_fstat(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_FSTAT, cb);
req->file.fd = fd;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__fstat(req);
return req->result;
}
}
int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path,
const char* new_path, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_RENAME, cb);
err = fs__capture_path(req, path, new_path, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__rename(req);
return req->result;
}
}
int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_FSYNC, cb);
req->file.fd = fd;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__fsync(req);
return req->result;
}
}
int uv_fs_fdatasync(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_FDATASYNC, cb);
req->file.fd = fd;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__fdatasync(req);
return req->result;
}
}
int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file fd,
int64_t offset, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_FTRUNCATE, cb);
req->file.fd = fd;
req->fs.info.offset = offset;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__ftruncate(req);
return req->result;
}
}
int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file fd_out,
uv_file fd_in, int64_t in_offset, size_t length, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_SENDFILE, cb);
req->file.fd = fd_in;
req->fs.info.fd_out = fd_out;
req->fs.info.offset = in_offset;
req->fs.info.bufsml[0].len = length;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__sendfile(req);
return req->result;
}
}
int uv_fs_access(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int flags,
uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_ACCESS, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err)
return uv_translate_sys_error(err);
req->flags = flags;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
}
fs__access(req);
return req->result;
}
int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_CHMOD, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
req->fs.info.mode = mode;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__chmod(req);
return req->result;
}
}
int uv_fs_fchmod(uv_loop_t* loop, uv_fs_t* req, uv_file fd, int mode,
uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_FCHMOD, cb);
req->file.fd = fd;
req->fs.info.mode = mode;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__fchmod(req);
return req->result;
}
}
int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime,
double mtime, uv_fs_cb cb) {
int err;
uv_fs_req_init(loop, req, UV_FS_UTIME, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
req->fs.time.atime = atime;
req->fs.time.mtime = mtime;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__utime(req);
return req->result;
}
}
int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file fd, double atime,
double mtime, uv_fs_cb cb) {
uv_fs_req_init(loop, req, UV_FS_FUTIME, cb);
req->file.fd = fd;
req->fs.time.atime = atime;
req->fs.time.mtime = mtime;
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__futime(req);
return req->result;
}
}