/* 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 #include #include #include #include #include #include #include #include #include #include "uv.h" #include "internal.h" #include "req-inl.h" #include "handle-inl.h" #include #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'\\')) { /* \??\:\ */ 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\\\ - make sure the final path looks like */ /* \\\\ */ 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 \??\:\ as symlink. */ /* Junctions can also be used as mount points, like \??\Volume{}, */ /* 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. */ 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; } }