%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/node_file.cc |
// 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_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 "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"
#if defined(__MINGW32__) || defined(_MSC_VER)
# include <io.h>
#endif
namespace node {
namespace fs {
using v8::Array;
using v8::BigInt;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Function;
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::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::String;
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
std::string Basename(const std::string& str, const std::string& extension) {
// Remove everything leading up to and including the final path separator.
std::string::size_type pos = str.find_last_of(kPathSeparator);
// Starting index for the resulting string
std::size_t start_pos = 0;
// String size to return
std::size_t str_size = str.size();
if (pos != std::string::npos) {
start_pos = pos + 1;
str_size -= start_pos;
}
// Strip away the extension, if any.
if (str_size >= extension.size() &&
str.compare(str.size() - extension.size(),
extension.size(), extension) == 0) {
str_size -= extension.size();
}
return str.substr(start_pos, str_size);
}
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__);
#define FS_SYNC_TRACE_END(syscall, ...) \
if (GET_TRACE_ENABLED) \
TRACE_EVENT_END( \
TRACING_CATEGORY_NODE2(fs, sync), TRACE_NAME(syscall), ##__VA_ARGS__);
#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)
: AsyncWrap(binding_data->env(), obj, AsyncWrap::PROVIDER_FILEHANDLE),
StreamBase(env()),
fd_(fd),
binding_data_(binding_data) {
MakeWeak();
StreamBase::AttachToObject(GetObject());
}
FileHandle* FileHandle::New(BindingData* binding_data,
int fd,
Local<Object> obj,
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);
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())
maybeOffset = args[1]->IntegerValue(realm->context()).FromJust();
if (args[2]->IsNumber())
maybeLength = args[2]->IntegerValue(realm->context()).FromJust();
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_);
}
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) };
}
// Close the file descriptor if it hasn't already been closed. A process
// warning will be emitted using a SetImmediate to avoid calling back to
// JS during GC. If closing the fd fails at this point, a fatal exception
// will crash the process immediately.
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; };
err_detail detail { ret, fd_ };
AfterClose();
if (ret < 0) {
// Do not unref this
env()->SetImmediate([detail](Environment* env) {
char msg[70];
snprintf(msg, arraysize(msg),
"Closing file descriptor %d on garbage collection failed",
detail.fd);
// 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.
HandleScope handle_scope(env->isolate());
env->ThrowUVException(detail.ret, "close", msg);
});
return;
}
// If the close was successful, we still want to emit a process warning
// to notify that the file descriptor was gc'd. We want to be noisy about
// this because not explicitly closing the FileHandle is a bug.
env()->SetImmediate([detail](Environment* env) {
ProcessEmitWarning(env,
"Closing file descriptor %d on garbage collection",
detail.fd);
if (env->filehandle_close_warning()) {
env->set_filehandle_close_warning(false);
USE(ProcessEmitDeprecationWarning(
env,
"Closing a FileHandle object on garbage collection is deprecated. "
"Please close FileHandle objects explicitly using "
"FileHandle.prototype.close(). In the future, an error will be "
"thrown if a file descriptor is closed during garbage collection.",
"DEP0137"));
}
}, CallbackFlags::kUnrefed);
}
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 = maybe_resolver.ToLocalChecked();
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.Holder());
Local<Promise> ret;
if (!fd->ClosePromise().ToLocal(&ret)) return;
args.GetReturnValue().Set(ret);
}
void FileHandle::ReleaseFD(const FunctionCallbackInfo<Value>& args) {
FileHandle* fd;
ASSIGN_OR_RETURN_UNWRAP(&fd, args.Holder());
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());
}
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 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));
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()));
node::url::FromNamespacedPath(&first_path);
Local<Value> path;
Local<Value> error;
if (!StringBytes::Encode(req_wrap->env()->isolate(), first_path.c_str(),
req_wrap->encoding(),
&error).ToLocal(&path)) {
return req_wrap->Reject(error);
}
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;
Local<Value> error;
if (after.Proceed()) {
link = StringBytes::Encode(req_wrap->env()->isolate(),
req->path,
req_wrap->encoding(),
&error);
if (link.IsEmpty())
req_wrap->Reject(error);
else
req_wrap->Resolve(link.ToLocalChecked());
}
}
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;
Local<Value> error;
if (after.Proceed()) {
link = StringBytes::Encode(req_wrap->env()->isolate(),
static_cast<const char*>(req->ptr),
req_wrap->encoding(),
&error);
if (link.IsEmpty())
req_wrap->Reject(error);
else
req_wrap->Resolve(link.ToLocalChecked());
}
}
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();
Local<Value> error;
int r;
std::vector<Local<Value>> name_v;
std::vector<Local<Value>> type_v;
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;
if (!StringBytes::Encode(isolate, ent.name, req_wrap->encoding(), &error)
.ToLocal(&filename)) {
return req_wrap->Reject(error);
}
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);
CHECK(args[1]->IsInt32());
int mode = args[1].As<Int32>()->Value();
BufferValue path(isolate, args[0]);
CHECK_NOT_NULL(*path);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
if (argc > 2) { // access(path, mode, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
CHECK_NOT_NULL(req_wrap_async);
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)
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);
CHECK(args[0]->IsInt32());
int fd = args[0].As<Int32>()->Value();
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);
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) {
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.
static void InternalModuleStat(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsString());
node::Utf8Value path(env->isolate(), args[0]);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
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->st_mode & S_IFDIR);
}
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;
}
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);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
bool use_bigint = args[1]->IsTrue();
if (!args[2]->IsUndefined()) { // stat(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_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
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)
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, 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);
bool use_bigint = args[1]->IsTrue();
if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
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);
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);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
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);
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)
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);
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);
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);
const auto src_view = src.ToStringView();
// 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);
BufferValue dest(isolate, args[1]);
CHECK_NOT_NULL(*dest);
const auto dest_view = dest.ToStringView();
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, dest_view);
if (argc > 2) { // link(src, dest, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
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)
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);
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);
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> error;
MaybeLocal<Value> rc = StringBytes::Encode(isolate,
link_path,
encoding,
&error);
if (rc.IsEmpty()) {
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(rc.ToLocalChecked());
}
}
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);
auto view_old_path = old_path.ToStringView();
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, view_old_path);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, view_old_path);
BufferValue new_path(isolate, args[1]);
CHECK_NOT_NULL(*new_path);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env,
permission::PermissionScope::kFileSystemWrite,
new_path.ToStringView());
if (argc > 2) { // rename(old_path, new_path, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
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)
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);
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);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
if (argc > 1) { // unlink(path, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 1);
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, "unlink", UTF8, AfterNoArgs,
uv_fs_unlink, *path);
} else { // unlink(path)
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);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
if (argc > 1) {
FSReqBase* req_wrap_async = GetReqWrap(args, 1); // rmdir(path, req)
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);
}
}
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().size() == 0) {
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().size() == 0) {
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().size() == 0) {
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_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().size() == 0) {
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);
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);
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> error;
std::string first_path(req_wrap_sync.continuation_data()->first_path());
node::url::FromNamespacedPath(&first_path);
MaybeLocal<Value> path = StringBytes::Encode(env->isolate(),
first_path.c_str(),
UTF8, &error);
if (path.IsEmpty()) {
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(path.ToLocalChecked());
}
} 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);
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
if (argc > 2) { // realpath(path, encoding, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
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)
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> error;
MaybeLocal<Value> rc = StringBytes::Encode(isolate,
link_path,
encoding,
&error);
if (rc.IsEmpty()) {
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(rc.ToLocalChecked());
}
}
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);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
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);
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)
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;
std::vector<Local<Value>> name_v;
std::vector<Local<Value>> type_v;
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> error;
MaybeLocal<Value> filename = StringBytes::Encode(isolate,
ent.name,
encoding,
&error);
if (filename.IsEmpty()) {
isolate->ThrowException(error);
return;
}
name_v.push_back(filename.ToLocalChecked());
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);
}
}
}
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);
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;
if (argc > 3) { // open(path, flags, mode, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
CHECK_NOT_NULL(req_wrap_async);
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)
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);
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 = SyncCall(env, args[4], &req_wrap_sync, "open",
uv_fs_open, *path, flags, mode);
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);
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);
BufferValue src(isolate, args[0]);
CHECK_NOT_NULL(*src);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
BufferValue dest(isolate, args[1]);
CHECK_NOT_NULL(*dest);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
CHECK(args[2]->IsInt32());
const int flags = args[2].As<Int32>()->Value();
if (argc > 3) { // copyFile(src, dest, flags, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
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)
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);
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
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 = SyncCall(env, args[6], &req_wrap_sync, "write",
uv_fs_write, fd, &uvbuf, 1, pos);
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);
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
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 = chunks->Get(env->context(), i).ToLocalChecked();
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);
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);
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
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 && IsLittleEndian() && string->IsExternalTwoByte()) {
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 {
req_wrap_async->SetReturnValue(args);
}
} else { // write(fd, string, pos, enc, undefined, ctx)
CHECK_EQ(argc, 6);
FSReqWrapSync req_wrap_sync;
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);
FS_SYNC_TRACE_BEGIN(write);
int bytesWritten = SyncCall(env, args[5], &req_wrap_sync, "write",
uv_fs_write, fd, &uvbuf, 1, pos);
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);
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);
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
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);
if (CheckOpenPermissions(env, path, flags).IsNothing()) return;
FS_SYNC_TRACE_BEGIN(open);
file = uv_fs_open(nullptr, &req, *path, flags, O_RDONLY, nullptr);
FS_SYNC_TRACE_END(open);
if (req.result < 0) {
uv_fs_req_cleanup(&req);
// req will be cleaned up by scope leave.
return env->ThrowUVException(req.result, "open", nullptr, path.out());
}
}
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);
});
std::string result{};
char buffer[8192];
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
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(req.result, "read", nullptr);
}
if (r <= 0) {
break;
}
result.append(buf.base, r);
}
FS_SYNC_TRACE_END(read);
args.GetReturnValue().Set(
ToV8Value(env->context(), result, isolate).ToLocalChecked());
}
// 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);
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
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 = buffers->Get(env->context(), i).ToLocalChecked();
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);
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);
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);
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);
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);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
CHECK(IsSafeJsInt(args[1]));
const uv_uid_t uid = static_cast<uv_uid_t>(args[1].As<Integer>()->Value());
CHECK(IsSafeJsInt(args[2]));
const uv_gid_t gid = static_cast<uv_gid_t>(args[2].As<Integer>()->Value());
if (argc > 3) { // chown(path, uid, gid, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
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)
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);
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
CHECK(IsSafeJsInt(args[1]));
const uv_uid_t uid = static_cast<uv_uid_t>(args[1].As<Integer>()->Value());
CHECK(IsSafeJsInt(args[2]));
const uv_gid_t gid = static_cast<uv_gid_t>(args[2].As<Integer>()->Value());
if (argc > 3) { // fchown(fd, uid, gid, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
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);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, path.ToStringView());
CHECK(IsSafeJsInt(args[1]));
const uv_uid_t uid = static_cast<uv_uid_t>(args[1].As<Integer>()->Value());
CHECK(IsSafeJsInt(args[2]));
const uv_gid_t gid = static_cast<uv_gid_t>(args[2].As<Integer>()->Value());
if (argc > 3) { // lchown(path, uid, gid, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
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)
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);
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);
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);
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);
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);
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]);
static constexpr const char* const suffix = "XXXXXX";
const auto length = tmpl.length();
tmpl.AllocateSufficientStorage(length + strlen(suffix));
snprintf(tmpl.out() + length, tmpl.length(), "%s", suffix);
CHECK_NOT_NULL(*tmpl);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, tmpl.ToStringView());
const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
if (argc > 2) { // mkdtemp(tmpl, encoding, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
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)
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> error;
MaybeLocal<Value> rc =
StringBytes::Encode(isolate, req_wrap_sync.req.path, encoding, &error);
if (rc.IsEmpty()) {
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(rc.ToLocalChecked());
}
}
static void GetFormatOfExtensionlessFile(
const FunctionCallbackInfo<Value>& args) {
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsString());
Environment* env = Environment::GetCurrent(args);
node::Utf8Value input(args.GetIsolate(), args[0]);
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);
}
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->st_mode & S_IFDIR);
}
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();
Utf8Value utf8_package_json_url(isolate, args[0]);
auto package_json_url =
ada::parse<ada::url_aggregator>(utf8_package_json_url.ToStringView());
if (!package_json_url) {
THROW_ERR_INVALID_URL(isolate, "Invalid URL");
return;
}
ada::result<ada::url_aggregator> file_path_url;
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();
file_path_url = ada::parse<ada::url_aggregator>(
std::string("./") + package_config_main, &package_json_url.value());
if (!file_path_url) {
THROW_ERR_INVALID_URL(isolate, "Invalid URL");
return;
}
initial_file_path = node::url::FileURLToPath(env, *file_path_url);
if (!initial_file_path.has_value()) {
return;
}
node::url::FromNamespacedPath(&initial_file_path.value());
for (int i = 0; i < legacy_main_extensions_with_main_end; i++) {
file_path = *initial_file_path + std::string(legacy_main_extensions[i]);
switch (FilePathIsFile(env, file_path)) {
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();
}
}
}
file_path_url =
ada::parse<ada::url_aggregator>("./index", &package_json_url.value());
if (!file_path_url) {
THROW_ERR_INVALID_URL(isolate, "Invalid URL");
return;
}
initial_file_path = node::url::FileURLToPath(env, *file_path_url);
if (!initial_file_path.has_value()) {
return;
}
node::url::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]);
switch (FilePathIsFile(env, file_path)) {
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();
}
}
std::optional<std::string> module_path =
node::url::FileURLToPath(env, *package_json_url);
std::optional<std::string> module_base;
if (!module_path.has_value()) {
return;
}
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",
*module_path,
*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(context->GetIsolate());
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;
}
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, "mkdir", MKDir);
SetMethod(isolate, target, "readdir", ReadDir);
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);
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 FSReqCallback
Local<FunctionTemplate> fst = NewFunctionTemplate(isolate, NewFSReqCallback);
fst->InstanceTemplate()->SetInternalFieldCount(
FSReqBase::kInternalFieldCount);
fst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
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, "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(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(MKDir);
registry->Register(ReadDir);
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(Chmod);
registry->Register(FChmod);
registry->Register(Chown);
registry->Register(FChown);
registry->Register(LChown);
registry->Register(UTimes);
registry->Register(FUTimes);
registry->Register(LUTimes);
registry->Register(Mkdtemp);
registry->Register(NewFSReqCallback);
registry->Register(FileHandle::New);
registry->Register(FileHandle::Close);
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)