// 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 "node_file-inl.h" #include "aliased_buffer.h" #include "memory_tracker-inl.h" #include "node_buffer.h" #include "node_process.h" #include "node_stat_watcher.h" #include "util-inl.h" #include "tracing/trace_event.h" #include "req_wrap-inl.h" #include "stream_base-inl.h" #include "string_bytes.h" #include "string_search.h" #include #include #include #include #include #include #if defined(__MINGW32__) || defined(_MSC_VER) # include #endif #include namespace node { namespace fs { using v8::Array; using v8::Context; using v8::DontDelete; 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::Local; using v8::MaybeLocal; using v8::Number; using v8::Object; using v8::ObjectTemplate; using v8::Promise; using v8::PropertyAttribute; using v8::ReadOnly; using v8::String; using v8::Symbol; using v8::Uint32; using v8::Undefined; using v8::Value; #ifndef S_ISDIR # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #endif #ifdef __POSIX__ constexpr char kPathSeparator = '/'; #else const char* const kPathSeparator = "\\/"; #endif inline int64_t GetOffset(Local value) { return IsSafeJsInt(value) ? value.As()->Value() : -1; } #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__); // 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() {} FSReqBase::~FSReqBase() {} 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(Environment* env, Local obj, int fd) : AsyncWrap(env, obj, AsyncWrap::PROVIDER_FILEHANDLE), StreamBase(env), fd_(fd) { MakeWeak(); StreamBase::AttachToObject(GetObject()); } FileHandle* FileHandle::New(Environment* env, int fd, Local obj) { if (obj.IsEmpty() && !env->fd_constructor_template() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } PropertyAttribute attr = static_cast(ReadOnly | DontDelete); if (obj->DefineOwnProperty(env->context(), env->fd_string(), Integer::New(env->isolate(), fd), attr) .IsNothing()) { return nullptr; } return new FileHandle(env, obj, fd); } void FileHandle::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args.IsConstructCall()); CHECK(args[0]->IsInt32()); FileHandle* handle = FileHandle::New(env, args[0].As()->Value(), args.This()); if (handle == nullptr) return; if (args[1]->IsNumber()) handle->read_offset_ = args[1]->IntegerValue(env->context()).FromJust(); if (args[2]->IsNumber()) handle->read_length_ = args[2]->IntegerValue(env->context()).FromJust(); } 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_); } // 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_) return; uv_fs_t req; int ret = uv_fs_close(env()->event_loop(), &req, fd_, nullptr); uv_fs_req_cleanup(&req); AfterClose(); struct err_detail { int ret; int fd; }; err_detail detail { ret, fd_ }; 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()->SetUnrefImmediate([detail](Environment* env) { ProcessEmitWarning(env, "Closing file descriptor %d on garbage collection", detail.fd); }); } void FileHandle::CloseReq::Resolve() { Isolate* isolate = env()->isolate(); HandleScope scope(isolate); InternalCallbackScope callback_scope(this); Local promise = promise_.Get(isolate); Local resolver = promise.As(); resolver->Resolve(env()->context(), Undefined(isolate)).Check(); } void FileHandle::CloseReq::Reject(Local reason) { Isolate* isolate = env()->isolate(); HandleScope scope(isolate); InternalCallbackScope callback_scope(this); Local promise = promise_.Get(isolate); Local resolver = promise.As(); resolver->Reject(env()->context(), reason).Check(); } FileHandle* FileHandle::CloseReq::file_handle() { Isolate* isolate = env()->isolate(); HandleScope scope(isolate); Local val = ref_.Get(isolate); Local obj = val.As(); return Unwrap(obj); } FileHandle::CloseReq::CloseReq(Environment* env, Local obj, Local promise, Local 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 FileHandle::ClosePromise() { Isolate* isolate = env()->isolate(); EscapableHandleScope scope(isolate); Local context = env()->context(); auto maybe_resolver = Promise::Resolver::New(context); CHECK(!maybe_resolver.IsEmpty()); Local resolver = maybe_resolver.ToLocalChecked(); Local promise = resolver.As(); CHECK(!reading_); if (!closed_ && !closing_) { closing_ = true; Local close_req_obj; if (!env() ->fdclose_constructor_template() ->NewInstance(env()->context()) .ToLocal(&close_req_obj)) { return MaybeLocal(); } CloseReq* req = new CloseReq(env(), close_req_obj, promise, object()); auto AfterClose = uv_fs_callback_t{[](uv_fs_t* req) { std::unique_ptr close(CloseReq::from_req(req)); CHECK_NOT_NULL(close); close->file_handle()->AfterClose(); Isolate* isolate = close->env()->isolate(); if (req->result < 0) { close->Reject(UVException(isolate, req->result, "close")); } else { close->Resolve(); } }}; int ret = req->Dispatch(uv_fs_close, fd_, AfterClose); if (ret < 0) { req->Reject(UVException(isolate, ret, "close")); delete req; } } else { // Already closed. Just reject the promise immediately resolver->Reject(context, UVException(isolate, UV_EBADF, "close")) .Check(); } return scope.Escape(promise); } void FileHandle::Close(const FunctionCallbackInfo& args) { FileHandle* fd; ASSIGN_OR_RETURN_UNWRAP(&fd, args.Holder()); Local ret; if (!fd->ClosePromise().ToLocal(&ret)) return; args.GetReturnValue().Set(ret); } void FileHandle::ReleaseFD(const FunctionCallbackInfo& args) { FileHandle* fd; ASSIGN_OR_RETURN_UNWRAP(&fd, args.Holder()); // Just act as if this FileHandle has been closed. fd->AfterClose(); } void FileHandle::AfterClose() { closing_ = false; closed_ = true; 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 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; std::unique_ptr 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 = env()->file_handle_read_wrap_freelist(); if (freelist.size() > 0) { read_wrap = std::move(freelist.back()); freelist.pop_back(); read_wrap->AsyncReset(); read_wrap->file_handle_ = this; } else { Local wrap_obj; if (!env() ->filehandlereadwrap_template() ->NewInstance(env()->context()) .ToLocal(&wrap_obj)) { return UV_EBUSY; } read_wrap = std::make_unique(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); 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); 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. std::unique_ptr read_wrap = std::move(handle->current_read_); int 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 wanted_freelist_fill = 100; auto& freelist = handle->env()->file_handle_read_wrap_freelist(); if (freelist.size() < wanted_freelist_fill) { 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> FileHandleCloseWrap; ShutdownWrap* FileHandle::CreateShutdownWrap(Local object) { return new FileHandleCloseWrap(this, object); } int FileHandle::DoShutdown(ShutdownWrap* req_wrap) { FileHandleCloseWrap* wrap = static_cast(req_wrap); closing_ = true; wrap->Dispatch(uv_fs_close, fd_, uv_fs_callback_t{[](uv_fs_t* req) { FileHandleCloseWrap* wrap = static_cast( FileHandleCloseWrap::from_req(req)); FileHandle* handle = static_cast(wrap->stream()); handle->AfterClose(); int result = req->result; uv_fs_req_cleanup(req); wrap->Done(result); }}); return 0; } void FSReqCallback::Reject(Local reject) { MakeCallback(env()->oncomplete_string(), 1, &reject); } void FSReqCallback::ResolveStat(const uv_stat_t* stat) { Resolve(FillGlobalStatsArray(env(), use_bigint(), stat)); } void FSReqCallback::Resolve(Local value) { Local argv[2] { Null(env()->isolate()), value }; MakeCallback(env()->oncomplete_string(), value->IsUndefined() ? 1 : arraysize(argv), argv); } void FSReqCallback::SetReturnValue(const FunctionCallbackInfo& args) { args.GetReturnValue().SetUndefined(); } void NewFSReqCallback(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); new FSReqCallback(env, 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() { uv_fs_req_cleanup(wrap_->req()); delete wrap_; } // 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) { wrap_->Reject(UVException(wrap_->env()->isolate(), req->result, wrap_->syscall(), nullptr, req->path, wrap_->data())); } bool FSReqAfterScope::Proceed() { 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); 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); if (after.Proceed()) { req_wrap->ResolveStat(&req->statbuf); } } void AfterInteger(uv_fs_t* req) { FSReqBase* req_wrap = FSReqBase::from_req(req); FSReqAfterScope after(req_wrap, req); if (after.Proceed()) req_wrap->Resolve(Integer::New(req_wrap->env()->isolate(), req->result)); } void AfterOpenFileHandle(uv_fs_t* req) { FSReqBase* req_wrap = FSReqBase::from_req(req); FSReqAfterScope after(req_wrap, req); if (after.Proceed()) { FileHandle* fd = FileHandle::New(req_wrap->env(), req->result); if (fd == nullptr) return; req_wrap->Resolve(fd->object()); } } void AfterStringPath(uv_fs_t* req) { FSReqBase* req_wrap = FSReqBase::from_req(req); FSReqAfterScope after(req_wrap, req); MaybeLocal link; Local 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); MaybeLocal link; Local error; if (after.Proceed()) { link = StringBytes::Encode(req_wrap->env()->isolate(), static_cast(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); if (!after.Proceed()) { return; } Environment* env = req_wrap->env(); Local error; int r; std::vector> name_v; for (int i = 0; ; i++) { uv_dirent_t ent; r = uv_fs_scandir_next(req, &ent); if (r == UV_EOF) break; if (r != 0) { return req_wrap->Reject(UVException( env->isolate(), r, nullptr, req_wrap->syscall(), req->path)); } MaybeLocal filename = StringBytes::Encode(env->isolate(), ent.name, req_wrap->encoding(), &error); if (filename.IsEmpty()) return req_wrap->Reject(error); name_v.push_back(filename.ToLocalChecked()); } req_wrap->Resolve(Array::New(env->isolate(), name_v.data(), name_v.size())); } void AfterScanDirWithTypes(uv_fs_t* req) { FSReqBase* req_wrap = FSReqBase::from_req(req); FSReqAfterScope after(req_wrap, req); if (!after.Proceed()) { return; } Environment* env = req_wrap->env(); Isolate* isolate = env->isolate(); Local error; int r; std::vector> name_v; std::vector> type_v; for (int i = 0; ; i++) { 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)); } MaybeLocal filename = StringBytes::Encode(isolate, ent.name, req_wrap->encoding(), &error); if (filename.IsEmpty()) return req_wrap->Reject(error); name_v.push_back(filename.ToLocalChecked()); type_v.emplace_back(Integer::New(isolate, ent.type)); } Local result = Array::New(isolate, 2); result->Set(env->context(), 0, Array::New(isolate, name_v.data(), name_v.size())).Check(); result->Set(env->context(), 1, Array::New(isolate, type_v.data(), type_v.size())).Check(); req_wrap->Resolve(result); } void Access(const FunctionCallbackInfo& 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()->Value(); BufferValue path(isolate, args[0]); CHECK_NOT_NULL(*path); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { // access(path, mode, req) AsyncCall(env, req_wrap_async, args, "access", UTF8, AfterNoArgs, uv_fs_access, *path, mode); } else { // access(path, mode, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(access); SyncCall(env, args[3], &req_wrap_sync, "access", uv_fs_access, *path, mode); FS_SYNC_TRACE_END(access); } } void Close(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); CHECK_GE(argc, 2); CHECK(args[0]->IsInt32()); int fd = args[0].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); if (req_wrap_async != nullptr) { // close(fd, req) AsyncCall(env, req_wrap_async, args, "close", UTF8, AfterNoArgs, uv_fs_close, fd); } else { // close(fd, undefined, ctx) CHECK_EQ(argc, 3); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(close); SyncCall(env, args[2], &req_wrap_sync, "close", uv_fs_close, fd); FS_SYNC_TRACE_END(close); } } // Used to speed up module loading. Returns the contents of the file as // a string or undefined when the file cannot be opened or "main" is not found // in the file. static void InternalModuleReadJSON(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); uv_loop_t* loop = env->event_loop(); CHECK(args[0]->IsString()); node::Utf8Value path(isolate, args[0]); if (strlen(*path) != path.length()) return; // Contains a nul byte. uv_fs_t open_req; const int fd = uv_fs_open(loop, &open_req, *path, O_RDONLY, 0, nullptr); uv_fs_req_cleanup(&open_req); if (fd < 0) { return; } std::shared_ptr defer_close(nullptr, [fd, loop] (...) { uv_fs_t close_req; CHECK_EQ(0, uv_fs_close(loop, &close_req, fd, nullptr)); uv_fs_req_cleanup(&close_req); }); const size_t kBlockSize = 32 << 10; std::vector chars; int64_t offset = 0; ssize_t numchars; do { const size_t start = chars.size(); chars.resize(start + kBlockSize); uv_buf_t buf; buf.base = &chars[start]; buf.len = kBlockSize; uv_fs_t read_req; numchars = uv_fs_read(loop, &read_req, fd, &buf, 1, offset, nullptr); uv_fs_req_cleanup(&read_req); if (numchars < 0) return; offset += numchars; } while (static_cast(numchars) == kBlockSize); size_t start = 0; if (offset >= 3 && 0 == memcmp(&chars[0], "\xEF\xBB\xBF", 3)) { start = 3; // Skip UTF-8 BOM. } const size_t size = offset - start; if (size == 0 || ( size == SearchString(&chars[start], size, "\"main\"") && size == SearchString(&chars[start], size, "\"exports\"") && size == SearchString(&chars[start], size, "\"type\""))) { args.GetReturnValue().Set(env->empty_object_string()); } else { Local chars_string = String::NewFromUtf8(isolate, &chars[start], v8::NewStringType::kNormal, size).ToLocalChecked(); args.GetReturnValue().Set(chars_string); } } // 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& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsString()); node::Utf8Value path(env->isolate(), args[0]); 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(req.ptr); rc = !!(s->st_mode & S_IFDIR); } uv_fs_req_cleanup(&req); args.GetReturnValue().Set(rc); } static void Stat(const FunctionCallbackInfo& 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); bool use_bigint = args[1]->IsTrue(); FSReqBase* req_wrap_async = GetReqWrap(env, args[2], use_bigint); if (req_wrap_async != nullptr) { // stat(path, use_bigint, req) AsyncCall(env, req_wrap_async, args, "stat", UTF8, AfterStat, uv_fs_stat, *path); } else { // stat(path, use_bigint, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(stat); int err = SyncCall(env, args[3], &req_wrap_sync, "stat", uv_fs_stat, *path); FS_SYNC_TRACE_END(stat); if (err != 0) { return; // error info is in ctx } Local arr = FillGlobalStatsArray(env, use_bigint, static_cast(req_wrap_sync.req.ptr)); args.GetReturnValue().Set(arr); } } static void LStat(const FunctionCallbackInfo& 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); bool use_bigint = args[1]->IsTrue(); FSReqBase* req_wrap_async = GetReqWrap(env, args[2], use_bigint); if (req_wrap_async != nullptr) { // lstat(path, use_bigint, req) AsyncCall(env, req_wrap_async, args, "lstat", UTF8, AfterStat, uv_fs_lstat, *path); } else { // lstat(path, use_bigint, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(lstat); int err = SyncCall(env, args[3], &req_wrap_sync, "lstat", uv_fs_lstat, *path); FS_SYNC_TRACE_END(lstat); if (err != 0) { return; // error info is in ctx } Local arr = FillGlobalStatsArray(env, use_bigint, static_cast(req_wrap_sync.req.ptr)); args.GetReturnValue().Set(arr); } } static void FStat(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); CHECK_GE(argc, 2); CHECK(args[0]->IsInt32()); int fd = args[0].As()->Value(); bool use_bigint = args[1]->IsTrue(); FSReqBase* req_wrap_async = GetReqWrap(env, args[2], use_bigint); if (req_wrap_async != nullptr) { // fstat(fd, use_bigint, req) AsyncCall(env, req_wrap_async, args, "fstat", UTF8, AfterStat, uv_fs_fstat, fd); } else { // fstat(fd, use_bigint, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(fstat); int err = SyncCall(env, args[3], &req_wrap_sync, "fstat", uv_fs_fstat, fd); FS_SYNC_TRACE_END(fstat); if (err != 0) { return; // error info is in ctx } Local arr = FillGlobalStatsArray(env, use_bigint, static_cast(req_wrap_sync.req.ptr)); args.GetReturnValue().Set(arr); } } static void Symlink(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); int argc = args.Length(); CHECK_GE(argc, 4); BufferValue target(isolate, args[0]); CHECK_NOT_NULL(*target); BufferValue path(isolate, args[1]); CHECK_NOT_NULL(*path); CHECK(args[2]->IsInt32()); int flags = args[2].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // symlink(target, path, flags, req) AsyncDestCall(env, req_wrap_async, args, "symlink", *path, path.length(), UTF8, AfterNoArgs, uv_fs_symlink, *target, *path, flags); } else { // symlink(target, path, flags, undefinec, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(symlink); SyncCall(env, args[4], &req_wrap_sync, "symlink", uv_fs_symlink, *target, *path, flags); FS_SYNC_TRACE_END(symlink); } } static void Link(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); int argc = args.Length(); CHECK_GE(argc, 3); BufferValue src(isolate, args[0]); CHECK_NOT_NULL(*src); BufferValue dest(isolate, args[1]); CHECK_NOT_NULL(*dest); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { // link(src, dest, req) AsyncDestCall(env, req_wrap_async, args, "link", *dest, dest.length(), UTF8, AfterNoArgs, uv_fs_link, *src, *dest); } else { // link(src, dest) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(link); SyncCall(env, args[3], &req_wrap_sync, "link", uv_fs_link, *src, *dest); FS_SYNC_TRACE_END(link); } } static void ReadLink(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); int argc = args.Length(); CHECK_GE(argc, 3); BufferValue path(isolate, args[0]); CHECK_NOT_NULL(*path); const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { // readlink(path, encoding, req) AsyncCall(env, req_wrap_async, args, "readlink", encoding, AfterStringPtr, uv_fs_readlink, *path); } else { CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(readlink); int err = SyncCall(env, args[3], &req_wrap_sync, "readlink", uv_fs_readlink, *path); FS_SYNC_TRACE_END(readlink); if (err < 0) { return; // syscall failed, no need to continue, error info is in ctx } const char* link_path = static_cast(req_wrap_sync.req.ptr); Local error; MaybeLocal rc = StringBytes::Encode(isolate, link_path, encoding, &error); if (rc.IsEmpty()) { Local ctx = args[3].As(); ctx->Set(env->context(), env->error_string(), error).Check(); return; } args.GetReturnValue().Set(rc.ToLocalChecked()); } } static void Rename(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); int argc = args.Length(); CHECK_GE(argc, 3); BufferValue old_path(isolate, args[0]); CHECK_NOT_NULL(*old_path); BufferValue new_path(isolate, args[1]); CHECK_NOT_NULL(*new_path); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { AsyncDestCall(env, req_wrap_async, args, "rename", *new_path, new_path.length(), UTF8, AfterNoArgs, uv_fs_rename, *old_path, *new_path); } else { CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(rename); SyncCall(env, args[3], &req_wrap_sync, "rename", uv_fs_rename, *old_path, *new_path); FS_SYNC_TRACE_END(rename); } } static void FTruncate(const FunctionCallbackInfo& 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()->Value(); CHECK(IsSafeJsInt(args[1])); const int64_t len = args[1].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { AsyncCall(env, req_wrap_async, args, "ftruncate", UTF8, AfterNoArgs, uv_fs_ftruncate, fd, len); } else { CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(ftruncate); SyncCall(env, args[3], &req_wrap_sync, "ftruncate", uv_fs_ftruncate, fd, len); FS_SYNC_TRACE_END(ftruncate); } } static void Fdatasync(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); CHECK_GE(argc, 2); CHECK(args[0]->IsInt32()); const int fd = args[0].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); if (req_wrap_async != nullptr) { AsyncCall(env, req_wrap_async, args, "fdatasync", UTF8, AfterNoArgs, uv_fs_fdatasync, fd); } else { CHECK_EQ(argc, 3); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(fdatasync); SyncCall(env, args[2], &req_wrap_sync, "fdatasync", uv_fs_fdatasync, fd); FS_SYNC_TRACE_END(fdatasync); } } static void Fsync(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); CHECK_GE(argc, 2); CHECK(args[0]->IsInt32()); const int fd = args[0].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); if (req_wrap_async != nullptr) { AsyncCall(env, req_wrap_async, args, "fsync", UTF8, AfterNoArgs, uv_fs_fsync, fd); } else { CHECK_EQ(argc, 3); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(fsync); SyncCall(env, args[2], &req_wrap_sync, "fsync", uv_fs_fsync, fd); FS_SYNC_TRACE_END(fsync); } } static void Unlink(const FunctionCallbackInfo& 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); FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); if (req_wrap_async != nullptr) { AsyncCall(env, req_wrap_async, args, "unlink", UTF8, AfterNoArgs, uv_fs_unlink, *path); } else { CHECK_EQ(argc, 3); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(unlink); SyncCall(env, args[2], &req_wrap_sync, "unlink", uv_fs_unlink, *path); FS_SYNC_TRACE_END(unlink); } } static void RMDir(const FunctionCallbackInfo& 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); FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); // rmdir(path, req) if (req_wrap_async != nullptr) { AsyncCall(env, req_wrap_async, args, "rmdir", UTF8, AfterNoArgs, uv_fs_rmdir, *path); } else { // rmdir(path, undefined, ctx) CHECK_EQ(argc, 3); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(rmdir); SyncCall(env, args[2], &req_wrap_sync, "rmdir", 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) { FSContinuationData continuation_data(req, mode, cb); continuation_data.PushPath(std::move(path)); while (continuation_data.paths().size() > 0) { std::string next_path = continuation_data.PopPath(); int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr); while (true) { switch (err) { case 0: if (continuation_data.paths().size() == 0) { return 0; } break; case UV_ENOENT: { std::string dirname = next_path.substr(0, next_path.find_last_of(kPathSeparator)); if (dirname != next_path) { continuation_data.PushPath(std::move(next_path)); continuation_data.PushPath(std::move(dirname)); } else if (continuation_data.paths().size() == 0) { err = UV_EEXIST; continue; } break; } case UV_EPERM: { return err; } 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 && 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(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 = req->result; while (true) { switch (err) { case 0: { if (req_wrap->continuation_data()->paths().size() == 0) { req_wrap->continuation_data()->Done(0); } else { uv_fs_req_cleanup(req); MKDirpAsync(loop, req, path.c_str(), req_wrap->continuation_data()->mode(), nullptr); } break; } case UV_ENOENT: { std::string dirname = path.substr(0, path.find_last_of(kPathSeparator)); if (dirname != path) { req_wrap->continuation_data()->PushPath(std::move(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; } case UV_EPERM: { req_wrap->continuation_data()->Done(err); break; } default: uv_fs_req_cleanup(req); // Stash err for use in the callback. req->data = reinterpret_cast(static_cast(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 = req->result; if (reinterpret_cast(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; uv_fs_req_cleanup(req); req_wrap->continuation_data()->Done(err); }}); if (err < 0) req_wrap->continuation_data()->Done(err); break; } break; } }}); return err; } static void MKDir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); CHECK_GE(argc, 4); BufferValue path(env->isolate(), args[0]); CHECK_NOT_NULL(*path); CHECK(args[1]->IsInt32()); const int mode = args[1].As()->Value(); CHECK(args[2]->IsBoolean()); bool mkdirp = args[2]->IsTrue(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // mkdir(path, mode, req) AsyncCall(env, req_wrap_async, args, "mkdir", UTF8, AfterNoArgs, mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode); } else { // mkdir(path, mode, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(mkdir); if (mkdirp) { SyncCall(env, args[4], &req_wrap_sync, "mkdir", MKDirpSync, *path, mode); } else { SyncCall(env, args[4], &req_wrap_sync, "mkdir", uv_fs_mkdir, *path, mode); } FS_SYNC_TRACE_END(mkdir); } } static void RealPath(const FunctionCallbackInfo& 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); const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { // realpath(path, encoding, req) AsyncCall(env, req_wrap_async, args, "realpath", encoding, AfterStringPtr, uv_fs_realpath, *path); } else { // realpath(path, encoding, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(realpath); int err = SyncCall(env, args[3], &req_wrap_sync, "realpath", uv_fs_realpath, *path); FS_SYNC_TRACE_END(realpath); if (err < 0) { return; // syscall failed, no need to continue, error info is in ctx } const char* link_path = static_cast(req_wrap_sync.req.ptr); Local error; MaybeLocal rc = StringBytes::Encode(isolate, link_path, encoding, &error); if (rc.IsEmpty()) { Local ctx = args[3].As(); ctx->Set(env->context(), env->error_string(), error).Check(); return; } args.GetReturnValue().Set(rc.ToLocalChecked()); } } static void ReadDir(const FunctionCallbackInfo& 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); const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); bool with_types = args[2]->IsTrue(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // readdir(path, encoding, withTypes, req) if (with_types) { AsyncCall(env, req_wrap_async, args, "scandir", encoding, AfterScanDirWithTypes, uv_fs_scandir, *path, 0 /*flags*/); } else { AsyncCall(env, req_wrap_async, args, "scandir", encoding, AfterScanDir, uv_fs_scandir, *path, 0 /*flags*/); } } else { // readdir(path, encoding, withTypes, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(readdir); int err = SyncCall(env, args[4], &req_wrap_sync, "scandir", uv_fs_scandir, *path, 0 /*flags*/); FS_SYNC_TRACE_END(readdir); if (err < 0) { return; // syscall failed, no need to continue, error info is in ctx } CHECK_GE(req_wrap_sync.req.result, 0); int r; std::vector> name_v; std::vector> type_v; for (int i = 0; ; i++) { uv_dirent_t ent; r = uv_fs_scandir_next(&(req_wrap_sync.req), &ent); if (r == UV_EOF) break; if (r != 0) { Local ctx = args[4].As(); ctx->Set(env->context(), env->errno_string(), Integer::New(isolate, r)).Check(); ctx->Set(env->context(), env->syscall_string(), OneByteString(isolate, "readdir")).Check(); return; } Local error; MaybeLocal filename = StringBytes::Encode(isolate, ent.name, encoding, &error); if (filename.IsEmpty()) { Local ctx = args[4].As(); ctx->Set(env->context(), env->error_string(), error).Check(); return; } name_v.push_back(filename.ToLocalChecked()); if (with_types) { type_v.emplace_back(Integer::New(isolate, ent.type)); } } Local names = Array::New(isolate, name_v.data(), name_v.size()); if (with_types) { Local result = Array::New(isolate, 2); result->Set(env->context(), 0, names).Check(); result->Set(env->context(), 1, Array::New(isolate, type_v.data(), type_v.size())).Check(); args.GetReturnValue().Set(result); } else { args.GetReturnValue().Set(names); } } } static void Open(const FunctionCallbackInfo& 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()->Value(); CHECK(args[2]->IsInt32()); const int mode = args[2].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // open(path, flags, mode, req) AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterInteger, uv_fs_open, *path, flags, mode); } else { // open(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); args.GetReturnValue().Set(result); } } static void OpenFileHandle(const FunctionCallbackInfo& 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); CHECK(args[1]->IsInt32()); const int flags = args[1].As()->Value(); CHECK(args[2]->IsInt32()); const int mode = args[2].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // openFileHandle(path, flags, mode, req) 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(env, result); if (fd == nullptr) return; args.GetReturnValue().Set(fd->object()); } } static void CopyFile(const FunctionCallbackInfo& 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); BufferValue dest(isolate, args[1]); CHECK_NOT_NULL(*dest); CHECK(args[2]->IsInt32()); const int flags = args[2].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // copyFile(src, dest, flags, req) AsyncDestCall(env, req_wrap_async, args, "copyfile", *dest, dest.length(), UTF8, AfterNoArgs, uv_fs_copyfile, *src, *dest, flags); } else { // copyFile(src, dest, flags, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(copyfile); SyncCall(env, args[4], &req_wrap_sync, "copyfile", 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& 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()->Value(); CHECK(Buffer::HasInstance(args[1])); Local buffer_obj = args[1].As(); 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()->Value(); CHECK_GE(off_64, 0); CHECK_LE(static_cast(off_64), buffer_length); const size_t off = static_cast(off_64); CHECK(args[3]->IsInt32()); const size_t len = static_cast(args[3].As()->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(env, args[5]); if (req_wrap_async != nullptr) { // write(fd, buffer, off, len, pos, req) 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& 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()->Value(); CHECK(args[1]->IsArray()); Local chunks = args[1].As(); int64_t pos = GetOffset(args[2]); MaybeStackBuffer iovs(chunks->Length()); for (uint32_t i = 0; i < iovs.length(); i++) { Local chunk = chunks->Get(env->context(), i).ToLocalChecked(); CHECK(Buffer::HasInstance(chunk)); iovs[i] = uv_buf_init(Buffer::Data(chunk), Buffer::Length(chunk)); } FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // writeBuffers(fd, chunks, pos, req) AsyncCall(env, req_wrap_async, args, "write", UTF8, AfterInteger, uv_fs_write, fd, *iovs, iovs.length(), pos); } else { // writeBuffers(fd, chunks, pos, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(write); int bytesWritten = SyncCall(env, args[4], &req_wrap_sync, "write", uv_fs_write, fd, *iovs, iovs.length(), pos); FS_SYNC_TRACE_END(write, "bytesWritten", bytesWritten); 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& 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()->Value(); const int64_t pos = GetOffset(args[2]); const auto enc = ParseEncoding(isolate, args[3], UTF8); Local value = args[1]; char* buf = nullptr; size_t len; FSReqBase* req_wrap_async = GetReqWrap(env, 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(); if ((enc == ASCII || enc == LATIN1) && string->IsExternalOneByte()) { auto ext = string->GetExternalOneByteStringResource(); buf = const_cast(ext->data()); len = ext->length(); } else if (enc == UCS2 && IsLittleEndian() && string->IsExternal()) { auto ext = string->GetExternalStringResource(); buf = reinterpret_cast(const_cast(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); 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); } } /* * 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& 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()->Value(); CHECK(Buffer::HasInstance(args[1])); Local buffer_obj = args[1].As(); 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()->Value(); CHECK_GE(off_64, 0); CHECK_LT(static_cast(off_64), buffer_length); const size_t off = static_cast(off_64); CHECK(args[3]->IsInt32()); const size_t len = static_cast(args[3].As()->Value()); CHECK(Buffer::IsWithinBounds(off, len, buffer_length)); CHECK(IsSafeJsInt(args[4])); const int64_t pos = args[4].As()->Value(); char* buf = buffer_data + off; uv_buf_t uvbuf = uv_buf_init(buf, len); FSReqBase* req_wrap_async = GetReqWrap(env, args[5]); if (req_wrap_async != nullptr) { // read(fd, buffer, offset, len, pos, req) AsyncCall(env, req_wrap_async, args, "read", UTF8, AfterInteger, uv_fs_read, fd, &uvbuf, 1, pos); } else { // read(fd, buffer, offset, len, pos, undefined, ctx) CHECK_EQ(argc, 7); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(read); const int bytesRead = SyncCall(env, args[6], &req_wrap_sync, "read", uv_fs_read, fd, &uvbuf, 1, pos); FS_SYNC_TRACE_END(read, "bytesRead", bytesRead); args.GetReturnValue().Set(bytesRead); } } /* fs.chmod(path, mode); * Wrapper for chmod(1) / EIO_CHMOD */ static void Chmod(const FunctionCallbackInfo& 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); CHECK(args[1]->IsInt32()); int mode = args[1].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { // chmod(path, mode, req) AsyncCall(env, req_wrap_async, args, "chmod", UTF8, AfterNoArgs, uv_fs_chmod, *path, mode); } else { // chmod(path, mode, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(chmod); SyncCall(env, args[3], &req_wrap_sync, "chmod", 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& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); CHECK_GE(argc, 2); CHECK(args[0]->IsInt32()); const int fd = args[0].As()->Value(); CHECK(args[1]->IsInt32()); const int mode = args[1].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { // fchmod(fd, mode, req) AsyncCall(env, req_wrap_async, args, "fchmod", UTF8, AfterNoArgs, uv_fs_fchmod, fd, mode); } else { // fchmod(fd, mode, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(fchmod); SyncCall(env, args[3], &req_wrap_sync, "fchmod", 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& 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]->IsUint32()); const uv_uid_t uid = static_cast(args[1].As()->Value()); CHECK(args[2]->IsUint32()); const uv_gid_t gid = static_cast(args[2].As()->Value()); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // chown(path, uid, gid, req) AsyncCall(env, req_wrap_async, args, "chown", UTF8, AfterNoArgs, uv_fs_chown, *path, uid, gid); } else { // chown(path, uid, gid, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(chown); SyncCall(env, args[4], &req_wrap_sync, "chown", 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& 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()->Value(); CHECK(args[1]->IsUint32()); const uv_uid_t uid = static_cast(args[1].As()->Value()); CHECK(args[2]->IsUint32()); const uv_gid_t gid = static_cast(args[2].As()->Value()); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // fchown(fd, uid, gid, req) AsyncCall(env, req_wrap_async, args, "fchown", UTF8, AfterNoArgs, uv_fs_fchown, fd, uid, gid); } else { // fchown(fd, uid, gid, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(fchown); SyncCall(env, args[4], &req_wrap_sync, "fchown", uv_fs_fchown, fd, uid, gid); FS_SYNC_TRACE_END(fchown); } } static void LChown(const FunctionCallbackInfo& 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]->IsUint32()); const uv_uid_t uid = static_cast(args[1].As()->Value()); CHECK(args[2]->IsUint32()); const uv_gid_t gid = static_cast(args[2].As()->Value()); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // lchown(path, uid, gid, req) AsyncCall(env, req_wrap_async, args, "lchown", UTF8, AfterNoArgs, uv_fs_lchown, *path, uid, gid); } else { // lchown(path, uid, gid, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(lchown); SyncCall(env, args[4], &req_wrap_sync, "lchown", uv_fs_lchown, *path, uid, gid); FS_SYNC_TRACE_END(lchown); } } static void UTimes(const FunctionCallbackInfo& 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]->IsNumber()); const double atime = args[1].As()->Value(); CHECK(args[2]->IsNumber()); const double mtime = args[2].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // utimes(path, atime, mtime, req) AsyncCall(env, req_wrap_async, args, "utime", UTF8, AfterNoArgs, uv_fs_utime, *path, atime, mtime); } else { // utimes(path, atime, mtime, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(utimes); SyncCall(env, args[4], &req_wrap_sync, "utime", uv_fs_utime, *path, atime, mtime); FS_SYNC_TRACE_END(utimes); } } static void FUTimes(const FunctionCallbackInfo& 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()->Value(); CHECK(args[1]->IsNumber()); const double atime = args[1].As()->Value(); CHECK(args[2]->IsNumber()); const double mtime = args[2].As()->Value(); FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); if (req_wrap_async != nullptr) { // futimes(fd, atime, mtime, req) AsyncCall(env, req_wrap_async, args, "futime", UTF8, AfterNoArgs, uv_fs_futime, fd, atime, mtime); } else { // futimes(fd, atime, mtime, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(futimes); SyncCall(env, args[4], &req_wrap_sync, "futime", uv_fs_futime, fd, atime, mtime); FS_SYNC_TRACE_END(futimes); } } static void Mkdtemp(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); const int argc = args.Length(); CHECK_GE(argc, 2); BufferValue tmpl(isolate, args[0]); CHECK_NOT_NULL(*tmpl); const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); FSReqBase* req_wrap_async = GetReqWrap(env, args[2]); if (req_wrap_async != nullptr) { // mkdtemp(tmpl, encoding, req) AsyncCall(env, req_wrap_async, args, "mkdtemp", encoding, AfterStringPath, uv_fs_mkdtemp, *tmpl); } else { // mkdtemp(tmpl, encoding, undefined, ctx) CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(mkdtemp); SyncCall(env, args[3], &req_wrap_sync, "mkdtemp", uv_fs_mkdtemp, *tmpl); FS_SYNC_TRACE_END(mkdtemp); const char* path = req_wrap_sync.req.path; Local error; MaybeLocal rc = StringBytes::Encode(isolate, path, encoding, &error); if (rc.IsEmpty()) { Local ctx = args[3].As(); ctx->Set(env->context(), env->error_string(), error).Check(); return; } args.GetReturnValue().Set(rc.ToLocalChecked()); } } void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); env->SetMethod(target, "access", Access); env->SetMethod(target, "close", Close); env->SetMethod(target, "open", Open); env->SetMethod(target, "openFileHandle", OpenFileHandle); env->SetMethod(target, "read", Read); env->SetMethod(target, "fdatasync", Fdatasync); env->SetMethod(target, "fsync", Fsync); env->SetMethod(target, "rename", Rename); env->SetMethod(target, "ftruncate", FTruncate); env->SetMethod(target, "rmdir", RMDir); env->SetMethod(target, "mkdir", MKDir); env->SetMethod(target, "readdir", ReadDir); env->SetMethod(target, "internalModuleReadJSON", InternalModuleReadJSON); env->SetMethod(target, "internalModuleStat", InternalModuleStat); env->SetMethod(target, "stat", Stat); env->SetMethod(target, "lstat", LStat); env->SetMethod(target, "fstat", FStat); env->SetMethod(target, "link", Link); env->SetMethod(target, "symlink", Symlink); env->SetMethod(target, "readlink", ReadLink); env->SetMethod(target, "unlink", Unlink); env->SetMethod(target, "writeBuffer", WriteBuffer); env->SetMethod(target, "writeBuffers", WriteBuffers); env->SetMethod(target, "writeString", WriteString); env->SetMethod(target, "realpath", RealPath); env->SetMethod(target, "copyFile", CopyFile); env->SetMethod(target, "chmod", Chmod); env->SetMethod(target, "fchmod", FChmod); // env->SetMethod(target, "lchmod", LChmod); env->SetMethod(target, "chown", Chown); env->SetMethod(target, "fchown", FChown); env->SetMethod(target, "lchown", LChown); env->SetMethod(target, "utimes", UTimes); env->SetMethod(target, "futimes", FUTimes); env->SetMethod(target, "mkdtemp", Mkdtemp); target ->Set(context, FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"), Integer::New( isolate, static_cast(FsStatsOffset::kFsStatsFieldsNumber))) .Check(); target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "statValues"), env->fs_stats_field_array()->GetJSArray()).Check(); target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "bigintStatValues"), env->fs_stats_field_bigint_array()->GetJSArray()).Check(); StatWatcher::Initialize(env, target); // Create FunctionTemplate for FSReqCallback Local fst = env->NewFunctionTemplate(NewFSReqCallback); fst->InstanceTemplate()->SetInternalFieldCount(1); fst->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local wrapString = FIXED_ONE_BYTE_STRING(isolate, "FSReqCallback"); fst->SetClassName(wrapString); target ->Set(context, wrapString, fst->GetFunction(env->context()).ToLocalChecked()) .Check(); // Create FunctionTemplate for FileHandleReadWrap. There’s no need // to do anything in the constructor, so we only store the instance template. Local fh_rw = FunctionTemplate::New(isolate); fh_rw->InstanceTemplate()->SetInternalFieldCount(1); fh_rw->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local fhWrapString = FIXED_ONE_BYTE_STRING(isolate, "FileHandleReqWrap"); fh_rw->SetClassName(fhWrapString); env->set_filehandlereadwrap_template( fst->InstanceTemplate()); // Create Function Template for FSReqPromise Local fpt = FunctionTemplate::New(isolate); fpt->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local promiseString = FIXED_ONE_BYTE_STRING(isolate, "FSReqPromise"); fpt->SetClassName(promiseString); Local fpo = fpt->InstanceTemplate(); fpo->SetInternalFieldCount(1); env->set_fsreqpromise_constructor_template(fpo); // Create FunctionTemplate for FileHandle Local fd = env->NewFunctionTemplate(FileHandle::New); fd->Inherit(AsyncWrap::GetConstructorTemplate(env)); env->SetProtoMethod(fd, "close", FileHandle::Close); env->SetProtoMethod(fd, "releaseFD", FileHandle::ReleaseFD); Local fdt = fd->InstanceTemplate(); fdt->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); Local handleString = FIXED_ONE_BYTE_STRING(isolate, "FileHandle"); fd->SetClassName(handleString); StreamBase::AddMethods(env, fd); target ->Set(context, handleString, fd->GetFunction(env->context()).ToLocalChecked()) .Check(); env->set_fd_constructor_template(fdt); // Create FunctionTemplate for FileHandle::CloseReq Local fdclose = FunctionTemplate::New(isolate); fdclose->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "FileHandleCloseReq")); fdclose->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local fdcloset = fdclose->InstanceTemplate(); fdcloset->SetInternalFieldCount(1); env->set_fdclose_constructor_template(fdcloset); Local use_promises_symbol = Symbol::New(isolate, FIXED_ONE_BYTE_STRING(isolate, "use promises")); env->set_fs_use_promises_symbol(use_promises_symbol); target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "kUsePromises"), use_promises_symbol).Check(); } } // namespace fs } // end namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs, node::fs::Initialize)