| /* 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 <malloc.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" |
| |
| |
| #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 { \ |
| if (!QueueUserWorkItem(&uv_fs_thread_proc, \ |
| req, \ |
| WT_EXECUTEDEFAULT)) { \ |
| uv__set_sys_error((loop), GetLastError()); \ |
| return -1; \ |
| } \ |
| uv__req_register(loop, req); \ |
| } while (0) |
| |
| #define SET_UV_LAST_ERROR_FROM_REQ(req) \ |
| uv__set_error(req->loop, req->errorno, req->sys_errno_) |
| |
| #define SET_REQ_RESULT(req, result_value) \ |
| do { \ |
| req->result = (result_value); \ |
| if (req->result == -1) { \ |
| req->sys_errno_ = _doserrno; \ |
| req->errorno = uv_translate_sys_error(req->sys_errno_); \ |
| } \ |
| } while (0) |
| |
| #define SET_REQ_WIN32_ERROR(req, sys_errno) \ |
| do { \ |
| req->result = -1; \ |
| req->sys_errno_ = (sys_errno); \ |
| req->errorno = uv_translate_sys_error(req->sys_errno_); \ |
| } while (0) |
| |
| #define SET_REQ_UV_ERROR(req, uv_errno, sys_errno) \ |
| do { \ |
| req->result = -1; \ |
| req->sys_errno_ = (sys_errno); \ |
| req->errorno = (uv_errno); \ |
| } while (0) |
| |
| #define VERIFY_FD(fd, req) \ |
| if (fd == -1) { \ |
| req->result = -1; \ |
| req->errorno = UV_EBADF; \ |
| req->sys_errno_ = ERROR_INVALID_HANDLE; \ |
| return; \ |
| } |
| |
| #define FILETIME_TO_TIME_T(filetime) \ |
| ((*((uint64_t*) &(filetime)) - 116444736000000000ULL) / 10000000ULL) |
| |
| #define TIME_T_TO_FILETIME(time, filetime_ptr) \ |
| do { \ |
| *(uint64_t*) (filetime_ptr) = ((int64_t) (time) * 10000000LL) + \ |
| 116444736000000000ULL; \ |
| } 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; |
| |
| |
| void uv_fs_init() { |
| _fmode = _O_BINARY; |
| } |
| |
| |
| INLINE static int fs__capture_path(uv_loop_t* loop, 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, new_pathw_len; |
| |
| /* 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) { |
| uv__set_sys_error(loop, GetLastError()); |
| return -1; |
| } |
| |
| 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) { |
| uv__set_sys_error(loop, GetLastError()); |
| return -1; |
| } |
| |
| buf_sz += new_pathw_len * sizeof(WCHAR); |
| } |
| |
| |
| if (buf_sz == 0) { |
| req->pathw = NULL; |
| req->new_pathw = NULL; |
| req->path = NULL; |
| return 0; |
| } |
| |
| buf = (char*) malloc(buf_sz); |
| if (buf == NULL) { |
| uv__set_artificial_error(loop, UV_ENOMEM); |
| return -1; |
| } |
| |
| pos = buf; |
| |
| if (path != NULL) { |
| DWORD r = MultiByteToWideChar(CP_UTF8, |
| 0, |
| path, |
| -1, |
| (WCHAR*) pos, |
| pathw_len); |
| assert(r == pathw_len); |
| req->pathw = (WCHAR*) pos; |
| pos += r * sizeof(WCHAR); |
| } else { |
| req->pathw = NULL; |
| } |
| |
| if (new_path != NULL) { |
| DWORD r = MultiByteToWideChar(CP_UTF8, |
| 0, |
| new_path, |
| -1, |
| (WCHAR*) pos, |
| new_pathw_len); |
| assert(r == new_pathw_len); |
| req->new_pathw = (WCHAR*) pos; |
| pos += r * sizeof(WCHAR); |
| } else { |
| req->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->errorno = UV_OK; |
| req->path = NULL; |
| |
| if (cb != NULL) { |
| req->cb = cb; |
| memset(&req->overlapped, 0, sizeof(req->overlapped)); |
| } |
| } |
| |
| |
| static int is_path_dir(const WCHAR* path) { |
| DWORD attr = GetFileAttributesW(path); |
| |
| if (attr != INVALID_FILE_ATTRIBUTES) { |
| return attr & FILE_ATTRIBUTE_DIRECTORY ? 1 : 0; |
| } else { |
| return 0; |
| } |
| } |
| |
| |
| INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr, |
| int64_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; |
| char* target; |
| int 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; |
| } |
| |
| /* If needed, compute the length of the target. */ |
| if (target_ptr != NULL || target_len_ptr != NULL) { |
| /* Compute the length of the target. */ |
| target_len = WideCharToMultiByte(CP_UTF8, |
| 0, |
| w_target, |
| w_target_len, |
| NULL, |
| 0, |
| NULL, |
| NULL); |
| if (target_len == 0) { |
| return -1; |
| } |
| } |
| |
| /* If requested, allocate memory and convert to UTF8. */ |
| if (target_ptr != NULL) { |
| int r; |
| target = (char*) malloc(target_len + 1); |
| if (target == NULL) { |
| SetLastError(ERROR_OUTOFMEMORY); |
| return -1; |
| } |
| |
| r = WideCharToMultiByte(CP_UTF8, |
| 0, |
| w_target, |
| w_target_len, |
| target, |
| target_len, |
| NULL, |
| NULL); |
| assert(r == target_len); |
| target[target_len] = '\0'; |
| |
| *target_ptr = target; |
| } |
| |
| if (target_len_ptr != NULL) { |
| *target_len_ptr = target_len; |
| } |
| |
| return 0; |
| } |
| |
| |
| 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->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->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->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); |
| 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->fd; |
| int result; |
| |
| VERIFY_FD(fd, req); |
| |
| result = _close(fd); |
| SET_REQ_RESULT(req, result); |
| } |
| |
| |
| void fs__read(uv_fs_t* req) { |
| int fd = req->fd; |
| size_t length = req->length; |
| int64_t offset = req->offset; |
| HANDLE handle; |
| OVERLAPPED overlapped, *overlapped_ptr; |
| LARGE_INTEGER offset_; |
| DWORD bytes; |
| DWORD error; |
| |
| VERIFY_FD(fd, req); |
| |
| handle = (HANDLE) _get_osfhandle(fd); |
| if (handle == INVALID_HANDLE_VALUE) { |
| SET_REQ_RESULT(req, -1); |
| return; |
| } |
| |
| if (length > INT_MAX) { |
| SET_REQ_WIN32_ERROR(req, ERROR_INSUFFICIENT_BUFFER); |
| return; |
| } |
| |
| if (offset != -1) { |
| memset(&overlapped, 0, sizeof overlapped); |
| |
| offset_.QuadPart = offset; |
| overlapped.Offset = offset_.LowPart; |
| overlapped.OffsetHigh = offset_.HighPart; |
| |
| overlapped_ptr = &overlapped; |
| } else { |
| overlapped_ptr = NULL; |
| } |
| |
| if (ReadFile(handle, req->buf, req->length, &bytes, overlapped_ptr)) { |
| 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->fd; |
| size_t length = req->length; |
| int64_t offset = req->offset; |
| HANDLE handle; |
| OVERLAPPED overlapped, *overlapped_ptr; |
| LARGE_INTEGER offset_; |
| DWORD bytes; |
| |
| VERIFY_FD(fd, req); |
| |
| handle = (HANDLE) _get_osfhandle(fd); |
| if (handle == INVALID_HANDLE_VALUE) { |
| SET_REQ_RESULT(req, -1); |
| return; |
| } |
| |
| if (length > INT_MAX) { |
| SET_REQ_WIN32_ERROR(req, ERROR_INSUFFICIENT_BUFFER); |
| return; |
| } |
| |
| if (offset != -1) { |
| memset(&overlapped, 0, sizeof overlapped); |
| |
| offset_.QuadPart = offset; |
| overlapped.Offset = offset_.LowPart; |
| overlapped.OffsetHigh = offset_.HighPart; |
| |
| overlapped_ptr = &overlapped; |
| } else { |
| overlapped_ptr = NULL; |
| } |
| |
| if (WriteFile(handle, req->buf, length, &bytes, overlapped_ptr)) { |
| SET_REQ_RESULT(req, bytes); |
| } else { |
| SET_REQ_WIN32_ERROR(req, GetLastError()); |
| } |
| } |
| |
| |
| void fs__rmdir(uv_fs_t* req) { |
| int result = _wrmdir(req->pathw); |
| SET_REQ_RESULT(req, result); |
| } |
| |
| |
| void fs__unlink(uv_fs_t* req) { |
| const WCHAR* pathw = req->pathw; |
| HANDLE handle; |
| BY_HANDLE_FILE_INFORMATION info; |
| FILE_DISPOSITION_INFORMATION disposition; |
| IO_STATUS_BLOCK iosb; |
| NTSTATUS status; |
| |
| handle = CreateFileW(pathw, |
| FILE_READ_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; |
| } |
| } |
| |
| /* 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->pathw); |
| SET_REQ_RESULT(req, result); |
| } |
| |
| |
| void fs__readdir(uv_fs_t* req) { |
| WCHAR* pathw = req->pathw; |
| size_t len = wcslen(pathw); |
| int result, size; |
| WCHAR* buf = NULL, *ptr, *name; |
| HANDLE dir; |
| WIN32_FIND_DATAW ent = { 0 }; |
| size_t buf_char_len = 4096; |
| WCHAR* path2; |
| const WCHAR* fmt; |
| |
| if (len == 0) { |
| fmt = L"./*"; |
| } else if (pathw[len - 1] == L'/' || pathw[len - 1] == L'\\') { |
| fmt = L"%s*"; |
| } else { |
| fmt = L"%s\\*"; |
| } |
| |
| /* Figure out whether path is a file or a directory. */ |
| if (!(GetFileAttributesW(pathw) & FILE_ATTRIBUTE_DIRECTORY)) { |
| req->result = -1; |
| req->errorno = UV_ENOTDIR; |
| req->sys_errno_ = ERROR_SUCCESS; |
| return; |
| } |
| |
| path2 = (WCHAR*)malloc(sizeof(WCHAR) * (len + 4)); |
| if (!path2) { |
| uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); |
| } |
| |
| #ifdef _MSC_VER |
| swprintf(path2, len + 3, fmt, pathw); |
| #else |
| swprintf(path2, fmt, pathw); |
| #endif |
| dir = FindFirstFileW(path2, &ent); |
| free(path2); |
| |
| if(dir == INVALID_HANDLE_VALUE) { |
| SET_REQ_WIN32_ERROR(req, GetLastError()); |
| return; |
| } |
| |
| result = 0; |
| |
| do { |
| name = ent.cFileName; |
| |
| if (name[0] != L'.' || (name[1] && (name[1] != L'.' || name[2]))) { |
| len = wcslen(name); |
| |
| if (!buf) { |
| buf = (WCHAR*)malloc(buf_char_len * sizeof(WCHAR)); |
| if (!buf) { |
| uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); |
| } |
| |
| ptr = buf; |
| } |
| |
| while ((ptr - buf) + len + 1 > buf_char_len) { |
| buf_char_len *= 2; |
| path2 = buf; |
| buf = (WCHAR*)realloc(buf, buf_char_len * sizeof(WCHAR)); |
| if (!buf) { |
| uv_fatal_error(ERROR_OUTOFMEMORY, "realloc"); |
| } |
| |
| ptr = buf + (ptr - path2); |
| } |
| |
| wcscpy(ptr, name); |
| ptr += len + 1; |
| result++; |
| } |
| } while(FindNextFileW(dir, &ent)); |
| |
| FindClose(dir); |
| |
| if (buf) { |
| /* Convert result to UTF8. */ |
| size = uv_utf16_to_utf8(buf, buf_char_len, NULL, 0); |
| if (!size) { |
| SET_REQ_WIN32_ERROR(req, GetLastError()); |
| return; |
| } |
| |
| req->ptr = (char*)malloc(size + 1); |
| if (!req->ptr) { |
| uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); |
| } |
| |
| size = uv_utf16_to_utf8(buf, buf_char_len, (char*)req->ptr, size); |
| if (!size) { |
| free(buf); |
| free(req->ptr); |
| req->ptr = NULL; |
| SET_REQ_WIN32_ERROR(req, GetLastError()); |
| return; |
| } |
| free(buf); |
| |
| ((char*)req->ptr)[size] = '\0'; |
| req->flags |= UV_FS_FREE_PTR; |
| } else { |
| req->ptr = NULL; |
| } |
| |
| SET_REQ_RESULT(req, result); |
| } |
| |
| |
| INLINE static int fs__stat_handle(HANDLE handle, uv_statbuf_t* statbuf) { |
| BY_HANDLE_FILE_INFORMATION info; |
| |
| if (!GetFileInformationByHandle(handle, &info)) { |
| return -1; |
| } |
| |
| /* TODO: set st_dev, st_rdev and st_ino to something meaningful. */ |
| statbuf->st_ino = 0; |
| statbuf->st_dev = 0; |
| statbuf->st_rdev = 0; |
| |
| statbuf->st_gid = 0; |
| statbuf->st_uid = 0; |
| |
| statbuf->st_mode = 0; |
| |
| if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { |
| if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0) { |
| return -1; |
| } |
| statbuf->st_mode |= S_IFLNK; |
| } else if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
| statbuf->st_mode |= _S_IFDIR; |
| statbuf->st_size = 0; |
| } else { |
| statbuf->st_mode |= _S_IFREG; |
| statbuf->st_size = ((int64_t) info.nFileSizeHigh << 32) + |
| (int64_t) info.nFileSizeLow; |
| } |
| |
| if (info.dwFileAttributes & 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)); |
| } |
| |
| statbuf->st_mtime = FILETIME_TO_TIME_T(info.ftLastWriteTime); |
| statbuf->st_atime = FILETIME_TO_TIME_T(info.ftLastAccessTime); |
| statbuf->st_ctime = FILETIME_TO_TIME_T(info.ftCreationTime); |
| |
| statbuf->st_nlink = (info.nNumberOfLinks <= SHRT_MAX) ? |
| (short) info.nNumberOfLinks : SHRT_MAX; |
| |
| 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->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->pathw); |
| fs__stat_impl(req, 0); |
| } |
| |
| |
| static void fs__lstat(uv_fs_t* req) { |
| fs__stat_prepare_path(req->pathw); |
| fs__stat_impl(req, 1); |
| } |
| |
| |
| static void fs__fstat(uv_fs_t* req) { |
| int fd = req->fd; |
| HANDLE handle; |
| |
| VERIFY_FD(fd, req); |
| |
| handle = (HANDLE) _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->pathw, req->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->fd; |
| int result; |
| |
| VERIFY_FD(fd, req); |
| |
| result = FlushFileBuffers((HANDLE) _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->fd; |
| HANDLE handle; |
| NTSTATUS status; |
| IO_STATUS_BLOCK io_status; |
| FILE_END_OF_FILE_INFORMATION eof_info; |
| |
| VERIFY_FD(fd, req); |
| |
| handle = (HANDLE)_get_osfhandle(fd); |
| |
| eof_info.EndOfFile.QuadPart = req->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->fd, fd_out = req->fd_out; |
| size_t length = req->length; |
| int64_t offset = req->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*) malloc(buf_size); |
| if (!buf) { |
| uv_fatal_error(ERROR_OUTOFMEMORY, "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; |
| } |
| } |
| |
| free(buf); |
| |
| SET_REQ_RESULT(req, result); |
| } |
| |
| |
| static void fs__chmod(uv_fs_t* req) { |
| int result = _wchmod(req->pathw, req->mode); |
| SET_REQ_RESULT(req, result); |
| } |
| |
| |
| static void fs__fchmod(uv_fs_t* req) { |
| int fd = req->fd; |
| HANDLE handle; |
| NTSTATUS nt_status; |
| IO_STATUS_BLOCK io_status; |
| FILE_BASIC_INFORMATION file_info; |
| |
| VERIFY_FD(fd, req); |
| |
| handle = (HANDLE) _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->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->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->atime, req->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->fd; |
| HANDLE handle; |
| VERIFY_FD(fd, req); |
| |
| handle = (HANDLE) _get_osfhandle(fd); |
| |
| if (handle == INVALID_HANDLE_VALUE) { |
| SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE); |
| return; |
| } |
| |
| if (fs__utime_handle(handle, req->atime, req->mtime) != 0) { |
| SET_REQ_WIN32_ERROR(req, GetLastError()); |
| return; |
| } |
| |
| req->result = 0; |
| } |
| |
| |
| static void fs__link(uv_fs_t* req) { |
| DWORD r = CreateHardLinkW(req->new_pathw, req->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*)malloc(needed_buf_size); |
| if (!buffer) { |
| uv_fatal_error(ERROR_OUTOFMEMORY, "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_ALL, |
| 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); |
| free(buffer); |
| |
| SET_REQ_RESULT(req, 0); |
| return; |
| |
| error: |
| 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->pathw; |
| WCHAR* new_pathw = req->new_pathw; |
| int flags = req->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->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 void fs__chown(uv_fs_t* req) { |
| req->result = 0; |
| } |
| |
| |
| static void fs__fchown(uv_fs_t* req) { |
| req->result = 0; |
| } |
| |
| |
| static DWORD WINAPI uv_fs_thread_proc(void* parameter) { |
| uv_fs_t* req = (uv_fs_t*) parameter; |
| uv_loop_t* loop = req->loop; |
| |
| assert(req != NULL); |
| 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(CHMOD, chmod) |
| XX(FCHMOD, fchmod) |
| XX(FSYNC, fsync) |
| XX(FDATASYNC, fdatasync) |
| XX(UNLINK, unlink) |
| XX(RMDIR, rmdir) |
| XX(MKDIR, mkdir) |
| XX(RENAME, rename) |
| XX(READDIR, readdir) |
| XX(LINK, link) |
| XX(SYMLINK, symlink) |
| XX(READLINK, readlink) |
| XX(CHOWN, chown) |
| XX(FCHOWN, fchown); |
| default: |
| assert(!"bad uv_fs_type"); |
| } |
| |
| POST_COMPLETION_FOR_REQ(loop, req); |
| return 0; |
| } |
| |
| |
| int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, |
| int mode, uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_OPEN, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| req->file_flags = flags; |
| req->mode = mode; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__open(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__close(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file fd, void* buf, |
| size_t length, int64_t offset, uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_READ, cb); |
| |
| req->fd = fd; |
| req->buf = buf; |
| req->length = length; |
| req->offset = offset; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__read(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_write(uv_loop_t* loop, uv_fs_t* req, uv_file fd, void* buf, |
| size_t length, int64_t offset, uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_WRITE, cb); |
| |
| req->fd = fd; |
| req->buf = buf; |
| req->length = length; |
| req->offset = offset; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__write(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path, |
| uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_UNLINK, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__unlink(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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) { |
| uv_fs_req_init(loop, req, UV_FS_MKDIR, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| req->mode = mode; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__mkdir(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_RMDIR, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__rmdir(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, |
| uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_READDIR, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| req->file_flags; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__readdir(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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) { |
| uv_fs_req_init(loop, req, UV_FS_LINK, cb); |
| |
| if (fs__capture_path(loop, req, path, new_path, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__link(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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) { |
| uv_fs_req_init(loop, req, UV_FS_SYMLINK, cb); |
| |
| if (fs__capture_path(loop, req, path, new_path, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| req->file_flags = flags; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__symlink(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path, |
| uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_READLINK, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__readlink(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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) { |
| uv_fs_req_init(loop, req, UV_FS_CHOWN, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__chown(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_STAT, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__stat(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) { |
| uv_fs_req_init(loop, req, UV_FS_LSTAT, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__lstat(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__fstat(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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) { |
| uv_fs_req_init(loop, req, UV_FS_RENAME, cb); |
| |
| if (fs__capture_path(loop, req, path, new_path, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__rename(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__fsync(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__fdatasync(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd; |
| req->offset = offset; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__ftruncate(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd_in; |
| req->fd_out = fd_out; |
| req->offset = in_offset; |
| req->length = length; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__sendfile(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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) { |
| uv_fs_req_init(loop, req, UV_FS_CHMOD, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| req->mode = mode; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__chmod(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd; |
| req->mode = mode; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__fchmod(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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) { |
| uv_fs_req_init(loop, req, UV_FS_UTIME, cb); |
| |
| if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) { |
| return -1; |
| } |
| |
| req->atime = atime; |
| req->mtime = mtime; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__utime(req); |
| SET_UV_LAST_ERROR_FROM_REQ(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->fd = fd; |
| req->atime = atime; |
| req->mtime = mtime; |
| |
| if (cb) { |
| QUEUE_FS_TP_JOB(loop, req); |
| return 0; |
| } else { |
| fs__futime(req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| return req->result; |
| } |
| } |
| |
| |
| void uv_process_fs_req(uv_loop_t* loop, uv_fs_t* req) { |
| assert(req->cb); |
| uv__req_unregister(loop, req); |
| SET_UV_LAST_ERROR_FROM_REQ(req); |
| 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) |
| free(req->pathw); |
| |
| if (req->flags & UV_FS_FREE_PTR) |
| free(req->ptr); |
| |
| req->path = NULL; |
| req->pathw = NULL; |
| req->new_pathw = NULL; |
| req->ptr = NULL; |
| |
| req->flags |= UV_FS_CLEANEDUP; |
| } |
| |