mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
`FileHandle::TransferData` uses `-1` to indicate that it no longer owns a file descriptor. However, its destructor only closes descriptors greater than 0. If a transferred `FileHandle` owns fd 0 and the message is discarded, the descriptor is left open. Treat fd 0 like any other valid descriptor and only skip `-1`. Signed-off-by: Christian Aurich <christian.aurichzm@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66095 Reviewed-By: Xuguang Mei <meixuguang@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
5852 lines
194 KiB
C++
5852 lines
194 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_);
|
||
}
|
||
|
||
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 tho 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_};
|
||
Local<Value> exception = UVException(wrap_->env()->isolate(),
|
||
static_cast<int>(req->result),
|
||
wrap_->syscall(),
|
||
nullptr,
|
||
req->path,
|
||
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());
|
||
}
|
||
}
|
||
|
||
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()));
|
||
Local<Value> path;
|
||
TryCatch try_catch(req_wrap->env()->isolate());
|
||
if (!StringBytes::Encode(req_wrap->env()->isolate(),
|
||
first_path.c_str(),
|
||
req_wrap->encoding())
|
||
.ToLocal(&path)) {
|
||
CHECK(try_catch.CanContinue());
|
||
return req_wrap->Reject(try_catch.Exception());
|
||
}
|
||
return req_wrap->Resolve(path);
|
||
}
|
||
}
|
||
|
||
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))
|
||
MaybeLocal<Value> link;
|
||
|
||
if (after.Proceed()) {
|
||
TryCatch try_catch(req_wrap->env()->isolate());
|
||
link = StringBytes::Encode(
|
||
req_wrap->env()->isolate(), req->path, req_wrap->encoding());
|
||
if (link.IsEmpty()) {
|
||
CHECK(try_catch.CanContinue());
|
||
req_wrap->Reject(try_catch.Exception());
|
||
} else {
|
||
Local<Value> val;
|
||
if (link.ToLocal(&val)) req_wrap->Resolve(val);
|
||
}
|
||
}
|
||
}
|
||
|
||
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))
|
||
MaybeLocal<Value> link;
|
||
|
||
if (after.Proceed()) {
|
||
TryCatch try_catch(req_wrap->env()->isolate());
|
||
link = StringBytes::Encode(req_wrap->env()->isolate(),
|
||
static_cast<const char*>(req->ptr),
|
||
req_wrap->encoding());
|
||
if (link.IsEmpty()) {
|
||
CHECK(try_catch.CanContinue());
|
||
req_wrap->Reject(try_catch.Exception());
|
||
} else {
|
||
Local<Value> val;
|
||
if (link.ToLocal(&val)) req_wrap->Resolve(val);
|
||
}
|
||
}
|
||
}
|
||
|
||
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) {
|
||
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) {
|
||
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()));
|
||
}
|
||
|
||
Local<Value> value;
|
||
TryCatch try_catch(isolate);
|
||
if (!MarshalRecursiveReadDir(isolate, walk_, encoding_, with_types_)
|
||
.ToLocal(&value)) {
|
||
CHECK(try_catch.CanContinue());
|
||
return req_wrap->Reject(try_catch.Exception());
|
||
}
|
||
req_wrap->Resolve(value);
|
||
}
|
||
|
||
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.
|
||
uv_fs_req_cleanup(&req);
|
||
big_cap = kMinChunk;
|
||
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);
|
||
}
|
||
}
|
||
|
||
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]);
|
||
const auto prefix_length = tmpl.length();
|
||
static constexpr std::string_view suffix = "XXXXXX";
|
||
tmpl.AllocateSufficientStorage(prefix_length + suffix.size() + 1);
|
||
memcpy(tmpl.out() + prefix_length, suffix.data(), suffix.size());
|
||
tmpl.SetLengthAndZeroTerminate(prefix_length + suffix.size());
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
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 (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);
|
||
|
||
#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);
|
||
#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)
|