| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| #include "node_file.h" // NOLINT(build/include_inline) |
| #include "ada.h" |
| #include "aliased_buffer-inl.h" |
| #include "memory_tracker-inl.h" |
| #include "node_buffer.h" |
| #include "node_debug.h" |
| #include "node_errors.h" |
| #include "node_external_reference.h" |
| #include "node_file-inl.h" |
| #include "node_metadata.h" |
| #include "node_process-inl.h" |
| #include "node_stat_watcher.h" |
| #include "node_url.h" |
| #include "path.h" |
| #include "permission/permission.h" |
| #include "util-inl.h" |
| |
| #include "tracing/trace_event.h" |
| |
| #include "req_wrap-inl.h" |
| #include "stream_base-inl.h" |
| #include "string_bytes.h" |
| #include "uv.h" |
| #include "v8-fast-api-calls.h" |
| |
| #include <errno.h> |
| #include <cerrno> |
| #include <cstdio> |
| #include <filesystem> |
| |
| #if defined(__MINGW32__) || defined(_MSC_VER) |
| # include <io.h> |
| #endif |
| |
| #ifdef _WIN32 |
| #include <windows.h> |
| #else |
| #include <unistd.h> |
| #endif |
| |
| namespace node { |
| |
| namespace fs { |
| |
| using v8::Array; |
| using v8::BigInt; |
| using v8::Context; |
| using v8::EscapableHandleScope; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::HandleScope; |
| using v8::Int32; |
| using v8::Integer; |
| using v8::Isolate; |
| using v8::JustVoid; |
| using v8::Local; |
| using v8::LocalVector; |
| using v8::Maybe; |
| using v8::MaybeLocal; |
| using v8::Nothing; |
| using v8::Number; |
| using v8::Object; |
| using v8::ObjectTemplate; |
| using v8::Promise; |
| using v8::String; |
| using v8::TryCatch; |
| using v8::Undefined; |
| using v8::Value; |
| |
| #ifndef S_ISDIR |
| # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) |
| #endif |
| |
| #ifdef __POSIX__ |
| constexpr char kPathSeparator = '/'; |
| #else |
| const char* const kPathSeparator = "\\/"; |
| #endif |
| |
| inline int64_t GetOffset(Local<Value> value) { |
| return IsSafeJsInt(value) ? value.As<Integer>()->Value() : -1; |
| } |
| |
| static const char* get_fs_func_name_by_type(uv_fs_type req_type) { |
| switch (req_type) { |
| #define FS_TYPE_TO_NAME(type, name) \ |
| case UV_FS_##type: \ |
| return name; |
| FS_TYPE_TO_NAME(OPEN, "open") |
| FS_TYPE_TO_NAME(CLOSE, "close") |
| FS_TYPE_TO_NAME(READ, "read") |
| FS_TYPE_TO_NAME(WRITE, "write") |
| FS_TYPE_TO_NAME(SENDFILE, "sendfile") |
| FS_TYPE_TO_NAME(STAT, "stat") |
| FS_TYPE_TO_NAME(LSTAT, "lstat") |
| FS_TYPE_TO_NAME(FSTAT, "fstat") |
| FS_TYPE_TO_NAME(FTRUNCATE, "ftruncate") |
| FS_TYPE_TO_NAME(UTIME, "utime") |
| FS_TYPE_TO_NAME(FUTIME, "futime") |
| FS_TYPE_TO_NAME(ACCESS, "access") |
| FS_TYPE_TO_NAME(CHMOD, "chmod") |
| FS_TYPE_TO_NAME(FCHMOD, "fchmod") |
| FS_TYPE_TO_NAME(FSYNC, "fsync") |
| FS_TYPE_TO_NAME(FDATASYNC, "fdatasync") |
| FS_TYPE_TO_NAME(UNLINK, "unlink") |
| FS_TYPE_TO_NAME(RMDIR, "rmdir") |
| FS_TYPE_TO_NAME(MKDIR, "mkdir") |
| FS_TYPE_TO_NAME(MKDTEMP, "mkdtemp") |
| FS_TYPE_TO_NAME(RENAME, "rename") |
| FS_TYPE_TO_NAME(SCANDIR, "scandir") |
| FS_TYPE_TO_NAME(LINK, "link") |
| FS_TYPE_TO_NAME(SYMLINK, "symlink") |
| FS_TYPE_TO_NAME(READLINK, "readlink") |
| FS_TYPE_TO_NAME(CHOWN, "chown") |
| FS_TYPE_TO_NAME(FCHOWN, "fchown") |
| FS_TYPE_TO_NAME(REALPATH, "realpath") |
| FS_TYPE_TO_NAME(COPYFILE, "copyfile") |
| FS_TYPE_TO_NAME(LCHOWN, "lchown") |
| FS_TYPE_TO_NAME(STATFS, "statfs") |
| FS_TYPE_TO_NAME(MKSTEMP, "mkstemp") |
| FS_TYPE_TO_NAME(LUTIME, "lutime") |
| #undef FS_TYPE_TO_NAME |
| default: |
| return "unknown"; |
| } |
| } |
| |
| #define TRACE_NAME(name) "fs.sync." #name |
| #define GET_TRACE_ENABLED \ |
| (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( \ |
| TRACING_CATEGORY_NODE2(fs, sync)) != 0) |
| #define FS_SYNC_TRACE_BEGIN(syscall, ...) \ |
| if (GET_TRACE_ENABLED) \ |
| TRACE_EVENT_BEGIN( \ |
| TRACING_CATEGORY_NODE2(fs, sync), TRACE_NAME(syscall), ##__VA_ARGS__); |
| #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, |
| std::string original_name) |
| : AsyncWrap(binding_data->env(), obj, AsyncWrap::PROVIDER_FILEHANDLE), |
| StreamBase(env()), |
| original_name_(std::move(original_name)), |
| fd_(fd), |
| binding_data_(binding_data) { |
| MakeWeak(); |
| StreamBase::AttachToObject(GetObject()); |
| } |
| |
| FileHandle* FileHandle::New(BindingData* binding_data, |
| int fd, |
| Local<Object> obj, |
| std::string original_name, |
| std::optional<int64_t> maybeOffset, |
| std::optional<int64_t> maybeLength) { |
| Environment* env = binding_data->env(); |
| if (obj.IsEmpty() && !env->fd_constructor_template() |
| ->NewInstance(env->context()) |
| .ToLocal(&obj)) { |
| return nullptr; |
| } |
| auto handle = new FileHandle(binding_data, obj, fd, original_name); |
| if (maybeOffset.has_value()) handle->read_offset_ = maybeOffset.value(); |
| if (maybeLength.has_value()) handle->read_length_ = maybeLength.value(); |
| return handle; |
| } |
| |
| void FileHandle::New(const FunctionCallbackInfo<Value>& args) { |
| CHECK(args.IsConstructCall()); |
| CHECK(args[0]->IsInt32()); |
| Realm* realm = Realm::GetCurrent(args); |
| BindingData* binding_data = realm->GetBindingData<BindingData>(); |
| |
| std::optional<int64_t> maybeOffset = std::nullopt; |
| std::optional<int64_t> maybeLength = std::nullopt; |
| if (args[1]->IsNumber()) { |
| int64_t val; |
| if (!args[1]->IntegerValue(realm->context()).To(&val)) { |
| return; |
| } |
| maybeOffset = val; |
| } |
| if (args[2]->IsNumber()) { |
| int64_t val; |
| if (!args[2]->IntegerValue(realm->context()).To(&val)) { |
| return; |
| } |
| maybeLength = val; |
| } |
| |
| FileHandle::New(binding_data, |
| args[0].As<Int32>()->Value(), |
| args.This(), |
| {}, |
| maybeOffset, |
| maybeLength); |
| } |
| |
| FileHandle::~FileHandle() { |
| CHECK(!closing_); // We should not be deleting while explicitly closing! |
| Close(); // Close synchronously and emit warning |
| CHECK(closed_); // We have to be closed at the point |
| } |
| |
| int FileHandle::DoWrite(WriteWrap* w, |
| uv_buf_t* bufs, |
| size_t count, |
| uv_stream_t* send_handle) { |
| return UV_ENOSYS; // Not implemented (yet). |
| } |
| |
| void FileHandle::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackField("current_read", current_read_); |
| tracker->TrackField("original_name", original_name_); |
| } |
| |
| BaseObject::TransferMode FileHandle::GetTransferMode() const { |
| return reading_ || closing_ || closed_ |
| ? TransferMode::kDisallowCloneAndTransfer |
| : TransferMode::kTransferable; |
| } |
| |
| std::unique_ptr<worker::TransferData> FileHandle::TransferForMessaging() { |
| CHECK_NE(GetTransferMode(), TransferMode::kDisallowCloneAndTransfer); |
| auto ret = std::make_unique<TransferData>(fd_); |
| closed_ = true; |
| return ret; |
| } |
| |
| FileHandle::TransferData::TransferData(int fd) : fd_(fd) {} |
| |
| FileHandle::TransferData::~TransferData() { |
| if (fd_ > 0) { |
| uv_fs_t close_req; |
| CHECK_NE(fd_, -1); |
| FS_SYNC_TRACE_BEGIN(close); |
| CHECK_EQ(0, uv_fs_close(nullptr, &close_req, fd_, nullptr)); |
| FS_SYNC_TRACE_END(close); |
| uv_fs_req_cleanup(&close_req); |
| } |
| } |
| |
| BaseObjectPtr<BaseObject> FileHandle::TransferData::Deserialize( |
| Environment* env, |
| v8::Local<v8::Context> context, |
| std::unique_ptr<worker::TransferData> self) { |
| BindingData* bd = Realm::GetBindingData<BindingData>(context); |
| if (bd == nullptr) return {}; |
| |
| int fd = fd_; |
| fd_ = -1; |
| return BaseObjectPtr<BaseObject> { FileHandle::New(bd, fd) }; |
| } |
| |
| // Throw an exception if the file handle has not yet been closed. |
| inline void FileHandle::Close() { |
| if (closed_ || closing_) return; |
| |
| uv_fs_t req; |
| CHECK_NE(fd_, -1); |
| FS_SYNC_TRACE_BEGIN(close); |
| int ret = uv_fs_close(env()->event_loop(), &req, fd_, nullptr); |
| FS_SYNC_TRACE_END(close); |
| uv_fs_req_cleanup(&req); |
| |
| struct err_detail { |
| int ret; |
| int fd; |
| std::string name; |
| }; |
| |
| err_detail detail{ret, fd_, original_name_}; |
| |
| AfterClose(); |
| |
| // Even though we closed the file descriptor, we still throw an error |
| // if the FileHandle object was not closed before garbage collection. |
| // Because this method is called during garbage collection, we will defer |
| // throwing the error until the next immediate queue tick so as not |
| // to interfere with the gc process. |
| // |
| // This exception will end up being fatal for the process because |
| // it is being thrown from within the SetImmediate handler and |
| // there is no JS stack to bubble it to. In other words, tearing |
| // down the process is the only reasonable thing we can do here. |
| env()->SetImmediate([detail](Environment* env) { |
| HandleScope handle_scope(env->isolate()); |
| static constexpr std::string_view unknown_path = "<unknown path>"; |
| std::string_view filename = |
| detail.name.empty() ? unknown_path : detail.name; |
| |
| // If there was an error while trying to close the file descriptor, |
| // we will throw that instead. |
| if (detail.ret < 0) { |
| auto formatted = SPrintF( |
| "Closing file descriptor %d on garbage collection failed (%s)", |
| detail.fd, |
| filename); |
| HandleScope handle_scope(env->isolate()); |
| env->ThrowUVException(detail.ret, "close", formatted.c_str()); |
| return; |
| } |
| |
| THROW_ERR_INVALID_STATE( |
| env, |
| "A FileHandle object was closed during garbage collection. " |
| "This used to be allowed with a deprecation warning but is now " |
| "considered an error. Please close FileHandle objects explicitly. " |
| "File descriptor: %d (%s)", |
| detail.fd, |
| filename); |
| }); |
| } |
| |
| void FileHandle::CloseReq::Resolve() { |
| Isolate* isolate = env()->isolate(); |
| HandleScope scope(isolate); |
| Context::Scope context_scope(env()->context()); |
| InternalCallbackScope callback_scope(this); |
| Local<Promise> promise = promise_.Get(isolate); |
| Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>(); |
| resolver->Resolve(env()->context(), Undefined(isolate)).Check(); |
| } |
| |
| void FileHandle::CloseReq::Reject(Local<Value> reason) { |
| Isolate* isolate = env()->isolate(); |
| HandleScope scope(isolate); |
| Context::Scope context_scope(env()->context()); |
| InternalCallbackScope callback_scope(this); |
| Local<Promise> promise = promise_.Get(isolate); |
| Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>(); |
| resolver->Reject(env()->context(), reason).Check(); |
| } |
| |
| FileHandle* FileHandle::CloseReq::file_handle() { |
| Isolate* isolate = env()->isolate(); |
| HandleScope scope(isolate); |
| Local<Value> val = ref_.Get(isolate); |
| Local<Object> obj = val.As<Object>(); |
| return Unwrap<FileHandle>(obj); |
| } |
| |
| FileHandle::CloseReq::CloseReq(Environment* env, |
| Local<Object> obj, |
| Local<Promise> promise, |
| Local<Value> ref) |
| : ReqWrap(env, obj, AsyncWrap::PROVIDER_FILEHANDLECLOSEREQ) { |
| promise_.Reset(env->isolate(), promise); |
| ref_.Reset(env->isolate(), ref); |
| } |
| |
| FileHandle::CloseReq::~CloseReq() { |
| uv_fs_req_cleanup(req()); |
| promise_.Reset(); |
| ref_.Reset(); |
| } |
| |
| void FileHandle::CloseReq::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackField("promise", promise_); |
| tracker->TrackField("ref", ref_); |
| } |
| |
| |
| |
| // Closes this FileHandle asynchronously and returns a Promise that will be |
| // resolved when the callback is invoked, or rejects with a UVException if |
| // there was a problem closing the fd. This is the preferred mechanism for |
| // closing the FD object even tho the object will attempt to close |
| // automatically on gc. |
| MaybeLocal<Promise> FileHandle::ClosePromise() { |
| Isolate* isolate = env()->isolate(); |
| EscapableHandleScope scope(isolate); |
| Local<Context> context = env()->context(); |
| |
| Local<Value> close_resolver = |
| object()->GetInternalField(FileHandle::kClosingPromiseSlot).As<Value>(); |
| if (close_resolver->IsPromise()) { |
| return close_resolver.As<Promise>(); |
| } |
| |
| CHECK(!closed_); |
| CHECK(!closing_); |
| CHECK(!reading_); |
| |
| auto maybe_resolver = Promise::Resolver::New(context); |
| CHECK(!maybe_resolver.IsEmpty()); |
| Local<Promise::Resolver> resolver; |
| if (!maybe_resolver.ToLocal(&resolver)) return {}; |
| Local<Promise> promise = resolver.As<Promise>(); |
| |
| Local<Object> close_req_obj; |
| if (!env()->fdclose_constructor_template() |
| ->NewInstance(env()->context()).ToLocal(&close_req_obj)) { |
| return MaybeLocal<Promise>(); |
| } |
| closing_ = true; |
| object()->SetInternalField(FileHandle::kClosingPromiseSlot, promise); |
| |
| CloseReq* req = new CloseReq(env(), close_req_obj, promise, object()); |
| auto AfterClose = uv_fs_callback_t{[](uv_fs_t* req) { |
| CloseReq* req_wrap = CloseReq::from_req(req); |
| FS_ASYNC_TRACE_END1( |
| req->fs_type, req_wrap, "result", static_cast<int>(req->result)) |
| BaseObjectPtr<CloseReq> close(req_wrap); |
| CHECK(close); |
| close->file_handle()->AfterClose(); |
| if (!close->env()->can_call_into_js()) return; |
| Isolate* isolate = close->env()->isolate(); |
| if (req->result < 0) { |
| HandleScope handle_scope(isolate); |
| close->Reject( |
| UVException(isolate, static_cast<int>(req->result), "close")); |
| } else { |
| close->Resolve(); |
| } |
| }}; |
| CHECK_NE(fd_, -1); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, req) |
| int ret = req->Dispatch(uv_fs_close, fd_, AfterClose); |
| if (ret < 0) { |
| req->Reject(UVException(isolate, ret, "close")); |
| delete req; |
| } |
| |
| return scope.Escape(promise); |
| } |
| |
| void FileHandle::Close(const FunctionCallbackInfo<Value>& args) { |
| FileHandle* fd; |
| ASSIGN_OR_RETURN_UNWRAP(&fd, args.This()); |
| Local<Promise> ret; |
| if (!fd->ClosePromise().ToLocal(&ret)) return; |
| args.GetReturnValue().Set(ret); |
| } |
| |
| |
| void FileHandle::ReleaseFD(const FunctionCallbackInfo<Value>& args) { |
| FileHandle* fd; |
| ASSIGN_OR_RETURN_UNWRAP(&fd, args.This()); |
| fd->Release(); |
| } |
| |
| int FileHandle::Release() { |
| int fd = GetFD(); |
| // Just pretend that Close was called and we're all done. |
| AfterClose(); |
| return fd; |
| } |
| |
| void FileHandle::AfterClose() { |
| closing_ = false; |
| closed_ = true; |
| fd_ = -1; |
| if (reading_ && !persistent().IsEmpty()) |
| EmitRead(UV_EOF); |
| } |
| |
| void FileHandleReadWrap::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackField("buffer", buffer_); |
| tracker->TrackField("file_handle", this->file_handle_); |
| } |
| |
| FileHandleReadWrap::FileHandleReadWrap(FileHandle* handle, Local<Object> obj) |
| : ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQCALLBACK), |
| file_handle_(handle) {} |
| |
| int FileHandle::ReadStart() { |
| if (!IsAlive() || IsClosing()) |
| return UV_EOF; |
| |
| reading_ = true; |
| |
| if (current_read_) |
| return 0; |
| |
| BaseObjectPtr<FileHandleReadWrap> read_wrap; |
| |
| if (read_length_ == 0) { |
| EmitRead(UV_EOF); |
| return 0; |
| } |
| |
| { |
| // Create a new FileHandleReadWrap or re-use one. |
| // Either way, we need these two scopes for AsyncReset() or otherwise |
| // for creating the new instance. |
| HandleScope handle_scope(env()->isolate()); |
| AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(this); |
| |
| auto& freelist = binding_data_->file_handle_read_wrap_freelist; |
| if (freelist.size() > 0) { |
| read_wrap = std::move(freelist.back()); |
| freelist.pop_back(); |
| // Use a fresh async resource. |
| // Lifetime is ensured via AsyncWrap::resource_. |
| Local<Object> resource = Object::New(env()->isolate()); |
| USE(resource->Set( |
| env()->context(), env()->handle_string(), read_wrap->object())); |
| read_wrap->AsyncReset(resource); |
| read_wrap->file_handle_ = this; |
| } else { |
| Local<Object> wrap_obj; |
| if (!env() |
| ->filehandlereadwrap_template() |
| ->NewInstance(env()->context()) |
| .ToLocal(&wrap_obj)) { |
| return UV_EBUSY; |
| } |
| read_wrap = MakeDetachedBaseObject<FileHandleReadWrap>(this, wrap_obj); |
| } |
| } |
| int64_t recommended_read = 65536; |
| if (read_length_ >= 0 && read_length_ <= recommended_read) |
| recommended_read = read_length_; |
| |
| read_wrap->buffer_ = EmitAlloc(recommended_read); |
| |
| current_read_ = std::move(read_wrap); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, current_read_.get()) |
| current_read_->Dispatch(uv_fs_read, |
| fd_, |
| ¤t_read_->buffer_, |
| 1, |
| read_offset_, |
| uv_fs_callback_t{[](uv_fs_t* req) { |
| FileHandle* handle; |
| { |
| FileHandleReadWrap* req_wrap = FileHandleReadWrap::from_req(req); |
| FS_ASYNC_TRACE_END1( |
| req->fs_type, req_wrap, "result", static_cast<int>(req->result)) |
| handle = req_wrap->file_handle_; |
| CHECK_EQ(handle->current_read_.get(), req_wrap); |
| } |
| |
| // ReadStart() checks whether current_read_ is set to determine whether |
| // a read is in progress. Moving it into a local variable makes sure that |
| // the ReadStart() call below doesn't think we're still actively reading. |
| BaseObjectPtr<FileHandleReadWrap> read_wrap = |
| std::move(handle->current_read_); |
| |
| ssize_t result = req->result; |
| uv_buf_t buffer = read_wrap->buffer_; |
| |
| uv_fs_req_cleanup(req); |
| |
| // Push the read wrap back to the freelist, or let it be destroyed |
| // once we’re exiting the current scope. |
| constexpr size_t kWantedFreelistFill = 100; |
| auto& freelist = handle->binding_data_->file_handle_read_wrap_freelist; |
| if (freelist.size() < kWantedFreelistFill) { |
| read_wrap->Reset(); |
| freelist.emplace_back(std::move(read_wrap)); |
| } |
| |
| if (result >= 0) { |
| // Read at most as many bytes as we originally planned to. |
| if (handle->read_length_ >= 0 && handle->read_length_ < result) |
| result = handle->read_length_; |
| |
| // If we read data and we have an expected length, decrease it by |
| // how much we have read. |
| if (handle->read_length_ >= 0) |
| handle->read_length_ -= result; |
| |
| // If we have an offset, increase it by how much we have read. |
| if (handle->read_offset_ >= 0) |
| handle->read_offset_ += result; |
| } |
| |
| // Reading 0 bytes from a file always means EOF, or that we reached |
| // the end of the requested range. |
| if (result == 0) |
| result = UV_EOF; |
| |
| handle->EmitRead(result, buffer); |
| |
| // Start over, if EmitRead() didn’t tell us to stop. |
| if (handle->reading_) |
| handle->ReadStart(); |
| }}); |
| |
| return 0; |
| } |
| |
| int FileHandle::ReadStop() { |
| reading_ = false; |
| return 0; |
| } |
| |
| typedef SimpleShutdownWrap<ReqWrap<uv_fs_t>> FileHandleCloseWrap; |
| |
| ShutdownWrap* FileHandle::CreateShutdownWrap(Local<Object> object) { |
| return new FileHandleCloseWrap(this, object); |
| } |
| |
| int FileHandle::DoShutdown(ShutdownWrap* req_wrap) { |
| if (closing_ || closed_) { |
| req_wrap->Done(0); |
| return 1; |
| } |
| FileHandleCloseWrap* wrap = static_cast<FileHandleCloseWrap*>(req_wrap); |
| closing_ = true; |
| CHECK_NE(fd_, -1); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, wrap) |
| wrap->Dispatch(uv_fs_close, fd_, uv_fs_callback_t{[](uv_fs_t* req) { |
| FileHandleCloseWrap* wrap = static_cast<FileHandleCloseWrap*>( |
| FileHandleCloseWrap::from_req(req)); |
| FS_ASYNC_TRACE_END1( |
| req->fs_type, wrap, "result", static_cast<int>(req->result)) |
| FileHandle* handle = static_cast<FileHandle*>(wrap->stream()); |
| handle->AfterClose(); |
| |
| int result = static_cast<int>(req->result); |
| uv_fs_req_cleanup(req); |
| wrap->Done(result); |
| }}); |
| |
| return 0; |
| } |
| |
| |
| void FSReqCallback::Reject(Local<Value> reject) { |
| MakeCallback(env()->oncomplete_string(), 1, &reject); |
| } |
| |
| void FSReqCallback::ResolveStat(const uv_stat_t* stat) { |
| Resolve(FillGlobalStatsArray(binding_data(), use_bigint(), stat)); |
| } |
| |
| void FSReqCallback::ResolveStatFs(const uv_statfs_t* stat) { |
| Resolve(FillGlobalStatFsArray(binding_data(), use_bigint(), stat)); |
| } |
| |
| void FSReqCallback::Resolve(Local<Value> value) { |
| Local<Value> argv[2] { |
| Null(env()->isolate()), |
| value |
| }; |
| MakeCallback(env()->oncomplete_string(), |
| value->IsUndefined() ? 1 : arraysize(argv), |
| argv); |
| } |
| |
| void FSReqCallback::SetReturnValue(const FunctionCallbackInfo<Value>& args) { |
| args.GetReturnValue().SetUndefined(); |
| } |
| |
| void NewFSReqCallback(const FunctionCallbackInfo<Value>& args) { |
| CHECK(args.IsConstructCall()); |
| BindingData* binding_data = Realm::GetBindingData<BindingData>(args); |
| new FSReqCallback(binding_data, args.This(), args[0]->IsTrue()); |
| } |
| |
| 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), {}, req->path); |
| if (fd == nullptr) return; |
| req_wrap->Resolve(fd->object()); |
| } |
| } |
| |
| void AfterMkdirp(uv_fs_t* req) { |
| FSReqBase* req_wrap = FSReqBase::from_req(req); |
| FSReqAfterScope after(req_wrap, req); |
| FS_ASYNC_TRACE_END1( |
| req->fs_type, req_wrap, "result", static_cast<int>(req->result)) |
| if (after.Proceed()) { |
| std::string first_path(req_wrap->continuation_data()->first_path()); |
| if (first_path.empty()) |
| return req_wrap->Resolve(Undefined(req_wrap->env()->isolate())); |
| Local<Value> path; |
| TryCatch try_catch(req_wrap->env()->isolate()); |
| if (!StringBytes::Encode(req_wrap->env()->isolate(), |
| first_path.c_str(), |
| req_wrap->encoding()) |
| .ToLocal(&path)) { |
| CHECK(try_catch.CanContinue()); |
| return req_wrap->Reject(try_catch.Exception()); |
| } |
| return req_wrap->Resolve(path); |
| } |
| } |
| |
| void AfterStringPath(uv_fs_t* req) { |
| FSReqBase* req_wrap = FSReqBase::from_req(req); |
| FSReqAfterScope after(req_wrap, req); |
| FS_ASYNC_TRACE_END1( |
| req->fs_type, req_wrap, "result", static_cast<int>(req->result)) |
| MaybeLocal<Value> link; |
| |
| if (after.Proceed()) { |
| TryCatch try_catch(req_wrap->env()->isolate()); |
| link = StringBytes::Encode( |
| req_wrap->env()->isolate(), req->path, req_wrap->encoding()); |
| if (link.IsEmpty()) { |
| CHECK(try_catch.CanContinue()); |
| req_wrap->Reject(try_catch.Exception()); |
| } else { |
| Local<Value> val; |
| if (link.ToLocal(&val)) req_wrap->Resolve(val); |
| } |
| } |
| } |
| |
| void AfterStringPtr(uv_fs_t* req) { |
| FSReqBase* req_wrap = FSReqBase::from_req(req); |
| FSReqAfterScope after(req_wrap, req); |
| FS_ASYNC_TRACE_END1( |
| req->fs_type, req_wrap, "result", static_cast<int>(req->result)) |
| MaybeLocal<Value> link; |
| |
| if (after.Proceed()) { |
| TryCatch try_catch(req_wrap->env()->isolate()); |
| link = StringBytes::Encode(req_wrap->env()->isolate(), |
| static_cast<const char*>(req->ptr), |
| req_wrap->encoding()); |
| if (link.IsEmpty()) { |
| CHECK(try_catch.CanContinue()); |
| req_wrap->Reject(try_catch.Exception()); |
| } else { |
| Local<Value> val; |
| if (link.ToLocal(&val)) req_wrap->Resolve(val); |
| } |
| } |
| } |
| |
| void AfterScanDir(uv_fs_t* req) { |
| FSReqBase* req_wrap = FSReqBase::from_req(req); |
| FSReqAfterScope after(req_wrap, req); |
| FS_ASYNC_TRACE_END1( |
| req->fs_type, req_wrap, "result", static_cast<int>(req->result)) |
| if (!after.Proceed()) { |
| return; |
| } |
| |
| Environment* env = req_wrap->env(); |
| Isolate* isolate = env->isolate(); |
| int r; |
| |
| LocalVector<Value> name_v(isolate); |
| LocalVector<Value> type_v(isolate); |
| |
| const bool with_file_types = req_wrap->with_file_types(); |
| |
| for (;;) { |
| uv_dirent_t ent; |
| |
| r = uv_fs_scandir_next(req, &ent); |
| if (r == UV_EOF) |
| break; |
| if (r != 0) { |
| return req_wrap->Reject( |
| UVException(isolate, r, nullptr, req_wrap->syscall(), req->path)); |
| } |
| |
| Local<Value> filename; |
| TryCatch try_catch(isolate); |
| if (!StringBytes::Encode(isolate, ent.name, req_wrap->encoding()) |
| .ToLocal(&filename)) { |
| CHECK(try_catch.CanContinue()); |
| return req_wrap->Reject(try_catch.Exception()); |
| } |
| name_v.push_back(filename); |
| |
| if (with_file_types) type_v.emplace_back(Integer::New(isolate, ent.type)); |
| } |
| |
| if (with_file_types) { |
| Local<Value> result[] = {Array::New(isolate, name_v.data(), name_v.size()), |
| Array::New(isolate, type_v.data(), type_v.size())}; |
| req_wrap->Resolve(Array::New(isolate, result, arraysize(result))); |
| } else { |
| req_wrap->Resolve(Array::New(isolate, name_v.data(), name_v.size())); |
| } |
| } |
| |
| void Access(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| Isolate* isolate = env->isolate(); |
| HandleScope scope(isolate); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); // path, mode |
| |
| int mode; |
| if (!GetValidFileMode(env, args[1], UV_FS_ACCESS).To(&mode)) { |
| return; |
| } |
| |
| BufferValue path(isolate, args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| if (argc > 2) { // access(path, mode, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemRead, |
| path.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_ACCESS, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "access", UTF8, AfterNoArgs, |
| uv_fs_access, *path, mode); |
| } else { // access(path, mode) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); |
| FSReqWrapSync req_wrap_sync("access", *path); |
| FS_SYNC_TRACE_BEGIN(access); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_access, *path, mode); |
| FS_SYNC_TRACE_END(access); |
| } |
| } |
| |
| void Close(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 1); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| env->RemoveUnmanagedFd(fd); |
| |
| if (argc > 1) { // close(fd, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 1); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "close", UTF8, AfterNoArgs, |
| uv_fs_close, fd); |
| } else { // close(fd) |
| FSReqWrapSync req_wrap_sync("close"); |
| FS_SYNC_TRACE_BEGIN(close); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_close, fd); |
| FS_SYNC_TRACE_END(close); |
| } |
| } |
| |
| static void ExistsSync(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| CHECK_GE(args.Length(), 1); |
| |
| BufferValue path(isolate, args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); |
| |
| uv_fs_t req; |
| auto make = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); }); |
| FS_SYNC_TRACE_BEGIN(access); |
| int err = uv_fs_access(nullptr, &req, path.out(), 0, nullptr); |
| FS_SYNC_TRACE_END(access); |
| |
| #ifdef _WIN32 |
| // In case of an invalid symlink, `uv_fs_access` on win32 |
| // will **not** return an error and is therefore not enough. |
| // Double check with `uv_fs_stat()`. |
| if (err == 0) { |
| uv_fs_req_cleanup(&req); |
| FS_SYNC_TRACE_BEGIN(stat); |
| err = uv_fs_stat(nullptr, &req, path.out(), nullptr); |
| FS_SYNC_TRACE_END(stat); |
| } |
| #endif // _WIN32 |
| |
| args.GetReturnValue().Set(err == 0); |
| } |
| |
| // Used to speed up module loading. Returns 0 if the path refers to |
| // a file, 1 when it's a directory or < 0 on error (usually -ENOENT.) |
| // The speedup comes from not creating thousands of Stat and Error objects. |
| // Do not expose this function through public API as it doesn't hold |
| // Permission Model checks. |
| static void InternalModuleStat(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsString()); |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| uv_fs_t req; |
| int rc = uv_fs_stat(env->event_loop(), &req, *path, nullptr); |
| if (rc == 0) { |
| const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr); |
| rc = S_ISDIR(s->st_mode); |
| } |
| uv_fs_req_cleanup(&req); |
| |
| args.GetReturnValue().Set(rc); |
| } |
| |
| constexpr bool is_uv_error_except_no_entry(int result) { |
| return result < 0 && result != UV_ENOENT; |
| } |
| |
| constexpr bool is_uv_error_except_no_entry_dir(int result) { |
| return result < 0 && !(result == UV_ENOENT || result == UV_ENOTDIR); |
| } |
| |
| static void Stat(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| BindingData* binding_data = realm->GetBindingData<BindingData>(); |
| Environment* env = realm->env(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(realm->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| bool use_bigint = args[1]->IsTrue(); |
| if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemRead, |
| path.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "stat", UTF8, AfterStat, |
| uv_fs_stat, *path); |
| } else { // stat(path, use_bigint, undefined, do_not_throw_if_no_entry) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); |
| bool do_not_throw_if_no_entry = args[3]->IsFalse(); |
| FSReqWrapSync req_wrap_sync("stat", *path); |
| FS_SYNC_TRACE_BEGIN(stat); |
| int result; |
| if (do_not_throw_if_no_entry) { |
| result = SyncCallAndThrowIf(is_uv_error_except_no_entry_dir, |
| env, |
| &req_wrap_sync, |
| uv_fs_stat, |
| *path); |
| } else { |
| result = SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_stat, *path); |
| } |
| FS_SYNC_TRACE_END(stat); |
| if (is_uv_error(result)) { |
| return; |
| } |
| Local<Value> arr = FillGlobalStatsArray(binding_data, use_bigint, |
| static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr)); |
| args.GetReturnValue().Set(arr); |
| } |
| } |
| |
| static void LStat(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| BindingData* binding_data = realm->GetBindingData<BindingData>(); |
| Environment* env = realm->env(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(realm->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| bool use_bigint = args[1]->IsTrue(); |
| if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_LSTAT, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "lstat", UTF8, AfterStat, |
| uv_fs_lstat, *path); |
| } else { // lstat(path, use_bigint, undefined, throw_if_no_entry) |
| bool do_not_throw_if_no_entry = args[3]->IsFalse(); |
| FSReqWrapSync req_wrap_sync("lstat", *path); |
| FS_SYNC_TRACE_BEGIN(lstat); |
| int result; |
| if (do_not_throw_if_no_entry) { |
| result = SyncCallAndThrowIf( |
| is_uv_error_except_no_entry, env, &req_wrap_sync, uv_fs_lstat, *path); |
| } else { |
| result = SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_lstat, *path); |
| } |
| FS_SYNC_TRACE_END(lstat); |
| if (is_uv_error(result)) { |
| return; |
| } |
| |
| Local<Value> arr = FillGlobalStatsArray(binding_data, use_bigint, |
| static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr)); |
| args.GetReturnValue().Set(arr); |
| } |
| } |
| |
| static void FStat(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| BindingData* binding_data = realm->GetBindingData<BindingData>(); |
| Environment* env = realm->env(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| |
| bool use_bigint = args[1]->IsTrue(); |
| if (!args[2]->IsUndefined()) { // fstat(fd, use_bigint, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_FSTAT, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "fstat", UTF8, AfterStat, |
| uv_fs_fstat, fd); |
| } else { // fstat(fd, use_bigint, undefined, do_not_throw_error) |
| bool do_not_throw_error = args[2]->IsTrue(); |
| const auto should_throw = [do_not_throw_error](int result) { |
| return is_uv_error(result) && !do_not_throw_error; |
| }; |
| FSReqWrapSync req_wrap_sync("fstat"); |
| FS_SYNC_TRACE_BEGIN(fstat); |
| int err = |
| SyncCallAndThrowIf(should_throw, env, &req_wrap_sync, uv_fs_fstat, fd); |
| FS_SYNC_TRACE_END(fstat); |
| if (is_uv_error(err)) { |
| return; |
| } |
| |
| Local<Value> arr = FillGlobalStatsArray(binding_data, use_bigint, |
| static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr)); |
| args.GetReturnValue().Set(arr); |
| } |
| } |
| |
| static void StatFs(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| BindingData* binding_data = realm->GetBindingData<BindingData>(); |
| Environment* env = realm->env(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| BufferValue path(realm->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| bool use_bigint = args[1]->IsTrue(); |
| if (argc > 2) { // statfs(path, use_bigint, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemRead, |
| path.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_STATFS, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, |
| req_wrap_async, |
| args, |
| "statfs", |
| UTF8, |
| AfterStatFs, |
| uv_fs_statfs, |
| *path); |
| } else { // statfs(path, use_bigint) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); |
| FSReqWrapSync req_wrap_sync("statfs", *path); |
| FS_SYNC_TRACE_BEGIN(statfs); |
| int result = |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_statfs, *path); |
| FS_SYNC_TRACE_END(statfs); |
| if (is_uv_error(result)) { |
| return; |
| } |
| |
| Local<Value> arr = FillGlobalStatFsArray( |
| binding_data, |
| use_bigint, |
| static_cast<const uv_statfs_t*>(req_wrap_sync.req.ptr)); |
| args.GetReturnValue().Set(arr); |
| } |
| } |
| |
| static void Symlink(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue target(isolate, args[0]); |
| CHECK_NOT_NULL(*target); |
| auto target_view = target.ToStringView(); |
| // To avoid bypass the symlink target should be allowed to read and write |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, target_view); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, target_view); |
| |
| BufferValue path(isolate, args[1]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); |
| |
| CHECK(args[2]->IsInt32()); |
| int flags = args[2].As<Int32>()->Value(); |
| |
| if (argc > 3) { // symlink(target, path, flags, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN2(UV_FS_SYMLINK, |
| req_wrap_async, |
| "target", |
| TRACE_STR_COPY(*target), |
| "path", |
| TRACE_STR_COPY(*path)) |
| AsyncDestCall(env, req_wrap_async, args, "symlink", *path, path.length(), |
| UTF8, AfterNoArgs, uv_fs_symlink, *target, *path, flags); |
| } else { // symlink(target, path, flags, undefined, ctx) |
| FSReqWrapSync req_wrap_sync("symlink", *target, *path); |
| FS_SYNC_TRACE_BEGIN(symlink); |
| SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_symlink, *target, *path, flags); |
| FS_SYNC_TRACE_END(symlink); |
| } |
| } |
| |
| static void Link(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| BufferValue src(isolate, args[0]); |
| CHECK_NOT_NULL(*src); |
| ToNamespacedPath(env, &src); |
| |
| const auto src_view = src.ToStringView(); |
| |
| BufferValue dest(isolate, args[1]); |
| CHECK_NOT_NULL(*dest); |
| ToNamespacedPath(env, &dest); |
| |
| const auto dest_view = dest.ToStringView(); |
| |
| if (argc > 2) { // link(src, dest, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| // To avoid bypass the link target should be allowed to read and write |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemRead, |
| src_view); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| src_view); |
| |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| dest_view); |
| FS_ASYNC_TRACE_BEGIN2(UV_FS_LINK, |
| req_wrap_async, |
| "src", |
| TRACE_STR_COPY(*src), |
| "dest", |
| TRACE_STR_COPY(*dest)) |
| AsyncDestCall(env, req_wrap_async, args, "link", *dest, dest.length(), UTF8, |
| AfterNoArgs, uv_fs_link, *src, *dest); |
| } else { // link(src, dest) |
| // To avoid bypass the link target should be allowed to read and write |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, src_view); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, src_view); |
| |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, dest_view); |
| FSReqWrapSync req_wrap_sync("link", *src, *dest); |
| FS_SYNC_TRACE_BEGIN(link); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_link, *src, *dest); |
| FS_SYNC_TRACE_END(link); |
| } |
| } |
| |
| static void ReadLink(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| BufferValue path(isolate, args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); |
| |
| const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); |
| |
| if (argc > 2) { // readlink(path, encoding, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_READLINK, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "readlink", encoding, AfterStringPtr, |
| uv_fs_readlink, *path); |
| } else { // readlink(path, encoding) |
| FSReqWrapSync req_wrap_sync("readlink", *path); |
| FS_SYNC_TRACE_BEGIN(readlink); |
| int err = |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_readlink, *path); |
| FS_SYNC_TRACE_END(readlink); |
| if (err < 0) { |
| return; |
| } |
| const char* link_path = static_cast<const char*>(req_wrap_sync.req.ptr); |
| |
| Local<Value> ret; |
| if (StringBytes::Encode(isolate, link_path, encoding).ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| } |
| |
| static void Rename(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| BufferValue old_path(isolate, args[0]); |
| CHECK_NOT_NULL(*old_path); |
| ToNamespacedPath(env, &old_path); |
| auto view_old_path = old_path.ToStringView(); |
| |
| BufferValue new_path(isolate, args[1]); |
| CHECK_NOT_NULL(*new_path); |
| ToNamespacedPath(env, &new_path); |
| |
| if (argc > 2) { // rename(old_path, new_path, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemRead, |
| view_old_path); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| view_old_path); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| new_path.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN2(UV_FS_RENAME, |
| req_wrap_async, |
| "old_path", |
| TRACE_STR_COPY(*old_path), |
| "new_path", |
| TRACE_STR_COPY(*new_path)) |
| AsyncDestCall(env, req_wrap_async, args, "rename", *new_path, |
| new_path.length(), UTF8, AfterNoArgs, uv_fs_rename, |
| *old_path, *new_path); |
| } else { // rename(old_path, new_path) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, view_old_path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, view_old_path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemWrite, |
| new_path.ToStringView()); |
| FSReqWrapSync req_wrap_sync("rename", *old_path, *new_path); |
| FS_SYNC_TRACE_BEGIN(rename); |
| SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_rename, *old_path, *new_path); |
| FS_SYNC_TRACE_END(rename); |
| } |
| } |
| |
| static void FTruncate(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| |
| CHECK(IsSafeJsInt(args[1])); |
| const int64_t len = args[1].As<Integer>()->Value(); |
| |
| if (argc > 2) { // ftruncate(fd, len, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_FTRUNCATE, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "ftruncate", UTF8, AfterNoArgs, |
| uv_fs_ftruncate, fd, len); |
| } else { // ftruncate(fd, len) |
| FSReqWrapSync req_wrap_sync("ftruncate"); |
| FS_SYNC_TRACE_BEGIN(ftruncate); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_ftruncate, fd, len); |
| FS_SYNC_TRACE_END(ftruncate); |
| } |
| } |
| |
| static void Fdatasync(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 1); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| |
| if (argc > 1) { // fdatasync(fd, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 1); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_FDATASYNC, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "fdatasync", UTF8, AfterNoArgs, |
| uv_fs_fdatasync, fd); |
| } else { // fdatasync(fd) |
| FSReqWrapSync req_wrap_sync("fdatasync"); |
| FS_SYNC_TRACE_BEGIN(fdatasync); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fdatasync, fd); |
| FS_SYNC_TRACE_END(fdatasync); |
| } |
| } |
| |
| static void Fsync(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 1); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| |
| if (argc > 1) { |
| FSReqBase* req_wrap_async = GetReqWrap(args, 1); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_FSYNC, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "fsync", UTF8, AfterNoArgs, |
| uv_fs_fsync, fd); |
| } else { |
| FSReqWrapSync req_wrap_sync("fsync"); |
| FS_SYNC_TRACE_BEGIN(fsync); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fsync, fd); |
| FS_SYNC_TRACE_END(fsync); |
| } |
| } |
| |
| static void Unlink(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 1); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| if (argc > 1) { // unlink(path, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 1); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| path.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_UNLINK, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "unlink", UTF8, AfterNoArgs, |
| uv_fs_unlink, *path); |
| } else { // unlink(path) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemWrite, |
| path.ToStringView()); |
| FSReqWrapSync req_wrap_sync("unlink", *path); |
| FS_SYNC_TRACE_BEGIN(unlink); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_unlink, *path); |
| FS_SYNC_TRACE_END(unlink); |
| } |
| } |
| |
| static void RMDir(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 1); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); |
| |
| if (argc > 1) { |
| FSReqBase* req_wrap_async = GetReqWrap(args, 1); // rmdir(path, req) |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_RMDIR, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "rmdir", UTF8, AfterNoArgs, |
| uv_fs_rmdir, *path); |
| } else { // rmdir(path) |
| FSReqWrapSync req_wrap_sync("rmdir", *path); |
| FS_SYNC_TRACE_BEGIN(rmdir); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_rmdir, *path); |
| FS_SYNC_TRACE_END(rmdir); |
| } |
| } |
| |
| static void RmSync(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| CHECK_EQ(args.Length(), 4); // path, maxRetries, recursive, retryDelay |
| |
| BufferValue path(isolate, args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); |
| auto file_path = std::filesystem::path(path.ToStringView()); |
| std::error_code error; |
| auto file_status = std::filesystem::status(file_path, error); |
| |
| if (file_status.type() == std::filesystem::file_type::not_found) { |
| return; |
| } |
| |
| int maxRetries = args[1].As<Int32>()->Value(); |
| int recursive = args[2]->IsTrue(); |
| int retryDelay = args[3].As<Int32>()->Value(); |
| |
| // File is a directory and recursive is false |
| if (file_status.type() == std::filesystem::file_type::directory && |
| !recursive) { |
| return THROW_ERR_FS_EISDIR( |
| isolate, "Path is a directory: %s", file_path.c_str()); |
| } |
| |
| // Allowed errors are: |
| // - EBUSY: std::errc::device_or_resource_busy |
| // - EMFILE: std::errc::too_many_files_open |
| // - ENFILE: std::errc::too_many_files_open_in_system |
| // - ENOTEMPTY: std::errc::directory_not_empty |
| // - EPERM: std::errc::operation_not_permitted |
| auto can_omit_error = [](std::error_code error) -> bool { |
| return (error == std::errc::device_or_resource_busy || |
| error == std::errc::too_many_files_open || |
| error == std::errc::too_many_files_open_in_system || |
| error == std::errc::directory_not_empty || |
| error == std::errc::operation_not_permitted); |
| }; |
| |
| int i = 1; |
| |
| while (maxRetries >= 0) { |
| if (recursive) { |
| std::filesystem::remove_all(file_path, error); |
| } else { |
| std::filesystem::remove(file_path, error); |
| } |
| |
| if (!error || error == std::errc::no_such_file_or_directory) { |
| return; |
| } else if (!can_omit_error(error)) { |
| break; |
| } |
| |
| if (retryDelay > 0) { |
| #ifdef _WIN32 |
| Sleep(i * retryDelay / 1000); |
| #else |
| sleep(i * retryDelay / 1000); |
| #endif |
| } |
| maxRetries--; |
| i++; |
| } |
| |
| // On Windows path::c_str() returns wide char, convert to std::string first. |
| std::string file_path_str = file_path.string(); |
| const char* path_c_str = file_path_str.c_str(); |
| #ifdef _WIN32 |
| int permission_denied_error = EPERM; |
| #else |
| int permission_denied_error = EACCES; |
| #endif // !_WIN32 |
| |
| if (error == std::errc::operation_not_permitted) { |
| std::string message = "Operation not permitted: " + file_path_str; |
| return env->ThrowErrnoException(EPERM, "rm", message.c_str(), path_c_str); |
| } else if (error == std::errc::directory_not_empty) { |
| std::string message = "Directory not empty: " + file_path_str; |
| return env->ThrowErrnoException( |
| ENOTEMPTY, "rm", message.c_str(), path_c_str); |
| } else if (error == std::errc::not_a_directory) { |
| std::string message = "Not a directory: " + file_path_str; |
| return env->ThrowErrnoException(ENOTDIR, "rm", message.c_str(), path_c_str); |
| } else if (error == std::errc::permission_denied) { |
| std::string message = "Permission denied: " + file_path_str; |
| return env->ThrowErrnoException( |
| permission_denied_error, "rm", message.c_str(), path_c_str); |
| } |
| |
| std::string message = "Unknown error: " + error.message(); |
| return env->ThrowErrnoException( |
| UV_UNKNOWN, "rm", message.c_str(), path_c_str); |
| } |
| |
| int MKDirpSync(uv_loop_t* loop, |
| uv_fs_t* req, |
| const std::string& path, |
| int mode, |
| uv_fs_cb cb) { |
| FSReqWrapSync* req_wrap = ContainerOf(&FSReqWrapSync::req, req); |
| |
| // on the first iteration of algorithm, stash state information. |
| if (req_wrap->continuation_data() == nullptr) { |
| req_wrap->set_continuation_data( |
| std::make_unique<FSContinuationData>(req, mode, cb)); |
| req_wrap->continuation_data()->PushPath(std::move(path)); |
| } |
| |
| while (req_wrap->continuation_data()->paths().size() > 0) { |
| std::string next_path = req_wrap->continuation_data()->PopPath(); |
| int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, nullptr); |
| while (true) { |
| switch (err) { |
| // Note: uv_fs_req_cleanup in terminal paths will be called by |
| // ~FSReqWrapSync(): |
| case 0: |
| req_wrap->continuation_data()->MaybeSetFirstPath(next_path); |
| if (req_wrap->continuation_data()->paths().empty()) { |
| return 0; |
| } |
| break; |
| case UV_EACCES: |
| case UV_ENOSPC: |
| case UV_ENOTDIR: |
| case UV_EPERM: { |
| return err; |
| } |
| case UV_ENOENT: { |
| std::string dirname = |
| next_path.substr(0, next_path.find_last_of(kPathSeparator)); |
| if (dirname != next_path) { |
| req_wrap->continuation_data()->PushPath(std::move(next_path)); |
| req_wrap->continuation_data()->PushPath(std::move(dirname)); |
| } else if (req_wrap->continuation_data()->paths().empty()) { |
| err = UV_EEXIST; |
| continue; |
| } |
| break; |
| } |
| default: |
| uv_fs_req_cleanup(req); |
| int orig_err = err; |
| err = uv_fs_stat(loop, req, next_path.c_str(), nullptr); |
| if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) { |
| uv_fs_req_cleanup(req); |
| if (orig_err == UV_EEXIST && |
| req_wrap->continuation_data()->paths().size() > 0) { |
| return UV_ENOTDIR; |
| } |
| return UV_EEXIST; |
| } |
| if (err < 0) return err; |
| break; |
| } |
| break; |
| } |
| uv_fs_req_cleanup(req); |
| } |
| |
| return 0; |
| } |
| |
| int MKDirpAsync(uv_loop_t* loop, |
| uv_fs_t* req, |
| const char* path, |
| int mode, |
| uv_fs_cb cb) { |
| FSReqBase* req_wrap = FSReqBase::from_req(req); |
| // on the first iteration of algorithm, stash state information. |
| if (req_wrap->continuation_data() == nullptr) { |
| req_wrap->set_continuation_data( |
| std::make_unique<FSContinuationData>(req, mode, cb)); |
| req_wrap->continuation_data()->PushPath(std::move(path)); |
| } |
| |
| // on each iteration of algorithm, mkdir directory on top of stack. |
| std::string next_path = req_wrap->continuation_data()->PopPath(); |
| int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, |
| uv_fs_callback_t{[](uv_fs_t* req) { |
| FSReqBase* req_wrap = FSReqBase::from_req(req); |
| Environment* env = req_wrap->env(); |
| uv_loop_t* loop = env->event_loop(); |
| std::string path = req->path; |
| int err = static_cast<int>(req->result); |
| |
| while (true) { |
| switch (err) { |
| // Note: uv_fs_req_cleanup in terminal paths will be called by |
| // FSReqAfterScope::~FSReqAfterScope() |
| case 0: { |
| if (req_wrap->continuation_data()->paths().empty()) { |
| req_wrap->continuation_data()->MaybeSetFirstPath(path); |
| req_wrap->continuation_data()->Done(0); |
| } else { |
| req_wrap->continuation_data()->MaybeSetFirstPath(path); |
| uv_fs_req_cleanup(req); |
| MKDirpAsync(loop, req, path.c_str(), |
| req_wrap->continuation_data()->mode(), nullptr); |
| } |
| break; |
| } |
| case UV_EACCES: |
| case UV_ENOSPC: |
| case UV_ENOTDIR: |
| case UV_EPERM: { |
| req_wrap->continuation_data()->Done(err); |
| break; |
| } |
| case UV_ENOENT: { |
| std::string dirname = |
| path.substr(0, path.find_last_of(kPathSeparator)); |
| if (dirname != path) { |
| req_wrap->continuation_data()->PushPath(path); |
| req_wrap->continuation_data()->PushPath(std::move(dirname)); |
| } else if (req_wrap->continuation_data()->paths().empty()) { |
| err = UV_EEXIST; |
| continue; |
| } |
| uv_fs_req_cleanup(req); |
| MKDirpAsync(loop, req, path.c_str(), |
| req_wrap->continuation_data()->mode(), nullptr); |
| break; |
| } |
| default: |
| uv_fs_req_cleanup(req); |
| // Stash err for use in the callback. |
| req->data = reinterpret_cast<void*>(static_cast<intptr_t>(err)); |
| int err = uv_fs_stat(loop, req, path.c_str(), |
| uv_fs_callback_t{[](uv_fs_t* req) { |
| FSReqBase* req_wrap = FSReqBase::from_req(req); |
| int err = static_cast<int>(req->result); |
| if (reinterpret_cast<intptr_t>(req->data) == UV_EEXIST && |
| req_wrap->continuation_data()->paths().size() > 0) { |
| if (err == 0 && S_ISDIR(req->statbuf.st_mode)) { |
| Environment* env = req_wrap->env(); |
| uv_loop_t* loop = env->event_loop(); |
| std::string path = req->path; |
| uv_fs_req_cleanup(req); |
| MKDirpAsync(loop, req, path.c_str(), |
| req_wrap->continuation_data()->mode(), nullptr); |
| return; |
| } |
| err = UV_ENOTDIR; |
| } |
| // verify that the path pointed to is actually a directory. |
| if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST; |
| req_wrap->continuation_data()->Done(err); |
| }}); |
| if (err < 0) req_wrap->continuation_data()->Done(err); |
| break; |
| } |
| break; |
| } |
| }}); |
| |
| return err; |
| } |
| |
| static void MKDir(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); |
| |
| CHECK(args[1]->IsInt32()); |
| const int mode = args[1].As<Int32>()->Value(); |
| |
| CHECK(args[2]->IsBoolean()); |
| bool mkdirp = args[2]->IsTrue(); |
| |
| if (argc > 3) { // mkdir(path, mode, recursive, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_UNLINK, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "mkdir", UTF8, |
| mkdirp ? AfterMkdirp : AfterNoArgs, |
| mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode); |
| } else { // mkdir(path, mode, recursive) |
| FSReqWrapSync req_wrap_sync("mkdir", *path); |
| FS_SYNC_TRACE_BEGIN(mkdir); |
| if (mkdirp) { |
| env->PrintSyncTrace(); |
| int err = MKDirpSync( |
| env->event_loop(), &req_wrap_sync.req, *path, mode, nullptr); |
| if (is_uv_error(err)) { |
| env->ThrowUVException(err, "mkdir", nullptr, *path); |
| return; |
| } |
| if (!req_wrap_sync.continuation_data()->first_path().empty()) { |
| Local<Value> ret; |
| std::string first_path(req_wrap_sync.continuation_data()->first_path()); |
| if (StringBytes::Encode(env->isolate(), first_path.c_str(), UTF8) |
| .ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| } else { |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_mkdir, *path, mode); |
| } |
| FS_SYNC_TRACE_END(mkdir); |
| } |
| } |
| |
| static void RealPath(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| BufferValue path(isolate, args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); |
| |
| if (argc > 2) { // realpath(path, encoding, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| 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> ret; |
| if (StringBytes::Encode(isolate, link_path, encoding).ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| } |
| |
| static void ReadDir(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(isolate, args[0]); |
| CHECK_NOT_NULL(*path); |
| #ifdef _WIN32 |
| // On Windows, some API functions accept paths with trailing slashes, |
| // while others do not. This code checks if the input path ends with |
| // a slash (either '/' or '\\') and, if so, ensures that the processed |
| // path also ends with a trailing backslash ('\\'). |
| bool slashCheck = false; |
| if (path.ToStringView().ends_with("/") || |
| path.ToStringView().ends_with("\\")) { |
| slashCheck = true; |
| } |
| #endif |
| |
| ToNamespacedPath(env, &path); |
| |
| #ifdef _WIN32 |
| if (slashCheck && !path.ToStringView().ends_with("\\")) { |
| size_t new_length = path.length() + 1; |
| path.AllocateSufficientStorage(new_length + 1); |
| path.SetLengthAndZeroTerminate(new_length); |
| path.out()[new_length - 1] = '\\'; |
| } |
| #endif |
| |
| const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); |
| |
| bool with_types = args[2]->IsTrue(); |
| |
| if (argc > 3) { // readdir(path, encoding, withTypes, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemRead, |
| path.ToStringView()); |
| req_wrap_async->set_with_file_types(with_types); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_SCANDIR, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, |
| req_wrap_async, |
| args, |
| "scandir", |
| encoding, |
| AfterScanDir, |
| uv_fs_scandir, |
| *path, |
| 0 /*flags*/); |
| } else { // readdir(path, encoding, withTypes) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); |
| FSReqWrapSync req_wrap_sync("scandir", *path); |
| FS_SYNC_TRACE_BEGIN(readdir); |
| int err = SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_scandir, *path, 0 /*flags*/); |
| FS_SYNC_TRACE_END(readdir); |
| if (is_uv_error(err)) { |
| return; |
| } |
| |
| int r; |
| LocalVector<Value> name_v(isolate); |
| LocalVector<Value> type_v(isolate); |
| |
| for (;;) { |
| uv_dirent_t ent; |
| |
| r = uv_fs_scandir_next(&(req_wrap_sync.req), &ent); |
| if (r == UV_EOF) |
| break; |
| if (is_uv_error(r)) { |
| env->ThrowUVException(r, "scandir", nullptr, *path); |
| return; |
| } |
| |
| Local<Value> fn; |
| if (!StringBytes::Encode(isolate, ent.name, encoding).ToLocal(&fn)) { |
| return; |
| } |
| |
| name_v.push_back(fn); |
| |
| if (with_types) { |
| type_v.emplace_back(Integer::New(isolate, ent.type)); |
| } |
| } |
| |
| |
| Local<Array> names = Array::New(isolate, name_v.data(), name_v.size()); |
| if (with_types) { |
| Local<Value> result[] = { |
| names, |
| Array::New(isolate, type_v.data(), type_v.size()) |
| }; |
| args.GetReturnValue().Set(Array::New(isolate, result, arraysize(result))); |
| } else { |
| args.GetReturnValue().Set(names); |
| } |
| } |
| } |
| |
| static inline Maybe<void> AsyncCheckOpenPermissions(Environment* env, |
| FSReqBase* req_wrap, |
| const BufferValue& path, |
| int flags) { |
| // These flags capture the intention of the open() call. |
| const int rwflags = flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR); |
| |
| // These flags have write-like side effects even with O_RDONLY, at least on |
| // some operating systems. On Windows, for example, O_RDONLY | O_TEMPORARY |
| // can be used to delete a file. Bizarre. |
| const int write_as_side_effect = flags & (UV_FS_O_APPEND | UV_FS_O_CREAT | |
| UV_FS_O_TRUNC | UV_FS_O_TEMPORARY); |
| |
| auto pathView = path.ToStringView(); |
| if (rwflags != UV_FS_O_WRONLY) { |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap, |
| permission::PermissionScope::kFileSystemRead, |
| pathView, |
| Nothing<void>()); |
| } |
| if (rwflags != UV_FS_O_RDONLY || write_as_side_effect) { |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap, |
| permission::PermissionScope::kFileSystemWrite, |
| pathView, |
| Nothing<void>()); |
| } |
| return JustVoid(); |
| } |
| |
| static inline Maybe<void> CheckOpenPermissions(Environment* env, |
| const BufferValue& path, |
| int flags) { |
| // These flags capture the intention of the open() call. |
| const int rwflags = flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR); |
| |
| // These flags have write-like side effects even with O_RDONLY, at least on |
| // some operating systems. On Windows, for example, O_RDONLY | O_TEMPORARY |
| // can be used to delete a file. Bizarre. |
| const int write_as_side_effect = flags & (UV_FS_O_APPEND | UV_FS_O_CREAT | |
| UV_FS_O_TRUNC | UV_FS_O_TEMPORARY); |
| |
| auto pathView = path.ToStringView(); |
| if (rwflags != UV_FS_O_WRONLY) { |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemRead, |
| pathView, |
| Nothing<void>()); |
| } |
| if (rwflags != UV_FS_O_RDONLY || write_as_side_effect) { |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemWrite, |
| pathView, |
| Nothing<void>()); |
| } |
| return JustVoid(); |
| } |
| |
| static void Open(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| CHECK(args[1]->IsInt32()); |
| const int flags = args[1].As<Int32>()->Value(); |
| |
| CHECK(args[2]->IsInt32()); |
| const int mode = args[2].As<Int32>()->Value(); |
| |
| if (argc > 3) { // open(path, flags, mode, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| if (AsyncCheckOpenPermissions(env, req_wrap_async, path, flags).IsNothing()) |
| return; |
| req_wrap_async->set_is_plain_open(true); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_OPEN, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterInteger, |
| uv_fs_open, *path, flags, mode); |
| } else { // open(path, flags, mode) |
| if (CheckOpenPermissions(env, path, flags).IsNothing()) return; |
| FSReqWrapSync req_wrap_sync("open", *path); |
| FS_SYNC_TRACE_BEGIN(open); |
| int result = SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_open, *path, flags, mode); |
| FS_SYNC_TRACE_END(open); |
| if (is_uv_error(result)) return; |
| env->AddUnmanagedFd(result); |
| args.GetReturnValue().Set(result); |
| } |
| } |
| |
| static void OpenFileHandle(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| BindingData* binding_data = realm->GetBindingData<BindingData>(); |
| Environment* env = realm->env(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(realm->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| CHECK(args[1]->IsInt32()); |
| const int flags = args[1].As<Int32>()->Value(); |
| |
| CHECK(args[2]->IsInt32()); |
| const int mode = args[2].As<Int32>()->Value(); |
| |
| if (CheckOpenPermissions(env, path, flags).IsNothing()) return; |
| |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| if (req_wrap_async != nullptr) { // openFileHandle(path, flags, mode, req) |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_OPEN, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterOpenFileHandle, |
| uv_fs_open, *path, flags, mode); |
| } else { // openFileHandle(path, flags, mode, undefined, ctx) |
| CHECK_EQ(argc, 5); |
| FSReqWrapSync req_wrap_sync; |
| FS_SYNC_TRACE_BEGIN(open); |
| int result; |
| if (!SyncCall(env, |
| args[4], |
| &req_wrap_sync, |
| "open", |
| uv_fs_open, |
| *path, |
| flags, |
| mode) |
| .To(&result)) { |
| // v8 error occurred while setting the context. propagate! |
| return; |
| } |
| FS_SYNC_TRACE_END(open); |
| if (result < 0) { |
| return; // syscall failed, no need to continue, error info is in ctx |
| } |
| FileHandle* fd = FileHandle::New(binding_data, result, {}, path.ToString()); |
| if (fd == nullptr) return; |
| args.GetReturnValue().Set(fd->object()); |
| } |
| } |
| |
| static void CopyFile(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); // src, dest, flags |
| |
| int flags; |
| if (!GetValidFileMode(env, args[2], UV_FS_COPYFILE).To(&flags)) { |
| return; |
| } |
| |
| BufferValue src(isolate, args[0]); |
| CHECK_NOT_NULL(*src); |
| ToNamespacedPath(env, &src); |
| |
| BufferValue dest(isolate, args[1]); |
| CHECK_NOT_NULL(*dest); |
| ToNamespacedPath(env, &dest); |
| |
| if (argc > 3) { // copyFile(src, dest, flags, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemRead, |
| src.ToStringView()); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| dest.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN2(UV_FS_COPYFILE, |
| req_wrap_async, |
| "src", |
| TRACE_STR_COPY(*src), |
| "dest", |
| TRACE_STR_COPY(*dest)) |
| AsyncDestCall(env, req_wrap_async, args, "copyfile", |
| *dest, dest.length(), UTF8, AfterNoArgs, |
| uv_fs_copyfile, *src, *dest, flags); |
| } else { // copyFile(src, dest, flags) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, src.ToStringView()); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemWrite, |
| dest.ToStringView()); |
| FSReqWrapSync req_wrap_sync("copyfile", *src, *dest); |
| FS_SYNC_TRACE_BEGIN(copyfile); |
| SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_copyfile, *src, *dest, flags); |
| FS_SYNC_TRACE_END(copyfile); |
| } |
| } |
| |
| // Wrapper for write(2). |
| // |
| // bytesWritten = write(fd, buffer, offset, length, position, callback) |
| // 0 fd integer. file descriptor |
| // 1 buffer the data to write |
| // 2 offset where in the buffer to start from |
| // 3 length how much to write |
| // 4 position if integer, position to write at in the file. |
| // if null, write from the current position |
| static void WriteBuffer(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 4); |
| |
| 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; |
| if (!SyncCall(env, |
| args[6], |
| &req_wrap_sync, |
| "write", |
| uv_fs_write, |
| fd, |
| &uvbuf, |
| 1, |
| pos) |
| .To(&bytesWritten)) { |
| FS_SYNC_TRACE_END(write, "bytesWritten", 0); |
| return; |
| } |
| FS_SYNC_TRACE_END(write, "bytesWritten", bytesWritten); |
| args.GetReturnValue().Set(bytesWritten); |
| } |
| } |
| |
| |
| // Wrapper for writev(2). |
| // |
| // bytesWritten = writev(fd, chunks, position, callback) |
| // 0 fd integer. file descriptor |
| // 1 chunks array of buffers to write |
| // 2 position if integer, position to write at in the file. |
| // if null, write from the current position |
| static void WriteBuffers(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| 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; |
| if (!chunks->Get(env->context(), i).ToLocal(&chunk)) return; |
| CHECK(Buffer::HasInstance(chunk)); |
| iovs[i] = uv_buf_init(Buffer::Data(chunk), Buffer::Length(chunk)); |
| } |
| |
| if (argc > 3) { // writeBuffers(fd, chunks, pos, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async) |
| AsyncCall(env, |
| req_wrap_async, |
| args, |
| "write", |
| UTF8, |
| AfterInteger, |
| uv_fs_write, |
| fd, |
| *iovs, |
| iovs.length(), |
| pos); |
| } else { // writeBuffers(fd, chunks, pos) |
| FSReqWrapSync req_wrap_sync("write"); |
| FS_SYNC_TRACE_BEGIN(write); |
| int bytesWritten = SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_write, fd, *iovs, iovs.length(), pos); |
| FS_SYNC_TRACE_END(write, "bytesWritten", bytesWritten); |
| if (is_uv_error(bytesWritten)) { |
| return; |
| } |
| args.GetReturnValue().Set(bytesWritten); |
| } |
| } |
| |
| |
| // Wrapper for write(2). |
| // |
| // bytesWritten = write(fd, string, position, enc, callback) |
| // 0 fd integer. file descriptor |
| // 1 string non-buffer values are converted to strings |
| // 2 position if integer, position to write at in the file. |
| // if null, write from the current position |
| // 3 enc encoding of string |
| static void WriteString(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 4); |
| 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 && string->IsExternalTwoByte()) { |
| if constexpr (IsLittleEndian()) { |
| auto ext = string->GetExternalStringResource(); |
| buf = reinterpret_cast<char*>(const_cast<uint16_t*>(ext->data())); |
| len = ext->length() * sizeof(*ext->data()); |
| } |
| } |
| } |
| |
| if (is_async) { // write(fd, string, pos, enc, req) |
| CHECK_NOT_NULL(req_wrap_async); |
| if (!StringBytes::StorageSize(isolate, value, enc).To(&len)) return; |
| FSReqBase::FSReqBuffer& stack_buffer = |
| req_wrap_async->Init("write", len, enc); |
| // StorageSize may return too large a char, so correct the actual length |
| // by the write size |
| len = StringBytes::Write(isolate, *stack_buffer, len, args[1], enc); |
| stack_buffer.SetLengthAndZeroTerminate(len); |
| uv_buf_t uvbuf = uv_buf_init(*stack_buffer, len); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async) |
| int err = req_wrap_async->Dispatch(uv_fs_write, |
| fd, |
| &uvbuf, |
| 1, |
| pos, |
| AfterInteger); |
| if (err < 0) { |
| uv_fs_t* uv_req = req_wrap_async->req(); |
| uv_req->result = err; |
| uv_req->path = nullptr; |
| AfterInteger(uv_req); // after may delete req_wrap_async if there is |
| // an error |
| } |
| } else { // write(fd, string, pos, enc, undefined, ctx) |
| CHECK_EQ(argc, 6); |
| FSReqBase::FSReqBuffer stack_buffer; |
| if (buf == nullptr) { |
| if (!StringBytes::StorageSize(isolate, value, enc).To(&len)) |
| return; |
| stack_buffer.AllocateSufficientStorage(len + 1); |
| // StorageSize may return too large a char, so correct the actual length |
| // by the write size |
| len = StringBytes::Write(isolate, *stack_buffer, |
| len, args[1], enc); |
| stack_buffer.SetLengthAndZeroTerminate(len); |
| buf = *stack_buffer; |
| } |
| uv_buf_t uvbuf = uv_buf_init(buf, len); |
| FSReqWrapSync req_wrap_sync("write"); |
| FS_SYNC_TRACE_BEGIN(write); |
| int bytesWritten; |
| if (!SyncCall(env, |
| args[5], |
| &req_wrap_sync, |
| "write", |
| uv_fs_write, |
| fd, |
| &uvbuf, |
| 1, |
| pos) |
| .To(&bytesWritten)) { |
| FS_SYNC_TRACE_END(write, "bytesWritten", 0); |
| return; |
| } |
| FS_SYNC_TRACE_END(write, "bytesWritten", bytesWritten); |
| args.GetReturnValue().Set(bytesWritten); |
| } |
| } |
| |
| static void WriteFileUtf8(const FunctionCallbackInfo<Value>& args) { |
| // Fast C++ path for fs.writeFileSync(path, data) with utf8 encoding |
| // (file, data, options.flag, options.mode) |
| |
| Environment* env = Environment::GetCurrent(args); |
| auto isolate = env->isolate(); |
| |
| CHECK_EQ(args.Length(), 4); |
| |
| BufferValue value(isolate, args[1]); |
| CHECK_NOT_NULL(*value); |
| |
| CHECK(args[2]->IsInt32()); |
| const int flags = args[2].As<Int32>()->Value(); |
| |
| CHECK(args[3]->IsInt32()); |
| const int mode = args[3].As<Int32>()->Value(); |
| |
| uv_file file; |
| |
| bool is_fd = args[0]->IsInt32(); |
| |
| // Check for file descriptor |
| if (is_fd) { |
| file = args[0].As<Int32>()->Value(); |
| } else { |
| BufferValue path(isolate, args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| if (CheckOpenPermissions(env, path, flags).IsNothing()) return; |
| |
| FSReqWrapSync req_open("open", *path); |
| |
| FS_SYNC_TRACE_BEGIN(open); |
| file = |
| SyncCallAndThrowOnError(env, &req_open, uv_fs_open, *path, flags, mode); |
| FS_SYNC_TRACE_END(open); |
| |
| if (is_uv_error(file)) { |
| return; |
| } |
| } |
| |
| int bytesWritten = 0; |
| uint32_t offset = 0; |
| |
| const size_t length = value.length(); |
| uv_buf_t uvbuf = uv_buf_init(value.out(), length); |
| |
| FS_SYNC_TRACE_BEGIN(write); |
| while (offset < length) { |
| FSReqWrapSync req_write("write"); |
| bytesWritten = SyncCallAndThrowOnError( |
| env, &req_write, uv_fs_write, file, &uvbuf, 1, -1); |
| |
| // Write errored out |
| if (bytesWritten < 0) { |
| break; |
| } |
| |
| offset += bytesWritten; |
| DCHECK_LE(offset, length); |
| uvbuf.base += bytesWritten; |
| uvbuf.len -= bytesWritten; |
| } |
| FS_SYNC_TRACE_END(write); |
| |
| if (!is_fd) { |
| FSReqWrapSync req_close("close"); |
| |
| FS_SYNC_TRACE_BEGIN(close); |
| int result = SyncCallAndThrowOnError(env, &req_close, uv_fs_close, file); |
| FS_SYNC_TRACE_END(close); |
| |
| if (is_uv_error(result)) { |
| return; |
| } |
| } |
| } |
| |
| /* |
| * Wrapper for read(2). |
| * |
| * bytesRead = fs.read(fd, buffer, offset, length, position) |
| * |
| * 0 fd int32. file descriptor |
| * 1 buffer instance of Buffer |
| * 2 offset int64. offset to start reading into inside buffer |
| * 3 length int32. length to read |
| * 4 position int64. file position - -1 for current position |
| */ |
| static void Read(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 5); |
| |
| 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); |
| ToNamespacedPath(env, &path); |
| if (CheckOpenPermissions(env, path, flags).IsNothing()) return; |
| |
| FS_SYNC_TRACE_BEGIN(open); |
| file = uv_fs_open(nullptr, &req, *path, flags, 0666, nullptr); |
| FS_SYNC_TRACE_END(open); |
| if (req.result < 0) { |
| uv_fs_req_cleanup(&req); |
| return env->ThrowUVException( |
| static_cast<int>(req.result), "open", nullptr, path.out()); |
| } |
| uv_fs_req_cleanup(&req); |
| } |
| |
| auto defer_close = OnScopeLeave([file, is_fd, &req]() { |
| if (!is_fd) { |
| FS_SYNC_TRACE_BEGIN(close); |
| CHECK_EQ(0, uv_fs_close(nullptr, &req, file, nullptr)); |
| FS_SYNC_TRACE_END(close); |
| } |
| uv_fs_req_cleanup(&req); |
| }); |
| |
| 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( |
| static_cast<int>(req.result), "read", nullptr); |
| } |
| if (r <= 0) { |
| break; |
| } |
| result.append(buf.base, r); |
| } |
| FS_SYNC_TRACE_END(read); |
| |
| Local<Value> val; |
| if (!ToV8Value(env->context(), result, isolate).ToLocal(&val)) { |
| return; |
| } |
| |
| args.GetReturnValue().Set(val); |
| } |
| |
| // 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; |
| if (!buffers->Get(env->context(), i).ToLocal(&buffer)) return; |
| CHECK(Buffer::HasInstance(buffer)); |
| iovs[i] = uv_buf_init(Buffer::Data(buffer), Buffer::Length(buffer)); |
| } |
| |
| if (argc > 3) { // readBuffers(fd, buffers, pos, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "read", UTF8, AfterInteger, |
| uv_fs_read, fd, *iovs, iovs.length(), pos); |
| } else { // readBuffers(fd, buffers, undefined, ctx) |
| FSReqWrapSync req_wrap_sync("read"); |
| FS_SYNC_TRACE_BEGIN(read); |
| int bytesRead = SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_read, fd, *iovs, iovs.length(), pos); |
| FS_SYNC_TRACE_END(read, "bytesRead", bytesRead); |
| if (is_uv_error(bytesRead)) { |
| return; |
| } |
| args.GetReturnValue().Set(bytesRead); |
| } |
| } |
| |
| |
| /* fs.chmod(path, mode); |
| * Wrapper for chmod(1) / EIO_CHMOD |
| */ |
| static void Chmod(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); |
| |
| CHECK(args[1]->IsInt32()); |
| int mode = args[1].As<Int32>()->Value(); |
| |
| if (argc > 2) { // chmod(path, mode, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_CHMOD, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "chmod", UTF8, AfterNoArgs, |
| uv_fs_chmod, *path, mode); |
| } else { // chmod(path, mode) |
| FSReqWrapSync req_wrap_sync("chmod", *path); |
| FS_SYNC_TRACE_BEGIN(chmod); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_chmod, *path, mode); |
| FS_SYNC_TRACE_END(chmod); |
| } |
| } |
| |
| |
| /* fs.fchmod(fd, mode); |
| * Wrapper for fchmod(1) / EIO_FCHMOD |
| */ |
| static void FChmod(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| |
| CHECK(args[1]->IsInt32()); |
| const int mode = args[1].As<Int32>()->Value(); |
| |
| if (argc > 2) { // fchmod(fd, mode, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_FCHMOD, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "fchmod", UTF8, AfterNoArgs, |
| uv_fs_fchmod, fd, mode); |
| } else { // fchmod(fd, mode) |
| FSReqWrapSync req_wrap_sync("fchmod"); |
| FS_SYNC_TRACE_BEGIN(fchmod); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fchmod, fd, mode); |
| FS_SYNC_TRACE_END(fchmod); |
| } |
| } |
| |
| /* fs.chown(path, uid, gid); |
| * Wrapper for chown(1) / EIO_CHOWN |
| */ |
| static void Chown(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| CHECK(IsSafeJsInt(args[1])); |
| const auto uid = FromV8Value<uv_uid_t, true>(args[1]); |
| |
| CHECK(IsSafeJsInt(args[2])); |
| const auto gid = FromV8Value<uv_gid_t, true>(args[2]); |
| |
| if (argc > 3) { // chown(path, uid, gid, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| path.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_CHOWN, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "chown", UTF8, AfterNoArgs, |
| uv_fs_chown, *path, uid, gid); |
| } else { // chown(path, uid, gid) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemWrite, |
| path.ToStringView()); |
| FSReqWrapSync req_wrap_sync("chown", *path); |
| FS_SYNC_TRACE_BEGIN(chown); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_chown, *path, uid, gid); |
| FS_SYNC_TRACE_END(chown); |
| } |
| } |
| |
| |
| /* fs.fchown(fd, uid, gid); |
| * Wrapper for fchown(1) / EIO_FCHOWN |
| */ |
| static void FChown(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| |
| CHECK(IsSafeJsInt(args[1])); |
| const auto uid = FromV8Value<uv_uid_t, true>(args[1]); |
| |
| CHECK(IsSafeJsInt(args[2])); |
| const auto gid = FromV8Value<uv_gid_t, true>(args[2]); |
| |
| if (argc > 3) { // fchown(fd, uid, gid, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_FCHOWN, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "fchown", UTF8, AfterNoArgs, |
| uv_fs_fchown, fd, uid, gid); |
| } else { // fchown(fd, uid, gid) |
| FSReqWrapSync req_wrap_sync("fchown"); |
| FS_SYNC_TRACE_BEGIN(fchown); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_fchown, fd, uid, gid); |
| FS_SYNC_TRACE_END(fchown); |
| } |
| } |
| |
| |
| static void LChown(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| |
| CHECK(IsSafeJsInt(args[1])); |
| const auto uid = FromV8Value<uv_uid_t, true>(args[1]); |
| |
| CHECK(IsSafeJsInt(args[2])); |
| const auto gid = FromV8Value<uv_gid_t, true>(args[2]); |
| |
| if (argc > 3) { // lchown(path, uid, gid, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| path.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_LCHOWN, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "lchown", UTF8, AfterNoArgs, |
| uv_fs_lchown, *path, uid, gid); |
| } else { // lchown(path, uid, gid) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemWrite, |
| path.ToStringView()); |
| FSReqWrapSync req_wrap_sync("lchown", *path); |
| FS_SYNC_TRACE_BEGIN(lchown); |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_lchown, *path, uid, gid); |
| FS_SYNC_TRACE_END(lchown); |
| } |
| } |
| |
| |
| static void UTimes(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); |
| |
| CHECK(args[1]->IsNumber()); |
| const double atime = args[1].As<Number>()->Value(); |
| |
| CHECK(args[2]->IsNumber()); |
| const double mtime = args[2].As<Number>()->Value(); |
| |
| if (argc > 3) { // utimes(path, atime, mtime, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_UTIME, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "utime", UTF8, AfterNoArgs, |
| uv_fs_utime, *path, atime, mtime); |
| } else { // utimes(path, atime, mtime) |
| FSReqWrapSync req_wrap_sync("utime", *path); |
| FS_SYNC_TRACE_BEGIN(utimes); |
| SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_utime, *path, atime, mtime); |
| FS_SYNC_TRACE_END(utimes); |
| } |
| } |
| |
| static void FUTimes(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| int fd; |
| if (!GetValidatedFd(env, args[0]).To(&fd)) { |
| return; |
| } |
| |
| CHECK(args[1]->IsNumber()); |
| const double atime = args[1].As<Number>()->Value(); |
| |
| CHECK(args[2]->IsNumber()); |
| const double mtime = args[2].As<Number>()->Value(); |
| |
| if (argc > 3) { // futimes(fd, atime, mtime, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN0(UV_FS_FUTIME, req_wrap_async) |
| AsyncCall(env, req_wrap_async, args, "futime", UTF8, AfterNoArgs, |
| uv_fs_futime, fd, atime, mtime); |
| } else { // futimes(fd, atime, mtime) |
| FSReqWrapSync req_wrap_sync("futime"); |
| FS_SYNC_TRACE_BEGIN(futimes); |
| SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_futime, fd, atime, mtime); |
| FS_SYNC_TRACE_END(futimes); |
| } |
| } |
| |
| static void LUTimes(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 3); |
| |
| BufferValue path(env->isolate(), args[0]); |
| CHECK_NOT_NULL(*path); |
| ToNamespacedPath(env, &path); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, path.ToStringView()); |
| |
| CHECK(args[1]->IsNumber()); |
| const double atime = args[1].As<Number>()->Value(); |
| |
| CHECK(args[2]->IsNumber()); |
| const double mtime = args[2].As<Number>()->Value(); |
| |
| if (argc > 3) { // lutimes(path, atime, mtime, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 3); |
| CHECK_NOT_NULL(req_wrap_async); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_LUTIME, req_wrap_async, "path", TRACE_STR_COPY(*path)) |
| AsyncCall(env, req_wrap_async, args, "lutime", UTF8, AfterNoArgs, |
| uv_fs_lutime, *path, atime, mtime); |
| } else { // lutimes(path, atime, mtime) |
| FSReqWrapSync req_wrap_sync("lutime", *path); |
| FS_SYNC_TRACE_BEGIN(lutimes); |
| SyncCallAndThrowOnError( |
| env, &req_wrap_sync, uv_fs_lutime, *path, atime, mtime); |
| FS_SYNC_TRACE_END(lutimes); |
| } |
| } |
| |
| static void Mkdtemp(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| BufferValue tmpl(isolate, args[0]); |
| 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); |
| |
| const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); |
| |
| if (argc > 2) { // mkdtemp(tmpl, encoding, req) |
| FSReqBase* req_wrap_async = GetReqWrap(args, 2); |
| CHECK_NOT_NULL(req_wrap_async); |
| ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| req_wrap_async, |
| permission::PermissionScope::kFileSystemWrite, |
| tmpl.ToStringView()); |
| FS_ASYNC_TRACE_BEGIN1( |
| UV_FS_MKDTEMP, req_wrap_async, "path", TRACE_STR_COPY(*tmpl)) |
| AsyncCall(env, req_wrap_async, args, "mkdtemp", encoding, AfterStringPath, |
| uv_fs_mkdtemp, *tmpl); |
| } else { // mkdtemp(tmpl, encoding) |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemWrite, |
| tmpl.ToStringView()); |
| FSReqWrapSync req_wrap_sync("mkdtemp", *tmpl); |
| FS_SYNC_TRACE_BEGIN(mkdtemp); |
| int result = |
| SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_mkdtemp, *tmpl); |
| FS_SYNC_TRACE_END(mkdtemp); |
| if (is_uv_error(result)) { |
| return; |
| } |
| Local<Value> ret; |
| if (StringBytes::Encode(isolate, req_wrap_sync.req.path, encoding) |
| .ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| } |
| |
| static void GetFormatOfExtensionlessFile( |
| const FunctionCallbackInfo<Value>& args) { |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsString()); |
| |
| Environment* env = Environment::GetCurrent(args); |
| BufferValue input(args.GetIsolate(), args[0]); |
| ToNamespacedPath(env, &input); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, input.ToStringView()); |
| |
| uv_fs_t req; |
| FS_SYNC_TRACE_BEGIN(open) |
| uv_file file = uv_fs_open(nullptr, &req, input.out(), O_RDONLY, 0, nullptr); |
| FS_SYNC_TRACE_END(open); |
| |
| if (req.result < 0) { |
| return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT); |
| } |
| |
| auto cleanup = OnScopeLeave([&req, &file]() { |
| FS_SYNC_TRACE_BEGIN(close); |
| CHECK_EQ(0, uv_fs_close(nullptr, &req, file, nullptr)); |
| FS_SYNC_TRACE_END(close); |
| uv_fs_req_cleanup(&req); |
| }); |
| |
| char buffer[4]; |
| uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer)); |
| int err = uv_fs_read(nullptr, &req, file, &buf, 1, 0, nullptr); |
| |
| if (err < 0) { |
| return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT); |
| } |
| |
| // We do this by taking advantage of the fact that all Wasm files start with |
| // the header `0x00 0x61 0x73 0x6d` |
| if (buffer[0] == 0x00 && buffer[1] == 0x61 && buffer[2] == 0x73 && |
| buffer[3] == 0x6d) { |
| return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_WASM); |
| } |
| |
| return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT); |
| } |
| |
| static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| CHECK_EQ(args.Length(), 4); // src, dest, dereference, recursive |
| |
| BufferValue src(isolate, args[0]); |
| CHECK_NOT_NULL(*src); |
| ToNamespacedPath(env, &src); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, src.ToStringView()); |
| |
| auto src_path = src.ToPath(); |
| |
| BufferValue dest(isolate, args[1]); |
| CHECK_NOT_NULL(*dest); |
| ToNamespacedPath(env, &dest); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView()); |
| |
| auto dest_path = dest.ToPath(); |
| bool dereference = args[2]->IsTrue(); |
| bool recursive = args[3]->IsTrue(); |
| |
| std::error_code error_code; |
| auto src_status = dereference |
| ? std::filesystem::symlink_status(src_path, error_code) |
| : std::filesystem::status(src_path, error_code); |
| |
| if (error_code) { |
| #ifdef _WIN32 |
| int errorno = uv_translate_sys_error(error_code.value()); |
| #else |
| int errorno = |
| error_code.value() > 0 ? -error_code.value() : error_code.value(); |
| #endif |
| return env->ThrowUVException( |
| errorno, dereference ? "stat" : "lstat", nullptr, src.out()); |
| } |
| auto dest_status = |
| dereference ? std::filesystem::status(dest_path, error_code) |
| : std::filesystem::symlink_status(dest_path, error_code); |
| |
| bool dest_exists = !error_code && dest_status.type() != |
| std::filesystem::file_type::not_found; |
| bool src_is_dir = |
| (src_status.type() == std::filesystem::file_type::directory) || |
| (dereference && src_status.type() == std::filesystem::file_type::symlink); |
| |
| auto src_path_str = ConvertPathToUTF8(src_path); |
| auto dest_path_str = ConvertPathToUTF8(dest_path); |
| |
| if (!error_code) { |
| // Check if src and dest are identical. |
| if (std::filesystem::equivalent(src_path, dest_path)) { |
| static constexpr const char* message = |
| "src and dest cannot be the same %s"; |
| return THROW_ERR_FS_CP_EINVAL(env, message, dest_path_str); |
| } |
| |
| const bool dest_is_dir = |
| dest_status.type() == std::filesystem::file_type::directory; |
| if (src_is_dir && !dest_is_dir) { |
| static constexpr const char* message = |
| "Cannot overwrite non-directory %s with directory %s"; |
| return THROW_ERR_FS_CP_DIR_TO_NON_DIR( |
| env, message, dest_path_str, src_path_str); |
| } |
| |
| if (!src_is_dir && dest_is_dir) { |
| static constexpr const char* message = |
| "Cannot overwrite directory %s with non-directory %s"; |
| return THROW_ERR_FS_CP_NON_DIR_TO_DIR( |
| env, message, dest_path_str, src_path_str); |
| } |
| } |
| |
| if (!src_path_str.ends_with(std::filesystem::path::preferred_separator)) { |
| src_path_str += std::filesystem::path::preferred_separator; |
| } |
| // Check if dest_path is a subdirectory of src_path. |
| if (src_is_dir && dest_path_str.starts_with(src_path_str)) { |
| static constexpr const char* message = |
| "Cannot copy %s to a subdirectory of self %s"; |
| return THROW_ERR_FS_CP_EINVAL(env, message, src_path_str, dest_path_str); |
| } |
| |
| auto dest_parent = dest_path.parent_path(); |
| // "/" parent is itself. Therefore, we need to check if the parent is the same |
| // as itself. |
| while (src_path.parent_path() != dest_parent && |
| dest_parent.has_parent_path() && |
| dest_parent.parent_path() != dest_parent) { |
| if (std::filesystem::equivalent( |
| src_path, dest_path.parent_path(), error_code)) { |
| static constexpr const char* message = |
| "Cannot copy %s to a subdirectory of self %s"; |
| return THROW_ERR_FS_CP_EINVAL(env, message, src_path_str, dest_path_str); |
| } |
| |
| // If equivalent fails, it's highly likely that dest_parent does not exist |
| if (error_code) { |
| break; |
| } |
| |
| dest_parent = dest_parent.parent_path(); |
| } |
| |
| if (src_is_dir && !recursive) { |
| static constexpr const char* message = |
| "Recursive option not enabled, cannot copy a directory: %s"; |
| return THROW_ERR_FS_EISDIR(env, message, src_path_str); |
| } |
| |
| switch (src_status.type()) { |
| case std::filesystem::file_type::socket: { |
| static constexpr const char* message = "Cannot copy a socket file: %s"; |
| return THROW_ERR_FS_CP_SOCKET(env, message, dest_path_str); |
| } |
| case std::filesystem::file_type::fifo: { |
| static constexpr const char* message = "Cannot copy a FIFO pipe: %s"; |
| return THROW_ERR_FS_CP_FIFO_PIPE(env, message, dest_path_str); |
| } |
| case std::filesystem::file_type::unknown: { |
| static constexpr const char* message = |
| "Cannot copy an unknown file type: %s"; |
| return THROW_ERR_FS_CP_UNKNOWN(env, message, dest_path_str); |
| } |
| default: |
| break; |
| } |
| |
| // Optimization opportunity: Check if this "exists" call is good for |
| // performance. |
| if (!dest_exists || !std::filesystem::exists(dest_path.parent_path())) { |
| std::filesystem::create_directories(dest_path.parent_path(), error_code); |
| } |
| } |
| |
| static bool CopyUtimes(const std::filesystem::path& src, |
| const std::filesystem::path& dest, |
| Environment* env) { |
| uv_fs_t req; |
| auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); }); |
| |
| auto src_path_str = ConvertPathToUTF8(src); |
| int result = uv_fs_stat(nullptr, &req, src_path_str.c_str(), nullptr); |
| if (is_uv_error(result)) { |
| env->ThrowUVException(result, "stat", nullptr, src_path_str.c_str()); |
| return false; |
| } |
| |
| const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr); |
| const double source_atime = s->st_atim.tv_sec + s->st_atim.tv_nsec / 1e9; |
| const double source_mtime = s->st_mtim.tv_sec + s->st_mtim.tv_nsec / 1e9; |
| |
| auto dest_file_path_str = ConvertPathToUTF8(dest); |
| int utime_result = uv_fs_utime(nullptr, |
| &req, |
| dest_file_path_str.c_str(), |
| source_atime, |
| source_mtime, |
| nullptr); |
| if (is_uv_error(utime_result)) { |
| env->ThrowUVException( |
| utime_result, "utime", nullptr, dest_file_path_str.c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| static void CpSyncOverrideFile(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| CHECK_EQ(args.Length(), 4); // src, dest, mode, preserveTimestamps |
| |
| BufferValue src(isolate, args[0]); |
| CHECK_NOT_NULL(*src); |
| ToNamespacedPath(env, &src); |
| |
| BufferValue dest(isolate, args[1]); |
| CHECK_NOT_NULL(*dest); |
| ToNamespacedPath(env, &dest); |
| |
| int mode; |
| if (!GetValidFileMode(env, args[2], UV_FS_COPYFILE).To(&mode)) { |
| return; |
| } |
| |
| bool preserve_timestamps = args[3]->IsTrue(); |
| |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemRead, src.ToStringView()); |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView()); |
| |
| std::error_code error; |
| |
| if (!std::filesystem::remove(*dest, error)) { |
| return env->ThrowStdErrException(error, "unlink", *dest); |
| } |
| |
| if (mode == 0) { |
| // if no mode is specified use the faster std::filesystem API |
| if (!std::filesystem::copy_file(*src, *dest, error)) { |
| return env->ThrowStdErrException(error, "cp", *dest); |
| } |
| } else { |
| uv_fs_t req; |
| auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); }); |
| auto result = uv_fs_copyfile(nullptr, &req, *src, *dest, mode, nullptr); |
| if (is_uv_error(result)) { |
| return env->ThrowUVException(result, "cp", nullptr, *src, *dest); |
| } |
| } |
| |
| if (preserve_timestamps) { |
| CopyUtimes(*src, *dest, env); |
| } |
| } |
| |
| std::vector<std::string> normalizePathToArray( |
| const std::filesystem::path& path) { |
| std::vector<std::string> parts; |
| std::filesystem::path absPath = std::filesystem::absolute(path); |
| for (const auto& part : absPath) { |
| if (!part.empty()) parts.push_back(part.string()); |
| } |
| return parts; |
| } |
| |
| bool isInsideDir(const std::filesystem::path& src, |
| const std::filesystem::path& dest) { |
| auto srcArr = normalizePathToArray(src); |
| auto destArr = normalizePathToArray(dest); |
| if (srcArr.size() > destArr.size()) return false; |
| return std::equal(srcArr.begin(), srcArr.end(), destArr.begin()); |
| } |
| |
| static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) { |
| CHECK_EQ(args.Length(), 7); // src, dest, force, dereference, errorOnExist, |
| // verbatimSymlinks, preserveTimestamps |
| |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| BufferValue src(isolate, args[0]); |
| CHECK_NOT_NULL(*src); |
| ToNamespacedPath(env, &src); |
| |
| BufferValue dest(isolate, args[1]); |
| CHECK_NOT_NULL(*dest); |
| ToNamespacedPath(env, &dest); |
| |
| bool force = args[2]->IsTrue(); |
| bool dereference = args[3]->IsTrue(); |
| bool error_on_exist = args[4]->IsTrue(); |
| bool verbatim_symlinks = args[5]->IsTrue(); |
| bool preserve_timestamps = args[6]->IsTrue(); |
| |
| std::error_code error; |
| std::filesystem::create_directories(*dest, error); |
| if (error) { |
| return env->ThrowStdErrException(error, "cp", *dest); |
| } |
| |
| auto file_copy_opts = std::filesystem::copy_options::recursive; |
| if (force) { |
| file_copy_opts |= std::filesystem::copy_options::overwrite_existing; |
| } else if (error_on_exist) { |
| file_copy_opts |= std::filesystem::copy_options::none; |
| } else { |
| file_copy_opts |= std::filesystem::copy_options::skip_existing; |
| } |
| |
| std::function<bool(std::filesystem::path, std::filesystem::path)> |
| copy_dir_contents; |
| copy_dir_contents = [verbatim_symlinks, |
| ©_dir_contents, |
| &env, |
| file_copy_opts, |
| preserve_timestamps, |
| force, |
| error_on_exist, |
| dereference, |
| &isolate](std::filesystem::path src, |
| std::filesystem::path dest) { |
| std::error_code error; |
| for (auto dir_entry : std::filesystem::directory_iterator(src)) { |
| auto dest_file_path = dest / dir_entry.path().filename(); |
| auto dest_str = ConvertPathToUTF8(dest); |
| |
| if (dir_entry.is_symlink()) { |
| if (verbatim_symlinks) { |
| std::filesystem::copy_symlink( |
| dir_entry.path(), dest_file_path, error); |
| if (error) { |
| env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| return false; |
| } |
| } else { |
| auto symlink_target = |
| std::filesystem::read_symlink(dir_entry.path().c_str(), error); |
| if (error) { |
| env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| return false; |
| } |
| |
| if (std::filesystem::exists(dest_file_path)) { |
| if (std::filesystem::is_symlink((dest_file_path.c_str()))) { |
| auto current_dest_symlink_target = |
| std::filesystem::read_symlink(dest_file_path.c_str(), error); |
| if (error) { |
| env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| return false; |
| } |
| |
| if (!dereference && |
| std::filesystem::is_directory(symlink_target) && |
| isInsideDir(symlink_target, current_dest_symlink_target)) { |
| static constexpr const char* message = |
| "Cannot copy %s to a subdirectory of self %s"; |
| THROW_ERR_FS_CP_EINVAL( |
| env, message, symlink_target, current_dest_symlink_target); |
| return false; |
| } |
| |
| // Prevent copy if src is a subdir of dest since unlinking |
| // dest in this case would result in removing src contents |
| // and therefore a broken symlink would be created. |
| if (std::filesystem::is_directory(dest_file_path) && |
| isInsideDir(current_dest_symlink_target, symlink_target)) { |
| static constexpr const char* message = |
| "cannot overwrite %s with %s"; |
| THROW_ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY( |
| env, message, current_dest_symlink_target, symlink_target); |
| return false; |
| } |
| |
| // symlinks get overridden by cp even if force: false, this is |
| // being applied here for backward compatibility, but is it |
| // correct? or is it a bug? |
| std::filesystem::remove(dest_file_path, error); |
| if (error) { |
| env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| return false; |
| } |
| } else if (std::filesystem::is_regular_file(dest_file_path)) { |
| if (!dereference || (!force && error_on_exist)) { |
| auto dest_file_path_str = ConvertPathToUTF8(dest_file_path); |
| env->ThrowStdErrException( |
| std::make_error_code(std::errc::file_exists), |
| "cp", |
| dest_file_path_str.c_str()); |
| return false; |
| } |
| } |
| } |
| auto symlink_target_absolute = std::filesystem::weakly_canonical( |
| std::filesystem::absolute(src / symlink_target)); |
| if (dir_entry.is_directory()) { |
| std::filesystem::create_directory_symlink( |
| symlink_target_absolute, dest_file_path, error); |
| } else { |
| std::filesystem::create_symlink( |
| symlink_target_absolute, dest_file_path, error); |
| } |
| if (error) { |
| env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| return false; |
| } |
| } |
| } else if (dir_entry.is_directory()) { |
| auto entry_dir_path = src / dir_entry.path().filename(); |
| std::filesystem::create_directory(dest_file_path); |
| auto success = copy_dir_contents(entry_dir_path, dest_file_path); |
| if (!success) { |
| return false; |
| } |
| } else if (dir_entry.is_regular_file()) { |
| std::filesystem::copy_file( |
| dir_entry.path(), dest_file_path, file_copy_opts, error); |
| if (error) { |
| if (error.value() == EEXIST) { |
| THROW_ERR_FS_CP_EEXIST(isolate, |
| "[ERR_FS_CP_EEXIST]: Target already exists: " |
| "cp returned EEXIST (%s already exists)", |
| dest_file_path); |
| return false; |
| } |
| env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| return false; |
| } |
| |
| if (preserve_timestamps && |
| !CopyUtimes(dir_entry.path(), dest_file_path, env)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| }; |
| |
| copy_dir_contents(std::filesystem::path(*src), std::filesystem::path(*dest)); |
| } |
| |
| BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile( |
| Environment* env, const std::string& file_path) { |
| THROW_IF_INSUFFICIENT_PERMISSIONS( |
| env, |
| permission::PermissionScope::kFileSystemRead, |
| file_path, |
| BindingData::FilePathIsFileReturnType::kThrowInsufficientPermissions); |
| |
| uv_fs_t req; |
| |
| int rc = uv_fs_stat(env->event_loop(), &req, file_path.c_str(), nullptr); |
| |
| if (rc == 0) { |
| const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr); |
| rc = S_ISDIR(s->st_mode); |
| } |
| |
| uv_fs_req_cleanup(&req); |
| |
| // rc is 0 if the path refers to a file |
| if (rc == 0) return BindingData::FilePathIsFileReturnType::kIsFile; |
| |
| return BindingData::FilePathIsFileReturnType::kIsNotFile; |
| } |
| |
| namespace { |
| |
| // define the final index of the algorithm resolution |
| // when packageConfig.main is defined. |
| constexpr uint8_t legacy_main_extensions_with_main_end = 7; |
| // define the final index of the algorithm resolution |
| // when packageConfig.main is NOT defined |
| constexpr uint8_t legacy_main_extensions_package_fallback_end = 10; |
| // the possible file extensions that should be tested |
| // 0-6: when packageConfig.main is defined |
| // 7-9: when packageConfig.main is NOT defined, |
| // or when the previous case didn't found the file |
| constexpr std::array<std::string_view, 10> legacy_main_extensions = { |
| "", |
| ".js", |
| ".json", |
| ".node", |
| "/index.js", |
| "/index.json", |
| "/index.node", |
| ".js", |
| ".json", |
| ".node"}; |
| |
| } // namespace |
| |
| void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) { |
| CHECK_GE(args.Length(), 1); |
| CHECK(args[0]->IsString()); |
| |
| Environment* env = Environment::GetCurrent(args); |
| auto isolate = env->isolate(); |
| |
| auto utf8_package_path = Utf8Value(isolate, args[0]).ToString(); |
| |
| std::string package_initial_file = ""; |
| |
| std::optional<std::string> initial_file_path; |
| std::string file_path; |
| |
| if (args.Length() >= 2 && args[1]->IsString()) { |
| auto package_config_main = Utf8Value(isolate, args[1]).ToString(); |
| |
| initial_file_path = |
| PathResolve(env, {utf8_package_path, package_config_main}); |
| FromNamespacedPath(&initial_file_path.value()); |
| |
| package_initial_file = *initial_file_path; |
| |
| for (int i = 0; i < legacy_main_extensions_with_main_end; i++) { |
| file_path = *initial_file_path + std::string(legacy_main_extensions[i]); |
| // TODO(anonrig): Remove this when ToNamespacedPath supports std::string |
| Local<Value> local_file_path; |
| if (!Buffer::Copy(env->isolate(), file_path.c_str(), file_path.size()) |
| .ToLocal(&local_file_path)) { |
| return; |
| } |
| BufferValue buff_file_path(isolate, local_file_path); |
| ToNamespacedPath(env, &buff_file_path); |
| |
| switch (FilePathIsFile(env, buff_file_path.ToString())) { |
| case BindingData::FilePathIsFileReturnType::kIsFile: |
| return args.GetReturnValue().Set(i); |
| case BindingData::FilePathIsFileReturnType::kIsNotFile: |
| continue; |
| case BindingData::FilePathIsFileReturnType:: |
| kThrowInsufficientPermissions: |
| // the default behavior when do not have permission is to return |
| // and exit the execution of the method as soon as possible |
| // the internal function will throw the exception |
| return; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| } |
| |
| initial_file_path = PathResolve(env, {utf8_package_path, "./index"}); |
| if (!initial_file_path.has_value()) { |
| return; |
| } |
| |
| FromNamespacedPath(&initial_file_path.value()); |
| |
| for (int i = legacy_main_extensions_with_main_end; |
| i < legacy_main_extensions_package_fallback_end; |
| i++) { |
| file_path = *initial_file_path + std::string(legacy_main_extensions[i]); |
| // TODO(anonrig): Remove this when ToNamespacedPath supports std::string |
| Local<Value> local_file_path; |
| if (!Buffer::Copy(env->isolate(), file_path.c_str(), file_path.size()) |
| .ToLocal(&local_file_path)) { |
| return; |
| } |
| BufferValue buff_file_path(isolate, local_file_path); |
| ToNamespacedPath(env, &buff_file_path); |
| |
| switch (FilePathIsFile(env, buff_file_path.ToString())) { |
| case BindingData::FilePathIsFileReturnType::kIsFile: |
| return args.GetReturnValue().Set(i); |
| case BindingData::FilePathIsFileReturnType::kIsNotFile: |
| continue; |
| case BindingData::FilePathIsFileReturnType::kThrowInsufficientPermissions: |
| // the default behavior when do not have permission is to return |
| // and exit the execution of the method as soon as possible |
| // the internal function will throw the exception |
| return; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| if (package_initial_file == "") |
| package_initial_file = *initial_file_path + ".js"; |
| |
| std::optional<std::string> module_base; |
| |
| if (args.Length() >= 3 && args[2]->IsString()) { |
| Utf8Value utf8_base_path(isolate, args[2]); |
| auto base_url = |
| ada::parse<ada::url_aggregator>(utf8_base_path.ToStringView()); |
| |
| if (!base_url) { |
| THROW_ERR_INVALID_URL(isolate, "Invalid URL"); |
| return; |
| } |
| |
| module_base = node::url::FileURLToPath(env, *base_url); |
| if (!module_base.has_value()) { |
| return; |
| } |
| } else { |
| THROW_ERR_INVALID_ARG_TYPE( |
| isolate, |
| "The \"base\" argument must be of type string or an instance of URL."); |
| return; |
| } |
| |
| THROW_ERR_MODULE_NOT_FOUND(isolate, |
| "Cannot find package '%s' imported from %s", |
| package_initial_file, |
| *module_base); |
| } |
| |
| void BindingData::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackField("stats_field_array", stats_field_array); |
| tracker->TrackField("stats_field_bigint_array", stats_field_bigint_array); |
| tracker->TrackField("statfs_field_array", statfs_field_array); |
| tracker->TrackField("statfs_field_bigint_array", statfs_field_bigint_array); |
| tracker->TrackField("file_handle_read_wrap_freelist", |
| file_handle_read_wrap_freelist); |
| } |
| |
| BindingData::BindingData(Realm* realm, |
| v8::Local<v8::Object> wrap, |
| InternalFieldInfo* info) |
| : SnapshotableObject(realm, wrap, type_int), |
| stats_field_array(realm->isolate(), |
| kFsStatsBufferLength, |
| MAYBE_FIELD_PTR(info, stats_field_array)), |
| stats_field_bigint_array(realm->isolate(), |
| kFsStatsBufferLength, |
| MAYBE_FIELD_PTR(info, stats_field_bigint_array)), |
| statfs_field_array(realm->isolate(), |
| kFsStatFsBufferLength, |
| MAYBE_FIELD_PTR(info, statfs_field_array)), |
| statfs_field_bigint_array( |
| realm->isolate(), |
| kFsStatFsBufferLength, |
| MAYBE_FIELD_PTR(info, statfs_field_bigint_array)) { |
| Isolate* isolate = realm->isolate(); |
| Local<Context> context = realm->context(); |
| |
| if (info == nullptr) { |
| wrap->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "statValues"), |
| stats_field_array.GetJSArray()) |
| .Check(); |
| |
| wrap->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "bigintStatValues"), |
| stats_field_bigint_array.GetJSArray()) |
| .Check(); |
| |
| wrap->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "statFsValues"), |
| statfs_field_array.GetJSArray()) |
| .Check(); |
| |
| wrap->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "bigintStatFsValues"), |
| statfs_field_bigint_array.GetJSArray()) |
| .Check(); |
| } else { |
| stats_field_array.Deserialize(realm->context()); |
| stats_field_bigint_array.Deserialize(realm->context()); |
| statfs_field_array.Deserialize(realm->context()); |
| statfs_field_bigint_array.Deserialize(realm->context()); |
| } |
| stats_field_array.MakeWeak(); |
| stats_field_bigint_array.MakeWeak(); |
| statfs_field_array.MakeWeak(); |
| statfs_field_bigint_array.MakeWeak(); |
| } |
| |
| void BindingData::Deserialize(Local<Context> context, |
| Local<Object> holder, |
| int index, |
| InternalFieldInfoBase* info) { |
| DCHECK_IS_SNAPSHOT_SLOT(index); |
| HandleScope scope(Isolate::GetCurrent()); |
| Realm* realm = Realm::GetCurrent(context); |
| InternalFieldInfo* casted_info = static_cast<InternalFieldInfo*>(info); |
| BindingData* binding = |
| realm->AddBindingData<BindingData>(holder, casted_info); |
| CHECK_NOT_NULL(binding); |
| } |
| |
| bool BindingData::PrepareForSerialization(Local<Context> context, |
| v8::SnapshotCreator* creator) { |
| CHECK(file_handle_read_wrap_freelist.empty()); |
| DCHECK_NULL(internal_field_info_); |
| internal_field_info_ = InternalFieldInfoBase::New<InternalFieldInfo>(type()); |
| internal_field_info_->stats_field_array = |
| stats_field_array.Serialize(context, creator); |
| internal_field_info_->stats_field_bigint_array = |
| stats_field_bigint_array.Serialize(context, creator); |
| internal_field_info_->statfs_field_array = |
| statfs_field_array.Serialize(context, creator); |
| internal_field_info_->statfs_field_bigint_array = |
| statfs_field_bigint_array.Serialize(context, creator); |
| // Return true because we need to maintain the reference to the binding from |
| // JS land. |
| return true; |
| } |
| |
| InternalFieldInfoBase* BindingData::Serialize(int index) { |
| DCHECK_IS_SNAPSHOT_SLOT(index); |
| InternalFieldInfo* info = internal_field_info_; |
| internal_field_info_ = nullptr; |
| return info; |
| } |
| |
| void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, |
| Local<ObjectTemplate> target) { |
| Isolate* isolate = isolate_data->isolate(); |
| |
| SetMethod( |
| isolate, target, "legacyMainResolve", BindingData::LegacyMainResolve); |
| } |
| |
| void BindingData::RegisterExternalReferences( |
| ExternalReferenceRegistry* registry) { |
| registry->Register(BindingData::LegacyMainResolve); |
| } |
| |
| static void CreatePerIsolateProperties(IsolateData* isolate_data, |
| Local<ObjectTemplate> target) { |
| Isolate* isolate = isolate_data->isolate(); |
| |
| SetMethod(isolate, |
| target, |
| "getFormatOfExtensionlessFile", |
| GetFormatOfExtensionlessFile); |
| SetMethod(isolate, target, "access", Access); |
| SetMethod(isolate, target, "close", Close); |
| SetMethod(isolate, target, "existsSync", ExistsSync); |
| SetMethod(isolate, target, "open", Open); |
| SetMethod(isolate, target, "openFileHandle", OpenFileHandle); |
| SetMethod(isolate, target, "read", Read); |
| SetMethod(isolate, target, "readFileUtf8", ReadFileUtf8); |
| SetMethod(isolate, target, "readBuffers", ReadBuffers); |
| SetMethod(isolate, target, "fdatasync", Fdatasync); |
| SetMethod(isolate, target, "fsync", Fsync); |
| SetMethod(isolate, target, "rename", Rename); |
| SetMethod(isolate, target, "ftruncate", FTruncate); |
| SetMethod(isolate, target, "rmdir", RMDir); |
| SetMethod(isolate, target, "rmSync", RmSync); |
| SetMethod(isolate, target, "mkdir", MKDir); |
| SetMethod(isolate, target, "readdir", ReadDir); |
| SetMethod(isolate, target, "internalModuleStat", InternalModuleStat); |
| SetMethod(isolate, target, "stat", Stat); |
| SetMethod(isolate, target, "lstat", LStat); |
| SetMethod(isolate, target, "fstat", FStat); |
| SetMethod(isolate, target, "statfs", StatFs); |
| SetMethod(isolate, target, "link", Link); |
| SetMethod(isolate, target, "symlink", Symlink); |
| SetMethod(isolate, target, "readlink", ReadLink); |
| SetMethod(isolate, target, "unlink", Unlink); |
| SetMethod(isolate, target, "writeBuffer", WriteBuffer); |
| SetMethod(isolate, target, "writeBuffers", WriteBuffers); |
| SetMethod(isolate, target, "writeString", WriteString); |
| SetMethod(isolate, target, "writeFileUtf8", WriteFileUtf8); |
| SetMethod(isolate, target, "realpath", RealPath); |
| SetMethod(isolate, target, "copyFile", CopyFile); |
| |
| SetMethod(isolate, target, "chmod", Chmod); |
| SetMethod(isolate, target, "fchmod", FChmod); |
| |
| SetMethod(isolate, target, "chown", Chown); |
| SetMethod(isolate, target, "fchown", FChown); |
| SetMethod(isolate, target, "lchown", LChown); |
| |
| SetMethod(isolate, target, "utimes", UTimes); |
| SetMethod(isolate, target, "futimes", FUTimes); |
| SetMethod(isolate, target, "lutimes", LUTimes); |
| |
| SetMethod(isolate, target, "mkdtemp", Mkdtemp); |
| |
| SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths); |
| SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile); |
| SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir); |
| |
| 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(RmSync); |
| 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(CpSyncCheckPaths); |
| registry->Register(CpSyncOverrideFile); |
| registry->Register(CpSyncCopyDir); |
| |
| 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) |