diff --git a/lib/internal/fs/read/utf8.js b/lib/internal/fs/read/utf8.js index 5159db5988ee0b..027421601a5d68 100644 --- a/lib/internal/fs/read/utf8.js +++ b/lib/internal/fs/read/utf8.js @@ -2,7 +2,7 @@ const { handleErrorFromBinding } = require('internal/fs/utils'); -const binding = internalBinding('fs'); +const syncBinding = internalBinding('fs_sync'); /** * @param {string} path @@ -10,7 +10,7 @@ const binding = internalBinding('fs'); * @return {string} */ function readFileSyncUtf8(path, flag) { - const response = binding.readFileSync(path, flag); + const response = syncBinding.readFileUtf8(path, flag); if (typeof response === 'string') { return response; diff --git a/node.gyp b/node.gyp index 93e4235a0f3efd..fa366b7101feef 100644 --- a/node.gyp +++ b/node.gyp @@ -106,6 +106,7 @@ 'src/node_errors.cc', 'src/node_external_reference.cc', 'src/node_file.cc', + 'src/node_file_sync.cc', 'src/node_http_parser.cc', 'src/node_http2.cc', 'src/node_i18n.cc', @@ -222,6 +223,7 @@ 'src/node_external_reference.h', 'src/node_file.h', 'src/node_file-inl.h', + 'src/node_file_sync.h', 'src/node_http_common.h', 'src/node_http_common-inl.h', 'src/node_http2.h', diff --git a/src/base_object_types.h b/src/base_object_types.h index cb034f1d62b681..19faa3a8724b6e 100644 --- a/src/base_object_types.h +++ b/src/base_object_types.h @@ -12,6 +12,7 @@ namespace node { #define SERIALIZABLE_BINDING_TYPES(V) \ V(encoding_binding_data, encoding_binding::BindingData) \ V(fs_binding_data, fs::BindingData) \ + V(fs_sync_binding_data, fs_sync::BindingData) \ V(mksnapshot_binding_data, mksnapshot::BindingData) \ V(v8_binding_data, v8_utils::BindingData) \ V(blob_binding_data, BlobBindingData) \ diff --git a/src/node_binding.cc b/src/node_binding.cc index 97257d47c61738..9034f37c748974 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -42,6 +42,7 @@ V(fs) \ V(fs_dir) \ V(fs_event_wrap) \ + V(fs_sync) \ V(heap_utils) \ V(http2) \ V(http_parser) \ diff --git a/src/node_binding.h b/src/node_binding.h index 9f0692ca4e190b..faeeead580a4a7 100644 --- a/src/node_binding.h +++ b/src/node_binding.h @@ -37,6 +37,7 @@ static_assert(static_cast(NM_F_LINKED) == V(contextify) \ V(encoding_binding) \ V(fs) \ + V(fs_sync) \ V(mksnapshot) \ V(timers) \ V(process_methods) \ diff --git a/src/node_external_reference.h b/src/node_external_reference.h index ae37094c8e117e..00740bc53b73e8 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -95,6 +95,7 @@ class ExternalReferenceRegistry { V(fs) \ V(fs_dir) \ V(fs_event_wrap) \ + V(fs_sync) \ V(handle_wrap) \ V(heap_utils) \ V(messaging) \ diff --git a/src/node_file.cc b/src/node_file.cc index ee97188d266ed2..7ec09778ddbef8 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -1964,74 +1964,6 @@ static inline Maybe CheckOpenPermissions(Environment* env, return JustVoid(); } -static void ReadFileSync(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - auto isolate = env->isolate(); - - CHECK_GE(args.Length(), 2); - - BufferValue path(env->isolate(), args[0]); - CHECK_NOT_NULL(*path); - - CHECK(args[1]->IsInt32()); - const int flags = args[1].As()->Value(); - - if (CheckOpenPermissions(env, path, flags).IsNothing()) return; - - uv_fs_t req; - auto defer_req_cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); }); - - FS_SYNC_TRACE_BEGIN(open); - uv_file file = uv_fs_open(nullptr, &req, *path, flags, 438, nullptr); - FS_SYNC_TRACE_END(open); - if (req.result < 0) { - // req will be cleaned up by scope leave. - Local out[] = { - Integer::New(isolate, req.result), // errno - FIXED_ONE_BYTE_STRING(isolate, "open"), // syscall - }; - return args.GetReturnValue().Set(Array::New(isolate, out, arraysize(out))); - } - uv_fs_req_cleanup(&req); - - auto defer_close = OnScopeLeave([file]() { - uv_fs_t close_req; - CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr)); - uv_fs_req_cleanup(&close_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. - Local out[] = { - Integer::New(isolate, req.result), // errno - FIXED_ONE_BYTE_STRING(isolate, "read"), // syscall - }; - return args.GetReturnValue().Set( - Array::New(isolate, out, arraysize(out))); - } - uv_fs_req_cleanup(&req); - if (r <= 0) { - break; - } - result.append(buf.base, r); - } - FS_SYNC_TRACE_END(read); - - args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), - result.data(), - v8::NewStringType::kNormal, - result.size()) - .ToLocalChecked()); -} - static void Open(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -3188,7 +3120,6 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, SetMethod(isolate, target, "stat", Stat); SetMethod(isolate, target, "lstat", LStat); SetMethod(isolate, target, "fstat", FStat); - SetMethodNoSideEffect(isolate, target, "readFileSync", ReadFileSync); SetMethod(isolate, target, "statfs", StatFs); SetMethod(isolate, target, "link", Link); SetMethod(isolate, target, "symlink", Symlink); @@ -3306,7 +3237,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(Stat); registry->Register(LStat); registry->Register(FStat); - registry->Register(ReadFileSync); registry->Register(StatFs); registry->Register(Link); registry->Register(Symlink); diff --git a/src/node_file_sync.cc b/src/node_file_sync.cc new file mode 100644 index 00000000000000..dfd51f984dc65c --- /dev/null +++ b/src/node_file_sync.cc @@ -0,0 +1,214 @@ +#include "node_file_sync.h" +#include "memory_tracker-inl.h" +#include "node_buffer.h" +#include "node_errors.h" +#include "node_external_reference.h" +#include "node_file.h" +#include "node_metadata.h" +#include "permission/permission.h" +#include "util-inl.h" +#include "v8-fast-api-calls.h" +#include "v8.h" + +#include "tracing/trace_event.h" + +#include "string_bytes.h" + +#include + +namespace node { +namespace fs_sync { + +using v8::Array; +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Int32; +using v8::Integer; +using v8::Isolate; +using v8::JustVoid; +using v8::Local; +using v8::Maybe; +using v8::NewStringType; +using v8::Nothing; +using v8::Object; +using v8::ObjectTemplate; +using v8::String; +using v8::Value; + +#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__); + +// TODO(@anonrig): Remove the duplicate code from both node_file.cc and here. +static inline Maybe 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); + + // TODO(rafaelgss): it can be optimized to avoid two permission checks + auto pathView = path.ToStringView(); + if (rwflags != UV_FS_O_WRONLY) { + THROW_IF_INSUFFICIENT_PERMISSIONS( + env, + permission::PermissionScope::kFileSystemRead, + pathView, + Nothing()); + } + if (rwflags != UV_FS_O_RDONLY || write_as_side_effect) { + THROW_IF_INSUFFICIENT_PERMISSIONS( + env, + permission::PermissionScope::kFileSystemWrite, + pathView, + Nothing()); + } + return JustVoid(); +} + +void BindingData::MemoryInfo(MemoryTracker* tracker) const { + // TODO(@anonrig): Implement this +} + +BindingData::BindingData(Realm* realm, v8::Local object) + : SnapshotableObject(realm, object, type_int) { + // TODO(@anonrig): Implement this +} + +bool BindingData::PrepareForSerialization(v8::Local context, + v8::SnapshotCreator* 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 = + InternalFieldInfoBase::New(type()); + return info; +} + +void BindingData::Deserialize(v8::Local context, + v8::Local holder, + int index, + InternalFieldInfoBase* info) { + DCHECK_IS_SNAPSHOT_SLOT(index); + v8::HandleScope scope(context->GetIsolate()); + Realm* realm = Realm::GetCurrent(context); + BindingData* binding = realm->AddBindingData(holder); + CHECK_NOT_NULL(binding); +} + +void BindingData::ReadFileUtf8(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + auto isolate = env->isolate(); + + CHECK_GE(args.Length(), 2); + + BufferValue path(env->isolate(), args[0]); + CHECK_NOT_NULL(*path); + + CHECK(args[1]->IsInt32()); + const int flags = args[1].As()->Value(); + + if (CheckOpenPermissions(env, path, flags).IsNothing()) return; + + uv_fs_t req; + auto defer_req_cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); }); + + FS_SYNC_TRACE_BEGIN(open); + uv_file file = uv_fs_open(nullptr, &req, *path, flags, 438, nullptr); + FS_SYNC_TRACE_END(open); + if (req.result < 0) { + // req will be cleaned up by scope leave. + Local out[] = { + Integer::New(isolate, req.result), // errno + FIXED_ONE_BYTE_STRING(isolate, "open"), // syscall + }; + return args.GetReturnValue().Set(Array::New(isolate, out, arraysize(out))); + } + uv_fs_req_cleanup(&req); + + auto defer_close = OnScopeLeave([file]() { + uv_fs_t close_req; + CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr)); + uv_fs_req_cleanup(&close_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. + Local out[] = { + Integer::New(isolate, req.result), // errno + FIXED_ONE_BYTE_STRING(isolate, "read"), // syscall + }; + return args.GetReturnValue().Set( + Array::New(isolate, out, arraysize(out))); + } + uv_fs_req_cleanup(&req); + if (r <= 0) { + break; + } + result.append(buf.base, r); + } + FS_SYNC_TRACE_END(read); + + args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), + result.data(), + v8::NewStringType::kNormal, + result.size()) + .ToLocalChecked()); +} + +void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Isolate* isolate = isolate_data->isolate(); + SetMethodNoSideEffect(isolate, target, "readFileUtf8", ReadFileUtf8); +} + +void BindingData::CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { + Realm* realm = Realm::GetCurrent(context); + realm->AddBindingData(target); +} + +void BindingData::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + registry->Register(ReadFileUtf8); +} + +} // namespace fs_sync + +} // namespace node + +NODE_BINDING_CONTEXT_AWARE_INTERNAL( + fs_sync, node::fs_sync::BindingData::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT( + fs_sync, node::fs_sync::BindingData::CreatePerIsolateProperties) +NODE_BINDING_EXTERNAL_REFERENCE( + fs_sync, node::fs_sync::BindingData::RegisterExternalReferences) diff --git a/src/node_file_sync.h b/src/node_file_sync.h new file mode 100644 index 00000000000000..364142189e98e6 --- /dev/null +++ b/src/node_file_sync.h @@ -0,0 +1,51 @@ +#ifndef SRC_NODE_FILE_SYNC_H_ +#define SRC_NODE_FILE_SYNC_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include +#include "aliased_buffer.h" +#include "node.h" +#include "node_snapshotable.h" +#include "util.h" +#include "v8-fast-api-calls.h" +#include "v8.h" + +#include + +namespace node { +class ExternalReferenceRegistry; + +namespace fs_sync { + +class BindingData : public SnapshotableObject { + public: + BindingData(Realm* realm, v8::Local obj); + + using InternalFieldInfo = InternalFieldInfoBase; + + SERIALIZABLE_OBJECT_METHODS() + SET_BINDING_ID(fs_sync_binding_data) + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_SELF_SIZE(BindingData) + SET_MEMORY_INFO_NAME(BindingData) + + static void ReadFileUtf8(const v8::FunctionCallbackInfo& args); + + static void CreatePerIsolateProperties(IsolateData* isolate_data, + v8::Local ctor); + static void CreatePerContextProperties(v8::Local target, + v8::Local unused, + v8::Local context, + void* priv); + static void RegisterExternalReferences(ExternalReferenceRegistry* registry); +}; + +} // namespace fs_sync + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_FILE_SYNC_H_ diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 2c10f176c94033..8b358ec5d57e1a 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -16,6 +16,7 @@ #include "node_errors.h" #include "node_external_reference.h" #include "node_file.h" +#include "node_file_sync.h" #include "node_internals.h" #include "node_main_instance.h" #include "node_metadata.h" diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 9abe2dee22c1c7..fe19309ff840b6 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -69,6 +69,7 @@ const expectedModules = new Set([ 'NativeModule internal/v8/startup_snapshot', 'NativeModule internal/process/signal', 'Internal Binding fs', + 'Internal Binding fs_sync', 'NativeModule internal/encoding', 'NativeModule internal/webstreams/util', 'NativeModule internal/webstreams/queuingstrategies',