mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
`ReadFileUtf8()` appends reads shorter than 8 KiB to a string and only moves to a heap buffer at the first full 8 KiB read. That buffer always started at 64 KiB, but a pipe can deliver more than that in short reads first, so copying the string into it overflowed the heap. With `big_len` past `big_cap`, the next read size also underflowed. Size the heap buffer to at least what was already read. When that is more than 64 KiB, the buffer is full and the existing growth step enlarges it before the next read. Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66939 Fixes: https://github.com/nodejs/node/issues/66341 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
5940 lines
197 KiB
C++
5940 lines
197 KiB
C++
// Copyright Joyent, Inc. and other Node contributors.
|
||
//
|
||
// 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 "node_file.h" // NOLINT(build/include_inline)
|
||
#include "ada.h"
|
||
#include "aliased_buffer-inl.h"
|
||
#include "memory_tracker-inl.h"
|
||
#include "node_buffer.h"
|
||
#include "node_debug.h"
|
||
#include "node_errors.h"
|
||
#include "node_external_reference.h"
|
||
#include "node_file-inl.h"
|
||
#include "node_metadata.h"
|
||
#include "node_process-inl.h"
|
||
#include "node_stat_watcher.h"
|
||
#include "node_url.h"
|
||
#include "path.h"
|
||
#include "permission/permission.h"
|
||
#include "util-inl.h"
|
||
|
||
#include "tracing/trace_event.h"
|
||
|
||
#include "req_wrap-inl.h"
|
||
#include "stream_base-inl.h"
|
||
#include "string_bytes.h"
|
||
#include "threadpoolwork-inl.h"
|
||
#include "uv.h"
|
||
#include "v8-fast-api-calls.h"
|
||
|
||
#include <errno.h>
|
||
#include <cerrno>
|
||
#include <cstdio>
|
||
#include <filesystem>
|
||
|
||
#if defined(__MINGW32__) || defined(_MSC_VER)
|
||
#include <io.h>
|
||
#endif
|
||
|
||
#ifdef _WIN32
|
||
#include <windows.h>
|
||
#else
|
||
#include <unistd.h>
|
||
#endif
|
||
|
||
namespace node {
|
||
|
||
namespace fs {
|
||
|
||
using v8::Array;
|
||
using v8::ArrayBuffer;
|
||
using v8::ArrayBufferView;
|
||
using v8::BigInt;
|
||
using v8::Context;
|
||
using v8::EscapableHandleScope;
|
||
using v8::FunctionCallbackInfo;
|
||
using v8::FunctionTemplate;
|
||
using v8::HandleScope;
|
||
using v8::Int32;
|
||
using v8::Integer;
|
||
using v8::Isolate;
|
||
using v8::JustVoid;
|
||
using v8::Local;
|
||
using v8::LocalVector;
|
||
using v8::Maybe;
|
||
using v8::MaybeLocal;
|
||
using v8::Nothing;
|
||
using v8::Null;
|
||
using v8::Number;
|
||
using v8::Object;
|
||
using v8::ObjectTemplate;
|
||
using v8::Promise;
|
||
using v8::String;
|
||
using v8::TryCatch;
|
||
using v8::Uint32Array;
|
||
using v8::Uint8Array;
|
||
using v8::Undefined;
|
||
using v8::Value;
|
||
|
||
#ifndef S_ISDIR
|
||
#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR)
|
||
#endif
|
||
|
||
#ifdef __POSIX__
|
||
constexpr char kPathSeparator = '/';
|
||
#else
|
||
const char* const kPathSeparator = "\\/";
|
||
#endif
|
||
|
||
inline int64_t GetOffset(Local<Value> value) {
|
||
return IsSafeJsInt(value) ? value.As<Integer>()->Value() : -1;
|
||
}
|
||
|
||
static const char* get_fs_func_name_by_type(uv_fs_type req_type) {
|
||
switch (req_type) {
|
||
#define FS_TYPE_TO_NAME(type, name) \
|
||
case UV_FS_##type: \
|
||
return name;
|
||
FS_TYPE_TO_NAME(OPEN, "open")
|
||
FS_TYPE_TO_NAME(CLOSE, "close")
|
||
FS_TYPE_TO_NAME(READ, "read")
|
||
FS_TYPE_TO_NAME(WRITE, "write")
|
||
FS_TYPE_TO_NAME(SENDFILE, "sendfile")
|
||
FS_TYPE_TO_NAME(STAT, "stat")
|
||
FS_TYPE_TO_NAME(LSTAT, "lstat")
|
||
FS_TYPE_TO_NAME(FSTAT, "fstat")
|
||
FS_TYPE_TO_NAME(FTRUNCATE, "ftruncate")
|
||
FS_TYPE_TO_NAME(UTIME, "utime")
|
||
FS_TYPE_TO_NAME(FUTIME, "futime")
|
||
FS_TYPE_TO_NAME(ACCESS, "access")
|
||
FS_TYPE_TO_NAME(CHMOD, "chmod")
|
||
FS_TYPE_TO_NAME(FCHMOD, "fchmod")
|
||
FS_TYPE_TO_NAME(FSYNC, "fsync")
|
||
FS_TYPE_TO_NAME(FDATASYNC, "fdatasync")
|
||
FS_TYPE_TO_NAME(UNLINK, "unlink")
|
||
FS_TYPE_TO_NAME(RMDIR, "rmdir")
|
||
FS_TYPE_TO_NAME(MKDIR, "mkdir")
|
||
FS_TYPE_TO_NAME(MKDTEMP, "mkdtemp")
|
||
FS_TYPE_TO_NAME(RENAME, "rename")
|
||
FS_TYPE_TO_NAME(SCANDIR, "scandir")
|
||
FS_TYPE_TO_NAME(LINK, "link")
|
||
FS_TYPE_TO_NAME(SYMLINK, "symlink")
|
||
FS_TYPE_TO_NAME(READLINK, "readlink")
|
||
FS_TYPE_TO_NAME(CHOWN, "chown")
|
||
FS_TYPE_TO_NAME(FCHOWN, "fchown")
|
||
FS_TYPE_TO_NAME(REALPATH, "realpath")
|
||
FS_TYPE_TO_NAME(COPYFILE, "copyfile")
|
||
FS_TYPE_TO_NAME(LCHOWN, "lchown")
|
||
FS_TYPE_TO_NAME(STATFS, "statfs")
|
||
FS_TYPE_TO_NAME(MKSTEMP, "mkstemp")
|
||
FS_TYPE_TO_NAME(LUTIME, "lutime")
|
||
#undef FS_TYPE_TO_NAME
|
||
default:
|
||
return "unknown";
|
||
}
|
||
}
|
||
|
||
#define TRACE_NAME(name) "fs.sync." #name
|
||
#define GET_TRACE_ENABLED \
|
||
(*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( \
|
||
TRACING_CATEGORY_NODE2(fs, sync)) != 0)
|
||
#define FS_SYNC_TRACE_BEGIN(syscall, ...) \
|
||
if (GET_TRACE_ENABLED) \
|
||
TRACE_EVENT_BEGIN( \
|
||
TRACING_CATEGORY_NODE2(fs, sync), TRACE_NAME(syscall), ##__VA_ARGS__);
|
||
#ifdef V8_USE_PERFETTO
|
||
#define FS_SYNC_TRACE_END(syscall, ...) \
|
||
if (GET_TRACE_ENABLED) \
|
||
TRACE_EVENT_END(TRACING_CATEGORY_NODE2(fs, sync), ##__VA_ARGS__);
|
||
#else
|
||
#define FS_SYNC_TRACE_END(syscall, ...) \
|
||
if (GET_TRACE_ENABLED) \
|
||
TRACE_EVENT_END( \
|
||
TRACING_CATEGORY_NODE2(fs, sync), TRACE_NAME(syscall), ##__VA_ARGS__);
|
||
#endif
|
||
|
||
#define FS_ASYNC_TRACE_BEGIN0(fs_type, id) \
|
||
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(TRACING_CATEGORY_NODE2(fs, async), \
|
||
get_fs_func_name_by_type(fs_type), \
|
||
id);
|
||
|
||
#define FS_ASYNC_TRACE_END0(fs_type, id) \
|
||
TRACE_EVENT_NESTABLE_ASYNC_END0(TRACING_CATEGORY_NODE2(fs, async), \
|
||
get_fs_func_name_by_type(fs_type), \
|
||
id);
|
||
|
||
#define FS_ASYNC_TRACE_BEGIN1(fs_type, id, name, value) \
|
||
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(TRACING_CATEGORY_NODE2(fs, async), \
|
||
get_fs_func_name_by_type(fs_type), \
|
||
id, \
|
||
name, \
|
||
value);
|
||
|
||
#define FS_ASYNC_TRACE_END1(fs_type, id, name, value) \
|
||
TRACE_EVENT_NESTABLE_ASYNC_END1(TRACING_CATEGORY_NODE2(fs, async), \
|
||
get_fs_func_name_by_type(fs_type), \
|
||
id, \
|
||
name, \
|
||
value);
|
||
|
||
#define FS_ASYNC_TRACE_BEGIN2(fs_type, id, name1, value1, name2, value2) \
|
||
TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(TRACING_CATEGORY_NODE2(fs, async), \
|
||
get_fs_func_name_by_type(fs_type), \
|
||
id, \
|
||
name1, \
|
||
value1, \
|
||
name2, \
|
||
value2);
|
||
|
||
#define FS_ASYNC_TRACE_END2(fs_type, id, name1, value1, name2, value2) \
|
||
TRACE_EVENT_NESTABLE_ASYNC_END2(TRACING_CATEGORY_NODE2(fs, async), \
|
||
get_fs_func_name_by_type(fs_type), \
|
||
id, \
|
||
name1, \
|
||
value1, \
|
||
name2, \
|
||
value2);
|
||
|
||
// We sometimes need to convert a C++ lambda function to a raw C-style function.
|
||
// This is helpful, because ReqWrap::Dispatch() does not recognize lambda
|
||
// functions, and thus does not wrap them properly.
|
||
typedef void (*uv_fs_callback_t)(uv_fs_t*);
|
||
|
||
void FSContinuationData::MemoryInfo(MemoryTracker* tracker) const {
|
||
tracker->TrackField("paths", paths_);
|
||
tracker->TrackField("enoent_paths", enoent_paths_);
|
||
}
|
||
|
||
FileHandleReadWrap::~FileHandleReadWrap() = default;
|
||
|
||
FSReqBase::~FSReqBase() = default;
|
||
|
||
void FSReqBase::MemoryInfo(MemoryTracker* tracker) const {
|
||
tracker->TrackField("continuation_data", continuation_data_);
|
||
}
|
||
|
||
// The FileHandle object wraps a file descriptor and will close it on garbage
|
||
// collection if necessary. If that happens, a process warning will be
|
||
// emitted (or a fatal exception will occur if the fd cannot be closed.)
|
||
FileHandle::FileHandle(BindingData* binding_data,
|
||
Local<Object> obj,
|
||
int fd,
|
||
std::string original_name)
|
||
: AsyncWrap(binding_data->env(), obj, AsyncWrap::PROVIDER_FILEHANDLE),
|
||
StreamBase(env()),
|
||
original_name_(std::move(original_name)),
|
||
fd_(fd),
|
||
binding_data_(binding_data) {
|
||
MakeWeak();
|
||
StreamBase::AttachToObject(GetObject());
|
||
}
|
||
|
||
FileHandle* FileHandle::New(BindingData* binding_data,
|
||
int fd,
|
||
Local<Object> obj,
|
||
std::string original_name,
|
||
std::optional<int64_t> maybeOffset,
|
||
std::optional<int64_t> maybeLength) {
|
||
Environment* env = binding_data->env();
|
||
if (obj.IsEmpty() && !env->fd_constructor_template()
|
||
->NewInstance(env->context())
|
||
.ToLocal(&obj)) {
|
||
return nullptr;
|
||
}
|
||
auto handle = new FileHandle(binding_data, obj, fd, original_name);
|
||
if (maybeOffset.has_value()) handle->read_offset_ = maybeOffset.value();
|
||
if (maybeLength.has_value()) handle->read_length_ = maybeLength.value();
|
||
return handle;
|
||
}
|
||
|
||
void FileHandle::New(const FunctionCallbackInfo<Value>& args) {
|
||
CHECK(args.IsConstructCall());
|
||
CHECK(args[0]->IsInt32());
|
||
Realm* realm = Realm::GetCurrent(args);
|
||
BindingData* binding_data = realm->GetBindingData<BindingData>();
|
||
|
||
std::optional<int64_t> maybeOffset = std::nullopt;
|
||
std::optional<int64_t> maybeLength = std::nullopt;
|
||
if (args[1]->IsNumber()) {
|
||
int64_t val;
|
||
if (!args[1]->IntegerValue(realm->context()).To(&val)) {
|
||
return;
|
||
}
|
||
maybeOffset = val;
|
||
}
|
||
if (args[2]->IsNumber()) {
|
||
int64_t val;
|
||
if (!args[2]->IntegerValue(realm->context()).To(&val)) {
|
||
return;
|
||
}
|
||
maybeLength = val;
|
||
}
|
||
|
||
FileHandle::New(binding_data,
|
||
args[0].As<Int32>()->Value(),
|
||
args.This(),
|
||
{},
|
||
maybeOffset,
|
||
maybeLength);
|
||
}
|
||
|
||
FileHandle::~FileHandle() {
|
||
CHECK(!closing_); // We should not be deleting while explicitly closing!
|
||
Close(); // Close synchronously and emit warning
|
||
CHECK(closed_); // We have to be closed at the point
|
||
}
|
||
|
||
int FileHandle::DoWrite(WriteWrap* w,
|
||
uv_buf_t* bufs,
|
||
size_t count,
|
||
uv_stream_t* send_handle) {
|
||
return UV_ENOSYS; // Not implemented (yet).
|
||
}
|
||
|
||
void FileHandle::MemoryInfo(MemoryTracker* tracker) const {
|
||
tracker->TrackField("current_read", current_read_);
|
||
tracker->TrackField("original_name", original_name_);
|
||
}
|
||
|
||
BaseObject::TransferMode FileHandle::GetTransferMode() const {
|
||
return reading_ || closing_ || closed_
|
||
? TransferMode::kDisallowCloneAndTransfer
|
||
: TransferMode::kTransferable;
|
||
}
|
||
|
||
std::unique_ptr<worker::TransferData> FileHandle::TransferForMessaging() {
|
||
CHECK_NE(GetTransferMode(), TransferMode::kDisallowCloneAndTransfer);
|
||
auto ret = std::make_unique<TransferData>(fd_);
|
||
closed_ = true;
|
||
return ret;
|
||
}
|
||
|
||
FileHandle::TransferData::TransferData(int fd) : fd_(fd) {}
|
||
|
||
FileHandle::TransferData::~TransferData() {
|
||
if (fd_ >= 0) {
|
||
uv_fs_t close_req;
|
||
CHECK_NE(fd_, -1);
|
||
FS_SYNC_TRACE_BEGIN(close);
|
||
CHECK_EQ(0, uv_fs_close(nullptr, &close_req, fd_, nullptr));
|
||
FS_SYNC_TRACE_END(close);
|
||
uv_fs_req_cleanup(&close_req);
|
||
}
|
||
}
|
||
|
||
BaseObjectPtr<BaseObject> FileHandle::TransferData::Deserialize(
|
||
Environment* env,
|
||
v8::Local<v8::Context> context,
|
||
std::unique_ptr<worker::TransferData> self) {
|
||
BindingData* bd = Realm::GetBindingData<BindingData>(context);
|
||
if (bd == nullptr) return {};
|
||
|
||
int fd = fd_;
|
||
fd_ = -1;
|
||
return BaseObjectPtr<BaseObject>{FileHandle::New(bd, fd)};
|
||
}
|
||
|
||
// Throw an exception if the file handle has not yet been closed.
|
||
inline void FileHandle::Close() {
|
||
if (closed_ || closing_) return;
|
||
|
||
uv_fs_t req;
|
||
CHECK_NE(fd_, -1);
|
||
FS_SYNC_TRACE_BEGIN(close);
|
||
int ret = uv_fs_close(env()->event_loop(), &req, fd_, nullptr);
|
||
FS_SYNC_TRACE_END(close);
|
||
uv_fs_req_cleanup(&req);
|
||
|
||
struct err_detail {
|
||
int ret;
|
||
int fd;
|
||
std::string name;
|
||
};
|
||
|
||
err_detail detail{ret, fd_, original_name_};
|
||
|
||
AfterClose();
|
||
|
||
// Even though we closed the file descriptor, we still throw an error
|
||
// if the FileHandle object was not closed before garbage collection.
|
||
// Because this method is called during garbage collection, we will defer
|
||
// throwing the error until the next immediate queue tick so as not
|
||
// to interfere with the gc process.
|
||
//
|
||
// This exception will end up being fatal for the process because
|
||
// it is being thrown from within the SetImmediate handler and
|
||
// there is no JS stack to bubble it to. In other words, tearing
|
||
// down the process is the only reasonable thing we can do here.
|
||
env()->SetImmediate([detail](Environment* env) {
|
||
HandleScope handle_scope(env->isolate());
|
||
static constexpr std::string_view unknown_path = "<unknown path>";
|
||
std::string_view filename =
|
||
detail.name.empty() ? unknown_path : detail.name;
|
||
|
||
// If there was an error while trying to close the file descriptor,
|
||
// we will throw that instead.
|
||
if (detail.ret < 0) {
|
||
auto formatted = SPrintF(
|
||
"Closing file descriptor %d on garbage collection failed (%s)",
|
||
detail.fd,
|
||
filename);
|
||
HandleScope handle_scope(env->isolate());
|
||
env->ThrowUVException(detail.ret, "close", formatted.c_str());
|
||
return;
|
||
}
|
||
|
||
THROW_ERR_INVALID_STATE(
|
||
env,
|
||
"A FileHandle object was closed during garbage collection. "
|
||
"This used to be allowed with a deprecation warning but is now "
|
||
"considered an error. Please close FileHandle objects explicitly. "
|
||
"File descriptor: %d (%s)",
|
||
detail.fd,
|
||
filename);
|
||
});
|
||
}
|
||
|
||
void FileHandle::CloseReq::Resolve() {
|
||
Isolate* isolate = env()->isolate();
|
||
HandleScope scope(isolate);
|
||
Context::Scope context_scope(env()->context());
|
||
InternalCallbackScope callback_scope(this);
|
||
Local<Promise> promise = promise_.Get(isolate);
|
||
Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>();
|
||
resolver->Resolve(env()->context(), Undefined(isolate)).Check();
|
||
}
|
||
|
||
void FileHandle::CloseReq::Reject(Local<Value> reason) {
|
||
Isolate* isolate = env()->isolate();
|
||
HandleScope scope(isolate);
|
||
Context::Scope context_scope(env()->context());
|
||
InternalCallbackScope callback_scope(this);
|
||
Local<Promise> promise = promise_.Get(isolate);
|
||
Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>();
|
||
resolver->Reject(env()->context(), reason).Check();
|
||
}
|
||
|
||
FileHandle* FileHandle::CloseReq::file_handle() {
|
||
Isolate* isolate = env()->isolate();
|
||
HandleScope scope(isolate);
|
||
Local<Value> val = ref_.Get(isolate);
|
||
Local<Object> obj = val.As<Object>();
|
||
return Unwrap<FileHandle>(obj);
|
||
}
|
||
|
||
FileHandle::CloseReq::CloseReq(Environment* env,
|
||
Local<Object> obj,
|
||
Local<Promise> promise,
|
||
Local<Value> ref)
|
||
: ReqWrap(env, obj, AsyncWrap::PROVIDER_FILEHANDLECLOSEREQ) {
|
||
promise_.Reset(env->isolate(), promise);
|
||
ref_.Reset(env->isolate(), ref);
|
||
}
|
||
|
||
FileHandle::CloseReq::~CloseReq() {
|
||
uv_fs_req_cleanup(req());
|
||
promise_.Reset();
|
||
ref_.Reset();
|
||
}
|
||
|
||
void FileHandle::CloseReq::MemoryInfo(MemoryTracker* tracker) const {
|
||
tracker->TrackField("promise", promise_);
|
||
tracker->TrackField("ref", ref_);
|
||
}
|
||
|
||
// Closes this FileHandle asynchronously and returns a Promise that will be
|
||
// resolved when the callback is invoked, or rejects with a UVException if
|
||
// there was a problem closing the fd. This is the preferred mechanism for
|
||
// closing the FD object even though the object will attempt to close
|
||
// automatically on gc.
|
||
MaybeLocal<Promise> FileHandle::ClosePromise() {
|
||
Isolate* isolate = env()->isolate();
|
||
EscapableHandleScope scope(isolate);
|
||
Local<Context> context = env()->context();
|
||
|
||
Local<Value> close_resolver =
|
||
object()->GetInternalField(FileHandle::kClosingPromiseSlot).As<Value>();
|
||
if (close_resolver->IsPromise()) {
|
||
return close_resolver.As<Promise>();
|
||
}
|
||
|
||
CHECK(!closed_);
|
||
CHECK(!closing_);
|
||
CHECK(!reading_);
|
||
|
||
auto maybe_resolver = Promise::Resolver::New(context);
|
||
CHECK(!maybe_resolver.IsEmpty());
|
||
Local<Promise::Resolver> resolver;
|
||
if (!maybe_resolver.ToLocal(&resolver)) return {};
|
||
Local<Promise> promise = resolver.As<Promise>();
|
||
|
||
Local<Object> close_req_obj;
|
||
if (!env()
|
||
->fdclose_constructor_template()
|
||
->NewInstance(env()->context())
|
||
.ToLocal(&close_req_obj)) {
|
||
return MaybeLocal<Promise>();
|
||
}
|
||
closing_ = true;
|
||
object()->SetInternalField(FileHandle::kClosingPromiseSlot, promise);
|
||
|
||
CloseReq* req = new CloseReq(env(), close_req_obj, promise, object());
|
||
auto AfterClose = uv_fs_callback_t{[](uv_fs_t* req) {
|
||
CloseReq* req_wrap = CloseReq::from_req(req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
BaseObjectPtr<CloseReq> close(req_wrap);
|
||
CHECK(close);
|
||
close->file_handle()->AfterClose();
|
||
if (!close->env()->can_call_into_js()) return;
|
||
Isolate* isolate = close->env()->isolate();
|
||
if (req->result < 0) {
|
||
HandleScope handle_scope(isolate);
|
||
close->Reject(
|
||
UVException(isolate, static_cast<int>(req->result), "close"));
|
||
} else {
|
||
close->Resolve();
|
||
}
|
||
}};
|
||
CHECK_NE(fd_, -1);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, req)
|
||
int ret = req->Dispatch(uv_fs_close, fd_, AfterClose);
|
||
if (ret < 0) {
|
||
req->Reject(UVException(isolate, ret, "close"));
|
||
delete req;
|
||
}
|
||
|
||
return scope.Escape(promise);
|
||
}
|
||
|
||
void FileHandle::Close(const FunctionCallbackInfo<Value>& args) {
|
||
FileHandle* fd;
|
||
ASSIGN_OR_RETURN_UNWRAP(&fd, args.This());
|
||
Local<Promise> ret;
|
||
if (!fd->ClosePromise().ToLocal(&ret)) return;
|
||
args.GetReturnValue().Set(ret);
|
||
}
|
||
|
||
void FileHandle::CloseSync(const FunctionCallbackInfo<Value>& args) {
|
||
FileHandle* fd;
|
||
ASSIGN_OR_RETURN_UNWRAP(&fd, args.This());
|
||
|
||
// Already closed or closing - no-op.
|
||
if (fd->closed_ || fd->closing_) return;
|
||
|
||
uv_fs_t req;
|
||
CHECK_NE(fd->fd_, -1);
|
||
FS_SYNC_TRACE_BEGIN(close);
|
||
int ret = uv_fs_close(fd->env()->event_loop(), &req, fd->fd_, nullptr);
|
||
FS_SYNC_TRACE_END(close);
|
||
uv_fs_req_cleanup(&req);
|
||
|
||
fd->AfterClose();
|
||
|
||
if (ret < 0) {
|
||
Environment* env = fd->env();
|
||
env->ThrowUVException(ret, "close");
|
||
}
|
||
}
|
||
|
||
void FileHandle::ReleaseFD(const FunctionCallbackInfo<Value>& args) {
|
||
FileHandle* fd;
|
||
ASSIGN_OR_RETURN_UNWRAP(&fd, args.This());
|
||
fd->Release();
|
||
}
|
||
|
||
int FileHandle::Release() {
|
||
int fd = GetFD();
|
||
// Just pretend that Close was called and we're all done.
|
||
AfterClose();
|
||
return fd;
|
||
}
|
||
|
||
void FileHandle::AfterClose() {
|
||
closing_ = false;
|
||
closed_ = true;
|
||
fd_ = -1;
|
||
if (reading_ && !persistent().IsEmpty()) EmitRead(UV_EOF);
|
||
}
|
||
|
||
void FileHandleReadWrap::MemoryInfo(MemoryTracker* tracker) const {
|
||
tracker->TrackField("buffer", buffer_);
|
||
tracker->TrackField("file_handle", this->file_handle_);
|
||
}
|
||
|
||
FileHandleReadWrap::FileHandleReadWrap(FileHandle* handle, Local<Object> obj)
|
||
: ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQCALLBACK),
|
||
file_handle_(handle) {}
|
||
|
||
int FileHandle::ReadStart() {
|
||
if (!IsAlive() || IsClosing()) return UV_EOF;
|
||
|
||
reading_ = true;
|
||
|
||
if (current_read_) return 0;
|
||
|
||
BaseObjectPtr<FileHandleReadWrap> read_wrap;
|
||
|
||
if (read_length_ == 0) {
|
||
EmitRead(UV_EOF);
|
||
return 0;
|
||
}
|
||
|
||
{
|
||
// Create a new FileHandleReadWrap or re-use one.
|
||
// Either way, we need these two scopes for AsyncReset() or otherwise
|
||
// for creating the new instance.
|
||
HandleScope handle_scope(env()->isolate());
|
||
AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(this);
|
||
|
||
auto& freelist = binding_data_->file_handle_read_wrap_freelist;
|
||
if (freelist.size() > 0) {
|
||
read_wrap = std::move(freelist.back());
|
||
freelist.pop_back();
|
||
// Use a fresh async resource.
|
||
// Lifetime is ensured via AsyncWrap::resource_.
|
||
Local<Object> resource = Object::New(env()->isolate());
|
||
USE(resource->Set(
|
||
env()->context(), env()->handle_string(), read_wrap->object()));
|
||
read_wrap->AsyncReset(resource);
|
||
read_wrap->file_handle_ = this;
|
||
} else {
|
||
Local<Object> wrap_obj;
|
||
if (!env()
|
||
->filehandlereadwrap_template()
|
||
->NewInstance(env()->context())
|
||
.ToLocal(&wrap_obj)) {
|
||
return UV_EBUSY;
|
||
}
|
||
read_wrap = MakeDetachedBaseObject<FileHandleReadWrap>(this, wrap_obj);
|
||
}
|
||
}
|
||
int64_t recommended_read = 65536;
|
||
if (read_length_ >= 0 && read_length_ <= recommended_read)
|
||
recommended_read = read_length_;
|
||
|
||
read_wrap->buffer_ = EmitAlloc(recommended_read);
|
||
|
||
current_read_ = std::move(read_wrap);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, current_read_.get())
|
||
current_read_->Dispatch(
|
||
uv_fs_read,
|
||
fd_,
|
||
¤t_read_->buffer_,
|
||
1,
|
||
read_offset_,
|
||
uv_fs_callback_t{[](uv_fs_t* req) {
|
||
FileHandle* handle;
|
||
{
|
||
FileHandleReadWrap* req_wrap = FileHandleReadWrap::from_req(req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
handle = req_wrap->file_handle_;
|
||
CHECK_EQ(handle->current_read_.get(), req_wrap);
|
||
}
|
||
|
||
// ReadStart() checks whether current_read_ is set to determine whether
|
||
// a read is in progress. Moving it into a local variable makes sure
|
||
// that the ReadStart() call below doesn't think we're still actively
|
||
// reading.
|
||
BaseObjectPtr<FileHandleReadWrap> read_wrap =
|
||
std::move(handle->current_read_);
|
||
|
||
ssize_t result = req->result;
|
||
uv_buf_t buffer = read_wrap->buffer_;
|
||
|
||
uv_fs_req_cleanup(req);
|
||
|
||
// Push the read wrap back to the freelist, or let it be destroyed
|
||
// once we’re exiting the current scope.
|
||
constexpr size_t kWantedFreelistFill = 100;
|
||
auto& freelist = handle->binding_data_->file_handle_read_wrap_freelist;
|
||
if (freelist.size() < kWantedFreelistFill) {
|
||
read_wrap->Reset();
|
||
freelist.emplace_back(std::move(read_wrap));
|
||
}
|
||
|
||
if (result >= 0) {
|
||
// Read at most as many bytes as we originally planned to.
|
||
if (handle->read_length_ >= 0 && handle->read_length_ < result)
|
||
result = handle->read_length_;
|
||
|
||
// If we read data and we have an expected length, decrease it by
|
||
// how much we have read.
|
||
if (handle->read_length_ >= 0) handle->read_length_ -= result;
|
||
|
||
// If we have an offset, increase it by how much we have read.
|
||
if (handle->read_offset_ >= 0) handle->read_offset_ += result;
|
||
}
|
||
|
||
// Reading 0 bytes from a file always means EOF, or that we reached
|
||
// the end of the requested range.
|
||
if (result == 0) result = UV_EOF;
|
||
|
||
handle->EmitRead(result, buffer);
|
||
|
||
// Start over, if EmitRead() didn’t tell us to stop.
|
||
if (handle->reading_) handle->ReadStart();
|
||
}});
|
||
|
||
return 0;
|
||
}
|
||
|
||
int FileHandle::ReadStop() {
|
||
reading_ = false;
|
||
return 0;
|
||
}
|
||
|
||
typedef SimpleShutdownWrap<ReqWrap<uv_fs_t>> FileHandleCloseWrap;
|
||
|
||
ShutdownWrap* FileHandle::CreateShutdownWrap(Local<Object> object) {
|
||
return new FileHandleCloseWrap(this, object);
|
||
}
|
||
|
||
int FileHandle::DoShutdown(ShutdownWrap* req_wrap) {
|
||
if (closing_ || closed_) {
|
||
req_wrap->Done(0);
|
||
return 1;
|
||
}
|
||
FileHandleCloseWrap* wrap = static_cast<FileHandleCloseWrap*>(req_wrap);
|
||
closing_ = true;
|
||
CHECK_NE(fd_, -1);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, wrap)
|
||
wrap->Dispatch(
|
||
uv_fs_close, fd_, uv_fs_callback_t{[](uv_fs_t* req) {
|
||
FileHandleCloseWrap* wrap = static_cast<FileHandleCloseWrap*>(
|
||
FileHandleCloseWrap::from_req(req));
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, wrap, "result", static_cast<int>(req->result))
|
||
FileHandle* handle = static_cast<FileHandle*>(wrap->stream());
|
||
handle->AfterClose();
|
||
|
||
int result = static_cast<int>(req->result);
|
||
uv_fs_req_cleanup(req);
|
||
wrap->Done(result);
|
||
}});
|
||
|
||
return 0;
|
||
}
|
||
|
||
void FSReqCallback::Reject(Local<Value> reject) {
|
||
MakeCallback(env()->oncomplete_string(), 1, &reject);
|
||
}
|
||
|
||
void FSReqCallback::ResolveStat(const uv_stat_t* stat) {
|
||
Resolve(FillGlobalStatsArray(binding_data(), use_bigint(), stat));
|
||
}
|
||
|
||
void FSReqCallback::ResolveStatFs(const uv_statfs_t* stat) {
|
||
Resolve(FillGlobalStatFsArray(binding_data(), use_bigint(), stat));
|
||
}
|
||
|
||
void FSReqCallback::Resolve(Local<Value> value) {
|
||
Local<Value> argv[2]{Null(env()->isolate()), value};
|
||
MakeCallback(env()->oncomplete_string(),
|
||
value->IsUndefined() ? 1 : arraysize(argv),
|
||
argv);
|
||
}
|
||
|
||
void FSReqCallback::SetReturnValue(const FunctionCallbackInfo<Value>& args) {
|
||
args.GetReturnValue().SetUndefined();
|
||
}
|
||
|
||
void NewFSReqCallback(const FunctionCallbackInfo<Value>& args) {
|
||
CHECK(args.IsConstructCall());
|
||
BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
|
||
new FSReqCallback(binding_data, args.This(), args[0]->IsTrue());
|
||
}
|
||
|
||
void CancelFSReq(const FunctionCallbackInfo<Value>& args) {
|
||
FSReqBase* req_wrap;
|
||
ASSIGN_OR_RETURN_UNWRAP(&req_wrap, args.This());
|
||
req_wrap->Cancel();
|
||
}
|
||
|
||
FSReqAfterScope::FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req)
|
||
: wrap_(wrap),
|
||
req_(req),
|
||
handle_scope_(wrap->env()->isolate()),
|
||
context_scope_(wrap->env()->context()) {
|
||
CHECK_EQ(wrap_->req(), req);
|
||
}
|
||
|
||
FSReqAfterScope::~FSReqAfterScope() {
|
||
Clear();
|
||
}
|
||
|
||
void FSReqAfterScope::Clear() {
|
||
if (!wrap_) return;
|
||
|
||
uv_fs_req_cleanup(wrap_->req());
|
||
wrap_->Detach();
|
||
wrap_.reset();
|
||
}
|
||
|
||
// TODO(joyeecheung): create a normal context object, and
|
||
// construct the actual errors in the JS land using the context.
|
||
// The context should include fds for some fs APIs, currently they are
|
||
// missing in the error messages. The path, dest, syscall, fd, .etc
|
||
// can be put into the context before the binding is even invoked,
|
||
// the only information that has to come from the C++ layer is the
|
||
// error number (and possibly the syscall for abstraction),
|
||
// which is also why the errors should have been constructed
|
||
// in JS for more flexibility.
|
||
void FSReqAfterScope::Reject(uv_fs_t* req) {
|
||
BaseObjectPtr<FSReqBase> wrap{wrap_};
|
||
// libuv clears req->path when mkstemp() fails, so the template that was
|
||
// saved in the request is reported instead.
|
||
const bool is_mkstemp = req->fs_type == UV_FS_MKSTEMP;
|
||
Local<Value> exception = UVException(wrap_->env()->isolate(),
|
||
static_cast<int>(req->result),
|
||
wrap_->syscall(),
|
||
nullptr,
|
||
is_mkstemp ? wrap_->data() : req->path,
|
||
is_mkstemp ? nullptr : wrap_->data());
|
||
Clear();
|
||
wrap->Reject(exception);
|
||
}
|
||
|
||
bool FSReqAfterScope::Proceed() {
|
||
if (!wrap_->env()->can_call_into_js()) {
|
||
return false;
|
||
}
|
||
|
||
if (req_->result < 0) {
|
||
Reject(req_);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
void AfterNoArgs(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (after.Proceed()) req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
|
||
}
|
||
|
||
void AfterStat(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (after.Proceed()) {
|
||
req_wrap->ResolveStat(&req->statbuf);
|
||
}
|
||
}
|
||
|
||
void AfterStatNoThrowIfNoEntry(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (req->result == UV_ENOENT || req->result == UV_ENOTDIR) {
|
||
req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
|
||
return;
|
||
}
|
||
|
||
if (after.Proceed()) {
|
||
req_wrap->ResolveStat(&req->statbuf);
|
||
}
|
||
}
|
||
|
||
void AfterStatFs(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (after.Proceed()) {
|
||
req_wrap->ResolveStatFs(static_cast<uv_statfs_t*>(req->ptr));
|
||
}
|
||
}
|
||
|
||
void AfterInteger(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
int result = static_cast<int>(req->result);
|
||
if (result >= 0 && req_wrap->is_plain_open())
|
||
req_wrap->env()->AddUnmanagedFd(result);
|
||
|
||
if (after.Proceed())
|
||
req_wrap->Resolve(Integer::New(req_wrap->env()->isolate(), result));
|
||
}
|
||
|
||
void AfterOpenFileHandle(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (after.Proceed()) {
|
||
FileHandle* fd = FileHandle::New(
|
||
req_wrap->binding_data(), static_cast<int>(req->result), {}, req->path);
|
||
if (fd == nullptr) return;
|
||
req_wrap->Resolve(fd->object());
|
||
}
|
||
}
|
||
|
||
// Delivers the result of mkstemp(): [path, fd], or [path, FileHandle].
|
||
static void AfterMkstempImpl(uv_fs_t* req, bool as_file_handle) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
const int fd = static_cast<int>(req->result);
|
||
if (after.Proceed()) {
|
||
Environment* env = req_wrap->env();
|
||
return ResolveMkstemp(
|
||
req_wrap,
|
||
fd,
|
||
[&]() {
|
||
return StringBytes::Encode(
|
||
env->isolate(), req->path, req_wrap->encoding());
|
||
},
|
||
[&]() {
|
||
if (!as_file_handle) return MkstempFd(env, fd);
|
||
return MkstempFileHandle([&]() {
|
||
return FileHandle::New(req_wrap->binding_data(), fd, {}, req->path);
|
||
});
|
||
});
|
||
}
|
||
// The environment is shutting down: nothing will receive the descriptor.
|
||
if (fd >= 0) CloseMkstempFd(fd);
|
||
}
|
||
|
||
static void AfterMkstemp(uv_fs_t* req) {
|
||
AfterMkstempImpl(req, false);
|
||
}
|
||
|
||
static void AfterMkstempFileHandle(uv_fs_t* req) {
|
||
AfterMkstempImpl(req, true);
|
||
}
|
||
|
||
void AfterMkdirp(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (after.Proceed()) {
|
||
std::string first_path(req_wrap->continuation_data()->first_path());
|
||
if (first_path.empty())
|
||
return req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
|
||
ResolveOrReject(req_wrap, [&]() {
|
||
return StringBytes::Encode(
|
||
req_wrap->env()->isolate(), first_path.c_str(), req_wrap->encoding());
|
||
});
|
||
}
|
||
}
|
||
|
||
void AfterStringPath(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (after.Proceed()) {
|
||
ResolveOrReject(req_wrap, [&]() {
|
||
return StringBytes::Encode(
|
||
req_wrap->env()->isolate(), req->path, req_wrap->encoding());
|
||
});
|
||
}
|
||
}
|
||
|
||
void AfterStringPtr(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (after.Proceed()) {
|
||
ResolveOrReject(req_wrap, [&]() {
|
||
return StringBytes::Encode(req_wrap->env()->isolate(),
|
||
static_cast<const char*>(req->ptr),
|
||
req_wrap->encoding());
|
||
});
|
||
}
|
||
}
|
||
|
||
void AfterScanDir(uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
FSReqAfterScope after(req_wrap, req);
|
||
FS_ASYNC_TRACE_END1(
|
||
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
|
||
if (!after.Proceed()) {
|
||
return;
|
||
}
|
||
|
||
Environment* env = req_wrap->env();
|
||
Isolate* isolate = env->isolate();
|
||
int r;
|
||
|
||
LocalVector<Value> name_v(isolate);
|
||
LocalVector<Value> type_v(isolate);
|
||
|
||
const bool with_file_types = req_wrap->with_file_types();
|
||
|
||
for (;;) {
|
||
uv_dirent_t ent;
|
||
|
||
r = uv_fs_scandir_next(req, &ent);
|
||
if (r == UV_EOF) break;
|
||
if (r != 0) {
|
||
return req_wrap->Reject(
|
||
UVException(isolate, r, nullptr, req_wrap->syscall(), req->path));
|
||
}
|
||
|
||
Local<Value> filename;
|
||
TryCatch try_catch(isolate);
|
||
if (!StringBytes::Encode(isolate, ent.name, req_wrap->encoding())
|
||
.ToLocal(&filename)) {
|
||
CHECK(try_catch.CanContinue());
|
||
return req_wrap->Reject(try_catch.Exception());
|
||
}
|
||
name_v.push_back(filename);
|
||
|
||
if (with_file_types) type_v.emplace_back(Integer::New(isolate, ent.type));
|
||
}
|
||
|
||
if (with_file_types) {
|
||
Local<Value> result[] = {Array::New(isolate, name_v.data(), name_v.size()),
|
||
Array::New(isolate, type_v.data(), type_v.size())};
|
||
req_wrap->Resolve(Array::New(isolate, result, arraysize(result)));
|
||
} else {
|
||
req_wrap->Resolve(Array::New(isolate, name_v.data(), name_v.size()));
|
||
}
|
||
}
|
||
|
||
void Access(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
Isolate* isolate = env->isolate();
|
||
HandleScope scope(isolate);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2); // path, mode
|
||
|
||
int mode;
|
||
if (!GetValidFileMode(env, args[1], UV_FS_ACCESS).To(&mode)) {
|
||
return;
|
||
}
|
||
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
if (argc > 2) { // access(path, mode, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_ACCESS, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"access",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_access,
|
||
*path,
|
||
mode);
|
||
} else { // access(path, mode)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("access", *path);
|
||
FS_SYNC_TRACE_BEGIN(access);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_access, *path, mode);
|
||
FS_SYNC_TRACE_END(access);
|
||
}
|
||
}
|
||
|
||
void Close(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 1);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
env->RemoveUnmanagedFd(fd);
|
||
|
||
if (argc > 1) { // close(fd, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 1);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, req_wrap_async)
|
||
AsyncCall(
|
||
env, req_wrap_async, args, "close", UTF8, AfterNoArgs, uv_fs_close, fd);
|
||
} else { // close(fd)
|
||
FSReqWrapSync req_wrap_sync("close");
|
||
FS_SYNC_TRACE_BEGIN(close);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_close, fd);
|
||
FS_SYNC_TRACE_END(close);
|
||
}
|
||
}
|
||
|
||
static void ExistsSync(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
CHECK_GE(args.Length(), 1);
|
||
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||
|
||
uv_fs_t req;
|
||
auto make = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
|
||
FS_SYNC_TRACE_BEGIN(access);
|
||
int err = uv_fs_access(nullptr, &req, path.out(), 0, nullptr);
|
||
FS_SYNC_TRACE_END(access);
|
||
|
||
#ifdef _WIN32
|
||
// In case of an invalid symlink, `uv_fs_access` on win32
|
||
// will **not** return an error and is therefore not enough.
|
||
// Double check with `uv_fs_stat()`.
|
||
if (err == 0) {
|
||
uv_fs_req_cleanup(&req);
|
||
FS_SYNC_TRACE_BEGIN(stat);
|
||
err = uv_fs_stat(nullptr, &req, path.out(), nullptr);
|
||
FS_SYNC_TRACE_END(stat);
|
||
}
|
||
#endif // _WIN32
|
||
|
||
args.GetReturnValue().Set(err == 0);
|
||
}
|
||
|
||
// Used to speed up module loading. Returns 0 if the path refers to
|
||
// a file, 1 when it's a directory or < 0 on error (usually -ENOENT.)
|
||
// The speedup comes from not creating thousands of Stat and Error objects.
|
||
// Do not expose this function through public API as it doesn't hold
|
||
// Permission Model checks.
|
||
static void InternalModuleStat(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
CHECK_EQ(args.Length(), 1);
|
||
CHECK(args[0]->IsString());
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
uv_fs_t req;
|
||
int rc = uv_fs_stat(env->event_loop(), &req, *path, nullptr);
|
||
if (rc == 0) {
|
||
const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
|
||
rc = S_ISDIR(s->st_mode);
|
||
}
|
||
uv_fs_req_cleanup(&req);
|
||
|
||
args.GetReturnValue().Set(rc);
|
||
}
|
||
|
||
constexpr bool is_uv_error_except_no_entry(int result) {
|
||
return result < 0 && result != UV_ENOENT;
|
||
}
|
||
|
||
constexpr bool is_uv_error_except_no_entry_dir(int result) {
|
||
return result < 0 && !(result == UV_ENOENT || result == UV_ENOTDIR);
|
||
}
|
||
|
||
static void Stat(const FunctionCallbackInfo<Value>& args) {
|
||
Realm* realm = Realm::GetCurrent(args);
|
||
BindingData* binding_data = realm->GetBindingData<BindingData>();
|
||
Environment* env = realm->env();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(realm->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
bool use_bigint = args[1]->IsTrue();
|
||
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req,
|
||
// do_not_throw_if_no_entry)
|
||
bool do_not_throw_if_no_entry = args[3]->IsFalse();
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
if (do_not_throw_if_no_entry) {
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"stat",
|
||
UTF8,
|
||
AfterStatNoThrowIfNoEntry,
|
||
uv_fs_stat,
|
||
*path);
|
||
} else {
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"stat",
|
||
UTF8,
|
||
AfterStat,
|
||
uv_fs_stat,
|
||
*path);
|
||
}
|
||
} else { // stat(path, use_bigint, undefined, do_not_throw_if_no_entry)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||
bool do_not_throw_if_no_entry = args[3]->IsFalse();
|
||
FSReqWrapSync req_wrap_sync("stat", *path);
|
||
FS_SYNC_TRACE_BEGIN(stat);
|
||
int result;
|
||
if (do_not_throw_if_no_entry) {
|
||
result = SyncCallAndThrowIf(is_uv_error_except_no_entry_dir,
|
||
env,
|
||
&req_wrap_sync,
|
||
uv_fs_stat,
|
||
*path);
|
||
} else {
|
||
result = SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_stat, *path);
|
||
}
|
||
FS_SYNC_TRACE_END(stat);
|
||
if (is_uv_error(result)) {
|
||
return;
|
||
}
|
||
Local<Value> arr = FillGlobalStatsArray(
|
||
binding_data,
|
||
use_bigint,
|
||
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
|
||
args.GetReturnValue().Set(arr);
|
||
}
|
||
}
|
||
|
||
static void LStat(const FunctionCallbackInfo<Value>& args) {
|
||
Realm* realm = Realm::GetCurrent(args);
|
||
BindingData* binding_data = realm->GetBindingData<BindingData>();
|
||
Environment* env = realm->env();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(realm->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
bool use_bigint = args[1]->IsTrue();
|
||
if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_LSTAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"lstat",
|
||
UTF8,
|
||
AfterStat,
|
||
uv_fs_lstat,
|
||
*path);
|
||
} else { // lstat(path, use_bigint, undefined, throw_if_no_entry)
|
||
bool do_not_throw_if_no_entry = args[3]->IsFalse();
|
||
FSReqWrapSync req_wrap_sync("lstat", *path);
|
||
FS_SYNC_TRACE_BEGIN(lstat);
|
||
int result;
|
||
if (do_not_throw_if_no_entry) {
|
||
result = SyncCallAndThrowIf(
|
||
is_uv_error_except_no_entry, env, &req_wrap_sync, uv_fs_lstat, *path);
|
||
} else {
|
||
result = SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_lstat, *path);
|
||
}
|
||
FS_SYNC_TRACE_END(lstat);
|
||
if (is_uv_error(result)) {
|
||
return;
|
||
}
|
||
|
||
Local<Value> arr = FillGlobalStatsArray(
|
||
binding_data,
|
||
use_bigint,
|
||
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
|
||
args.GetReturnValue().Set(arr);
|
||
}
|
||
}
|
||
|
||
static void FStat(const FunctionCallbackInfo<Value>& args) {
|
||
Realm* realm = Realm::GetCurrent(args);
|
||
BindingData* binding_data = realm->GetBindingData<BindingData>();
|
||
Environment* env = realm->env();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
bool use_bigint = args[1]->IsTrue();
|
||
if (!args[2]->IsUndefined()) { // fstat(fd, use_bigint, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_FSTAT, req_wrap_async)
|
||
AsyncCall(
|
||
env, req_wrap_async, args, "fstat", UTF8, AfterStat, uv_fs_fstat, fd);
|
||
} else { // fstat(fd, use_bigint, undefined, do_not_throw_error)
|
||
bool do_not_throw_error = args[2]->IsTrue();
|
||
const auto should_throw = [do_not_throw_error](int result) {
|
||
return is_uv_error(result) && !do_not_throw_error;
|
||
};
|
||
FSReqWrapSync req_wrap_sync("fstat");
|
||
FS_SYNC_TRACE_BEGIN(fstat);
|
||
int err =
|
||
SyncCallAndThrowIf(should_throw, env, &req_wrap_sync, uv_fs_fstat, fd);
|
||
FS_SYNC_TRACE_END(fstat);
|
||
if (is_uv_error(err)) {
|
||
return;
|
||
}
|
||
|
||
Local<Value> arr = FillGlobalStatsArray(
|
||
binding_data,
|
||
use_bigint,
|
||
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
|
||
args.GetReturnValue().Set(arr);
|
||
}
|
||
}
|
||
|
||
static void StatFs(const FunctionCallbackInfo<Value>& args) {
|
||
Realm* realm = Realm::GetCurrent(args);
|
||
BindingData* binding_data = realm->GetBindingData<BindingData>();
|
||
Environment* env = realm->env();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue path(realm->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
bool use_bigint = args[1]->IsTrue();
|
||
if (argc > 2) { // statfs(path, use_bigint, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_STATFS, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"statfs",
|
||
UTF8,
|
||
AfterStatFs,
|
||
uv_fs_statfs,
|
||
*path);
|
||
} else { // statfs(path, use_bigint)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("statfs", *path);
|
||
FS_SYNC_TRACE_BEGIN(statfs);
|
||
int result =
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_statfs, *path);
|
||
FS_SYNC_TRACE_END(statfs);
|
||
if (is_uv_error(result)) {
|
||
return;
|
||
}
|
||
|
||
Local<Value> arr = FillGlobalStatFsArray(
|
||
binding_data,
|
||
use_bigint,
|
||
static_cast<const uv_statfs_t*>(req_wrap_sync.req.ptr));
|
||
args.GetReturnValue().Set(arr);
|
||
}
|
||
}
|
||
|
||
static void Symlink(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue target(isolate, args[0]);
|
||
CHECK_NOT_NULL(*target);
|
||
auto target_view = target.ToStringView();
|
||
// To avoid bypass the symlink target should be allowed to read and write
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, target_view);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, target_view);
|
||
|
||
BufferValue path(isolate, args[1]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
|
||
|
||
CHECK(args[2]->IsInt32());
|
||
int flags = args[2].As<Int32>()->Value();
|
||
|
||
if (argc > 3) { // symlink(target, path, flags, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN2(UV_FS_SYMLINK,
|
||
req_wrap_async,
|
||
"target",
|
||
TRACE_STR_COPY(*target),
|
||
"path",
|
||
TRACE_STR_COPY(*path))
|
||
AsyncDestCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"symlink",
|
||
*path,
|
||
path.length(),
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_symlink,
|
||
*target,
|
||
*path,
|
||
flags);
|
||
} else { // symlink(target, path, flags, undefined, ctx)
|
||
FSReqWrapSync req_wrap_sync("symlink", *target, *path);
|
||
FS_SYNC_TRACE_BEGIN(symlink);
|
||
SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_symlink, *target, *path, flags);
|
||
FS_SYNC_TRACE_END(symlink);
|
||
}
|
||
}
|
||
|
||
static void Link(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue src(isolate, args[0]);
|
||
CHECK_NOT_NULL(*src);
|
||
ToNamespacedPath(env, &src);
|
||
|
||
const auto src_view = src.ToStringView();
|
||
|
||
BufferValue dest(isolate, args[1]);
|
||
CHECK_NOT_NULL(*dest);
|
||
ToNamespacedPath(env, &dest);
|
||
|
||
const auto dest_view = dest.ToStringView();
|
||
|
||
if (argc > 2) { // link(src, dest, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
// To avoid bypass the link target should be allowed to read and write
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
src_view);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
src_view);
|
||
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
dest_view);
|
||
FS_ASYNC_TRACE_BEGIN2(UV_FS_LINK,
|
||
req_wrap_async,
|
||
"src",
|
||
TRACE_STR_COPY(*src),
|
||
"dest",
|
||
TRACE_STR_COPY(*dest))
|
||
AsyncDestCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"link",
|
||
*dest,
|
||
dest.length(),
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_link,
|
||
*src,
|
||
*dest);
|
||
} else { // link(src, dest)
|
||
// To avoid bypass the link target should be allowed to read and write
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, src_view);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, src_view);
|
||
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, dest_view);
|
||
FSReqWrapSync req_wrap_sync("link", *src, *dest);
|
||
FS_SYNC_TRACE_BEGIN(link);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_link, *src, *dest);
|
||
FS_SYNC_TRACE_END(link);
|
||
}
|
||
}
|
||
|
||
static void ReadLink(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||
|
||
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
|
||
|
||
if (argc > 2) { // readlink(path, encoding, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_READLINK, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"readlink",
|
||
encoding,
|
||
AfterStringPtr,
|
||
uv_fs_readlink,
|
||
*path);
|
||
} else { // readlink(path, encoding)
|
||
FSReqWrapSync req_wrap_sync("readlink", *path);
|
||
FS_SYNC_TRACE_BEGIN(readlink);
|
||
int err =
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_readlink, *path);
|
||
FS_SYNC_TRACE_END(readlink);
|
||
if (err < 0) {
|
||
return;
|
||
}
|
||
const char* link_path = static_cast<const char*>(req_wrap_sync.req.ptr);
|
||
|
||
Local<Value> ret;
|
||
if (StringBytes::Encode(isolate, link_path, encoding).ToLocal(&ret)) {
|
||
args.GetReturnValue().Set(ret);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void Rename(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue old_path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*old_path);
|
||
ToNamespacedPath(env, &old_path);
|
||
auto view_old_path = old_path.ToStringView();
|
||
|
||
BufferValue new_path(isolate, args[1]);
|
||
CHECK_NOT_NULL(*new_path);
|
||
ToNamespacedPath(env, &new_path);
|
||
|
||
if (argc > 2) { // rename(old_path, new_path, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
view_old_path);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
view_old_path);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
new_path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN2(UV_FS_RENAME,
|
||
req_wrap_async,
|
||
"old_path",
|
||
TRACE_STR_COPY(*old_path),
|
||
"new_path",
|
||
TRACE_STR_COPY(*new_path))
|
||
AsyncDestCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"rename",
|
||
*new_path,
|
||
new_path.length(),
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_rename,
|
||
*old_path,
|
||
*new_path);
|
||
} else { // rename(old_path, new_path)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, view_old_path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, view_old_path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
new_path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("rename", *old_path, *new_path);
|
||
FS_SYNC_TRACE_BEGIN(rename);
|
||
SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_rename, *old_path, *new_path);
|
||
FS_SYNC_TRACE_END(rename);
|
||
}
|
||
}
|
||
|
||
static void FTruncate(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(IsSafeJsInt(args[1]));
|
||
const int64_t len = args[1].As<Integer>()->Value();
|
||
|
||
if (argc > 2) { // ftruncate(fd, len, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_FTRUNCATE, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"ftruncate",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_ftruncate,
|
||
fd,
|
||
len);
|
||
} else { // ftruncate(fd, len)
|
||
FSReqWrapSync req_wrap_sync("ftruncate");
|
||
FS_SYNC_TRACE_BEGIN(ftruncate);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_ftruncate, fd, len);
|
||
FS_SYNC_TRACE_END(ftruncate);
|
||
}
|
||
}
|
||
|
||
static void Fdatasync(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 1);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
if (argc > 1) { // fdatasync(fd, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 1);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_FDATASYNC, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"fdatasync",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_fdatasync,
|
||
fd);
|
||
} else { // fdatasync(fd)
|
||
FSReqWrapSync req_wrap_sync("fdatasync");
|
||
FS_SYNC_TRACE_BEGIN(fdatasync);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fdatasync, fd);
|
||
FS_SYNC_TRACE_END(fdatasync);
|
||
}
|
||
}
|
||
|
||
static void Fsync(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 1);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
if (argc > 1) {
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 1);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_FSYNC, req_wrap_async)
|
||
AsyncCall(
|
||
env, req_wrap_async, args, "fsync", UTF8, AfterNoArgs, uv_fs_fsync, fd);
|
||
} else {
|
||
FSReqWrapSync req_wrap_sync("fsync");
|
||
FS_SYNC_TRACE_BEGIN(fsync);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fsync, fd);
|
||
FS_SYNC_TRACE_END(fsync);
|
||
}
|
||
}
|
||
|
||
static void Unlink(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 1);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
if (argc > 1) { // unlink(path, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 1);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_UNLINK, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"unlink",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_unlink,
|
||
*path);
|
||
} else { // unlink(path)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("unlink", *path);
|
||
FS_SYNC_TRACE_BEGIN(unlink);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_unlink, *path);
|
||
FS_SYNC_TRACE_END(unlink);
|
||
}
|
||
}
|
||
|
||
static void RMDir(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 1);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
|
||
|
||
if (argc > 1) {
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 1); // rmdir(path, req)
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_RMDIR, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"rmdir",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_rmdir,
|
||
*path);
|
||
} else { // rmdir(path)
|
||
FSReqWrapSync req_wrap_sync("rmdir", *path);
|
||
FS_SYNC_TRACE_BEGIN(rmdir);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_rmdir, *path);
|
||
FS_SYNC_TRACE_END(rmdir);
|
||
}
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
static void ClearReadOnlyAttributeWHelper(const wchar_t* path) {
|
||
DWORD attrs = GetFileAttributesW(path);
|
||
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY)) {
|
||
SetFileAttributesW(path, attrs & ~FILE_ATTRIBUTE_READONLY);
|
||
}
|
||
}
|
||
|
||
static void ClearReadOnlyAttributeW(const std::filesystem::path& path,
|
||
bool recursive) {
|
||
std::error_code ec;
|
||
auto file_status = std::filesystem::symlink_status(path, ec);
|
||
if (ec) return;
|
||
|
||
if (recursive &&
|
||
file_status.type() == std::filesystem::file_type::directory) {
|
||
for (const auto& entry : std::filesystem::recursive_directory_iterator(
|
||
path,
|
||
std::filesystem::directory_options::skip_permission_denied,
|
||
ec)) {
|
||
std::error_code entry_ec;
|
||
auto entry_status = entry.symlink_status(entry_ec);
|
||
if (entry_ec) continue;
|
||
if (entry_status.type() != std::filesystem::file_type::symlink) {
|
||
ClearReadOnlyAttributeWHelper(entry.path().c_str());
|
||
}
|
||
}
|
||
}
|
||
|
||
if (file_status.type() != std::filesystem::file_type::symlink) {
|
||
ClearReadOnlyAttributeWHelper(path.c_str());
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static void RmSync(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
CHECK_EQ(args.Length(), 4); // path, maxRetries, recursive, retryDelay
|
||
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
|
||
auto file_path = path.ToPath();
|
||
std::error_code error;
|
||
auto file_status = std::filesystem::symlink_status(file_path, error);
|
||
|
||
if (file_status.type() == std::filesystem::file_type::not_found) {
|
||
return;
|
||
}
|
||
|
||
int maxRetries = args[1].As<Int32>()->Value();
|
||
int recursive = args[2]->IsTrue();
|
||
int retryDelay = args[3].As<Int32>()->Value();
|
||
|
||
// File is a directory and recursive is false
|
||
if (file_status.type() == std::filesystem::file_type::directory &&
|
||
!recursive) {
|
||
return THROW_ERR_FS_EISDIR(isolate, "Path is a directory: %s", path);
|
||
}
|
||
|
||
// Allowed errors are:
|
||
// - EBUSY: std::errc::device_or_resource_busy
|
||
// - EMFILE: std::errc::too_many_files_open
|
||
// - ENFILE: std::errc::too_many_files_open_in_system
|
||
// - ENOTEMPTY: std::errc::directory_not_empty
|
||
// - EPERM: std::errc::operation_not_permitted
|
||
auto can_omit_error = [](std::error_code error) -> bool {
|
||
return (error == std::errc::device_or_resource_busy ||
|
||
error == std::errc::too_many_files_open ||
|
||
error == std::errc::too_many_files_open_in_system ||
|
||
error == std::errc::directory_not_empty ||
|
||
#ifdef _WIN32
|
||
error == std::errc::permission_denied ||
|
||
#endif
|
||
error == std::errc::operation_not_permitted);
|
||
};
|
||
|
||
int i = 1;
|
||
#ifdef _WIN32
|
||
bool cleared_readonly = false;
|
||
#endif
|
||
|
||
while (maxRetries >= 0) {
|
||
if (recursive) {
|
||
std::filesystem::remove_all(file_path, error);
|
||
} else {
|
||
std::filesystem::remove(file_path, error);
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
// On Windows, libc++ does not clear the read-only attribute before
|
||
// removing a file (unlike MSVC STL which does). Attempt to clear it
|
||
// manually when we get EPERM (operation_not_permitted) so that read-only
|
||
// files can be deleted, matching the behavior of official Node.js builds.
|
||
if (error == std::errc::operation_not_permitted && !cleared_readonly) {
|
||
cleared_readonly = true;
|
||
ClearReadOnlyAttributeW(file_path, recursive);
|
||
if (recursive) {
|
||
std::filesystem::remove_all(file_path, error);
|
||
} else {
|
||
std::filesystem::remove(file_path, error);
|
||
}
|
||
}
|
||
#endif // _WIN32
|
||
|
||
if (!error || error == std::errc::no_such_file_or_directory) {
|
||
return;
|
||
} else if (!can_omit_error(error)) {
|
||
break;
|
||
}
|
||
|
||
if (retryDelay > 0) {
|
||
#ifdef _WIN32
|
||
// No conversion needed: Sleep() takes milliseconds.
|
||
Sleep(i * retryDelay);
|
||
#else
|
||
// sleep() takes seconds, so convert the millisecond delay.
|
||
sleep(i * retryDelay / 1000);
|
||
#endif
|
||
}
|
||
maxRetries--;
|
||
i++;
|
||
}
|
||
|
||
// On Windows path::c_str() returns wide char, convert to std::string first.
|
||
std::string file_path_str = ConvertPathToUTF8(file_path);
|
||
const char* path_c_str = file_path_str.c_str();
|
||
#ifdef _WIN32
|
||
int permission_denied_error = EPERM;
|
||
#else
|
||
int permission_denied_error = EACCES;
|
||
#endif // !_WIN32
|
||
|
||
if (error == std::errc::operation_not_permitted) {
|
||
std::string message = "Operation not permitted:";
|
||
return env->ThrowErrnoException(EPERM, "rm", message.c_str(), path_c_str);
|
||
} else if (error == std::errc::directory_not_empty) {
|
||
std::string message = "Directory not empty:";
|
||
return env->ThrowErrnoException(
|
||
ENOTEMPTY, "rm", message.c_str(), path_c_str);
|
||
} else if (error == std::errc::not_a_directory) {
|
||
std::string message = "Not a directory:";
|
||
return env->ThrowErrnoException(ENOTDIR, "rm", message.c_str(), path_c_str);
|
||
#ifdef _AIX
|
||
} else if (error == std::errc::permission_denied ||
|
||
error == std::errc::file_exists) {
|
||
// Workaround for clang libc++ bug on AIX: std::filesystem::remove_all()
|
||
// incorrectly returns EEXIST (17) instead of EACCES (13) for permission
|
||
// errors when trying to remove directories without proper permissions.
|
||
#else
|
||
} else if (error == std::errc::permission_denied) {
|
||
#endif
|
||
std::string message = "Permission denied:";
|
||
return env->ThrowErrnoException(
|
||
permission_denied_error, "rm", message.c_str(), path_c_str);
|
||
}
|
||
|
||
std::string message = "Unknown error: " + error.message();
|
||
return env->ThrowErrnoException(
|
||
UV_UNKNOWN, "rm", message.c_str(), path_c_str);
|
||
}
|
||
|
||
int MKDirpSync(uv_loop_t* loop,
|
||
uv_fs_t* req,
|
||
const std::string& path,
|
||
int mode,
|
||
uv_fs_cb cb) {
|
||
FSReqWrapSync* req_wrap = ContainerOf(&FSReqWrapSync::req, req);
|
||
|
||
// on the first iteration of algorithm, stash state information.
|
||
if (req_wrap->continuation_data() == nullptr) {
|
||
req_wrap->set_continuation_data(
|
||
std::make_unique<FSContinuationData>(req, mode, cb));
|
||
req_wrap->continuation_data()->PushPath(std::move(path));
|
||
}
|
||
|
||
while (req_wrap->continuation_data()->paths().size() > 0) {
|
||
std::string next_path = req_wrap->continuation_data()->PopPath();
|
||
int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr);
|
||
while (true) {
|
||
switch (err) {
|
||
// Note: uv_fs_req_cleanup in terminal paths will be called by
|
||
// ~FSReqWrapSync():
|
||
case 0:
|
||
req_wrap->continuation_data()->MaybeSetFirstPath(next_path);
|
||
if (req_wrap->continuation_data()->paths().empty()) {
|
||
return 0;
|
||
}
|
||
break;
|
||
case UV_EACCES:
|
||
case UV_ENOSPC:
|
||
case UV_ENOTDIR:
|
||
case UV_EPERM: {
|
||
return err;
|
||
}
|
||
case UV_ENOENT: {
|
||
std::string dirname =
|
||
next_path.substr(0, next_path.find_last_of(kPathSeparator));
|
||
if (dirname != next_path) {
|
||
if (!req_wrap->continuation_data()->ShouldRetryENOENT(next_path)) {
|
||
return err;
|
||
}
|
||
req_wrap->continuation_data()->PushPath(std::move(next_path));
|
||
req_wrap->continuation_data()->PushPath(std::move(dirname));
|
||
} else if (req_wrap->continuation_data()->paths().empty()) {
|
||
err = UV_EEXIST;
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
uv_fs_req_cleanup(req);
|
||
int orig_err = err;
|
||
err = uv_fs_stat(loop, req, next_path.c_str(), nullptr);
|
||
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) {
|
||
uv_fs_req_cleanup(req);
|
||
if (orig_err == UV_EEXIST &&
|
||
req_wrap->continuation_data()->paths().size() > 0) {
|
||
return UV_ENOTDIR;
|
||
}
|
||
return UV_EEXIST;
|
||
}
|
||
if (err < 0) return err;
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
uv_fs_req_cleanup(req);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int MKDirpAsync(
|
||
uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
// on the first iteration of algorithm, stash state information.
|
||
if (req_wrap->continuation_data() == nullptr) {
|
||
req_wrap->set_continuation_data(
|
||
std::make_unique<FSContinuationData>(req, mode, cb));
|
||
req_wrap->continuation_data()->PushPath(std::move(path));
|
||
}
|
||
|
||
// on each iteration of algorithm, mkdir directory on top of stack.
|
||
std::string next_path = req_wrap->continuation_data()->PopPath();
|
||
int err = uv_fs_mkdir(
|
||
loop, req, next_path.c_str(), mode, uv_fs_callback_t{[](uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
Environment* env = req_wrap->env();
|
||
uv_loop_t* loop = env->event_loop();
|
||
std::string path = req->path;
|
||
int err = static_cast<int>(req->result);
|
||
|
||
while (true) {
|
||
switch (err) {
|
||
// Note: uv_fs_req_cleanup in terminal paths will be called by
|
||
// FSReqAfterScope::~FSReqAfterScope()
|
||
case 0: {
|
||
if (req_wrap->continuation_data()->paths().empty()) {
|
||
req_wrap->continuation_data()->MaybeSetFirstPath(path);
|
||
req_wrap->continuation_data()->Done(0);
|
||
} else {
|
||
req_wrap->continuation_data()->MaybeSetFirstPath(path);
|
||
uv_fs_req_cleanup(req);
|
||
MKDirpAsync(loop,
|
||
req,
|
||
path.c_str(),
|
||
req_wrap->continuation_data()->mode(),
|
||
nullptr);
|
||
}
|
||
break;
|
||
}
|
||
case UV_EACCES:
|
||
case UV_ENOSPC:
|
||
case UV_ENOTDIR:
|
||
case UV_EPERM: {
|
||
req_wrap->continuation_data()->Done(err);
|
||
break;
|
||
}
|
||
case UV_ENOENT: {
|
||
std::string dirname =
|
||
path.substr(0, path.find_last_of(kPathSeparator));
|
||
if (dirname != path) {
|
||
if (!req_wrap->continuation_data()->ShouldRetryENOENT(path)) {
|
||
req_wrap->continuation_data()->Done(err);
|
||
break;
|
||
}
|
||
req_wrap->continuation_data()->PushPath(path);
|
||
req_wrap->continuation_data()->PushPath(std::move(dirname));
|
||
} else if (req_wrap->continuation_data()->paths().empty()) {
|
||
err = UV_EEXIST;
|
||
continue;
|
||
}
|
||
uv_fs_req_cleanup(req);
|
||
MKDirpAsync(loop,
|
||
req,
|
||
path.c_str(),
|
||
req_wrap->continuation_data()->mode(),
|
||
nullptr);
|
||
break;
|
||
}
|
||
default:
|
||
uv_fs_req_cleanup(req);
|
||
// Stash err for use in the callback.
|
||
req->data = reinterpret_cast<void*>(static_cast<intptr_t>(err));
|
||
int err = uv_fs_stat(
|
||
loop, req, path.c_str(), uv_fs_callback_t{[](uv_fs_t* req) {
|
||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||
int err = static_cast<int>(req->result);
|
||
if (reinterpret_cast<intptr_t>(req->data) == UV_EEXIST &&
|
||
req_wrap->continuation_data()->paths().size() > 0) {
|
||
if (err == 0 && S_ISDIR(req->statbuf.st_mode)) {
|
||
Environment* env = req_wrap->env();
|
||
uv_loop_t* loop = env->event_loop();
|
||
std::string path = req->path;
|
||
uv_fs_req_cleanup(req);
|
||
MKDirpAsync(loop,
|
||
req,
|
||
path.c_str(),
|
||
req_wrap->continuation_data()->mode(),
|
||
nullptr);
|
||
return;
|
||
}
|
||
err = UV_ENOTDIR;
|
||
}
|
||
// verify that the path pointed to is actually a directory.
|
||
if (err == 0 && !S_ISDIR(req->statbuf.st_mode))
|
||
err = UV_EEXIST;
|
||
req_wrap->continuation_data()->Done(err);
|
||
}});
|
||
if (err < 0) req_wrap->continuation_data()->Done(err);
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
}});
|
||
|
||
return err;
|
||
}
|
||
|
||
static void MKDir(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
|
||
|
||
CHECK(args[1]->IsInt32());
|
||
const int mode = args[1].As<Int32>()->Value();
|
||
|
||
CHECK(args[2]->IsBoolean());
|
||
bool mkdirp = args[2]->IsTrue();
|
||
|
||
if (argc > 3) { // mkdir(path, mode, recursive, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_UNLINK, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"mkdir",
|
||
UTF8,
|
||
mkdirp ? AfterMkdirp : AfterNoArgs,
|
||
mkdirp ? MKDirpAsync : uv_fs_mkdir,
|
||
*path,
|
||
mode);
|
||
} else { // mkdir(path, mode, recursive)
|
||
FSReqWrapSync req_wrap_sync("mkdir", *path);
|
||
FS_SYNC_TRACE_BEGIN(mkdir);
|
||
if (mkdirp) {
|
||
env->PrintSyncTrace();
|
||
int err = MKDirpSync(
|
||
env->event_loop(), &req_wrap_sync.req, *path, mode, nullptr);
|
||
if (is_uv_error(err)) {
|
||
env->ThrowUVException(err, "mkdir", nullptr, *path);
|
||
return;
|
||
}
|
||
if (!req_wrap_sync.continuation_data()->first_path().empty()) {
|
||
Local<Value> ret;
|
||
std::string first_path(req_wrap_sync.continuation_data()->first_path());
|
||
if (StringBytes::Encode(env->isolate(), first_path.c_str(), UTF8)
|
||
.ToLocal(&ret)) {
|
||
args.GetReturnValue().Set(ret);
|
||
}
|
||
}
|
||
} else {
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_mkdir, *path, mode);
|
||
}
|
||
FS_SYNC_TRACE_END(mkdir);
|
||
}
|
||
}
|
||
|
||
static void RealPath(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
|
||
|
||
if (argc > 2) { // realpath(path, encoding, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_REALPATH, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"realpath",
|
||
encoding,
|
||
AfterStringPtr,
|
||
uv_fs_realpath,
|
||
*path);
|
||
} else { // realpath(path, encoding, undefined, ctx)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("realpath", *path);
|
||
FS_SYNC_TRACE_BEGIN(realpath);
|
||
int err =
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_realpath, *path);
|
||
FS_SYNC_TRACE_END(realpath);
|
||
if (err < 0) {
|
||
return;
|
||
}
|
||
|
||
const char* link_path = static_cast<const char*>(req_wrap_sync.req.ptr);
|
||
|
||
Local<Value> ret;
|
||
if (StringBytes::Encode(isolate, link_path, encoding).ToLocal(&ret)) {
|
||
args.GetReturnValue().Set(ret);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void ReadDir(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
#ifdef _WIN32
|
||
// On Windows, some API functions accept paths with trailing slashes,
|
||
// while others do not. This code checks if the input path ends with
|
||
// a slash (either '/' or '\\') and, if so, ensures that the processed
|
||
// path also ends with a trailing backslash ('\\').
|
||
bool slashCheck = false;
|
||
if (path.ToStringView().ends_with("/") ||
|
||
path.ToStringView().ends_with("\\")) {
|
||
slashCheck = true;
|
||
}
|
||
#endif
|
||
|
||
ToNamespacedPath(env, &path);
|
||
|
||
#ifdef _WIN32
|
||
if (slashCheck && !path.ToStringView().ends_with("\\")) {
|
||
size_t new_length = path.length() + 1;
|
||
path.AllocateSufficientStorage(new_length + 1);
|
||
path.SetLengthAndZeroTerminate(new_length);
|
||
path.out()[new_length - 1] = '\\';
|
||
}
|
||
#endif
|
||
|
||
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
|
||
|
||
bool with_types = args[2]->IsTrue();
|
||
|
||
if (argc > 3) { // readdir(path, encoding, withTypes, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
path.ToStringView());
|
||
req_wrap_async->set_with_file_types(with_types);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_SCANDIR, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"scandir",
|
||
encoding,
|
||
AfterScanDir,
|
||
uv_fs_scandir,
|
||
*path,
|
||
0 /*flags*/);
|
||
} else { // readdir(path, encoding, withTypes)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("scandir", *path);
|
||
FS_SYNC_TRACE_BEGIN(readdir);
|
||
int err = SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_scandir, *path, 0 /*flags*/);
|
||
FS_SYNC_TRACE_END(readdir);
|
||
if (is_uv_error(err)) {
|
||
return;
|
||
}
|
||
|
||
int r;
|
||
LocalVector<Value> name_v(isolate);
|
||
LocalVector<Value> type_v(isolate);
|
||
|
||
for (;;) {
|
||
uv_dirent_t ent;
|
||
|
||
r = uv_fs_scandir_next(&(req_wrap_sync.req), &ent);
|
||
if (r == UV_EOF) break;
|
||
if (is_uv_error(r)) {
|
||
env->ThrowUVException(r, "scandir", nullptr, *path);
|
||
return;
|
||
}
|
||
|
||
Local<Value> fn;
|
||
if (!StringBytes::Encode(isolate, ent.name, encoding).ToLocal(&fn)) {
|
||
return;
|
||
}
|
||
|
||
name_v.push_back(fn);
|
||
|
||
if (with_types) {
|
||
type_v.emplace_back(Integer::New(isolate, ent.type));
|
||
}
|
||
}
|
||
|
||
Local<Array> names = Array::New(isolate, name_v.data(), name_v.size());
|
||
if (with_types) {
|
||
Local<Value> result[] = {
|
||
names, Array::New(isolate, type_v.data(), type_v.size())};
|
||
args.GetReturnValue().Set(Array::New(isolate, result, arraysize(result)));
|
||
} else {
|
||
args.GetReturnValue().Set(names);
|
||
}
|
||
}
|
||
}
|
||
|
||
namespace {
|
||
|
||
// Recursive readdir.
|
||
//
|
||
// The result is breadth-first: every entry of the root, then every entry of
|
||
// the first subdirectory, and so on, each directory's entries in the order
|
||
// uv_fs_scandir() reports them. Symbolic links to directories are followed,
|
||
// see https://github.com/nodejs/node/issues/52663.
|
||
//
|
||
// The tree is walked by one or more threads that share a queue of
|
||
// directories, and the breadth-first order is derived from the tree
|
||
// afterwards, so it does not depend on which thread scanned what. The walk
|
||
// does not touch the Environment; lib does not use it when the permission
|
||
// model is enabled, as every directory would need a check on the main thread.
|
||
|
||
// Maps an st_mode to the uv_dirent_type_t that uv_fs_scandir would report.
|
||
uv_dirent_type_t DirentTypeFromMode(uint64_t mode) {
|
||
switch (mode & S_IFMT) {
|
||
case S_IFREG:
|
||
return UV_DIRENT_FILE;
|
||
case S_IFDIR:
|
||
return UV_DIRENT_DIR;
|
||
case S_IFLNK:
|
||
return UV_DIRENT_LINK;
|
||
case S_IFCHR:
|
||
return UV_DIRENT_CHAR;
|
||
#ifdef S_IFIFO
|
||
case S_IFIFO:
|
||
return UV_DIRENT_FIFO;
|
||
#endif
|
||
#ifdef S_IFSOCK
|
||
case S_IFSOCK:
|
||
return UV_DIRENT_SOCKET;
|
||
#endif
|
||
#ifdef S_IFBLK
|
||
case S_IFBLK:
|
||
return UV_DIRENT_BLOCK;
|
||
#endif
|
||
default:
|
||
return UV_DIRENT_UNKNOWN;
|
||
}
|
||
}
|
||
|
||
// Appends `name` to `path`, with a separator unless `path` already ends in
|
||
// one (so that a root of "dir/" does not turn into "dir//name").
|
||
void AppendPathComponent(std::string* path, std::string_view name) {
|
||
if (name.empty()) return;
|
||
if (!path->empty()) {
|
||
#ifdef _WIN32
|
||
const bool has_separator = path->back() == '\\' || path->back() == '/';
|
||
if (!has_separator) *path += '\\';
|
||
#else
|
||
const bool has_separator = path->back() == '/';
|
||
if (!has_separator) *path += '/';
|
||
#endif
|
||
}
|
||
path->append(name);
|
||
}
|
||
|
||
struct ReadDirEntry {
|
||
// Offset and length of the name within ScannedDirectory::names.
|
||
uint32_t name_offset;
|
||
uint32_t name_length;
|
||
uint8_t type; // A uv_dirent_type_t.
|
||
// Whether the walk descends into this entry (a directory, or a symbolic
|
||
// link to one).
|
||
bool is_dir = false;
|
||
};
|
||
|
||
// One directory of the tree.
|
||
struct ScannedDirectory {
|
||
explicit ScannedDirectory(std::string relative)
|
||
: relative(std::move(relative)) {}
|
||
|
||
std::string_view name(const ReadDirEntry& entry) const {
|
||
return std::string_view(names).substr(entry.name_offset, entry.name_length);
|
||
}
|
||
|
||
// Path relative to the root; empty for the root itself.
|
||
const std::string relative;
|
||
// The entry names, concatenated.
|
||
std::string names;
|
||
std::vector<ReadDirEntry> entries;
|
||
// Indices (into RecursiveReadDir::dirs()) of the subdirectories, in entry
|
||
// order.
|
||
std::vector<uint32_t> subdirs;
|
||
};
|
||
|
||
class RecursiveReadDir {
|
||
public:
|
||
explicit RecursiveReadDir(std::string root) : root_(std::move(root)) {
|
||
dirs_.push_back(std::make_unique<ScannedDirectory>(""));
|
||
}
|
||
|
||
// Scans directories until none are left or an error occurred. May be
|
||
// called from several threads at once, which then share the work.
|
||
void Run();
|
||
void RunWithHelpers(int max_helpers);
|
||
|
||
// The following are only valid once every Run() has returned.
|
||
|
||
// 0 or a uv error code; error_path() is then the directory that failed.
|
||
int error() const { return error_; }
|
||
const std::string& error_path() const { return error_path_; }
|
||
|
||
const std::vector<std::unique_ptr<ScannedDirectory>>& dirs() const {
|
||
return dirs_;
|
||
}
|
||
|
||
// The indices of dirs() in breadth-first order.
|
||
std::vector<uint32_t> BreadthFirstOrder() const {
|
||
std::vector<uint32_t> order;
|
||
order.reserve(dirs_.size());
|
||
order.push_back(0);
|
||
for (size_t i = 0; i < order.size(); i++) {
|
||
for (uint32_t subdir : dirs_[order[i]]->subdirs) order.push_back(subdir);
|
||
}
|
||
return order;
|
||
}
|
||
|
||
private:
|
||
// Reads the directory at `path` into `dir`, resolving the type of entries
|
||
// where needed, and creates the ScannedDirectory of each subdirectory.
|
||
static int Scan(const std::string& path,
|
||
ScannedDirectory* dir,
|
||
std::vector<std::unique_ptr<ScannedDirectory>>* subdirs);
|
||
|
||
// Called with the lock held.
|
||
void MaybeStartHelper();
|
||
|
||
// The number of entries a walk has to have seen, with directories still
|
||
// to scan, before RunWithHelpers() starts a helper thread.
|
||
static constexpr size_t kHelperThreshold = 1024;
|
||
|
||
const std::string root_;
|
||
|
||
Mutex mutex_;
|
||
ConditionVariable cv_;
|
||
// Directories in the order they were found; the first next_ have been or
|
||
// are being scanned by one of the active_ threads.
|
||
std::vector<std::unique_ptr<ScannedDirectory>> dirs_;
|
||
size_t next_ = 0;
|
||
size_t active_ = 0;
|
||
size_t entries_seen_ = 0;
|
||
int error_ = 0;
|
||
std::string error_path_;
|
||
size_t max_helpers_ = 0;
|
||
std::vector<uv_thread_t> helpers_;
|
||
};
|
||
|
||
void RecursiveReadDir::RunWithHelpers(int max_helpers) {
|
||
max_helpers_ = max_helpers;
|
||
Run();
|
||
for (uv_thread_t& helper : helpers_) CHECK_EQ(uv_thread_join(&helper), 0);
|
||
}
|
||
|
||
void RecursiveReadDir::Run() {
|
||
std::vector<std::unique_ptr<ScannedDirectory>> subdirs;
|
||
std::string path;
|
||
Mutex::ScopedLock lock(mutex_);
|
||
for (;;) {
|
||
while (next_ == dirs_.size() && active_ > 0 && error_ == 0) {
|
||
cv_.Wait(lock);
|
||
}
|
||
if (next_ == dirs_.size() || error_ != 0) return;
|
||
|
||
ScannedDirectory* dir = dirs_[next_++].get();
|
||
active_++;
|
||
int r;
|
||
{
|
||
Mutex::ScopedUnlock unlock(lock);
|
||
path = root_;
|
||
AppendPathComponent(&path, dir->relative);
|
||
r = Scan(path, dir, &subdirs);
|
||
}
|
||
active_--;
|
||
|
||
if (r != 0) {
|
||
if (error_ == 0) {
|
||
error_ = r;
|
||
error_path_ = std::move(path);
|
||
}
|
||
} else {
|
||
for (std::unique_ptr<ScannedDirectory>& subdir : subdirs) {
|
||
dir->subdirs.push_back(static_cast<uint32_t>(dirs_.size()));
|
||
dirs_.push_back(std::move(subdir));
|
||
}
|
||
entries_seen_ += dir->entries.size();
|
||
}
|
||
// Wake the others if there is new work, or nothing left to wait for.
|
||
if (!subdirs.empty() || active_ == 0 || error_ != 0) cv_.Broadcast(lock);
|
||
subdirs.clear();
|
||
MaybeStartHelper();
|
||
}
|
||
}
|
||
|
||
void RecursiveReadDir::MaybeStartHelper() {
|
||
if (helpers_.size() >= max_helpers_ || entries_seen_ < kHelperThreshold ||
|
||
dirs_.size() - next_ < 2) {
|
||
return;
|
||
}
|
||
uv_thread_t helper;
|
||
int r = uv_thread_create(
|
||
&helper,
|
||
[](void* arg) { static_cast<RecursiveReadDir*>(arg)->Run(); },
|
||
this);
|
||
if (r == 0) {
|
||
helpers_.push_back(helper);
|
||
} else {
|
||
max_helpers_ = 0; // Carry on alone.
|
||
}
|
||
}
|
||
|
||
int RecursiveReadDir::Scan(
|
||
const std::string& path,
|
||
ScannedDirectory* dir,
|
||
std::vector<std::unique_ptr<ScannedDirectory>>* subdirs) {
|
||
uv_fs_t req;
|
||
int r = uv_fs_scandir(nullptr, &req, path.c_str(), 0, nullptr);
|
||
if (r >= 0) {
|
||
dir->entries.reserve(r);
|
||
uv_dirent_t ent;
|
||
while ((r = uv_fs_scandir_next(&req, &ent)) == 0) {
|
||
const size_t length = strlen(ent.name);
|
||
dir->entries.push_back({static_cast<uint32_t>(dir->names.size()),
|
||
static_cast<uint32_t>(length),
|
||
static_cast<uint8_t>(ent.type)});
|
||
dir->names.append(ent.name, length);
|
||
}
|
||
if (r == UV_EOF) r = 0;
|
||
}
|
||
uv_fs_req_cleanup(&req);
|
||
if (r != 0) return r;
|
||
|
||
std::string child;
|
||
for (ReadDirEntry& entry : dir->entries) {
|
||
const std::string_view name = dir->name(entry);
|
||
if (entry.type == UV_DIRENT_UNKNOWN || entry.type == UV_DIRENT_LINK) {
|
||
// The file system did not report a type, or the entry is a symbolic
|
||
// link that may point at a directory.
|
||
child = path;
|
||
AppendPathComponent(&child, name);
|
||
if (entry.type == UV_DIRENT_UNKNOWN) {
|
||
if (uv_fs_lstat(nullptr, &req, child.c_str(), nullptr) == 0) {
|
||
entry.type = DirentTypeFromMode(req.statbuf.st_mode);
|
||
}
|
||
uv_fs_req_cleanup(&req);
|
||
}
|
||
if (entry.type == UV_DIRENT_LINK) {
|
||
if (uv_fs_stat(nullptr, &req, child.c_str(), nullptr) == 0) {
|
||
entry.is_dir = S_ISDIR(req.statbuf.st_mode);
|
||
}
|
||
uv_fs_req_cleanup(&req);
|
||
}
|
||
}
|
||
if (entry.type == UV_DIRENT_DIR) entry.is_dir = true;
|
||
|
||
if (entry.is_dir) {
|
||
std::string relative = dir->relative;
|
||
AppendPathComponent(&relative, name);
|
||
subdirs->push_back(
|
||
std::make_unique<ScannedDirectory>(std::move(relative)));
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// Without file types the result is an array of paths relative to the root.
|
||
// With file types it is [names, types, counts, dirs]: `types` is a
|
||
// Uint8Array with the uv_dirent_type_t of every entry, `dirs` the relative
|
||
// path of every directory in the order their entries appear, and `counts` a
|
||
// Uint32Array with the number of entries in each of those directories. See
|
||
// getRecursiveDirents() in lib/internal/fs/utils.js.
|
||
MaybeLocal<Value> MarshalRecursiveReadDir(Isolate* isolate,
|
||
const RecursiveReadDir& walk,
|
||
enum encoding encoding,
|
||
bool with_types) {
|
||
EscapableHandleScope scope(isolate);
|
||
const std::vector<uint32_t> order = walk.BreadthFirstOrder();
|
||
|
||
LocalVector<Value> names(isolate);
|
||
LocalVector<Value> dirs(isolate);
|
||
std::vector<uint8_t> types;
|
||
std::vector<uint32_t> counts;
|
||
std::string path;
|
||
for (uint32_t index : order) {
|
||
const ScannedDirectory& dir = *walk.dirs()[index];
|
||
for (const ReadDirEntry& entry : dir.entries) {
|
||
std::string_view data = dir.name(entry);
|
||
if (!with_types) {
|
||
path = dir.relative;
|
||
AppendPathComponent(&path, data);
|
||
data = path;
|
||
}
|
||
Local<Value> name;
|
||
if (!StringBytes::Encode(isolate, data.data(), data.size(), encoding)
|
||
.ToLocal(&name)) {
|
||
return MaybeLocal<Value>();
|
||
}
|
||
names.push_back(name);
|
||
types.push_back(entry.type);
|
||
}
|
||
if (!with_types) continue;
|
||
counts.push_back(static_cast<uint32_t>(dir.entries.size()));
|
||
Local<Value> relative;
|
||
if (!StringBytes::Encode(
|
||
isolate, dir.relative.data(), dir.relative.size(), encoding)
|
||
.ToLocal(&relative)) {
|
||
return MaybeLocal<Value>();
|
||
}
|
||
dirs.push_back(relative);
|
||
}
|
||
|
||
if (!with_types) {
|
||
return scope.Escape(Array::New(isolate, names.data(), names.size()));
|
||
}
|
||
|
||
// One ArrayBuffer holding the types, then (4-byte aligned) the counts.
|
||
const size_t counts_offset = (types.size() + 3) & ~static_cast<size_t>(3);
|
||
Local<ArrayBuffer> ab = ArrayBuffer::New(
|
||
isolate, counts_offset + counts.size() * sizeof(uint32_t));
|
||
char* bytes = static_cast<char*>(ab->Data());
|
||
memcpy(bytes, types.data(), types.size());
|
||
memcpy(
|
||
bytes + counts_offset, counts.data(), counts.size() * sizeof(uint32_t));
|
||
|
||
Local<Value> parts[] = {
|
||
Array::New(isolate, names.data(), names.size()),
|
||
Uint8Array::New(ab, 0, types.size()),
|
||
Uint32Array::New(ab, counts_offset, counts.size()),
|
||
Array::New(isolate, dirs.data(), dirs.size()),
|
||
};
|
||
return scope.Escape(Array::New(isolate, parts, arraysize(parts)));
|
||
}
|
||
|
||
// The number of thread pool work items that share an asynchronous walk, and
|
||
// the number of helper threads a synchronous walk may start.
|
||
constexpr int kReadDirRecursiveWorkers = 4;
|
||
constexpr int kReadDirRecursiveSyncHelpers = kReadDirRecursiveWorkers - 1;
|
||
|
||
// An asynchronous recursive readdir: the walk, and the request it settles
|
||
// once every worker has finished.
|
||
class ReadDirRecursiveRequest {
|
||
public:
|
||
ReadDirRecursiveRequest(Environment* env,
|
||
FSReqBase* req_wrap,
|
||
std::string path,
|
||
enum encoding encoding,
|
||
bool with_types,
|
||
int workers)
|
||
: env_(env),
|
||
req_wrap_(req_wrap),
|
||
walk_(std::move(path)),
|
||
encoding_(encoding),
|
||
with_types_(with_types),
|
||
pending_(workers) {}
|
||
|
||
RecursiveReadDir* walk() { return &walk_; }
|
||
|
||
// Called on the main thread when a worker is done.
|
||
void OnWorkerDone(int status) {
|
||
CHECK(status == 0 || status == UV_ECANCELED);
|
||
if (status == UV_ECANCELED) cancelled_ = true;
|
||
if (--pending_ > 0) return;
|
||
|
||
Isolate* isolate = env_->isolate();
|
||
HandleScope handle_scope(isolate);
|
||
Context::Scope context_scope(env_->context());
|
||
|
||
// Release the request even if the environment is shutting down.
|
||
BaseObjectPtr<FSReqBase> req_wrap = std::move(req_wrap_);
|
||
req_wrap->Detach();
|
||
|
||
FS_ASYNC_TRACE_END1(UV_FS_SCANDIR, req_wrap.get(), "result", walk_.error())
|
||
if (cancelled_ || !env_->can_call_into_js()) return;
|
||
|
||
if (walk_.error() != 0) {
|
||
return req_wrap->Reject(UVException(isolate,
|
||
walk_.error(),
|
||
"scandir",
|
||
nullptr,
|
||
walk_.error_path().c_str()));
|
||
}
|
||
|
||
ResolveOrReject(req_wrap.get(), [&]() {
|
||
return MarshalRecursiveReadDir(isolate, walk_, encoding_, with_types_);
|
||
});
|
||
}
|
||
|
||
private:
|
||
Environment* const env_;
|
||
BaseObjectPtr<FSReqBase> req_wrap_;
|
||
RecursiveReadDir walk_;
|
||
const enum encoding encoding_;
|
||
const bool with_types_;
|
||
int pending_;
|
||
bool cancelled_ = false;
|
||
};
|
||
|
||
class ReadDirRecursiveWork final : public ThreadPoolWork {
|
||
public:
|
||
ReadDirRecursiveWork(Environment* env,
|
||
std::shared_ptr<ReadDirRecursiveRequest> request)
|
||
: ThreadPoolWork(env, "readdir_recursive"),
|
||
request_(std::move(request)) {}
|
||
|
||
void DoThreadPoolWork() override { request_->walk()->Run(); }
|
||
|
||
void AfterThreadPoolWork(int status) override {
|
||
std::unique_ptr<ReadDirRecursiveWork> self(this);
|
||
request_->OnWorkerDone(status);
|
||
}
|
||
|
||
private:
|
||
std::shared_ptr<ReadDirRecursiveRequest> request_;
|
||
};
|
||
|
||
} // namespace
|
||
|
||
// readdirRecursive(path, encoding, withTypes[, req])
|
||
static void ReadDirRecursive(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
|
||
|
||
bool with_types = args[2]->IsTrue();
|
||
|
||
// Every directory would need a permission check, and only the main thread
|
||
// can do those: lib walks the tree in JS when the permission model is on.
|
||
CHECK(!env->permission()->enabled());
|
||
|
||
if (argc > 3) { // readdirRecursive(path, encoding, withTypes, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
req_wrap_async->Init("scandir", nullptr, 0, encoding);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_SCANDIR, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
auto request =
|
||
std::make_shared<ReadDirRecursiveRequest>(env,
|
||
req_wrap_async,
|
||
path.ToString(),
|
||
encoding,
|
||
with_types,
|
||
kReadDirRecursiveWorkers);
|
||
for (int i = 0; i < kReadDirRecursiveWorkers; i++) {
|
||
(new ReadDirRecursiveWork(env, request))->ScheduleWork();
|
||
}
|
||
} else { // readdirRecursive(path, encoding, withTypes)
|
||
env->PrintSyncTrace();
|
||
FS_SYNC_TRACE_BEGIN(readdir);
|
||
RecursiveReadDir walk(path.ToString());
|
||
walk.RunWithHelpers(kReadDirRecursiveSyncHelpers);
|
||
FS_SYNC_TRACE_END(readdir);
|
||
|
||
if (walk.error() != 0) {
|
||
return env->ThrowUVException(
|
||
walk.error(), "scandir", nullptr, walk.error_path().c_str());
|
||
}
|
||
|
||
Local<Value> value;
|
||
if (MarshalRecursiveReadDir(isolate, walk, encoding, with_types)
|
||
.ToLocal(&value)) {
|
||
args.GetReturnValue().Set(value);
|
||
}
|
||
}
|
||
}
|
||
|
||
static inline Maybe<void> AsyncCheckOpenPermissions(Environment* env,
|
||
FSReqBase* req_wrap,
|
||
const BufferValue& path,
|
||
int flags) {
|
||
// These flags capture the intention of the open() call.
|
||
const int rwflags = flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR);
|
||
|
||
// These flags have write-like side effects even with O_RDONLY, at least on
|
||
// some operating systems. On Windows, for example, O_RDONLY | O_TEMPORARY
|
||
// can be used to delete a file. Bizarre.
|
||
const int write_as_side_effect = flags & (UV_FS_O_APPEND | UV_FS_O_CREAT |
|
||
UV_FS_O_TRUNC | UV_FS_O_TEMPORARY);
|
||
|
||
auto pathView = path.ToStringView();
|
||
if (rwflags != UV_FS_O_WRONLY) {
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
pathView,
|
||
Nothing<void>());
|
||
}
|
||
if (rwflags != UV_FS_O_RDONLY || write_as_side_effect) {
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
pathView,
|
||
Nothing<void>());
|
||
}
|
||
return JustVoid();
|
||
}
|
||
|
||
static inline Maybe<void> CheckOpenPermissions(Environment* env,
|
||
const BufferValue& path,
|
||
int flags) {
|
||
// These flags capture the intention of the open() call.
|
||
const int rwflags = flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR);
|
||
|
||
// These flags have write-like side effects even with O_RDONLY, at least on
|
||
// some operating systems. On Windows, for example, O_RDONLY | O_TEMPORARY
|
||
// can be used to delete a file. Bizarre.
|
||
const int write_as_side_effect = flags & (UV_FS_O_APPEND | UV_FS_O_CREAT |
|
||
UV_FS_O_TRUNC | UV_FS_O_TEMPORARY);
|
||
|
||
auto pathView = path.ToStringView();
|
||
if (rwflags != UV_FS_O_WRONLY) {
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
pathView,
|
||
Nothing<void>());
|
||
}
|
||
if (rwflags != UV_FS_O_RDONLY || write_as_side_effect) {
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
pathView,
|
||
Nothing<void>());
|
||
}
|
||
return JustVoid();
|
||
}
|
||
|
||
static void Open(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
CHECK(args[1]->IsInt32());
|
||
const int flags = args[1].As<Int32>()->Value();
|
||
|
||
CHECK(args[2]->IsInt32());
|
||
const int mode = args[2].As<Int32>()->Value();
|
||
|
||
if (argc > 3) { // open(path, flags, mode, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
if (AsyncCheckOpenPermissions(env, req_wrap_async, path, flags).IsNothing())
|
||
return;
|
||
req_wrap_async->set_is_plain_open(true);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_OPEN, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"open",
|
||
UTF8,
|
||
AfterInteger,
|
||
uv_fs_open,
|
||
*path,
|
||
flags,
|
||
mode);
|
||
} else { // open(path, flags, mode)
|
||
if (CheckOpenPermissions(env, path, flags).IsNothing()) return;
|
||
FSReqWrapSync req_wrap_sync("open", *path);
|
||
FS_SYNC_TRACE_BEGIN(open);
|
||
int result = SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_open, *path, flags, mode);
|
||
FS_SYNC_TRACE_END(open);
|
||
if (is_uv_error(result)) return;
|
||
env->AddUnmanagedFd(result);
|
||
args.GetReturnValue().Set(result);
|
||
}
|
||
}
|
||
|
||
static void OpenFileHandle(const FunctionCallbackInfo<Value>& args) {
|
||
Realm* realm = Realm::GetCurrent(args);
|
||
BindingData* binding_data = realm->GetBindingData<BindingData>();
|
||
Environment* env = realm->env();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(realm->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
CHECK(args[1]->IsInt32());
|
||
const int flags = args[1].As<Int32>()->Value();
|
||
|
||
CHECK(args[2]->IsInt32());
|
||
const int mode = args[2].As<Int32>()->Value();
|
||
|
||
if (CheckOpenPermissions(env, path, flags).IsNothing()) return;
|
||
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
if (req_wrap_async != nullptr) { // openFileHandle(path, flags, mode, req)
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_OPEN, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"open",
|
||
UTF8,
|
||
AfterOpenFileHandle,
|
||
uv_fs_open,
|
||
*path,
|
||
flags,
|
||
mode);
|
||
} else { // openFileHandle(path, flags, mode, undefined, ctx)
|
||
CHECK_EQ(argc, 5);
|
||
FSReqWrapSync req_wrap_sync;
|
||
FS_SYNC_TRACE_BEGIN(open);
|
||
int result;
|
||
if (!SyncCall(env,
|
||
args[4],
|
||
&req_wrap_sync,
|
||
"open",
|
||
uv_fs_open,
|
||
*path,
|
||
flags,
|
||
mode)
|
||
.To(&result)) {
|
||
// v8 error occurred while setting the context. propagate!
|
||
return;
|
||
}
|
||
FS_SYNC_TRACE_END(open);
|
||
if (result < 0) {
|
||
return; // syscall failed, no need to continue, error info is in ctx
|
||
}
|
||
FileHandle* fd = FileHandle::New(binding_data, result, {}, path.ToString());
|
||
if (fd == nullptr) return;
|
||
args.GetReturnValue().Set(fd->object());
|
||
}
|
||
}
|
||
|
||
static void CopyFile(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3); // src, dest, flags
|
||
|
||
int flags;
|
||
if (!GetValidFileMode(env, args[2], UV_FS_COPYFILE).To(&flags)) {
|
||
return;
|
||
}
|
||
|
||
BufferValue src(isolate, args[0]);
|
||
CHECK_NOT_NULL(*src);
|
||
ToNamespacedPath(env, &src);
|
||
|
||
BufferValue dest(isolate, args[1]);
|
||
CHECK_NOT_NULL(*dest);
|
||
ToNamespacedPath(env, &dest);
|
||
|
||
if (argc > 3) { // copyFile(src, dest, flags, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
src.ToStringView());
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
dest.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN2(UV_FS_COPYFILE,
|
||
req_wrap_async,
|
||
"src",
|
||
TRACE_STR_COPY(*src),
|
||
"dest",
|
||
TRACE_STR_COPY(*dest))
|
||
AsyncDestCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"copyfile",
|
||
*dest,
|
||
dest.length(),
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_copyfile,
|
||
*src,
|
||
*dest,
|
||
flags);
|
||
} else { // copyFile(src, dest, flags)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
dest.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("copyfile", *src, *dest);
|
||
FS_SYNC_TRACE_BEGIN(copyfile);
|
||
SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_copyfile, *src, *dest, flags);
|
||
FS_SYNC_TRACE_END(copyfile);
|
||
}
|
||
}
|
||
|
||
// Wrapper for write(2).
|
||
//
|
||
// bytesWritten = write(fd, buffer, offset, length, position, callback)
|
||
// 0 fd integer. file descriptor
|
||
// 1 buffer the data to write
|
||
// 2 offset where in the buffer to start from
|
||
// 3 length how much to write
|
||
// 4 position if integer, position to write at in the file.
|
||
// if null, write from the current position
|
||
static void WriteBuffer(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 4);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(Buffer::HasInstance(args[1]));
|
||
Local<Object> buffer_obj = args[1].As<Object>();
|
||
char* buffer_data = Buffer::Data(buffer_obj);
|
||
size_t buffer_length = Buffer::Length(buffer_obj);
|
||
|
||
CHECK(IsSafeJsInt(args[2]));
|
||
const int64_t off_64 = args[2].As<Integer>()->Value();
|
||
CHECK_GE(off_64, 0);
|
||
CHECK_LE(static_cast<uint64_t>(off_64), buffer_length);
|
||
const size_t off = static_cast<size_t>(off_64);
|
||
|
||
CHECK(args[3]->IsInt32());
|
||
const size_t len = static_cast<size_t>(args[3].As<Int32>()->Value());
|
||
CHECK(Buffer::IsWithinBounds(off, len, buffer_length));
|
||
CHECK_LE(len, buffer_length);
|
||
CHECK_GE(off + len, off);
|
||
|
||
const int64_t pos = GetOffset(args[4]);
|
||
|
||
char* buf = buffer_data + off;
|
||
uv_buf_t uvbuf = uv_buf_init(buf, len);
|
||
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 5);
|
||
if (req_wrap_async != nullptr) { // write(fd, buffer, off, len, pos, req)
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"write",
|
||
UTF8,
|
||
AfterInteger,
|
||
uv_fs_write,
|
||
fd,
|
||
&uvbuf,
|
||
1,
|
||
pos);
|
||
} else { // write(fd, buffer, off, len, pos, undefined, ctx)
|
||
CHECK_EQ(argc, 7);
|
||
FSReqWrapSync req_wrap_sync;
|
||
FS_SYNC_TRACE_BEGIN(write);
|
||
int bytesWritten;
|
||
if (!SyncCall(env,
|
||
args[6],
|
||
&req_wrap_sync,
|
||
"write",
|
||
uv_fs_write,
|
||
fd,
|
||
&uvbuf,
|
||
1,
|
||
pos)
|
||
.To(&bytesWritten)) {
|
||
FS_SYNC_TRACE_END(write, "bytesWritten", 0);
|
||
return;
|
||
}
|
||
FS_SYNC_TRACE_END(write, "bytesWritten", bytesWritten);
|
||
args.GetReturnValue().Set(bytesWritten);
|
||
}
|
||
}
|
||
|
||
// Wrapper for writev(2).
|
||
//
|
||
// bytesWritten = writev(fd, chunks, position, callback)
|
||
// 0 fd integer. file descriptor
|
||
// 1 chunks array of buffers to write
|
||
// 2 position if integer, position to write at in the file.
|
||
// if null, write from the current position
|
||
static void WriteBuffers(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(args[1]->IsArray());
|
||
Local<Array> chunks = args[1].As<Array>();
|
||
|
||
int64_t pos = GetOffset(args[2]);
|
||
|
||
MaybeStackBuffer<uv_buf_t> iovs(chunks->Length());
|
||
|
||
for (uint32_t i = 0; i < iovs.length(); i++) {
|
||
Local<Value> chunk;
|
||
if (!chunks->Get(env->context(), i).ToLocal(&chunk)) return;
|
||
CHECK(Buffer::HasInstance(chunk));
|
||
iovs[i] = uv_buf_init(Buffer::Data(chunk), Buffer::Length(chunk));
|
||
}
|
||
|
||
if (argc > 3) { // writeBuffers(fd, chunks, pos, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"write",
|
||
UTF8,
|
||
AfterInteger,
|
||
uv_fs_write,
|
||
fd,
|
||
*iovs,
|
||
iovs.length(),
|
||
pos);
|
||
} else { // writeBuffers(fd, chunks, pos)
|
||
FSReqWrapSync req_wrap_sync("write");
|
||
FS_SYNC_TRACE_BEGIN(write);
|
||
int bytesWritten = SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_write, fd, *iovs, iovs.length(), pos);
|
||
FS_SYNC_TRACE_END(write, "bytesWritten", bytesWritten);
|
||
if (is_uv_error(bytesWritten)) {
|
||
return;
|
||
}
|
||
args.GetReturnValue().Set(bytesWritten);
|
||
}
|
||
}
|
||
|
||
// Wrapper for write(2).
|
||
//
|
||
// bytesWritten = write(fd, string, position, enc, callback)
|
||
// 0 fd integer. file descriptor
|
||
// 1 string non-buffer values are converted to strings
|
||
// 2 position if integer, position to write at in the file.
|
||
// if null, write from the current position
|
||
// 3 enc encoding of string
|
||
static void WriteString(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 4);
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
const int64_t pos = GetOffset(args[2]);
|
||
|
||
const auto enc = ParseEncoding(isolate, args[3], UTF8);
|
||
|
||
Local<Value> value = args[1];
|
||
char* buf = nullptr;
|
||
size_t len;
|
||
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 4);
|
||
const bool is_async = req_wrap_async != nullptr;
|
||
|
||
// Avoid copying the string when it is externalized but only when:
|
||
// 1. The target encoding is compatible with the string's encoding, and
|
||
// 2. The write is synchronous, otherwise the string might get neutered
|
||
// while the request is in flight, and
|
||
// 3. For UCS2, when the host system is little-endian. Big-endian systems
|
||
// need to call StringBytes::Write() to ensure proper byte swapping.
|
||
// The const_casts are conceptually sound: memory is read but not written.
|
||
if (!is_async && value->IsString()) {
|
||
auto string = value.As<String>();
|
||
if ((enc == ASCII || enc == LATIN1) && string->IsExternalOneByte()) {
|
||
auto ext = string->GetExternalOneByteStringResource();
|
||
buf = const_cast<char*>(ext->data());
|
||
len = ext->length();
|
||
} else if (enc == UCS2 && string->IsExternalTwoByte()) {
|
||
if constexpr (IsLittleEndian()) {
|
||
auto ext = string->GetExternalStringResource();
|
||
buf = reinterpret_cast<char*>(const_cast<uint16_t*>(ext->data()));
|
||
len = ext->length() * sizeof(*ext->data());
|
||
}
|
||
}
|
||
}
|
||
|
||
if (is_async) { // write(fd, string, pos, enc, req)
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
if (!StringBytes::StorageSize(isolate, value, enc).To(&len)) return;
|
||
FSReqBase::FSReqBuffer& stack_buffer =
|
||
req_wrap_async->Init("write", len, enc);
|
||
// StorageSize may return too large a char, so correct the actual length
|
||
// by the write size
|
||
len = StringBytes::Write(isolate, *stack_buffer, len, args[1], enc);
|
||
stack_buffer.SetLengthAndZeroTerminate(len);
|
||
uv_buf_t uvbuf = uv_buf_init(*stack_buffer, len);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async)
|
||
int err =
|
||
req_wrap_async->Dispatch(uv_fs_write, fd, &uvbuf, 1, pos, AfterInteger);
|
||
if (err < 0) {
|
||
uv_fs_t* uv_req = req_wrap_async->req();
|
||
uv_req->result = err;
|
||
uv_req->path = nullptr;
|
||
AfterInteger(uv_req); // after may delete req_wrap_async if there is
|
||
// an error
|
||
}
|
||
} else { // write(fd, string, pos, enc, undefined, ctx)
|
||
CHECK_EQ(argc, 6);
|
||
FSReqBase::FSReqBuffer stack_buffer;
|
||
if (buf == nullptr) {
|
||
if (!StringBytes::StorageSize(isolate, value, enc).To(&len)) return;
|
||
stack_buffer.AllocateSufficientStorage(len + 1);
|
||
// StorageSize may return too large a char, so correct the actual length
|
||
// by the write size
|
||
len = StringBytes::Write(isolate, *stack_buffer, len, args[1], enc);
|
||
stack_buffer.SetLengthAndZeroTerminate(len);
|
||
buf = *stack_buffer;
|
||
}
|
||
uv_buf_t uvbuf = uv_buf_init(buf, len);
|
||
FSReqWrapSync req_wrap_sync("write");
|
||
FS_SYNC_TRACE_BEGIN(write);
|
||
int bytesWritten;
|
||
if (!SyncCall(env,
|
||
args[5],
|
||
&req_wrap_sync,
|
||
"write",
|
||
uv_fs_write,
|
||
fd,
|
||
&uvbuf,
|
||
1,
|
||
pos)
|
||
.To(&bytesWritten)) {
|
||
FS_SYNC_TRACE_END(write, "bytesWritten", 0);
|
||
return;
|
||
}
|
||
FS_SYNC_TRACE_END(write, "bytesWritten", bytesWritten);
|
||
args.GetReturnValue().Set(bytesWritten);
|
||
}
|
||
}
|
||
|
||
static void WriteFileUtf8(const FunctionCallbackInfo<Value>& args) {
|
||
// Fast C++ path for fs.writeFileSync(path, data) with utf8 encoding
|
||
// (file, data, options.flag, options.mode)
|
||
|
||
Environment* env = Environment::GetCurrent(args);
|
||
auto isolate = env->isolate();
|
||
|
||
CHECK_EQ(args.Length(), 4);
|
||
|
||
BufferValue value(isolate, args[1]);
|
||
CHECK_NOT_NULL(*value);
|
||
|
||
CHECK(args[2]->IsInt32());
|
||
const int flags = args[2].As<Int32>()->Value();
|
||
|
||
CHECK(args[3]->IsInt32());
|
||
const int mode = args[3].As<Int32>()->Value();
|
||
|
||
uv_file file;
|
||
|
||
bool is_fd = args[0]->IsInt32();
|
||
|
||
// Check for file descriptor
|
||
if (is_fd) {
|
||
file = args[0].As<Int32>()->Value();
|
||
} else {
|
||
BufferValue path(isolate, args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
if (CheckOpenPermissions(env, path, flags).IsNothing()) return;
|
||
|
||
FSReqWrapSync req_open("open", *path);
|
||
|
||
FS_SYNC_TRACE_BEGIN(open);
|
||
file =
|
||
SyncCallAndThrowOnError(env, &req_open, uv_fs_open, *path, flags, mode);
|
||
FS_SYNC_TRACE_END(open);
|
||
|
||
if (is_uv_error(file)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
int bytesWritten = 0;
|
||
uint32_t offset = 0;
|
||
|
||
const size_t length = value.length();
|
||
uv_buf_t uvbuf = uv_buf_init(value.out(), length);
|
||
|
||
FS_SYNC_TRACE_BEGIN(write);
|
||
while (offset < length) {
|
||
FSReqWrapSync req_write("write");
|
||
bytesWritten = SyncCallAndThrowOnError(
|
||
env, &req_write, uv_fs_write, file, &uvbuf, 1, -1);
|
||
|
||
// Write errored out
|
||
if (bytesWritten < 0) {
|
||
break;
|
||
}
|
||
|
||
offset += bytesWritten;
|
||
DCHECK_LE(offset, length);
|
||
uvbuf.base += bytesWritten;
|
||
uvbuf.len -= bytesWritten;
|
||
}
|
||
FS_SYNC_TRACE_END(write);
|
||
|
||
if (!is_fd) {
|
||
FSReqWrapSync req_close("close");
|
||
|
||
FS_SYNC_TRACE_BEGIN(close);
|
||
int result = SyncCallAndThrowOnError(env, &req_close, uv_fs_close, file);
|
||
FS_SYNC_TRACE_END(close);
|
||
|
||
if (is_uv_error(result)) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Wrapper for read(2).
|
||
*
|
||
* bytesRead = fs.read(fd, buffer, offset, length, position)
|
||
*
|
||
* 0 fd int32. file descriptor
|
||
* 1 buffer instance of Buffer
|
||
* 2 offset int64. offset to start reading into inside buffer
|
||
* 3 length int32. length to read
|
||
* 4 position int64. file position - -1 for current position
|
||
*/
|
||
static void Read(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 5);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(Buffer::HasInstance(args[1]));
|
||
Local<Object> buffer_obj = args[1].As<Object>();
|
||
char* buffer_data = Buffer::Data(buffer_obj);
|
||
size_t buffer_length = Buffer::Length(buffer_obj);
|
||
|
||
CHECK(IsSafeJsInt(args[2]));
|
||
const int64_t off_64 = args[2].As<Integer>()->Value();
|
||
CHECK_GE(off_64, 0);
|
||
CHECK_LT(static_cast<uint64_t>(off_64), buffer_length);
|
||
const size_t off = static_cast<size_t>(off_64);
|
||
|
||
CHECK(args[3]->IsInt32());
|
||
const size_t len = static_cast<size_t>(args[3].As<Int32>()->Value());
|
||
CHECK(Buffer::IsWithinBounds(off, len, buffer_length));
|
||
|
||
CHECK(IsSafeJsInt(args[4]) || args[4]->IsBigInt());
|
||
const int64_t pos = args[4]->IsNumber() ? args[4].As<Integer>()->Value()
|
||
: args[4].As<BigInt>()->Int64Value();
|
||
|
||
char* buf = buffer_data + off;
|
||
uv_buf_t uvbuf = uv_buf_init(buf, len);
|
||
|
||
if (argc > 5) { // read(fd, buffer, offset, len, pos, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 5);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"read",
|
||
UTF8,
|
||
AfterInteger,
|
||
uv_fs_read,
|
||
fd,
|
||
&uvbuf,
|
||
1,
|
||
pos);
|
||
} else { // read(fd, buffer, offset, len, pos)
|
||
FSReqWrapSync req_wrap_sync("read");
|
||
FS_SYNC_TRACE_BEGIN(read);
|
||
const int bytesRead = SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_read, fd, &uvbuf, 1, pos);
|
||
FS_SYNC_TRACE_END(read, "bytesRead", bytesRead);
|
||
|
||
if (is_uv_error(bytesRead)) {
|
||
return;
|
||
}
|
||
|
||
args.GetReturnValue().Set(bytesRead);
|
||
}
|
||
}
|
||
|
||
static void ReadFileUtf8(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
auto isolate = env->isolate();
|
||
|
||
CHECK_GE(args.Length(), 2);
|
||
|
||
CHECK(args[1]->IsInt32());
|
||
const int flags = args[1].As<Int32>()->Value();
|
||
|
||
uv_file file;
|
||
uv_fs_t req;
|
||
|
||
bool is_fd = args[0]->IsInt32();
|
||
|
||
// Check for file descriptor
|
||
if (is_fd) {
|
||
file = args[0].As<Int32>()->Value();
|
||
} else {
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
if (CheckOpenPermissions(env, path, flags).IsNothing()) return;
|
||
|
||
FS_SYNC_TRACE_BEGIN(open);
|
||
file = uv_fs_open(nullptr, &req, *path, flags, 0666, nullptr);
|
||
FS_SYNC_TRACE_END(open);
|
||
if (req.result < 0) {
|
||
uv_fs_req_cleanup(&req);
|
||
return env->ThrowUVException(
|
||
static_cast<int>(req.result), "open", nullptr, path.out());
|
||
}
|
||
uv_fs_req_cleanup(&req);
|
||
}
|
||
|
||
auto defer_close = OnScopeLeave([file, is_fd, &req]() {
|
||
if (!is_fd) {
|
||
FS_SYNC_TRACE_BEGIN(close);
|
||
CHECK_EQ(0, uv_fs_close(nullptr, &req, file, nullptr));
|
||
FS_SYNC_TRACE_END(close);
|
||
}
|
||
uv_fs_req_cleanup(&req);
|
||
});
|
||
|
||
// Past the first 8 KiB, read into one heap buffer sized from fstat(); the
|
||
// size is only a hint, reading continues until read() reports EOF.
|
||
std::string result{};
|
||
char buffer[8192];
|
||
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
|
||
|
||
char* big = nullptr;
|
||
size_t big_len = 0;
|
||
size_t big_cap = 0;
|
||
bool sized = false;
|
||
auto free_big = OnScopeLeave([&big]() { free(big); });
|
||
constexpr size_t kMinChunk = 64 * 1024;
|
||
constexpr size_t kMaxChunk = 8 * 1024 * 1024;
|
||
|
||
FS_SYNC_TRACE_BEGIN(read);
|
||
while (true) {
|
||
auto r = uv_fs_read(nullptr, &req, file, &buf, 1, -1, nullptr);
|
||
if (req.result < 0) {
|
||
FS_SYNC_TRACE_END(read);
|
||
// req will be cleaned up by scope leave.
|
||
return env->ThrowUVException(
|
||
static_cast<int>(req.result), "read", nullptr);
|
||
}
|
||
if (r <= 0) {
|
||
break;
|
||
}
|
||
if (big == nullptr) {
|
||
result.append(buf.base, r);
|
||
if (static_cast<size_t>(r) < sizeof(buffer)) {
|
||
continue;
|
||
}
|
||
// Switch to the heap buffer. A pipe can deliver more than kMinChunk in
|
||
// short reads before the first full one.
|
||
uv_fs_req_cleanup(&req);
|
||
big_cap = std::max(kMinChunk, result.size());
|
||
big = UncheckedMalloc<char>(big_cap);
|
||
if (big == nullptr) {
|
||
FS_SYNC_TRACE_END(read);
|
||
return THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
|
||
}
|
||
memcpy(big, result.data(), result.size());
|
||
big_len = result.size();
|
||
result = std::string();
|
||
} else {
|
||
big_len += static_cast<size_t>(r);
|
||
}
|
||
if (big_len == big_cap) {
|
||
// +1 leaves room for the read() that reports EOF.
|
||
size_t new_cap =
|
||
big_cap + std::min(kMaxChunk, std::max(kMinChunk, big_cap));
|
||
if (!sized) {
|
||
sized = true;
|
||
uv_fs_req_cleanup(&req);
|
||
uv_fs_t stat_req;
|
||
if (uv_fs_fstat(nullptr, &stat_req, file, nullptr) == 0) {
|
||
const uv_stat_t* const st =
|
||
static_cast<const uv_stat_t*>(stat_req.ptr);
|
||
if ((st->st_mode & S_IFMT) == S_IFREG &&
|
||
static_cast<uint64_t>(st->st_size) > big_len &&
|
||
static_cast<uint64_t>(st->st_size) <
|
||
static_cast<uint64_t>(v8::String::kMaxLength)) {
|
||
new_cap = static_cast<size_t>(st->st_size) + 1;
|
||
}
|
||
}
|
||
uv_fs_req_cleanup(&stat_req);
|
||
}
|
||
char* const grown = UncheckedRealloc<char>(big, new_cap);
|
||
if (grown == nullptr) {
|
||
FS_SYNC_TRACE_END(read);
|
||
return THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
|
||
}
|
||
big = grown;
|
||
big_cap = new_cap;
|
||
}
|
||
buf = uv_buf_init(big + big_len, std::min(kMaxChunk, big_cap - big_len));
|
||
}
|
||
FS_SYNC_TRACE_END(read);
|
||
|
||
Local<Value> val;
|
||
const std::string_view content = big != nullptr
|
||
? std::string_view(big, big_len)
|
||
: std::string_view(result);
|
||
if (!ToV8Value(env->context(), content, isolate).ToLocal(&val)) {
|
||
return;
|
||
}
|
||
|
||
args.GetReturnValue().Set(val);
|
||
}
|
||
|
||
// Reads a whole (small) file in ONE thread pool round trip -- open + fstat +
|
||
// read + close -- instead of one round trip per step, which is what dominates
|
||
// fs.readFile() for the typical small file and multiplies thread pool
|
||
// contention when many files are read at once. Files whose size exceeds
|
||
// `limit` are not read here: the job hands the open fd and the size back so
|
||
// that the caller continues with the chunked reader (which stays fair and
|
||
// abortable for large files); it then only saved the fstat() round trip.
|
||
//
|
||
// JS: const job = new ReadFileJob(path, flags, limit, trackFd);
|
||
// job.ondone = (err, buffer, fd, size, closeErr) => {...}; job.run(path);
|
||
// Exactly one of these outcomes is reported:
|
||
// err -- open/fstat/read failed (any fd opened here was closed);
|
||
// fd >= 0, size -- file is larger than `limit`: caller owns fd now (it is
|
||
// registered as an unmanaged fd iff trackFd, i.e. when the
|
||
// caller will close it through fs.close() rather than a
|
||
// FileHandle);
|
||
// buffer -- the whole content; closeErr set if only close() failed.
|
||
class ReadFileJob final : public AsyncWrap, public ThreadPoolWork {
|
||
public:
|
||
static void New(const FunctionCallbackInfo<Value>& args) {
|
||
CHECK(args.IsConstructCall());
|
||
Environment* env = Environment::GetCurrent(args);
|
||
CHECK_GE(args.Length(), 3);
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
CHECK(args[1]->IsInt32());
|
||
CHECK(args[2]->IsNumber());
|
||
const int flags = args[1].As<Int32>()->Value();
|
||
const double limit = args[2].As<Number>()->Value();
|
||
const bool track_fd = args.Length() > 3 && args[3]->IsTrue();
|
||
new ReadFileJob(env,
|
||
args.This(),
|
||
path.ToString(),
|
||
flags,
|
||
limit < 0 ? 0 : static_cast<uint64_t>(limit),
|
||
track_fd);
|
||
}
|
||
|
||
// Returns undefined when the job was scheduled, or the ERR_ACCESS_DENIED
|
||
// error the asynchronous open() would have delivered through its request
|
||
// (nothing is scheduled then; the caller passes it to the callback).
|
||
static void Run(const FunctionCallbackInfo<Value>& args) {
|
||
ReadFileJob* job;
|
||
ASSIGN_OR_RETURN_UNWRAP(&job, args.This());
|
||
Environment* env = job->AsyncWrap::env();
|
||
CHECK(!job->scheduled_);
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
Local<Value> access_error;
|
||
if (OpenPermissionError(env, path, job->flags_).ToLocal(&access_error)) {
|
||
args.GetReturnValue().Set(access_error);
|
||
return;
|
||
}
|
||
job->scheduled_ = true;
|
||
// Keep the wrapper alive while the work is in flight.
|
||
job->ClearWeak();
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, job)
|
||
job->ScheduleWork();
|
||
}
|
||
|
||
void DoThreadPoolWork() override {
|
||
uv_fs_t req;
|
||
int fd = uv_fs_open(nullptr, &req, path_.c_str(), flags_, 0666, nullptr);
|
||
uv_fs_req_cleanup(&req);
|
||
if (fd < 0) return Fail("open", fd);
|
||
|
||
int rc = uv_fs_fstat(nullptr, &req, fd, nullptr);
|
||
if (rc < 0) {
|
||
uv_fs_req_cleanup(&req);
|
||
Fail("fstat", rc);
|
||
CloseQuietly(fd);
|
||
return;
|
||
}
|
||
const uv_stat_t* const st = static_cast<const uv_stat_t*>(req.ptr);
|
||
const bool regular = (st->st_mode & S_IFMT) == S_IFREG;
|
||
const uint64_t size = regular ? static_cast<uint64_t>(st->st_size) : 0;
|
||
uv_fs_req_cleanup(&req);
|
||
|
||
if (size > limit_) {
|
||
// Too large to read in one go here: hand the fd back.
|
||
fd_ = fd;
|
||
size_ = size;
|
||
return;
|
||
}
|
||
|
||
// Known size: read exactly that much (like the chunked reader, stop when
|
||
// it has been read or at EOF, whichever comes first). Unknown size (0,
|
||
// e.g. procfs): grow until EOF.
|
||
size_t cap = size > 0 ? static_cast<size_t>(size) : kUnknownSizeChunk;
|
||
data_ = UncheckedMalloc<char>(cap);
|
||
if (data_ == nullptr) {
|
||
Fail("read", UV_ENOMEM);
|
||
CloseQuietly(fd);
|
||
return;
|
||
}
|
||
while (true) {
|
||
if (len_ == cap) {
|
||
if (size > 0) break; // Read all of the announced size.
|
||
// A file that claims size 0 keeps growing until EOF (or ENOMEM),
|
||
// like the chunked reader's buffer list did.
|
||
size_t new_cap = cap * 2;
|
||
char* grown = UncheckedRealloc<char>(data_, new_cap);
|
||
if (grown == nullptr) {
|
||
Fail("read", UV_ENOMEM);
|
||
break;
|
||
}
|
||
data_ = grown;
|
||
cap = new_cap;
|
||
}
|
||
uv_buf_t buf = uv_buf_init(data_ + len_,
|
||
static_cast<unsigned int>(std::min<size_t>(
|
||
cap - len_, kMaxReadChunk)));
|
||
int r = uv_fs_read(nullptr, &req, fd, &buf, 1, -1, nullptr);
|
||
uv_fs_req_cleanup(&req);
|
||
if (r < 0) {
|
||
Fail("read", r);
|
||
break;
|
||
}
|
||
if (r == 0) break;
|
||
len_ += static_cast<size_t>(r);
|
||
}
|
||
|
||
rc = uv_fs_close(nullptr, &req, fd, nullptr);
|
||
uv_fs_req_cleanup(&req);
|
||
if (rc < 0) close_error_ = rc;
|
||
if (error_ != 0) {
|
||
free(data_);
|
||
data_ = nullptr;
|
||
len_ = 0;
|
||
} else if (cap - len_ >= 4096) {
|
||
// Do not retain the slack of a size-0 (grown) or short file.
|
||
char* shrunk = UncheckedRealloc<char>(data_, len_ > 0 ? len_ : 1);
|
||
if (shrunk != nullptr) data_ = shrunk;
|
||
}
|
||
}
|
||
|
||
void AfterThreadPoolWork(int status) override {
|
||
Environment* env = AsyncWrap::env();
|
||
std::unique_ptr<ReadFileJob> self(this);
|
||
CHECK(status == 0 || status == UV_ECANCELED);
|
||
FS_ASYNC_TRACE_END0(UV_FS_READ, this)
|
||
if (status == UV_ECANCELED) {
|
||
if (fd_ >= 0) CloseQuietly(fd_);
|
||
return;
|
||
}
|
||
if (!env->can_call_into_js()) {
|
||
if (fd_ >= 0) CloseQuietly(fd_);
|
||
return;
|
||
}
|
||
HandleScope handle_scope(env->isolate());
|
||
Context::Scope context_scope(env->context());
|
||
Isolate* isolate = env->isolate();
|
||
|
||
Local<Value> argv[5] = {Null(isolate),
|
||
Undefined(isolate),
|
||
Integer::New(isolate, -1),
|
||
Undefined(isolate),
|
||
Undefined(isolate)};
|
||
if (error_ != 0) {
|
||
argv[0] = UVException(isolate, error_, syscall_, nullptr, path_.c_str());
|
||
} else if (fd_ >= 0) {
|
||
if (track_fd_) env->AddUnmanagedFd(fd_);
|
||
argv[2] = Integer::New(isolate, fd_);
|
||
argv[3] = Number::New(isolate, static_cast<double>(size_));
|
||
fd_ = -1;
|
||
} else {
|
||
Local<Object> buffer;
|
||
char* data = data_;
|
||
data_ = nullptr;
|
||
if (!Buffer::New(env, data, len_).ToLocal(&buffer)) {
|
||
// Buffer::New took ownership of data either way.
|
||
argv[0] = ERR_MEMORY_ALLOCATION_FAILED(isolate);
|
||
} else {
|
||
argv[1] = buffer;
|
||
}
|
||
if (close_error_ != 0) {
|
||
argv[4] = UVException(isolate, close_error_, "close");
|
||
}
|
||
}
|
||
MakeCallback(env->ondone_string(), arraysize(argv), argv);
|
||
}
|
||
|
||
~ReadFileJob() override {
|
||
free(data_);
|
||
if (fd_ >= 0) CloseQuietly(fd_);
|
||
}
|
||
|
||
bool IsNotIndicativeOfMemoryLeakAtExit() const override { return true; }
|
||
SET_NO_MEMORY_INFO()
|
||
SET_MEMORY_INFO_NAME(ReadFileJob)
|
||
SET_SELF_SIZE(ReadFileJob)
|
||
|
||
private:
|
||
friend class WriteFileJob;
|
||
static constexpr size_t kUnknownSizeChunk = 64 * 1024;
|
||
static constexpr size_t kMaxReadChunk = 256 * 1024 * 1024;
|
||
|
||
ReadFileJob(Environment* env,
|
||
Local<Object> object,
|
||
std::string&& path,
|
||
int flags,
|
||
uint64_t limit,
|
||
bool track_fd)
|
||
: AsyncWrap(env, object, AsyncWrap::PROVIDER_FSREQCALLBACK),
|
||
ThreadPoolWork(env, "fs.readfile"),
|
||
path_(std::move(path)),
|
||
limit_(limit),
|
||
flags_(flags),
|
||
track_fd_(track_fd) {
|
||
MakeWeak();
|
||
}
|
||
|
||
void Fail(const char* syscall, int error) {
|
||
syscall_ = syscall;
|
||
error_ = error;
|
||
}
|
||
|
||
// The permission checks AsyncCheckOpenPermissions() performs for open(),
|
||
// producing the error object instead of rejecting a request wrap.
|
||
static MaybeLocal<Value> OpenPermissionError(Environment* env,
|
||
const BufferValue& path,
|
||
int flags) {
|
||
if (!env->permission()->enabled()) [[likely]]
|
||
return {};
|
||
const int rwflags =
|
||
flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR);
|
||
const int write_as_side_effect =
|
||
flags &
|
||
(UV_FS_O_APPEND | UV_FS_O_CREAT | UV_FS_O_TRUNC | UV_FS_O_TEMPORARY);
|
||
const auto path_view = path.ToStringView();
|
||
auto denied = [&](permission::PermissionScope scope) -> MaybeLocal<Value> {
|
||
if (env->permission()->is_granted(env, scope, path_view) ||
|
||
env->permission()->warning_only()) {
|
||
return {};
|
||
}
|
||
Local<Value> err;
|
||
if (permission::CreateAccessDeniedError(env, scope, path_view)
|
||
.ToLocal(&err)) {
|
||
return err;
|
||
}
|
||
return Integer::New(env->isolate(), UV_EACCES);
|
||
};
|
||
if (rwflags != UV_FS_O_WRONLY) {
|
||
MaybeLocal<Value> err =
|
||
denied(permission::PermissionScope::kFileSystemRead);
|
||
if (!err.IsEmpty()) return err;
|
||
}
|
||
if (rwflags != UV_FS_O_RDONLY || write_as_side_effect) {
|
||
MaybeLocal<Value> err =
|
||
denied(permission::PermissionScope::kFileSystemWrite);
|
||
if (!err.IsEmpty()) return err;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
static void CloseQuietly(int fd) {
|
||
uv_fs_t req;
|
||
uv_fs_close(nullptr, &req, fd, nullptr);
|
||
uv_fs_req_cleanup(&req);
|
||
}
|
||
|
||
// Inputs.
|
||
std::string path_;
|
||
uint64_t limit_;
|
||
int flags_;
|
||
bool track_fd_;
|
||
bool scheduled_ = false;
|
||
// Results (written on the thread pool thread, read on the loop thread).
|
||
uint64_t size_ = 0;
|
||
size_t len_ = 0;
|
||
char* data_ = nullptr;
|
||
const char* syscall_ = nullptr;
|
||
int error_ = 0;
|
||
int close_error_ = 0;
|
||
int fd_ = -1;
|
||
};
|
||
|
||
// Writes a whole buffer to a file in ONE thread pool round trip -- open +
|
||
// write (until everything is written) + close -- for fs.writeFile() and
|
||
// fs.promises.writeFile() with a path, which otherwise pay one round trip per
|
||
// step.
|
||
//
|
||
// JS: const job = new WriteFileJob(path, flags, mode, buffer);
|
||
// job.ondone = (err) => {...}; job.run(path);
|
||
// `err` carries the syscall that failed ('open', 'write' or 'close'); the file
|
||
// descriptor opened here is always closed.
|
||
class WriteFileJob final : public AsyncWrap, public ThreadPoolWork {
|
||
public:
|
||
static void New(const FunctionCallbackInfo<Value>& args) {
|
||
CHECK(args.IsConstructCall());
|
||
Environment* env = Environment::GetCurrent(args);
|
||
CHECK_GE(args.Length(), 4);
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
CHECK(args[1]->IsInt32());
|
||
CHECK(args[2]->IsInt32());
|
||
CHECK(args[3]->IsArrayBufferView());
|
||
new WriteFileJob(env,
|
||
args.This(),
|
||
path.ToString(),
|
||
args[1].As<Int32>()->Value(),
|
||
args[2].As<Int32>()->Value(),
|
||
args[3].As<ArrayBufferView>());
|
||
}
|
||
|
||
// Returns undefined when the job was scheduled, or the ERR_ACCESS_DENIED
|
||
// error the asynchronous open() would have delivered (nothing is scheduled).
|
||
static void Run(const FunctionCallbackInfo<Value>& args) {
|
||
WriteFileJob* job;
|
||
ASSIGN_OR_RETURN_UNWRAP(&job, args.This());
|
||
Environment* env = job->AsyncWrap::env();
|
||
CHECK(!job->scheduled_);
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
Local<Value> access_error;
|
||
if (ReadFileJob::OpenPermissionError(env, path, job->flags_)
|
||
.ToLocal(&access_error)) {
|
||
args.GetReturnValue().Set(access_error);
|
||
return;
|
||
}
|
||
job->scheduled_ = true;
|
||
job->ClearWeak();
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, job)
|
||
job->ScheduleWork();
|
||
}
|
||
|
||
void DoThreadPoolWork() override {
|
||
uv_fs_t req;
|
||
int fd = uv_fs_open(nullptr, &req, path_.c_str(), flags_, mode_, nullptr);
|
||
uv_fs_req_cleanup(&req);
|
||
if (fd < 0) return Fail("open", fd);
|
||
|
||
size_t written = 0;
|
||
while (written < length_) {
|
||
uv_buf_t buf = uv_buf_init(data_ + written,
|
||
static_cast<unsigned int>(std::min<size_t>(
|
||
length_ - written, kMaxWriteChunk)));
|
||
int r = uv_fs_write(nullptr, &req, fd, &buf, 1, -1, nullptr);
|
||
uv_fs_req_cleanup(&req);
|
||
if (r < 0) {
|
||
Fail("write", r);
|
||
break;
|
||
}
|
||
written += static_cast<size_t>(r);
|
||
}
|
||
|
||
int rc = uv_fs_close(nullptr, &req, fd, nullptr);
|
||
uv_fs_req_cleanup(&req);
|
||
if (rc < 0 && error_ == 0) Fail("close", rc);
|
||
}
|
||
|
||
void AfterThreadPoolWork(int status) override {
|
||
Environment* env = AsyncWrap::env();
|
||
std::unique_ptr<WriteFileJob> self(this);
|
||
CHECK(status == 0 || status == UV_ECANCELED);
|
||
FS_ASYNC_TRACE_END0(UV_FS_WRITE, this)
|
||
if (status == UV_ECANCELED || !env->can_call_into_js()) return;
|
||
HandleScope handle_scope(env->isolate());
|
||
Context::Scope context_scope(env->context());
|
||
Isolate* isolate = env->isolate();
|
||
Local<Value> argv[1] = {Null(isolate)};
|
||
if (error_ != 0) {
|
||
argv[0] = UVException(isolate,
|
||
error_,
|
||
syscall_,
|
||
nullptr,
|
||
syscall_ == kOpen ? path_.c_str() : nullptr);
|
||
}
|
||
MakeCallback(env->ondone_string(), arraysize(argv), argv);
|
||
}
|
||
|
||
bool IsNotIndicativeOfMemoryLeakAtExit() const override { return true; }
|
||
void MemoryInfo(MemoryTracker* tracker) const override {
|
||
tracker->TrackField("buffer", buffer_);
|
||
if (copy_) tracker->TrackFieldWithSize("copy", length_);
|
||
}
|
||
SET_MEMORY_INFO_NAME(WriteFileJob)
|
||
SET_SELF_SIZE(WriteFileJob)
|
||
|
||
private:
|
||
static constexpr size_t kMaxWriteChunk = 256 * 1024 * 1024;
|
||
static constexpr const char* kOpen = "open";
|
||
|
||
WriteFileJob(Environment* env,
|
||
Local<Object> object,
|
||
std::string&& path,
|
||
int flags,
|
||
int mode,
|
||
Local<ArrayBufferView> view)
|
||
: AsyncWrap(env, object, AsyncWrap::PROVIDER_FSREQCALLBACK),
|
||
ThreadPoolWork(env, "fs.writefile"),
|
||
path_(std::move(path)),
|
||
flags_(flags),
|
||
mode_(mode) {
|
||
// Holding the backing store keeps the memory valid even if the buffer is
|
||
// detached or collected meanwhile; a resizable buffer can still have its
|
||
// pages decommitted by a shrink, so its contents are copied instead.
|
||
length_ = view->ByteLength();
|
||
backing_store_ = view->Buffer()->GetBackingStore();
|
||
if (backing_store_->IsResizableByUserJavaScript()) {
|
||
copy_.reset(new char[length_]);
|
||
memcpy(copy_.get(),
|
||
static_cast<char*>(backing_store_->Data()) + view->ByteOffset(),
|
||
length_);
|
||
data_ = copy_.get();
|
||
backing_store_.reset();
|
||
} else {
|
||
buffer_.Reset(env->isolate(), view);
|
||
data_ = static_cast<char*>(backing_store_->Data()) + view->ByteOffset();
|
||
}
|
||
MakeWeak();
|
||
}
|
||
|
||
void Fail(const char* syscall, int error) {
|
||
syscall_ = syscall;
|
||
error_ = error;
|
||
}
|
||
|
||
const std::string path_;
|
||
v8::Global<v8::ArrayBufferView> buffer_;
|
||
std::shared_ptr<v8::BackingStore> backing_store_;
|
||
std::unique_ptr<char[]> copy_;
|
||
char* data_ = nullptr;
|
||
size_t length_ = 0;
|
||
const int flags_;
|
||
const int mode_;
|
||
bool scheduled_ = false;
|
||
int error_ = 0;
|
||
const char* syscall_ = nullptr;
|
||
};
|
||
|
||
// Wrapper for readv(2).
|
||
//
|
||
// bytesRead = fs.readv(fd, buffers[, position], callback)
|
||
// 0 fd integer. file descriptor
|
||
// 1 buffers array of buffers to read
|
||
// 2 position if integer, position to read at in the file.
|
||
// if null, read from the current position
|
||
static void ReadBuffers(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(args[1]->IsArray());
|
||
Local<Array> buffers = args[1].As<Array>();
|
||
|
||
int64_t pos = GetOffset(args[2]); // -1 if not a valid JS int
|
||
|
||
MaybeStackBuffer<uv_buf_t> iovs(buffers->Length());
|
||
|
||
// Init uv buffers from ArrayBufferViews
|
||
for (uint32_t i = 0; i < iovs.length(); i++) {
|
||
Local<Value> buffer;
|
||
if (!buffers->Get(env->context(), i).ToLocal(&buffer)) return;
|
||
CHECK(Buffer::HasInstance(buffer));
|
||
iovs[i] = uv_buf_init(Buffer::Data(buffer), Buffer::Length(buffer));
|
||
}
|
||
|
||
if (argc > 3) { // readBuffers(fd, buffers, pos, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"read",
|
||
UTF8,
|
||
AfterInteger,
|
||
uv_fs_read,
|
||
fd,
|
||
*iovs,
|
||
iovs.length(),
|
||
pos);
|
||
} else { // readBuffers(fd, buffers, undefined, ctx)
|
||
FSReqWrapSync req_wrap_sync("read");
|
||
FS_SYNC_TRACE_BEGIN(read);
|
||
int bytesRead = SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_read, fd, *iovs, iovs.length(), pos);
|
||
FS_SYNC_TRACE_END(read, "bytesRead", bytesRead);
|
||
if (is_uv_error(bytesRead)) {
|
||
return;
|
||
}
|
||
args.GetReturnValue().Set(bytesRead);
|
||
}
|
||
}
|
||
|
||
/* fs.chmod(path, mode);
|
||
* Wrapper for chmod(1) / EIO_CHMOD
|
||
*/
|
||
static void Chmod(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
|
||
|
||
CHECK(args[1]->IsInt32());
|
||
int mode = args[1].As<Int32>()->Value();
|
||
|
||
if (argc > 2) { // chmod(path, mode, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_CHMOD, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"chmod",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_chmod,
|
||
*path,
|
||
mode);
|
||
} else { // chmod(path, mode)
|
||
FSReqWrapSync req_wrap_sync("chmod", *path);
|
||
FS_SYNC_TRACE_BEGIN(chmod);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_chmod, *path, mode);
|
||
FS_SYNC_TRACE_END(chmod);
|
||
}
|
||
}
|
||
|
||
/* fs.fchmod(fd, mode);
|
||
* Wrapper for fchmod(1) / EIO_FCHMOD
|
||
*/
|
||
static void FChmod(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(args[1]->IsInt32());
|
||
const int mode = args[1].As<Int32>()->Value();
|
||
|
||
if (argc > 2) { // fchmod(fd, mode, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_FCHMOD, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"fchmod",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_fchmod,
|
||
fd,
|
||
mode);
|
||
} else { // fchmod(fd, mode)
|
||
FSReqWrapSync req_wrap_sync("fchmod");
|
||
FS_SYNC_TRACE_BEGIN(fchmod);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fchmod, fd, mode);
|
||
FS_SYNC_TRACE_END(fchmod);
|
||
}
|
||
}
|
||
|
||
/* fs.chown(path, uid, gid);
|
||
* Wrapper for chown(1) / EIO_CHOWN
|
||
*/
|
||
static void Chown(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
CHECK(IsSafeJsInt(args[1]));
|
||
const auto uid = FromV8Value<uv_uid_t, true>(args[1]);
|
||
|
||
CHECK(IsSafeJsInt(args[2]));
|
||
const auto gid = FromV8Value<uv_gid_t, true>(args[2]);
|
||
|
||
if (argc > 3) { // chown(path, uid, gid, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_CHOWN, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"chown",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_chown,
|
||
*path,
|
||
uid,
|
||
gid);
|
||
} else { // chown(path, uid, gid)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("chown", *path);
|
||
FS_SYNC_TRACE_BEGIN(chown);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_chown, *path, uid, gid);
|
||
FS_SYNC_TRACE_END(chown);
|
||
}
|
||
}
|
||
|
||
/* fs.fchown(fd, uid, gid);
|
||
* Wrapper for fchown(1) / EIO_FCHOWN
|
||
*/
|
||
static void FChown(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(IsSafeJsInt(args[1]));
|
||
const auto uid = FromV8Value<uv_uid_t, true>(args[1]);
|
||
|
||
CHECK(IsSafeJsInt(args[2]));
|
||
const auto gid = FromV8Value<uv_gid_t, true>(args[2]);
|
||
|
||
if (argc > 3) { // fchown(fd, uid, gid, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_FCHOWN, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"fchown",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_fchown,
|
||
fd,
|
||
uid,
|
||
gid);
|
||
} else { // fchown(fd, uid, gid)
|
||
FSReqWrapSync req_wrap_sync("fchown");
|
||
FS_SYNC_TRACE_BEGIN(fchown);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fchown, fd, uid, gid);
|
||
FS_SYNC_TRACE_END(fchown);
|
||
}
|
||
}
|
||
|
||
static void LChown(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
|
||
CHECK(IsSafeJsInt(args[1]));
|
||
const auto uid = FromV8Value<uv_uid_t, true>(args[1]);
|
||
|
||
CHECK(IsSafeJsInt(args[2]));
|
||
const auto gid = FromV8Value<uv_gid_t, true>(args[2]);
|
||
|
||
if (argc > 3) { // lchown(path, uid, gid, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
path.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_LCHOWN, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"lchown",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_lchown,
|
||
*path,
|
||
uid,
|
||
gid);
|
||
} else { // lchown(path, uid, gid)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
path.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("lchown", *path);
|
||
FS_SYNC_TRACE_BEGIN(lchown);
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_lchown, *path, uid, gid);
|
||
FS_SYNC_TRACE_END(lchown);
|
||
}
|
||
}
|
||
|
||
static void UTimes(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
|
||
|
||
CHECK(args[1]->IsNumber());
|
||
const double atime = args[1].As<Number>()->Value();
|
||
|
||
CHECK(args[2]->IsNumber());
|
||
const double mtime = args[2].As<Number>()->Value();
|
||
|
||
if (argc > 3) { // utimes(path, atime, mtime, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_UTIME, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"utime",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_utime,
|
||
*path,
|
||
atime,
|
||
mtime);
|
||
} else { // utimes(path, atime, mtime)
|
||
FSReqWrapSync req_wrap_sync("utime", *path);
|
||
FS_SYNC_TRACE_BEGIN(utimes);
|
||
SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_utime, *path, atime, mtime);
|
||
FS_SYNC_TRACE_END(utimes);
|
||
}
|
||
}
|
||
|
||
static void FUTimes(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
int fd;
|
||
if (!GetValidatedFd(env, args[0]).To(&fd)) {
|
||
return;
|
||
}
|
||
|
||
CHECK(args[1]->IsNumber());
|
||
const double atime = args[1].As<Number>()->Value();
|
||
|
||
CHECK(args[2]->IsNumber());
|
||
const double mtime = args[2].As<Number>()->Value();
|
||
|
||
if (argc > 3) { // futimes(fd, atime, mtime, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN0(UV_FS_FUTIME, req_wrap_async)
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"futime",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_futime,
|
||
fd,
|
||
atime,
|
||
mtime);
|
||
} else { // futimes(fd, atime, mtime)
|
||
FSReqWrapSync req_wrap_sync("futime");
|
||
FS_SYNC_TRACE_BEGIN(futimes);
|
||
SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_futime, fd, atime, mtime);
|
||
FS_SYNC_TRACE_END(futimes);
|
||
}
|
||
}
|
||
|
||
static void LUTimes(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 3);
|
||
|
||
BufferValue path(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*path);
|
||
ToNamespacedPath(env, &path);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
|
||
|
||
CHECK(args[1]->IsNumber());
|
||
const double atime = args[1].As<Number>()->Value();
|
||
|
||
CHECK(args[2]->IsNumber());
|
||
const double mtime = args[2].As<Number>()->Value();
|
||
|
||
if (argc > 3) { // lutimes(path, atime, mtime, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_LUTIME, req_wrap_async, "path", TRACE_STR_COPY(*path))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"lutime",
|
||
UTF8,
|
||
AfterNoArgs,
|
||
uv_fs_lutime,
|
||
*path,
|
||
atime,
|
||
mtime);
|
||
} else { // lutimes(path, atime, mtime)
|
||
FSReqWrapSync req_wrap_sync("lutime", *path);
|
||
FS_SYNC_TRACE_BEGIN(lutimes);
|
||
SyncCallAndThrowOnError(
|
||
env, &req_wrap_sync, uv_fs_lutime, *path, atime, mtime);
|
||
FS_SYNC_TRACE_END(lutimes);
|
||
}
|
||
}
|
||
|
||
// Appends the placeholder that mkdtemp() and mkstemp() replace.
|
||
static void AppendTemplateSuffix(BufferValue* tmpl) {
|
||
static constexpr std::string_view suffix = "XXXXXX";
|
||
const auto prefix_length = tmpl->length();
|
||
tmpl->AllocateSufficientStorage(prefix_length + suffix.size() + 1);
|
||
memcpy(tmpl->out() + prefix_length, suffix.data(), suffix.size());
|
||
tmpl->SetLengthAndZeroTerminate(prefix_length + suffix.size());
|
||
}
|
||
|
||
static void Mkdtemp(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue tmpl(isolate, args[0]);
|
||
AppendTemplateSuffix(&tmpl);
|
||
|
||
CHECK_NOT_NULL(*tmpl);
|
||
|
||
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
|
||
|
||
if (argc > 2) { // mkdtemp(tmpl, encoding, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
req_wrap_async,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
tmpl.ToStringView());
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_MKDTEMP, req_wrap_async, "path", TRACE_STR_COPY(*tmpl))
|
||
AsyncCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"mkdtemp",
|
||
encoding,
|
||
AfterStringPath,
|
||
uv_fs_mkdtemp,
|
||
*tmpl);
|
||
} else { // mkdtemp(tmpl, encoding)
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemWrite,
|
||
tmpl.ToStringView());
|
||
FSReqWrapSync req_wrap_sync("mkdtemp", *tmpl);
|
||
FS_SYNC_TRACE_BEGIN(mkdtemp);
|
||
int result =
|
||
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_mkdtemp, *tmpl);
|
||
FS_SYNC_TRACE_END(mkdtemp);
|
||
if (is_uv_error(result)) {
|
||
return;
|
||
}
|
||
Local<Value> ret;
|
||
if (StringBytes::Encode(isolate, req_wrap_sync.req.path, encoding)
|
||
.ToLocal(&ret)) {
|
||
args.GetReturnValue().Set(ret);
|
||
}
|
||
}
|
||
}
|
||
|
||
// mkstemp() creates the file and opens it for reading and writing, so it
|
||
// needs the same permissions as the equivalent open().
|
||
static constexpr int kMkstempFlags =
|
||
UV_FS_O_RDWR | UV_FS_O_CREAT | UV_FS_O_EXCL;
|
||
|
||
static void Mkstemp(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
const int argc = args.Length();
|
||
CHECK_GE(argc, 2);
|
||
|
||
BufferValue tmpl(isolate, args[0]);
|
||
AppendTemplateSuffix(&tmpl);
|
||
CHECK_NOT_NULL(*tmpl);
|
||
|
||
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
|
||
|
||
if (argc > 2) { // mkstemp(tmpl, encoding, req)
|
||
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
|
||
CHECK_NOT_NULL(req_wrap_async);
|
||
if (AsyncCheckOpenPermissions(env, req_wrap_async, tmpl, kMkstempFlags)
|
||
.IsNothing()) {
|
||
return;
|
||
}
|
||
// The callback API gets a file descriptor, the promise API a FileHandle.
|
||
uv_fs_cb after =
|
||
args[2]->IsObject() ? AfterMkstemp : AfterMkstempFileHandle;
|
||
FS_ASYNC_TRACE_BEGIN1(
|
||
UV_FS_MKSTEMP, req_wrap_async, "path", TRACE_STR_COPY(*tmpl))
|
||
// The template is saved in the request to report it if mkstemp() fails.
|
||
AsyncDestCall(env,
|
||
req_wrap_async,
|
||
args,
|
||
"mkstemp",
|
||
*tmpl,
|
||
tmpl.length(),
|
||
encoding,
|
||
after,
|
||
uv_fs_mkstemp,
|
||
*tmpl);
|
||
} else { // mkstemp(tmpl, encoding)
|
||
if (CheckOpenPermissions(env, tmpl, kMkstempFlags).IsNothing()) return;
|
||
FSReqWrapSync req_wrap_sync("mkstemp", *tmpl);
|
||
FS_SYNC_TRACE_BEGIN(mkstemp);
|
||
int fd = SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_mkstemp, *tmpl);
|
||
FS_SYNC_TRACE_END(mkstemp);
|
||
if (is_uv_error(fd)) {
|
||
return;
|
||
}
|
||
MaybeLocal<Value> result = MkstempResult(
|
||
isolate,
|
||
fd,
|
||
[&]() {
|
||
return StringBytes::Encode(isolate, req_wrap_sync.req.path, encoding);
|
||
},
|
||
[&]() { return MkstempFd(env, fd); });
|
||
args.GetReturnValue().Set(result.FromMaybe(Local<Value>()));
|
||
}
|
||
}
|
||
|
||
static void GetFormatOfExtensionlessFile(
|
||
const FunctionCallbackInfo<Value>& args) {
|
||
CHECK_EQ(args.Length(), 1);
|
||
CHECK(args[0]->IsString());
|
||
|
||
Environment* env = Environment::GetCurrent(args);
|
||
BufferValue input(args.GetIsolate(), args[0]);
|
||
ToNamespacedPath(env, &input);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, input.ToStringView());
|
||
|
||
uv_fs_t req;
|
||
FS_SYNC_TRACE_BEGIN(open)
|
||
uv_file file = uv_fs_open(nullptr, &req, input.out(), O_RDONLY, 0, nullptr);
|
||
FS_SYNC_TRACE_END(open);
|
||
|
||
if (req.result < 0) {
|
||
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT);
|
||
}
|
||
|
||
auto cleanup = OnScopeLeave([&req, &file]() {
|
||
FS_SYNC_TRACE_BEGIN(close);
|
||
CHECK_EQ(0, uv_fs_close(nullptr, &req, file, nullptr));
|
||
FS_SYNC_TRACE_END(close);
|
||
uv_fs_req_cleanup(&req);
|
||
});
|
||
|
||
char buffer[4];
|
||
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
|
||
int err = uv_fs_read(nullptr, &req, file, &buf, 1, 0, nullptr);
|
||
|
||
if (err < 0) {
|
||
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT);
|
||
}
|
||
|
||
// We do this by taking advantage of the fact that all Wasm files start with
|
||
// the header `0x00 0x61 0x73 0x6d`
|
||
if (buffer[0] == 0x00 && buffer[1] == 0x61 && buffer[2] == 0x73 &&
|
||
buffer[3] == 0x6d) {
|
||
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_WASM);
|
||
}
|
||
|
||
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT);
|
||
}
|
||
|
||
static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
CHECK_EQ(args.Length(), 4); // src, dest, dereference, recursive
|
||
|
||
BufferValue src(isolate, args[0]);
|
||
CHECK_NOT_NULL(*src);
|
||
ToNamespacedPath(env, &src);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
|
||
|
||
auto src_path = src.ToPath();
|
||
|
||
BufferValue dest(isolate, args[1]);
|
||
CHECK_NOT_NULL(*dest);
|
||
ToNamespacedPath(env, &dest);
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
|
||
|
||
auto dest_path = dest.ToPath();
|
||
bool dereference = args[2]->IsTrue();
|
||
bool recursive = args[3]->IsTrue();
|
||
|
||
std::error_code error_code;
|
||
auto src_status = dereference
|
||
? std::filesystem::symlink_status(src_path, error_code)
|
||
: std::filesystem::status(src_path, error_code);
|
||
|
||
if (error_code) {
|
||
#ifdef _WIN32
|
||
int errorno = uv_translate_sys_error(error_code.value());
|
||
#else
|
||
int errorno =
|
||
error_code.value() > 0 ? -error_code.value() : error_code.value();
|
||
#endif
|
||
return env->ThrowUVException(
|
||
errorno, dereference ? "stat" : "lstat", nullptr, src.out());
|
||
}
|
||
auto dest_status =
|
||
dereference ? std::filesystem::status(dest_path, error_code)
|
||
: std::filesystem::symlink_status(dest_path, error_code);
|
||
|
||
bool dest_exists = !error_code && dest_status.type() !=
|
||
std::filesystem::file_type::not_found;
|
||
bool src_is_dir =
|
||
(src_status.type() == std::filesystem::file_type::directory) ||
|
||
(dereference && src_status.type() == std::filesystem::file_type::symlink);
|
||
|
||
auto src_path_str = ConvertPathToUTF8(src_path);
|
||
auto dest_path_str = ConvertPathToUTF8(dest_path);
|
||
|
||
if (!error_code) {
|
||
// Check if src and dest are identical.
|
||
if (std::filesystem::equivalent(src_path, dest_path)) {
|
||
static constexpr const char* message =
|
||
"src and dest cannot be the same %s";
|
||
return THROW_ERR_FS_CP_EINVAL(env, message, dest_path_str);
|
||
}
|
||
|
||
const bool dest_is_dir =
|
||
dest_status.type() == std::filesystem::file_type::directory;
|
||
if (src_is_dir && !dest_is_dir) {
|
||
static constexpr const char* message =
|
||
"Cannot overwrite non-directory %s with directory %s";
|
||
return THROW_ERR_FS_CP_DIR_TO_NON_DIR(
|
||
env, message, dest_path_str, src_path_str);
|
||
}
|
||
|
||
if (!src_is_dir && dest_is_dir) {
|
||
static constexpr const char* message =
|
||
"Cannot overwrite directory %s with non-directory %s";
|
||
return THROW_ERR_FS_CP_NON_DIR_TO_DIR(
|
||
env, message, dest_path_str, src_path_str);
|
||
}
|
||
}
|
||
|
||
if (!src_path_str.ends_with(std::filesystem::path::preferred_separator)) {
|
||
src_path_str += std::filesystem::path::preferred_separator;
|
||
}
|
||
// Check if dest_path is a subdirectory of src_path.
|
||
if (src_is_dir && dest_path_str.starts_with(src_path_str)) {
|
||
static constexpr const char* message =
|
||
"Cannot copy %s to a subdirectory of self %s";
|
||
return THROW_ERR_FS_CP_EINVAL(env, message, src_path_str, dest_path_str);
|
||
}
|
||
|
||
auto dest_parent = dest_path.parent_path();
|
||
// "/" parent is itself. Therefore, we need to check if the parent is the same
|
||
// as itself.
|
||
while (src_path.parent_path() != dest_parent &&
|
||
dest_parent.has_parent_path() &&
|
||
dest_parent.parent_path() != dest_parent) {
|
||
if (std::filesystem::equivalent(
|
||
src_path, dest_path.parent_path(), error_code)) {
|
||
static constexpr const char* message =
|
||
"Cannot copy %s to a subdirectory of self %s";
|
||
return THROW_ERR_FS_CP_EINVAL(env, message, src_path_str, dest_path_str);
|
||
}
|
||
|
||
// If equivalent fails, it's highly likely that dest_parent does not exist
|
||
if (error_code) {
|
||
break;
|
||
}
|
||
|
||
dest_parent = dest_parent.parent_path();
|
||
}
|
||
|
||
if (src_is_dir && !recursive) {
|
||
static constexpr const char* message =
|
||
"Recursive option not enabled, cannot copy a directory: %s";
|
||
return THROW_ERR_FS_EISDIR(env, message, src_path_str);
|
||
}
|
||
|
||
switch (src_status.type()) {
|
||
case std::filesystem::file_type::socket: {
|
||
static constexpr const char* message = "Cannot copy a socket file: %s";
|
||
return THROW_ERR_FS_CP_SOCKET(env, message, dest_path_str);
|
||
}
|
||
case std::filesystem::file_type::fifo: {
|
||
static constexpr const char* message = "Cannot copy a FIFO pipe: %s";
|
||
return THROW_ERR_FS_CP_FIFO_PIPE(env, message, dest_path_str);
|
||
}
|
||
case std::filesystem::file_type::unknown: {
|
||
static constexpr const char* message =
|
||
"Cannot copy an unknown file type: %s";
|
||
return THROW_ERR_FS_CP_UNKNOWN(env, message, dest_path_str);
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Optimization opportunity: Check if this "exists" call is good for
|
||
// performance.
|
||
if (!dest_exists || !std::filesystem::exists(dest_path.parent_path())) {
|
||
std::filesystem::create_directories(dest_path.parent_path(), error_code);
|
||
}
|
||
}
|
||
|
||
std::vector<std::string> normalizePathToArray(
|
||
const std::filesystem::path& path) {
|
||
std::vector<std::string> parts;
|
||
std::error_code error;
|
||
std::filesystem::path absPath = std::filesystem::absolute(path, error);
|
||
if (error) absPath = path;
|
||
#ifdef _WIN32
|
||
auto wstr = absPath.wstring();
|
||
if (wstr.starts_with(L"\\\\?\\")) {
|
||
absPath = std::filesystem::path(wstr.substr(4));
|
||
}
|
||
#endif
|
||
for (const auto& part : absPath) {
|
||
if (!part.empty()) parts.push_back(part.string());
|
||
}
|
||
return parts;
|
||
}
|
||
|
||
bool isInsideDir(const std::filesystem::path& src,
|
||
const std::filesystem::path& dest) {
|
||
auto srcArr = normalizePathToArray(src);
|
||
auto destArr = normalizePathToArray(dest);
|
||
if (srcArr.size() > destArr.size()) return false;
|
||
return std::equal(srcArr.begin(), srcArr.end(), destArr.begin());
|
||
}
|
||
|
||
namespace {
|
||
|
||
// An fs.cp error recorded on whatever thread performed the copy; Throw() /
|
||
// ToException() turn it into the error the JavaScript caller sees.
|
||
struct CpError {
|
||
enum Kind {
|
||
kNone,
|
||
kErrno,
|
||
kUv,
|
||
kEinval,
|
||
kSymlinkToSubdirectory,
|
||
kEexist,
|
||
kSocket,
|
||
kFifo,
|
||
kUnknown
|
||
};
|
||
Kind kind = kNone;
|
||
int code = 0;
|
||
const char* syscall = "cp";
|
||
std::string message;
|
||
std::string path;
|
||
|
||
static CpError Std(const std::error_code& error, const std::string& path) {
|
||
return {kErrno, error.value(), "cp", error.message(), path};
|
||
}
|
||
static CpError Uv(int code, const char* syscall, const std::string& path) {
|
||
return {kUv, code, syscall, {}, path};
|
||
}
|
||
|
||
Local<Value> ToException(Environment* env) const {
|
||
Isolate* isolate = env->isolate();
|
||
switch (kind) {
|
||
case kErrno:
|
||
return ErrnoException(
|
||
isolate, code, syscall, message.c_str(), path.c_str());
|
||
case kUv:
|
||
return UVException(isolate, code, syscall, nullptr, path.c_str());
|
||
case kEinval:
|
||
return ERR_FS_CP_EINVAL(isolate, "%s", message);
|
||
case kSymlinkToSubdirectory:
|
||
return ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY(isolate, "%s", message);
|
||
case kEexist:
|
||
return ERR_FS_CP_EEXIST(isolate, "%s", message);
|
||
// Sockets, FIFOs and unknown entries are reported to JS by kind and
|
||
// path (see CpDirJob), cpSync skips them; neither builds an error here.
|
||
case kSocket:
|
||
case kFifo:
|
||
case kUnknown:
|
||
case kNone:
|
||
break;
|
||
}
|
||
UNREACHABLE();
|
||
}
|
||
|
||
void Throw(Environment* env) const {
|
||
env->isolate()->ThrowException(ToException(env));
|
||
}
|
||
};
|
||
|
||
CpError CopyUtimes(const std::filesystem::path& src,
|
||
const std::filesystem::path& dest) {
|
||
uv_fs_t req;
|
||
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
|
||
|
||
auto src_path_str = ConvertPathToUTF8(src);
|
||
int result = uv_fs_stat(nullptr, &req, src_path_str.c_str(), nullptr);
|
||
if (is_uv_error(result)) {
|
||
return CpError::Uv(result, "stat", src_path_str);
|
||
}
|
||
|
||
const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
|
||
const double source_atime = s->st_atim.tv_sec + s->st_atim.tv_nsec / 1e9;
|
||
const double source_mtime = s->st_mtim.tv_sec + s->st_mtim.tv_nsec / 1e9;
|
||
|
||
auto dest_file_path_str = ConvertPathToUTF8(dest);
|
||
int utime_result = uv_fs_utime(nullptr,
|
||
&req,
|
||
dest_file_path_str.c_str(),
|
||
source_atime,
|
||
source_mtime,
|
||
nullptr);
|
||
if (is_uv_error(utime_result)) {
|
||
return CpError::Uv(utime_result, "utime", dest_file_path_str);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
struct CpDirOptions {
|
||
bool force;
|
||
bool dereference;
|
||
bool error_on_exist;
|
||
bool verbatim_symlinks;
|
||
bool preserve_timestamps;
|
||
// Set for fs.cp(), which only takes this path for a destination that did
|
||
// not exist: nothing already present is ever opened for writing or
|
||
// followed. Directories are created with mkdir() and files with an
|
||
// exclusive uv_fs_copyfile(), so anything that appears in their place
|
||
// (a symbolic link included) is EEXIST; sockets, FIFOs and unknown
|
||
// entries are rejected as the JavaScript walk does; relative link targets
|
||
// are resolved lexically, as path.resolve() would. fs.cpSync() merges
|
||
// into existing directories, skips those entries and canonicalizes link
|
||
// targets.
|
||
bool fresh_destination;
|
||
// fs.copyFile() mode flags (COPYFILE_FICLONE etc.) for fresh_destination.
|
||
int copyfile_flags;
|
||
};
|
||
|
||
CpError CopyFileFresh(const std::filesystem::path& src,
|
||
const std::filesystem::path& dest,
|
||
int flags) {
|
||
uv_fs_t req;
|
||
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
|
||
auto src_str = ConvertPathToUTF8(src);
|
||
auto dest_str = ConvertPathToUTF8(dest);
|
||
int rc = uv_fs_copyfile(nullptr,
|
||
&req,
|
||
src_str.c_str(),
|
||
dest_str.c_str(),
|
||
flags | UV_FS_COPYFILE_EXCL,
|
||
nullptr);
|
||
if (rc < 0) {
|
||
return CpError::Uv(rc, "copyfile", dest_str);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
// mkdir() that does not follow or accept anything already at `path`.
|
||
CpError MakeFreshDirectory(const std::filesystem::path& path) {
|
||
uv_fs_t req;
|
||
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
|
||
auto path_str = ConvertPathToUTF8(path);
|
||
int rc = uv_fs_mkdir(nullptr, &req, path_str.c_str(), 0777, nullptr);
|
||
if (rc < 0) {
|
||
return CpError::Uv(rc, "mkdir", path_str);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
// The recursive directory copy behind fs.cpSync() and, on the thread pool,
|
||
// fs.cp()/fsPromises.cp() when no filter function is involved. Runs on any
|
||
// thread; touches no JS.
|
||
CpError CopyDirRecursive(const std::filesystem::path& src_path,
|
||
const std::filesystem::path& dest_path,
|
||
const std::string& dest_display,
|
||
const CpDirOptions& options) {
|
||
std::error_code error;
|
||
bool dest_existed = false;
|
||
if (options.fresh_destination) {
|
||
CpError made = MakeFreshDirectory(dest_path);
|
||
if (made.kind != CpError::kNone) return made;
|
||
} else {
|
||
dest_existed = std::filesystem::exists(dest_path, error);
|
||
std::filesystem::create_directories(dest_path, error);
|
||
if (error) {
|
||
return CpError::Std(error, dest_display);
|
||
}
|
||
}
|
||
|
||
auto file_copy_opts = std::filesystem::copy_options::recursive;
|
||
if (options.force) {
|
||
file_copy_opts |= std::filesystem::copy_options::overwrite_existing;
|
||
} else if (options.error_on_exist) {
|
||
file_copy_opts |= std::filesystem::copy_options::none;
|
||
} else {
|
||
file_copy_opts |= std::filesystem::copy_options::skip_existing;
|
||
}
|
||
|
||
std::function<CpError(std::filesystem::path, std::filesystem::path)>
|
||
copy_dir_contents;
|
||
copy_dir_contents = [&options, ©_dir_contents, file_copy_opts](
|
||
std::filesystem::path src,
|
||
std::filesystem::path dest) -> CpError {
|
||
std::error_code error;
|
||
// Only the error_code overloads are used from here on: this runs on a
|
||
// thread pool thread and exceptions are disabled.
|
||
auto it = std::filesystem::directory_iterator(src, error);
|
||
if (error) {
|
||
return CpError::Std(error, ConvertPathToUTF8(src));
|
||
}
|
||
for (const auto end = std::filesystem::directory_iterator(); it != end;
|
||
it.increment(error)) {
|
||
if (error) {
|
||
return CpError::Std(error, ConvertPathToUTF8(src));
|
||
}
|
||
const auto& dir_entry = *it;
|
||
auto dest_file_path = dest / dir_entry.path().filename();
|
||
auto dest_str = ConvertPathToUTF8(dest);
|
||
|
||
// With dereference, links that resolve to a directory or a regular file
|
||
// fall through to the branches below, which follow symlinks. A link
|
||
// whose target cannot be reached has nothing to copy, and stat() reports
|
||
// why. std::filesystem::status() does not: it folds ENOTDIR into
|
||
// not_found, and on Windows its error_code carries a Win32 value where
|
||
// an errno is expected.
|
||
const bool is_symlink = dir_entry.is_symlink(error);
|
||
if (is_symlink && options.dereference) {
|
||
uv_fs_t req;
|
||
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
|
||
auto entry_str = ConvertPathToUTF8(dir_entry.path());
|
||
int rc = uv_fs_stat(nullptr, &req, entry_str.c_str(), nullptr);
|
||
if (rc < 0) {
|
||
return CpError::Uv(rc, "stat", entry_str);
|
||
}
|
||
}
|
||
|
||
if (is_symlink && !options.dereference) {
|
||
if (options.verbatim_symlinks) {
|
||
std::filesystem::copy_symlink(
|
||
dir_entry.path(), dest_file_path, error);
|
||
if (error) {
|
||
return CpError::Std(error, dest_str);
|
||
}
|
||
} else {
|
||
auto symlink_target =
|
||
std::filesystem::read_symlink(dir_entry.path().c_str(), error);
|
||
if (error) {
|
||
return CpError::Std(error, dest_str);
|
||
}
|
||
|
||
if (std::filesystem::exists(dest_file_path, error)) {
|
||
if (std::filesystem::is_symlink(dest_file_path, error)) {
|
||
auto current_dest_symlink_target =
|
||
std::filesystem::read_symlink(dest_file_path.c_str(), error);
|
||
if (error) {
|
||
return CpError::Std(error, dest_str);
|
||
}
|
||
|
||
if (!options.dereference &&
|
||
std::filesystem::is_directory(symlink_target, error) &&
|
||
isInsideDir(symlink_target, current_dest_symlink_target)) {
|
||
return {CpError::kEinval,
|
||
0,
|
||
"cp",
|
||
SPrintF("Cannot copy %s to a subdirectory of self %s",
|
||
symlink_target,
|
||
current_dest_symlink_target),
|
||
{}};
|
||
}
|
||
|
||
// Prevent copy if src is a subdir of dest since unlinking
|
||
// dest in this case would result in removing src contents
|
||
// and therefore a broken symlink would be created.
|
||
if (std::filesystem::is_directory(dest_file_path, error) &&
|
||
isInsideDir(current_dest_symlink_target, symlink_target)) {
|
||
return {CpError::kSymlinkToSubdirectory,
|
||
0,
|
||
"cp",
|
||
SPrintF("cannot overwrite %s with %s",
|
||
current_dest_symlink_target,
|
||
symlink_target),
|
||
{}};
|
||
}
|
||
|
||
// symlinks get overridden by cp even if force: false, this is
|
||
// being applied here for backward compatibility, but is it
|
||
// correct? or is it a bug?
|
||
std::filesystem::remove(dest_file_path, error);
|
||
if (error) {
|
||
return CpError::Std(error, dest_str);
|
||
}
|
||
} else if (std::filesystem::is_regular_file(dest_file_path,
|
||
error)) {
|
||
if (!options.dereference ||
|
||
(!options.force && options.error_on_exist)) {
|
||
return CpError::Std(
|
||
std::make_error_code(std::errc::file_exists),
|
||
ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
}
|
||
}
|
||
std::filesystem::path symlink_target_absolute;
|
||
if (options.fresh_destination) {
|
||
// As path.resolve() does: lexical only, absolute targets verbatim.
|
||
symlink_target_absolute =
|
||
symlink_target.is_absolute()
|
||
? symlink_target
|
||
: std::filesystem::absolute(src / symlink_target, error)
|
||
.lexically_normal();
|
||
} else {
|
||
symlink_target_absolute = std::filesystem::weakly_canonical(
|
||
std::filesystem::absolute(src / symlink_target, error), error);
|
||
}
|
||
if (error) {
|
||
return CpError::Std(error, dest_str);
|
||
}
|
||
#ifdef _WIN32
|
||
auto wstr = symlink_target_absolute.wstring();
|
||
if (wstr.starts_with(L"\\\\?\\")) {
|
||
symlink_target_absolute = std::filesystem::path(wstr.substr(4));
|
||
}
|
||
#endif
|
||
if (dir_entry.is_directory(error)) {
|
||
std::filesystem::create_directory_symlink(
|
||
symlink_target_absolute, dest_file_path, error);
|
||
} else {
|
||
std::filesystem::create_symlink(
|
||
symlink_target_absolute, dest_file_path, error);
|
||
}
|
||
if (error) {
|
||
return CpError::Std(error, dest_str);
|
||
}
|
||
}
|
||
} else if (dir_entry.is_directory(error)) {
|
||
auto entry_dir_path = src / dir_entry.path().filename();
|
||
bool created = true;
|
||
if (options.fresh_destination) {
|
||
CpError made = MakeFreshDirectory(dest_file_path);
|
||
if (made.kind != CpError::kNone) return made;
|
||
} else if (is_symlink) {
|
||
// Mirror the JavaScript walk: create the destination only when it
|
||
// does not exist, otherwise recurse into the existing path.
|
||
created = !std::filesystem::exists(dest_file_path, error);
|
||
if (error) {
|
||
return CpError::Std(error, ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
if (created) {
|
||
std::filesystem::create_directory(dest_file_path, error);
|
||
if (error) {
|
||
return CpError::Std(error, ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
}
|
||
} else {
|
||
created = std::filesystem::create_directory(dest_file_path, error);
|
||
if (error) {
|
||
return CpError::Std(error, ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
}
|
||
CpError inner = copy_dir_contents(entry_dir_path, dest_file_path);
|
||
if (inner.kind != CpError::kNone) {
|
||
return inner;
|
||
}
|
||
// A directory created by the copy gets the mode of its source once
|
||
// its contents are in (the source may be read-only).
|
||
if (created) {
|
||
std::filesystem::permissions(
|
||
dest_file_path, dir_entry.status(error).permissions(), error);
|
||
if (error) {
|
||
return CpError::Std(error, ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
}
|
||
if (options.preserve_timestamps) {
|
||
CpError stamped = CopyUtimes(entry_dir_path, dest_file_path);
|
||
if (stamped.kind != CpError::kNone) return stamped;
|
||
}
|
||
} else if (dir_entry.is_regular_file(error)) {
|
||
if (is_symlink && !options.fresh_destination) {
|
||
// Only a dereferenced link reaches this branch as a link, so what an
|
||
// occupied destination means here is settled the way the JavaScript
|
||
// walk settles it: replaced under force, left untouched otherwise.
|
||
// Replacing an existing destination unlinks the entry first, which is
|
||
// what keeps an existing link there from being written through.
|
||
std::error_code dest_error;
|
||
const bool dest_exists =
|
||
std::filesystem::exists(dest_file_path, dest_error);
|
||
if (dest_error) {
|
||
return CpError::Std(dest_error, ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
|
||
if (dest_exists) {
|
||
if (!options.force) {
|
||
if (options.error_on_exist) {
|
||
return {CpError::kEexist,
|
||
0,
|
||
"cp",
|
||
SPrintF("[ERR_FS_CP_EEXIST]: Target already exists: "
|
||
"cp returned EEXIST (%s already exists)",
|
||
dest_file_path),
|
||
{}};
|
||
}
|
||
continue;
|
||
}
|
||
|
||
std::filesystem::remove(dest_file_path, dest_error);
|
||
if (dest_error) {
|
||
return CpError::Std(dest_error,
|
||
ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
}
|
||
}
|
||
|
||
bool copied = true;
|
||
if (options.fresh_destination) {
|
||
CpError fresh = CopyFileFresh(
|
||
dir_entry.path(), dest_file_path, options.copyfile_flags);
|
||
if (fresh.kind != CpError::kNone) return fresh;
|
||
} else {
|
||
copied = std::filesystem::copy_file(
|
||
dir_entry.path(), dest_file_path, file_copy_opts, error);
|
||
}
|
||
if (error) {
|
||
if (error == std::errc::file_exists) {
|
||
return {CpError::kEexist,
|
||
0,
|
||
"cp",
|
||
SPrintF("[ERR_FS_CP_EEXIST]: Target already exists: "
|
||
"cp returned EEXIST (%s already exists)",
|
||
dest_file_path),
|
||
{}};
|
||
}
|
||
return CpError::Std(error, dest_str);
|
||
}
|
||
|
||
if (copied && options.preserve_timestamps) {
|
||
CpError utimes = CopyUtimes(dir_entry.path(), dest_file_path);
|
||
if (utimes.kind != CpError::kNone) {
|
||
return utimes;
|
||
}
|
||
}
|
||
// Both copies set the mode before writing the data, which clears
|
||
// setuid/setgid; put the source mode back once the data is in.
|
||
if (copied) {
|
||
std::filesystem::permissions(
|
||
dest_file_path, dir_entry.status(error).permissions(), error);
|
||
if (error) {
|
||
return CpError::Std(error, ConvertPathToUTF8(dest_file_path));
|
||
}
|
||
}
|
||
} else if (options.fresh_destination) {
|
||
CpError::Kind kind = dir_entry.is_socket(error) ? CpError::kSocket
|
||
: dir_entry.is_fifo(error) ? CpError::kFifo
|
||
: CpError::kUnknown;
|
||
return {kind, UV_EINVAL, "cp", {}, ConvertPathToUTF8(dest_file_path)};
|
||
}
|
||
}
|
||
return {};
|
||
};
|
||
|
||
CpError result = copy_dir_contents(src_path, dest_path);
|
||
if (result.kind != CpError::kNone) return result;
|
||
if (!dest_existed) {
|
||
std::filesystem::permissions(
|
||
dest_path,
|
||
std::filesystem::status(src_path, error).permissions(),
|
||
error);
|
||
if (error) {
|
||
return CpError::Std(error, dest_display);
|
||
}
|
||
}
|
||
if (options.preserve_timestamps) return CopyUtimes(src_path, dest_path);
|
||
return {};
|
||
}
|
||
|
||
} // namespace
|
||
|
||
static void CpSyncOverrideFile(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
CHECK_EQ(args.Length(), 4); // src, dest, mode, preserveTimestamps
|
||
|
||
BufferValue src(isolate, args[0]);
|
||
CHECK_NOT_NULL(*src);
|
||
ToNamespacedPath(env, &src);
|
||
|
||
BufferValue dest(isolate, args[1]);
|
||
CHECK_NOT_NULL(*dest);
|
||
ToNamespacedPath(env, &dest);
|
||
|
||
int mode;
|
||
if (!GetValidFileMode(env, args[2], UV_FS_COPYFILE).To(&mode)) {
|
||
return;
|
||
}
|
||
|
||
bool preserve_timestamps = args[3]->IsTrue();
|
||
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
|
||
|
||
auto src_path = src.ToPath();
|
||
auto dest_path = dest.ToPath();
|
||
|
||
std::error_code error;
|
||
|
||
if (!std::filesystem::remove(dest_path, error)) {
|
||
return env->ThrowStdErrException(error, "unlink", *dest);
|
||
}
|
||
|
||
if (mode == 0) {
|
||
// if no mode is specified use the faster std::filesystem API
|
||
if (!std::filesystem::copy_file(src_path, dest_path, error)) {
|
||
return env->ThrowStdErrException(error, "cp", *dest);
|
||
}
|
||
} else {
|
||
uv_fs_t req;
|
||
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
|
||
auto result = uv_fs_copyfile(nullptr, &req, *src, *dest, mode, nullptr);
|
||
if (is_uv_error(result)) {
|
||
return env->ThrowUVException(result, "cp", nullptr, *src, *dest);
|
||
}
|
||
}
|
||
|
||
if (preserve_timestamps) {
|
||
CpError error = CopyUtimes(src_path, dest_path);
|
||
if (error.kind != CpError::kNone) {
|
||
error.Throw(env);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
|
||
CHECK_EQ(args.Length(), 7); // src, dest, force, dereference, errorOnExist,
|
||
// verbatimSymlinks, preserveTimestamps
|
||
|
||
Environment* env = Environment::GetCurrent(args);
|
||
Isolate* isolate = env->isolate();
|
||
|
||
BufferValue src(isolate, args[0]);
|
||
CHECK_NOT_NULL(*src);
|
||
ToNamespacedPath(env, &src);
|
||
|
||
BufferValue dest(isolate, args[1]);
|
||
CHECK_NOT_NULL(*dest);
|
||
ToNamespacedPath(env, &dest);
|
||
|
||
bool force = args[2]->IsTrue();
|
||
bool dereference = args[3]->IsTrue();
|
||
bool error_on_exist = args[4]->IsTrue();
|
||
bool verbatim_symlinks = args[5]->IsTrue();
|
||
bool preserve_timestamps = args[6]->IsTrue();
|
||
|
||
auto src_path = src.ToPath();
|
||
auto dest_path = dest.ToPath();
|
||
|
||
CpError error = CopyDirRecursive(src_path,
|
||
dest_path,
|
||
dest.ToString(),
|
||
{force,
|
||
dereference,
|
||
error_on_exist,
|
||
verbatim_symlinks,
|
||
preserve_timestamps,
|
||
false,
|
||
0});
|
||
if (error.kind != CpError::kNone) {
|
||
error.Throw(env);
|
||
}
|
||
}
|
||
|
||
// JS: const job = new CpDirJob(src, dest, force, dereference, errorOnExist,
|
||
// verbatimSymlinks, preserveTimestamps);
|
||
// job.ondone = (err) => {...}; job.run();
|
||
// Runs CopyDirRecursive() on the thread pool for fs.cp()/fsPromises.cp().
|
||
class CpDirJob final : public AsyncWrap, public ThreadPoolWork {
|
||
public:
|
||
static void New(const FunctionCallbackInfo<Value>& args) {
|
||
CHECK(args.IsConstructCall());
|
||
Environment* env = Environment::GetCurrent(args);
|
||
CHECK_EQ(args.Length(), 8);
|
||
CHECK(args[7]->IsInt32());
|
||
BufferValue src(env->isolate(), args[0]);
|
||
CHECK_NOT_NULL(*src);
|
||
ToNamespacedPath(env, &src);
|
||
BufferValue dest(env->isolate(), args[1]);
|
||
CHECK_NOT_NULL(*dest);
|
||
ToNamespacedPath(env, &dest);
|
||
new CpDirJob(env,
|
||
args.This(),
|
||
src.ToPath(),
|
||
dest.ToPath(),
|
||
dest.ToString(),
|
||
{args[2]->IsTrue(),
|
||
args[3]->IsTrue(),
|
||
args[4]->IsTrue(),
|
||
args[5]->IsTrue(),
|
||
args[6]->IsTrue(),
|
||
true,
|
||
args[7].As<Int32>()->Value()});
|
||
}
|
||
|
||
static void Run(const FunctionCallbackInfo<Value>& args) {
|
||
CpDirJob* job;
|
||
ASSIGN_OR_RETURN_UNWRAP(&job, args.This());
|
||
CHECK(!job->scheduled_);
|
||
job->scheduled_ = true;
|
||
job->ClearWeak();
|
||
job->ScheduleWork();
|
||
}
|
||
|
||
void DoThreadPoolWork() override {
|
||
error_ = CopyDirRecursive(src_, dest_, dest_display_, options_);
|
||
}
|
||
|
||
void AfterThreadPoolWork(int status) override {
|
||
Environment* env = AsyncWrap::env();
|
||
std::unique_ptr<CpDirJob> self(this);
|
||
CHECK(status == 0 || status == UV_ECANCELED);
|
||
if (status == UV_ECANCELED || !env->can_call_into_js()) return;
|
||
Isolate* isolate = env->isolate();
|
||
HandleScope handle_scope(isolate);
|
||
Context::Scope context_scope(env->context());
|
||
Local<Value> argv[] = {
|
||
Null(isolate), Undefined(isolate), Undefined(isolate)};
|
||
const char* special = error_.kind == CpError::kSocket ? "socket"
|
||
: error_.kind == CpError::kFifo ? "fifo"
|
||
: error_.kind == CpError::kUnknown ? "unknown"
|
||
: nullptr;
|
||
if (special != nullptr) {
|
||
Local<Value> path;
|
||
if (!ToV8Value(env->context(), error_.path).ToLocal(&path)) return;
|
||
argv[1] = OneByteString(isolate, special);
|
||
argv[2] = path;
|
||
} else if (error_.kind != CpError::kNone) {
|
||
argv[0] = error_.ToException(env);
|
||
}
|
||
MakeCallback(env->ondone_string(), arraysize(argv), argv);
|
||
}
|
||
|
||
bool IsNotIndicativeOfMemoryLeakAtExit() const override { return true; }
|
||
SET_NO_MEMORY_INFO()
|
||
SET_MEMORY_INFO_NAME(CpDirJob)
|
||
SET_SELF_SIZE(CpDirJob)
|
||
|
||
private:
|
||
CpDirJob(Environment* env,
|
||
Local<Object> object,
|
||
std::filesystem::path&& src,
|
||
std::filesystem::path&& dest,
|
||
std::string&& dest_display,
|
||
CpDirOptions options)
|
||
: AsyncWrap(env, object, AsyncWrap::PROVIDER_FSREQCALLBACK),
|
||
ThreadPoolWork(env, "fs.cp"),
|
||
src_(std::move(src)),
|
||
dest_(std::move(dest)),
|
||
dest_display_(std::move(dest_display)),
|
||
options_(options) {
|
||
MakeWeak();
|
||
}
|
||
|
||
const std::filesystem::path src_;
|
||
const std::filesystem::path dest_;
|
||
const std::string dest_display_;
|
||
const CpDirOptions options_;
|
||
CpError error_;
|
||
bool scheduled_ = false;
|
||
};
|
||
|
||
BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile(
|
||
Environment* env, const std::string& file_path) {
|
||
THROW_IF_INSUFFICIENT_PERMISSIONS(
|
||
env,
|
||
permission::PermissionScope::kFileSystemRead,
|
||
file_path,
|
||
BindingData::FilePathIsFileReturnType::kThrowInsufficientPermissions);
|
||
|
||
uv_fs_t req;
|
||
|
||
int rc = uv_fs_stat(env->event_loop(), &req, file_path.c_str(), nullptr);
|
||
|
||
if (rc == 0) {
|
||
const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
|
||
rc = S_ISDIR(s->st_mode);
|
||
}
|
||
|
||
uv_fs_req_cleanup(&req);
|
||
|
||
// rc is 0 if the path refers to a file
|
||
if (rc == 0) return BindingData::FilePathIsFileReturnType::kIsFile;
|
||
|
||
return BindingData::FilePathIsFileReturnType::kIsNotFile;
|
||
}
|
||
|
||
namespace {
|
||
|
||
// define the final index of the algorithm resolution
|
||
// when packageConfig.main is defined.
|
||
constexpr uint8_t legacy_main_extensions_with_main_end = 7;
|
||
// define the final index of the algorithm resolution
|
||
// when packageConfig.main is NOT defined
|
||
constexpr uint8_t legacy_main_extensions_package_fallback_end = 10;
|
||
// the possible file extensions that should be tested
|
||
// 0-6: when packageConfig.main is defined
|
||
// 7-9: when packageConfig.main is NOT defined,
|
||
// or when the previous case didn't found the file
|
||
constexpr std::array<std::string_view, 10> legacy_main_extensions = {
|
||
"",
|
||
".js",
|
||
".json",
|
||
".node",
|
||
"/index.js",
|
||
"/index.json",
|
||
"/index.node",
|
||
".js",
|
||
".json",
|
||
".node"};
|
||
|
||
} // namespace
|
||
|
||
void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
|
||
CHECK_GE(args.Length(), 1);
|
||
CHECK(args[0]->IsString());
|
||
|
||
Environment* env = Environment::GetCurrent(args);
|
||
auto isolate = env->isolate();
|
||
|
||
auto utf8_package_path = Utf8Value(isolate, args[0]).ToString();
|
||
|
||
std::string package_initial_file = "";
|
||
|
||
std::optional<std::string> initial_file_path;
|
||
std::string file_path;
|
||
|
||
if (args.Length() >= 2 && args[1]->IsString()) {
|
||
auto package_config_main = Utf8Value(isolate, args[1]).ToString();
|
||
|
||
initial_file_path =
|
||
PathResolve(env, {utf8_package_path, package_config_main});
|
||
FromNamespacedPath(&initial_file_path.value());
|
||
|
||
package_initial_file = *initial_file_path;
|
||
|
||
for (int i = 0; i < legacy_main_extensions_with_main_end; i++) {
|
||
file_path = *initial_file_path + std::string(legacy_main_extensions[i]);
|
||
// TODO(anonrig): Remove this when ToNamespacedPath supports std::string
|
||
Local<Value> local_file_path;
|
||
if (!Buffer::Copy(env->isolate(), file_path.c_str(), file_path.size())
|
||
.ToLocal(&local_file_path)) {
|
||
return;
|
||
}
|
||
BufferValue buff_file_path(isolate, local_file_path);
|
||
ToNamespacedPath(env, &buff_file_path);
|
||
|
||
switch (FilePathIsFile(env, buff_file_path.ToString())) {
|
||
case BindingData::FilePathIsFileReturnType::kIsFile:
|
||
return args.GetReturnValue().Set(i);
|
||
case BindingData::FilePathIsFileReturnType::kIsNotFile:
|
||
continue;
|
||
case BindingData::FilePathIsFileReturnType::
|
||
kThrowInsufficientPermissions:
|
||
// the default behavior when do not have permission is to return
|
||
// and exit the execution of the method as soon as possible
|
||
// the internal function will throw the exception
|
||
return;
|
||
default:
|
||
UNREACHABLE();
|
||
}
|
||
}
|
||
}
|
||
|
||
initial_file_path = PathResolve(env, {utf8_package_path, "./index"});
|
||
if (!initial_file_path.has_value()) {
|
||
return;
|
||
}
|
||
|
||
FromNamespacedPath(&initial_file_path.value());
|
||
|
||
for (int i = legacy_main_extensions_with_main_end;
|
||
i < legacy_main_extensions_package_fallback_end;
|
||
i++) {
|
||
file_path = *initial_file_path + std::string(legacy_main_extensions[i]);
|
||
// TODO(anonrig): Remove this when ToNamespacedPath supports std::string
|
||
Local<Value> local_file_path;
|
||
if (!Buffer::Copy(env->isolate(), file_path.c_str(), file_path.size())
|
||
.ToLocal(&local_file_path)) {
|
||
return;
|
||
}
|
||
BufferValue buff_file_path(isolate, local_file_path);
|
||
ToNamespacedPath(env, &buff_file_path);
|
||
|
||
switch (FilePathIsFile(env, buff_file_path.ToString())) {
|
||
case BindingData::FilePathIsFileReturnType::kIsFile:
|
||
return args.GetReturnValue().Set(i);
|
||
case BindingData::FilePathIsFileReturnType::kIsNotFile:
|
||
continue;
|
||
case BindingData::FilePathIsFileReturnType::kThrowInsufficientPermissions:
|
||
// the default behavior when do not have permission is to return
|
||
// and exit the execution of the method as soon as possible
|
||
// the internal function will throw the exception
|
||
return;
|
||
default:
|
||
UNREACHABLE();
|
||
}
|
||
}
|
||
|
||
if (package_initial_file == "")
|
||
package_initial_file = *initial_file_path + ".js";
|
||
|
||
std::optional<std::string> module_base;
|
||
|
||
if (args.Length() >= 3 && args[2]->IsString()) {
|
||
Utf8Value utf8_base_path(isolate, args[2]);
|
||
auto base_url =
|
||
ada::parse<ada::url_aggregator>(utf8_base_path.ToStringView());
|
||
|
||
if (!base_url) {
|
||
THROW_ERR_INVALID_URL(isolate, "Invalid URL");
|
||
return;
|
||
}
|
||
|
||
module_base = node::url::FileURLToPath(env, *base_url);
|
||
if (!module_base.has_value()) {
|
||
return;
|
||
}
|
||
} else {
|
||
THROW_ERR_INVALID_ARG_TYPE(
|
||
isolate,
|
||
"The \"base\" argument must be of type string or an instance of URL.");
|
||
return;
|
||
}
|
||
|
||
THROW_ERR_MODULE_NOT_FOUND(isolate,
|
||
"Cannot find package '%s' imported from %s",
|
||
package_initial_file,
|
||
*module_base);
|
||
}
|
||
|
||
void BindingData::MemoryInfo(MemoryTracker* tracker) const {
|
||
tracker->TrackField("stats_field_array", stats_field_array);
|
||
tracker->TrackField("stats_field_bigint_array", stats_field_bigint_array);
|
||
tracker->TrackField("statfs_field_array", statfs_field_array);
|
||
tracker->TrackField("statfs_field_bigint_array", statfs_field_bigint_array);
|
||
tracker->TrackField("file_handle_read_wrap_freelist",
|
||
file_handle_read_wrap_freelist);
|
||
}
|
||
|
||
BindingData::BindingData(Realm* realm,
|
||
v8::Local<v8::Object> wrap,
|
||
InternalFieldInfo* info)
|
||
: SnapshotableObject(realm, wrap, type_int),
|
||
stats_field_array(realm->isolate(),
|
||
kFsStatsBufferLength,
|
||
MAYBE_FIELD_PTR(info, stats_field_array)),
|
||
stats_field_bigint_array(realm->isolate(),
|
||
kFsStatsBufferLength,
|
||
MAYBE_FIELD_PTR(info, stats_field_bigint_array)),
|
||
statfs_field_array(realm->isolate(),
|
||
kFsStatFsBufferLength,
|
||
MAYBE_FIELD_PTR(info, statfs_field_array)),
|
||
statfs_field_bigint_array(
|
||
realm->isolate(),
|
||
kFsStatFsBufferLength,
|
||
MAYBE_FIELD_PTR(info, statfs_field_bigint_array)) {
|
||
Isolate* isolate = realm->isolate();
|
||
Local<Context> context = realm->context();
|
||
|
||
if (info == nullptr) {
|
||
wrap->Set(context,
|
||
FIXED_ONE_BYTE_STRING(isolate, "statValues"),
|
||
stats_field_array.GetJSArray())
|
||
.Check();
|
||
|
||
wrap->Set(context,
|
||
FIXED_ONE_BYTE_STRING(isolate, "bigintStatValues"),
|
||
stats_field_bigint_array.GetJSArray())
|
||
.Check();
|
||
|
||
wrap->Set(context,
|
||
FIXED_ONE_BYTE_STRING(isolate, "statFsValues"),
|
||
statfs_field_array.GetJSArray())
|
||
.Check();
|
||
|
||
wrap->Set(context,
|
||
FIXED_ONE_BYTE_STRING(isolate, "bigintStatFsValues"),
|
||
statfs_field_bigint_array.GetJSArray())
|
||
.Check();
|
||
} else {
|
||
stats_field_array.Deserialize(realm->context());
|
||
stats_field_bigint_array.Deserialize(realm->context());
|
||
statfs_field_array.Deserialize(realm->context());
|
||
statfs_field_bigint_array.Deserialize(realm->context());
|
||
}
|
||
stats_field_array.MakeWeak();
|
||
stats_field_bigint_array.MakeWeak();
|
||
statfs_field_array.MakeWeak();
|
||
statfs_field_bigint_array.MakeWeak();
|
||
}
|
||
|
||
void BindingData::Deserialize(Local<Context> context,
|
||
Local<Object> holder,
|
||
int index,
|
||
InternalFieldInfoBase* info) {
|
||
DCHECK_IS_SNAPSHOT_SLOT(index);
|
||
HandleScope scope(Isolate::GetCurrent());
|
||
Realm* realm = Realm::GetCurrent(context);
|
||
InternalFieldInfo* casted_info = static_cast<InternalFieldInfo*>(info);
|
||
BindingData* binding =
|
||
realm->AddBindingData<BindingData>(holder, casted_info);
|
||
CHECK_NOT_NULL(binding);
|
||
}
|
||
|
||
bool BindingData::PrepareForSerialization(Local<Context> context,
|
||
v8::SnapshotCreator* creator) {
|
||
CHECK(file_handle_read_wrap_freelist.empty());
|
||
DCHECK_NULL(internal_field_info_);
|
||
internal_field_info_ = InternalFieldInfoBase::New<InternalFieldInfo>(type());
|
||
internal_field_info_->stats_field_array =
|
||
stats_field_array.Serialize(context, creator);
|
||
internal_field_info_->stats_field_bigint_array =
|
||
stats_field_bigint_array.Serialize(context, creator);
|
||
internal_field_info_->statfs_field_array =
|
||
statfs_field_array.Serialize(context, creator);
|
||
internal_field_info_->statfs_field_bigint_array =
|
||
statfs_field_bigint_array.Serialize(context, creator);
|
||
// Return true because we need to maintain the reference to the binding from
|
||
// JS land.
|
||
return true;
|
||
}
|
||
|
||
InternalFieldInfoBase* BindingData::Serialize(int index) {
|
||
DCHECK_IS_SNAPSHOT_SLOT(index);
|
||
InternalFieldInfo* info = internal_field_info_;
|
||
internal_field_info_ = nullptr;
|
||
return info;
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
static void HandleToFd(const FunctionCallbackInfo<Value>& args) {
|
||
Environment* env = Environment::GetCurrent(args);
|
||
CHECK_GE(args.Length(), 1);
|
||
CHECK(args[0]->IsBigInt());
|
||
|
||
int flags = 0;
|
||
if (args[1]->IsNumber()) {
|
||
flags = args[1].As<Int32>()->Value();
|
||
}
|
||
|
||
bool lossless;
|
||
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
|
||
if (!lossless) {
|
||
return THROW_ERR_OUT_OF_RANGE(env,
|
||
"windowsHandle does not fit into 64 bits");
|
||
}
|
||
intptr_t value = static_cast<intptr_t>(handle);
|
||
|
||
int fd = _open_osfhandle(value, flags);
|
||
if (fd == -1) {
|
||
return env->ThrowErrnoException(errno, "_open_osfhandle");
|
||
}
|
||
args.GetReturnValue().Set(fd);
|
||
}
|
||
#endif // _WIN32
|
||
|
||
void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
|
||
Local<ObjectTemplate> target) {
|
||
Isolate* isolate = isolate_data->isolate();
|
||
|
||
SetMethod(
|
||
isolate, target, "legacyMainResolve", BindingData::LegacyMainResolve);
|
||
}
|
||
|
||
void BindingData::RegisterExternalReferences(
|
||
ExternalReferenceRegistry* registry) {
|
||
registry->Register(BindingData::LegacyMainResolve);
|
||
}
|
||
|
||
static void CreatePerIsolateProperties(IsolateData* isolate_data,
|
||
Local<ObjectTemplate> target) {
|
||
Isolate* isolate = isolate_data->isolate();
|
||
|
||
SetMethod(isolate,
|
||
target,
|
||
"getFormatOfExtensionlessFile",
|
||
GetFormatOfExtensionlessFile);
|
||
SetMethod(isolate, target, "access", Access);
|
||
SetMethod(isolate, target, "close", Close);
|
||
SetMethod(isolate, target, "existsSync", ExistsSync);
|
||
SetMethod(isolate, target, "open", Open);
|
||
SetMethod(isolate, target, "openFileHandle", OpenFileHandle);
|
||
SetMethod(isolate, target, "read", Read);
|
||
SetMethod(isolate, target, "readFileUtf8", ReadFileUtf8);
|
||
SetMethod(isolate, target, "readBuffers", ReadBuffers);
|
||
SetMethod(isolate, target, "fdatasync", Fdatasync);
|
||
SetMethod(isolate, target, "fsync", Fsync);
|
||
SetMethod(isolate, target, "rename", Rename);
|
||
SetMethod(isolate, target, "ftruncate", FTruncate);
|
||
SetMethod(isolate, target, "rmdir", RMDir);
|
||
SetMethod(isolate, target, "rmSync", RmSync);
|
||
SetMethod(isolate, target, "mkdir", MKDir);
|
||
SetMethod(isolate, target, "readdir", ReadDir);
|
||
SetMethod(isolate, target, "readdirRecursive", ReadDirRecursive);
|
||
SetMethod(isolate, target, "internalModuleStat", InternalModuleStat);
|
||
SetMethod(isolate, target, "stat", Stat);
|
||
SetMethod(isolate, target, "lstat", LStat);
|
||
SetMethod(isolate, target, "fstat", FStat);
|
||
SetMethod(isolate, target, "statfs", StatFs);
|
||
SetMethod(isolate, target, "link", Link);
|
||
SetMethod(isolate, target, "symlink", Symlink);
|
||
SetMethod(isolate, target, "readlink", ReadLink);
|
||
SetMethod(isolate, target, "unlink", Unlink);
|
||
SetMethod(isolate, target, "writeBuffer", WriteBuffer);
|
||
SetMethod(isolate, target, "writeBuffers", WriteBuffers);
|
||
SetMethod(isolate, target, "writeString", WriteString);
|
||
SetMethod(isolate, target, "writeFileUtf8", WriteFileUtf8);
|
||
SetMethod(isolate, target, "realpath", RealPath);
|
||
SetMethod(isolate, target, "copyFile", CopyFile);
|
||
|
||
SetMethod(isolate, target, "chmod", Chmod);
|
||
SetMethod(isolate, target, "fchmod", FChmod);
|
||
|
||
SetMethod(isolate, target, "chown", Chown);
|
||
SetMethod(isolate, target, "fchown", FChown);
|
||
SetMethod(isolate, target, "lchown", LChown);
|
||
|
||
SetMethod(isolate, target, "utimes", UTimes);
|
||
SetMethod(isolate, target, "futimes", FUTimes);
|
||
SetMethod(isolate, target, "lutimes", LUTimes);
|
||
|
||
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
|
||
SetMethod(isolate, target, "mkstemp", Mkstemp);
|
||
|
||
#ifdef _WIN32
|
||
SetMethod(isolate, target, "handleToFd", HandleToFd);
|
||
#endif
|
||
|
||
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
|
||
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
|
||
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
|
||
|
||
Local<FunctionTemplate> cpj = NewFunctionTemplate(isolate, CpDirJob::New);
|
||
cpj->InstanceTemplate()->SetInternalFieldCount(CpDirJob::kInternalFieldCount);
|
||
cpj->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
SetProtoMethod(isolate, cpj, "run", CpDirJob::Run);
|
||
SetConstructorFunction(isolate, target, "CpDirJob", cpj);
|
||
|
||
StatWatcher::CreatePerIsolateProperties(isolate_data, target);
|
||
BindingData::CreatePerIsolateProperties(isolate_data, target);
|
||
|
||
target->Set(
|
||
FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"),
|
||
Integer::New(isolate,
|
||
static_cast<int32_t>(FsStatsOffset::kFsStatsFieldsNumber)));
|
||
|
||
// Create FunctionTemplate for ReadFileJob
|
||
Local<FunctionTemplate> rfj = NewFunctionTemplate(isolate, ReadFileJob::New);
|
||
rfj->InstanceTemplate()->SetInternalFieldCount(
|
||
ReadFileJob::kInternalFieldCount);
|
||
rfj->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
SetProtoMethod(isolate, rfj, "run", ReadFileJob::Run);
|
||
SetConstructorFunction(isolate, target, "ReadFileJob", rfj);
|
||
|
||
Local<FunctionTemplate> wfj = NewFunctionTemplate(isolate, WriteFileJob::New);
|
||
wfj->InstanceTemplate()->SetInternalFieldCount(
|
||
WriteFileJob::kInternalFieldCount);
|
||
wfj->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
SetProtoMethod(isolate, wfj, "run", WriteFileJob::Run);
|
||
SetConstructorFunction(isolate, target, "WriteFileJob", wfj);
|
||
|
||
// Create FunctionTemplate for FSReqCallback
|
||
Local<FunctionTemplate> fst = NewFunctionTemplate(isolate, NewFSReqCallback);
|
||
fst->InstanceTemplate()->SetInternalFieldCount(
|
||
FSReqBase::kInternalFieldCount);
|
||
fst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
SetProtoMethod(isolate, fst, "cancel", CancelFSReq);
|
||
SetConstructorFunction(isolate, target, "FSReqCallback", fst);
|
||
|
||
// Create FunctionTemplate for FileHandleReadWrap. There’s no need
|
||
// to do anything in the constructor, so we only store the instance template.
|
||
Local<FunctionTemplate> fh_rw = FunctionTemplate::New(isolate);
|
||
fh_rw->InstanceTemplate()->SetInternalFieldCount(
|
||
FSReqBase::kInternalFieldCount);
|
||
fh_rw->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
Local<String> fhWrapString =
|
||
FIXED_ONE_BYTE_STRING(isolate, "FileHandleReqWrap");
|
||
fh_rw->SetClassName(fhWrapString);
|
||
isolate_data->set_filehandlereadwrap_template(fst->InstanceTemplate());
|
||
|
||
// Create Function Template for FSReqPromise
|
||
Local<FunctionTemplate> fpt = FunctionTemplate::New(isolate);
|
||
fpt->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
Local<String> promiseString = FIXED_ONE_BYTE_STRING(isolate, "FSReqPromise");
|
||
fpt->SetClassName(promiseString);
|
||
Local<ObjectTemplate> fpo = fpt->InstanceTemplate();
|
||
fpo->SetInternalFieldCount(FSReqBase::kInternalFieldCount);
|
||
isolate_data->set_fsreqpromise_constructor_template(fpo);
|
||
|
||
// Create FunctionTemplate for FileHandle
|
||
Local<FunctionTemplate> fd = NewFunctionTemplate(isolate, FileHandle::New);
|
||
fd->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
SetProtoMethod(isolate, fd, "close", FileHandle::Close);
|
||
SetProtoMethod(isolate, fd, "closeSync", FileHandle::CloseSync);
|
||
SetProtoMethod(isolate, fd, "releaseFD", FileHandle::ReleaseFD);
|
||
Local<ObjectTemplate> fdt = fd->InstanceTemplate();
|
||
fdt->SetInternalFieldCount(FileHandle::kInternalFieldCount);
|
||
StreamBase::AddMethods(isolate_data, fd);
|
||
SetConstructorFunction(isolate, target, "FileHandle", fd);
|
||
isolate_data->set_fd_constructor_template(fdt);
|
||
|
||
// Create FunctionTemplate for FileHandle::CloseReq
|
||
Local<FunctionTemplate> fdclose = FunctionTemplate::New(isolate);
|
||
fdclose->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "FileHandleCloseReq"));
|
||
fdclose->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
|
||
Local<ObjectTemplate> fdcloset = fdclose->InstanceTemplate();
|
||
fdcloset->SetInternalFieldCount(FSReqBase::kInternalFieldCount);
|
||
isolate_data->set_fdclose_constructor_template(fdcloset);
|
||
|
||
target->Set(isolate, "kUsePromises", isolate_data->fs_use_promises_symbol());
|
||
}
|
||
|
||
static void CreatePerContextProperties(Local<Object> target,
|
||
Local<Value> unused,
|
||
Local<Context> context,
|
||
void* priv) {
|
||
Realm* realm = Realm::GetCurrent(context);
|
||
realm->AddBindingData<BindingData>(target);
|
||
}
|
||
|
||
BindingData* FSReqBase::binding_data() {
|
||
return binding_data_.get();
|
||
}
|
||
|
||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||
registry->Register(Access);
|
||
StatWatcher::RegisterExternalReferences(registry);
|
||
BindingData::RegisterExternalReferences(registry);
|
||
|
||
registry->Register(GetFormatOfExtensionlessFile);
|
||
registry->Register(Close);
|
||
registry->Register(ExistsSync);
|
||
registry->Register(Open);
|
||
registry->Register(ReadFileJob::New);
|
||
registry->Register(ReadFileJob::Run);
|
||
registry->Register(WriteFileJob::New);
|
||
registry->Register(WriteFileJob::Run);
|
||
registry->Register(OpenFileHandle);
|
||
registry->Register(Read);
|
||
registry->Register(ReadFileUtf8);
|
||
registry->Register(ReadBuffers);
|
||
registry->Register(Fdatasync);
|
||
registry->Register(Fsync);
|
||
registry->Register(Rename);
|
||
registry->Register(FTruncate);
|
||
registry->Register(RMDir);
|
||
registry->Register(RmSync);
|
||
registry->Register(MKDir);
|
||
registry->Register(ReadDir);
|
||
registry->Register(ReadDirRecursive);
|
||
registry->Register(InternalModuleStat);
|
||
registry->Register(Stat);
|
||
registry->Register(LStat);
|
||
registry->Register(FStat);
|
||
registry->Register(StatFs);
|
||
registry->Register(Link);
|
||
registry->Register(Symlink);
|
||
registry->Register(ReadLink);
|
||
registry->Register(Unlink);
|
||
registry->Register(WriteBuffer);
|
||
registry->Register(WriteBuffers);
|
||
registry->Register(WriteString);
|
||
registry->Register(WriteFileUtf8);
|
||
registry->Register(RealPath);
|
||
registry->Register(CopyFile);
|
||
|
||
registry->Register(CpSyncCheckPaths);
|
||
registry->Register(CpSyncOverrideFile);
|
||
registry->Register(CpSyncCopyDir);
|
||
registry->Register(CpDirJob::New);
|
||
registry->Register(CpDirJob::Run);
|
||
|
||
registry->Register(Chmod);
|
||
registry->Register(FChmod);
|
||
|
||
registry->Register(Chown);
|
||
registry->Register(FChown);
|
||
registry->Register(LChown);
|
||
|
||
registry->Register(UTimes);
|
||
registry->Register(FUTimes);
|
||
registry->Register(LUTimes);
|
||
|
||
registry->Register(Mkdtemp);
|
||
registry->Register(Mkstemp);
|
||
#ifdef _WIN32
|
||
registry->Register(HandleToFd);
|
||
#endif
|
||
registry->Register(NewFSReqCallback);
|
||
registry->Register(CancelFSReq);
|
||
|
||
registry->Register(FileHandle::New);
|
||
registry->Register(FileHandle::Close);
|
||
registry->Register(FileHandle::CloseSync);
|
||
registry->Register(FileHandle::ReleaseFD);
|
||
StreamBase::RegisterExternalReferences(registry);
|
||
}
|
||
|
||
} // namespace fs
|
||
|
||
} // end namespace node
|
||
|
||
NODE_BINDING_CONTEXT_AWARE_INTERNAL(fs, node::fs::CreatePerContextProperties)
|
||
NODE_BINDING_PER_ISOLATE_INIT(fs, node::fs::CreatePerIsolateProperties)
|
||
NODE_BINDING_EXTERNAL_REFERENCE(fs, node::fs::RegisterExternalReferences)
|