From 04e8f0749e2cac0f6db8a02ce40d817cb668d094 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sat, 7 Apr 2018 17:01:06 +0800 Subject: [PATCH] fs: support BigInt in fs.*stat and fs.watchFile Add the `bigint: true` option to all the `fs.*stat` methods and `fs.watchFile`. PR-URL: https://github.com/nodejs/node/pull/20220 Fixes: https://github.com/nodejs/node/issues/12115 Reviewed-By: Ben Noordhuis --- lib/fs.js | 56 +++++---- lib/internal/fs/promises.js | 19 ++- lib/internal/fs/utils.js | 16 ++- lib/internal/fs/watchers.js | 4 +- src/env-inl.h | 5 + src/env.cc | 1 + src/env.h | 3 + src/node_file.cc | 59 +++++---- src/node_file.h | 19 +-- src/node_internals.h | 12 +- src/node_stat_watcher.cc | 13 +- src/node_stat_watcher.h | 3 +- test/parallel/test-fs-stat-bigint.js | 145 ++++++++++++++++++++++ test/parallel/test-fs-sync-fd-leak.js | 2 +- test/parallel/test-fs-watchfile-bigint.js | 63 ++++++++++ 15 files changed, 341 insertions(+), 79 deletions(-) create mode 100644 test/parallel/test-fs-stat-bigint.js create mode 100644 test/parallel/test-fs-watchfile-bigint.js diff --git a/lib/fs.js b/lib/fs.js index 983625bc9b0af3..4a7eabc5117174 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -248,7 +248,7 @@ function readFileAfterOpen(err, fd) { const req = new FSReqWrap(); req.oncomplete = readFileAfterStat; req.context = context; - binding.fstat(fd, req); + binding.fstat(fd, false, req); } function readFileAfterStat(err, stats) { @@ -307,7 +307,7 @@ function readFile(path, options, callback) { function tryStatSync(fd, isUserFd) { const ctx = {}; - const stats = binding.fstat(fd, undefined, ctx); + const stats = binding.fstat(fd, false, undefined, ctx); if (ctx.errno !== undefined && !isUserFd) { fs.closeSync(fd); throw errors.uvException(ctx); @@ -760,55 +760,67 @@ function readdirSync(path, options) { return result; } -function fstat(fd, callback) { +function fstat(fd, options, callback) { + if (arguments.length < 3) { + callback = options; + options = {}; + } validateUint32(fd, 'fd'); - const req = new FSReqWrap(); + const req = new FSReqWrap(options.bigint); req.oncomplete = makeStatsCallback(callback); - binding.fstat(fd, req); + binding.fstat(fd, options.bigint, req); } -function lstat(path, callback) { +function lstat(path, options, callback) { + if (arguments.length < 3) { + callback = options; + options = {}; + } callback = makeStatsCallback(callback); path = getPathFromURL(path); validatePath(path); - const req = new FSReqWrap(); + const req = new FSReqWrap(options.bigint); req.oncomplete = callback; - binding.lstat(pathModule.toNamespacedPath(path), req); + binding.lstat(pathModule.toNamespacedPath(path), options.bigint, req); } -function stat(path, callback) { +function stat(path, options, callback) { + if (arguments.length < 3) { + callback = options; + options = {}; + } callback = makeStatsCallback(callback); path = getPathFromURL(path); validatePath(path); - const req = new FSReqWrap(); + const req = new FSReqWrap(options.bigint); req.oncomplete = callback; - binding.stat(pathModule.toNamespacedPath(path), req); + binding.stat(pathModule.toNamespacedPath(path), options.bigint, req); } -function fstatSync(fd) { +function fstatSync(fd, options = {}) { validateUint32(fd, 'fd'); const ctx = { fd }; - const stats = binding.fstat(fd, undefined, ctx); + const stats = binding.fstat(fd, options.bigint, undefined, ctx); handleErrorFromBinding(ctx); return getStatsFromBinding(stats); } -function lstatSync(path) { +function lstatSync(path, options = {}) { path = getPathFromURL(path); validatePath(path); const ctx = { path }; const stats = binding.lstat(pathModule.toNamespacedPath(path), - undefined, ctx); + options.bigint, undefined, ctx); handleErrorFromBinding(ctx); return getStatsFromBinding(stats); } -function statSync(path) { +function statSync(path, options = {}) { path = getPathFromURL(path); validatePath(path); const ctx = { path }; const stats = binding.stat(pathModule.toNamespacedPath(path), - undefined, ctx); + options.bigint, undefined, ctx); handleErrorFromBinding(ctx); return getStatsFromBinding(stats); } @@ -1264,7 +1276,7 @@ function watchFile(filename, options, listener) { if (stat === undefined) { if (!watchers) watchers = require('internal/fs/watchers'); - stat = new watchers.StatWatcher(); + stat = new watchers.StatWatcher(options.bigint); stat.start(filename, options.persistent, options.interval); statWatchers.set(filename, stat); } @@ -1379,7 +1391,7 @@ function realpathSync(p, options) { // On windows, check that the root exists. On unix there is no need. if (isWindows && !knownHard[base]) { const ctx = { path: base }; - binding.lstat(pathModule.toNamespacedPath(base), undefined, ctx); + binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx); handleErrorFromBinding(ctx); knownHard[base] = true; } @@ -1421,7 +1433,7 @@ function realpathSync(p, options) { const baseLong = pathModule.toNamespacedPath(base); const ctx = { path: base }; - const stats = binding.lstat(baseLong, undefined, ctx); + const stats = binding.lstat(baseLong, false, undefined, ctx); handleErrorFromBinding(ctx); if (!isFileType(stats, S_IFLNK)) { @@ -1444,7 +1456,7 @@ function realpathSync(p, options) { } if (linkTarget === null) { const ctx = { path: base }; - binding.stat(baseLong, undefined, ctx); + binding.stat(baseLong, false, undefined, ctx); handleErrorFromBinding(ctx); linkTarget = binding.readlink(baseLong, undefined, undefined, ctx); handleErrorFromBinding(ctx); @@ -1465,7 +1477,7 @@ function realpathSync(p, options) { // On windows, check that the root exists. On unix there is no need. if (isWindows && !knownHard[base]) { const ctx = { path: base }; - binding.lstat(pathModule.toNamespacedPath(base), undefined, ctx); + binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx); handleErrorFromBinding(ctx); knownHard[base] = true; } diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index fb42ebb935a6a7..c966e0a897900a 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -81,8 +81,8 @@ class FileHandle { return readFile(this, options); } - stat() { - return fstat(this); + stat(options) { + return fstat(this, options); } truncate(len = 0) { @@ -106,7 +106,6 @@ class FileHandle { } } - function validateFileHandle(handle) { if (!(handle instanceof FileHandle)) throw new ERR_INVALID_ARG_TYPE('filehandle', 'FileHandle', handle); @@ -127,7 +126,7 @@ async function writeFileHandle(filehandle, data, options) { } async function readFileHandle(filehandle, options) { - const statFields = await binding.fstat(filehandle.fd, kUsePromises); + const statFields = await binding.fstat(filehandle.fd, false, kUsePromises); let size; if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) { @@ -318,25 +317,25 @@ async function symlink(target, path, type_) { kUsePromises); } -async function fstat(handle) { +async function fstat(handle, options = { bigint: false }) { validateFileHandle(handle); - const result = await binding.fstat(handle.fd, kUsePromises); + const result = await binding.fstat(handle.fd, options.bigint, kUsePromises); return getStatsFromBinding(result); } -async function lstat(path) { +async function lstat(path, options = { bigint: false }) { path = getPathFromURL(path); validatePath(path); const result = await binding.lstat(pathModule.toNamespacedPath(path), - kUsePromises); + options.bigint, kUsePromises); return getStatsFromBinding(result); } -async function stat(path) { +async function stat(path, options = { bigint: false }) { path = getPathFromURL(path); validatePath(path); const result = await binding.stat(pathModule.toNamespacedPath(path), - kUsePromises); + options.bigint, kUsePromises); return getStatsFromBinding(result); } diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index a8c64e2b04d56b..7092bbcb633d81 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -114,7 +114,7 @@ function preprocessSymlinkDestination(path, type, linkPath) { } function dateFromNumeric(num) { - return new Date(num + 0.5); + return new Date(Number(num) + 0.5); } // Constructor for file stats. @@ -155,7 +155,15 @@ function Stats( } Stats.prototype._checkModeProperty = function(property) { - return ((this.mode & S_IFMT) === property); + if (isWindows && (property === S_IFIFO || property === S_IFBLK || + property === S_IFSOCK)) { + return false; // Some types are not available on Windows + } + if (typeof this.mode === 'bigint') { // eslint-disable-line valid-typeof + // eslint-disable-next-line no-undef + return (this.mode & BigInt(S_IFMT)) === BigInt(property); + } + return (this.mode & S_IFMT) === property; }; Stats.prototype.isDirectory = function() { @@ -189,9 +197,9 @@ Stats.prototype.isSocket = function() { function getStatsFromBinding(stats, offset = 0) { return new Stats(stats[0 + offset], stats[1 + offset], stats[2 + offset], stats[3 + offset], stats[4 + offset], stats[5 + offset], - stats[6 + offset] < 0 ? undefined : stats[6 + offset], + isWindows ? undefined : stats[6 + offset], // blksize stats[7 + offset], stats[8 + offset], - stats[9 + offset] < 0 ? undefined : stats[9 + offset], + isWindows ? undefined : stats[9 + offset], // blocks stats[10 + offset], stats[11 + offset], stats[12 + offset], stats[13 + offset]); } diff --git a/lib/internal/fs/watchers.js b/lib/internal/fs/watchers.js index 1007a5a13614d2..5fd6948f28b362 100644 --- a/lib/internal/fs/watchers.js +++ b/lib/internal/fs/watchers.js @@ -21,10 +21,10 @@ function emitStop(self) { self.emit('stop'); } -function StatWatcher() { +function StatWatcher(bigint) { EventEmitter.call(this); - this._handle = new _StatWatcher(); + this._handle = new _StatWatcher(bigint); // uv_fs_poll is a little more powerful than ev_stat but we curb it for // the sake of backwards compatibility diff --git a/src/env-inl.h b/src/env-inl.h index 165244895582ef..c84fdf0bb8215b 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -546,6 +546,11 @@ Environment::fs_stats_field_array() { return &fs_stats_field_array_; } +inline AliasedBuffer* +Environment::fs_stats_field_bigint_array() { + return &fs_stats_field_bigint_array_; +} + inline std::vector>& Environment::file_handle_read_wrap_freelist() { return file_handle_read_wrap_freelist_; diff --git a/src/env.cc b/src/env.cc index 8df59d1546dbdd..6f6e9f3920f4b1 100644 --- a/src/env.cc +++ b/src/env.cc @@ -116,6 +116,7 @@ Environment::Environment(IsolateData* isolate_data, #endif http_parser_buffer_(nullptr), fs_stats_field_array_(isolate_, kFsStatsFieldsLength * 2), + fs_stats_field_bigint_array_(isolate_, kFsStatsFieldsLength * 2), context_(context->GetIsolate(), context) { // We'll be creating new objects so make sure we've entered the context. v8::HandleScope handle_scope(isolate()); diff --git a/src/env.h b/src/env.h index ee734fe92058b6..9ad316c063ea72 100644 --- a/src/env.h +++ b/src/env.h @@ -694,6 +694,8 @@ class Environment { void set_debug_categories(const std::string& cats, bool enabled); inline AliasedBuffer* fs_stats_field_array(); + inline AliasedBuffer* + fs_stats_field_bigint_array(); // stat fields contains twice the number of entries because `fs.StatWatcher` // needs room to store data for *two* `fs.Stats` instances. @@ -914,6 +916,7 @@ class Environment { bool debug_enabled_[static_cast(DebugCategory::CATEGORY_COUNT)] = {0}; AliasedBuffer fs_stats_field_array_; + AliasedBuffer fs_stats_field_bigint_array_; std::vector> file_handle_read_wrap_freelist_; diff --git a/src/node_file.cc b/src/node_file.cc index d4f3cbfcfb99d7..a63e90fafb2926 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -49,6 +49,7 @@ namespace node { namespace fs { using v8::Array; +using v8::BigUint64Array; using v8::Context; using v8::EscapableHandleScope; using v8::Float64Array; @@ -413,7 +414,7 @@ void FSReqWrap::Reject(Local reject) { } void FSReqWrap::ResolveStat(const uv_stat_t* stat) { - Resolve(node::FillGlobalStatsArray(env(), stat)); + Resolve(node::FillGlobalStatsArray(env(), stat, use_bigint())); } void FSReqWrap::Resolve(Local value) { @@ -433,7 +434,7 @@ void FSReqWrap::SetReturnValue(const FunctionCallbackInfo& args) { void NewFSReqWrap(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args.GetIsolate()); - new FSReqWrap(env, args.This()); + new FSReqWrap(env, args.This(), args[0]->IsTrue()); } FSReqAfterScope::FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req) @@ -670,11 +671,16 @@ inline int SyncCall(Environment* env, Local ctx, FSReqWrapSync* req_wrap, return err; } -inline FSReqBase* GetReqWrap(Environment* env, Local value) { +inline FSReqBase* GetReqWrap(Environment* env, Local value, + bool use_bigint = false) { if (value->IsObject()) { return Unwrap(value.As()); } else if (value->StrictEquals(env->fs_use_promises_symbol())) { - return new FSReqPromise(env); + if (use_bigint) { + return new FSReqPromise(env, use_bigint); + } else { + return new FSReqPromise(env, use_bigint); + } } return nullptr; } @@ -825,22 +831,23 @@ static void Stat(const FunctionCallbackInfo& args) { BufferValue path(env->isolate(), args[0]); CHECK_NOT_NULL(*path); - FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); - if (req_wrap_async != nullptr) { // stat(path, req) + bool use_bigint = args[1]->IsTrue(); + FSReqBase* req_wrap_async = GetReqWrap(env, args[2], use_bigint); + if (req_wrap_async != nullptr) { // stat(path, use_bigint, req) AsyncCall(env, req_wrap_async, args, "stat", UTF8, AfterStat, uv_fs_stat, *path); - } else { // stat(path, undefined, ctx) - CHECK_EQ(argc, 3); + } else { // stat(path, use_bigint, undefined, ctx) + CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(stat); - int err = SyncCall(env, args[2], &req_wrap_sync, "stat", uv_fs_stat, *path); + int err = SyncCall(env, args[3], &req_wrap_sync, "stat", uv_fs_stat, *path); FS_SYNC_TRACE_END(stat); if (err != 0) { return; // error info is in ctx } Local arr = node::FillGlobalStatsArray(env, - static_cast(req_wrap_sync.req.ptr)); + static_cast(req_wrap_sync.req.ptr), use_bigint); args.GetReturnValue().Set(arr); } } @@ -849,20 +856,21 @@ static void LStat(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); - CHECK_GE(argc, 2); + CHECK_GE(argc, 3); BufferValue path(env->isolate(), args[0]); CHECK_NOT_NULL(*path); - FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); - if (req_wrap_async != nullptr) { // lstat(path, req) + bool use_bigint = args[1]->IsTrue(); + FSReqBase* req_wrap_async = GetReqWrap(env, args[2], use_bigint); + if (req_wrap_async != nullptr) { // lstat(path, use_bigint, req) AsyncCall(env, req_wrap_async, args, "lstat", UTF8, AfterStat, uv_fs_lstat, *path); - } else { // lstat(path, undefined, ctx) - CHECK_EQ(argc, 3); + } else { // lstat(path, use_bigint, undefined, ctx) + CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(lstat); - int err = SyncCall(env, args[2], &req_wrap_sync, "lstat", uv_fs_lstat, + int err = SyncCall(env, args[3], &req_wrap_sync, "lstat", uv_fs_lstat, *path); FS_SYNC_TRACE_END(lstat); if (err != 0) { @@ -870,7 +878,7 @@ static void LStat(const FunctionCallbackInfo& args) { } Local arr = node::FillGlobalStatsArray(env, - static_cast(req_wrap_sync.req.ptr)); + static_cast(req_wrap_sync.req.ptr), use_bigint); args.GetReturnValue().Set(arr); } } @@ -884,22 +892,23 @@ static void FStat(const FunctionCallbackInfo& args) { CHECK(args[0]->IsInt32()); int fd = args[0].As()->Value(); - FSReqBase* req_wrap_async = GetReqWrap(env, args[1]); - if (req_wrap_async != nullptr) { // fstat(fd, req) + bool use_bigint = args[1]->IsTrue(); + FSReqBase* req_wrap_async = GetReqWrap(env, args[2], use_bigint); + if (req_wrap_async != nullptr) { // fstat(fd, use_bigint, req) AsyncCall(env, req_wrap_async, args, "fstat", UTF8, AfterStat, uv_fs_fstat, fd); - } else { // fstat(fd, undefined, ctx) - CHECK_EQ(argc, 3); + } else { // fstat(fd, use_bigint, undefined, ctx) + CHECK_EQ(argc, 4); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(fstat); - int err = SyncCall(env, args[2], &req_wrap_sync, "fstat", uv_fs_fstat, fd); + int err = SyncCall(env, args[3], &req_wrap_sync, "fstat", uv_fs_fstat, fd); FS_SYNC_TRACE_END(fstat); if (err != 0) { return; // error info is in ctx } Local arr = node::FillGlobalStatsArray(env, - static_cast(req_wrap_sync.req.ptr)); + static_cast(req_wrap_sync.req.ptr), use_bigint); args.GetReturnValue().Set(arr); } } @@ -1911,6 +1920,10 @@ void Initialize(Local target, FIXED_ONE_BYTE_STRING(env->isolate(), "statValues"), env->fs_stats_field_array()->GetJSArray()).FromJust(); + target->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "bigintStatValues"), + env->fs_stats_field_bigint_array()->GetJSArray()).FromJust(); + StatWatcher::Initialize(env, target); // Create FunctionTemplate for FSReqWrap diff --git a/src/node_file.h b/src/node_file.h index 03e41097d5f12e..a14a1b0f85e108 100644 --- a/src/node_file.h +++ b/src/node_file.h @@ -26,8 +26,9 @@ class FSReqBase : public ReqWrap { public: typedef MaybeStackBuffer FSReqBuffer; - FSReqBase(Environment* env, Local req, AsyncWrap::ProviderType type) - : ReqWrap(env, req, type) { + FSReqBase(Environment* env, Local req, AsyncWrap::ProviderType type, + bool use_bigint) + : ReqWrap(env, req, type), use_bigint_(use_bigint) { } void Init(const char* syscall, @@ -66,11 +67,13 @@ class FSReqBase : public ReqWrap { enum encoding encoding() const { return encoding_; } size_t self_size() const override { return sizeof(*this); } + bool use_bigint() const { return use_bigint_; } private: enum encoding encoding_ = UTF8; bool has_data_ = false; const char* syscall_ = nullptr; + bool use_bigint_ = false; // Typically, the content of buffer_ is something like a file name, so // something around 64 bytes should be enough. @@ -81,8 +84,8 @@ class FSReqBase : public ReqWrap { class FSReqWrap : public FSReqBase { public: - FSReqWrap(Environment* env, Local req) - : FSReqBase(env, req, AsyncWrap::PROVIDER_FSREQWRAP) { } + FSReqWrap(Environment* env, Local req, bool use_bigint) + : FSReqBase(env, req, AsyncWrap::PROVIDER_FSREQWRAP, use_bigint) { } void Reject(Local reject) override; void Resolve(Local value) override; @@ -96,11 +99,12 @@ class FSReqWrap : public FSReqBase { template class FSReqPromise : public FSReqBase { public: - explicit FSReqPromise(Environment* env) + explicit FSReqPromise(Environment* env, bool use_bigint) : FSReqBase(env, env->fsreqpromise_constructor_template() ->NewInstance(env->context()).ToLocalChecked(), - AsyncWrap::PROVIDER_FSREQPROMISE), + AsyncWrap::PROVIDER_FSREQPROMISE, + use_bigint), stats_field_array_(env->isolate(), env->kFsStatsFieldsLength) { auto resolver = Promise::Resolver::New(env->context()).ToLocalChecked(); object()->Set(env->context(), env->promise_string(), @@ -135,8 +139,7 @@ class FSReqPromise : public FSReqBase { } void ResolveStat(const uv_stat_t* stat) override { - node::FillStatsArray(&stats_field_array_, stat); - Resolve(stats_field_array_.GetJSArray()); + Resolve(node::FillStatsArray(&stats_field_array_, stat)); } void SetReturnValue(const FunctionCallbackInfo& args) override { diff --git a/src/node_internals.h b/src/node_internals.h index f76aacd7e88a5d..49f1d4f230b6d7 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -337,14 +337,14 @@ v8::Local FillStatsArray(AliasedBuffer* fields_ptr, #if defined(__POSIX__) fields[offset + 6] = s->st_blksize; #else - fields[offset + 6] = -1; + fields[offset + 6] = 0; #endif fields[offset + 7] = s->st_ino; fields[offset + 8] = s->st_size; #if defined(__POSIX__) fields[offset + 9] = s->st_blocks; #else - fields[offset + 9] = -1; + fields[offset + 9] = 0; #endif // Dates. // NO-LINT because the fields are 'long' and we just want to cast to `unsigned` @@ -365,8 +365,14 @@ v8::Local FillStatsArray(AliasedBuffer* fields_ptr, inline v8::Local FillGlobalStatsArray(Environment* env, const uv_stat_t* s, + bool use_bigint = false, int offset = 0) { - return node::FillStatsArray(env->fs_stats_field_array(), s, offset); + if (use_bigint) { + return node::FillStatsArray( + env->fs_stats_field_bigint_array(), s, offset); + } else { + return node::FillStatsArray(env->fs_stats_field_array(), s, offset); + } } void SetupBootstrapObject(Environment* env, diff --git a/src/node_stat_watcher.cc b/src/node_stat_watcher.cc index 3749c2e21f2ad9..3f7da197b2a74d 100644 --- a/src/node_stat_watcher.cc +++ b/src/node_stat_watcher.cc @@ -75,9 +75,10 @@ void StatWatcher::Initialize(Environment* env, Local target) { } -StatWatcher::StatWatcher(Environment* env, Local wrap) +StatWatcher::StatWatcher(Environment* env, Local wrap, bool use_bigint) : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_STATWATCHER), - watcher_(new uv_fs_poll_t) { + watcher_(new uv_fs_poll_t), + use_bigint_(use_bigint) { MakeWeak(); uv_fs_poll_init(env->event_loop(), watcher_); watcher_->data = static_cast(this); @@ -102,8 +103,10 @@ void StatWatcher::Callback(uv_fs_poll_t* handle, HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - Local arr = node::FillGlobalStatsArray(env, curr); - node::FillGlobalStatsArray(env, prev, env->kFsStatsFieldsLength); + Local arr = node::FillGlobalStatsArray(env, curr, + wrap->use_bigint_); + node::FillGlobalStatsArray(env, prev, wrap->use_bigint_, + env->kFsStatsFieldsLength); Local argv[2] { Integer::New(env->isolate(), status), @@ -116,7 +119,7 @@ void StatWatcher::Callback(uv_fs_poll_t* handle, void StatWatcher::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); - new StatWatcher(env, args.This()); + new StatWatcher(env, args.This(), args[0]->IsTrue()); } bool StatWatcher::IsActive() { diff --git a/src/node_stat_watcher.h b/src/node_stat_watcher.h index 587203ff1edf46..0d0d263d5cc698 100644 --- a/src/node_stat_watcher.h +++ b/src/node_stat_watcher.h @@ -39,7 +39,7 @@ class StatWatcher : public AsyncWrap { static void Initialize(Environment* env, v8::Local target); protected: - StatWatcher(Environment* env, v8::Local wrap); + StatWatcher(Environment* env, v8::Local wrap, bool use_bigint); static void New(const v8::FunctionCallbackInfo& args); static void Start(const v8::FunctionCallbackInfo& args); @@ -57,6 +57,7 @@ class StatWatcher : public AsyncWrap { bool IsActive(); uv_fs_poll_t* watcher_; + const bool use_bigint_; }; } // namespace node diff --git a/test/parallel/test-fs-stat-bigint.js b/test/parallel/test-fs-stat-bigint.js new file mode 100644 index 00000000000000..4030a06fae3bc8 --- /dev/null +++ b/test/parallel/test-fs-stat-bigint.js @@ -0,0 +1,145 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const promiseFs = require('fs').promises; +const path = require('path'); +const tmpdir = require('../common/tmpdir'); +const { isDate } = require('util').types; + +common.crashOnUnhandledRejection(); +tmpdir.refresh(); + +const fn = path.join(tmpdir.path, 'test-file'); +fs.writeFileSync(fn, 'test'); + +let link; +if (!common.isWindows) { + link = path.join(tmpdir.path, 'symbolic-link'); + fs.symlinkSync(fn, link); +} + +function verifyStats(bigintStats, numStats) { + for (const key of Object.keys(numStats)) { + const val = numStats[key]; + if (isDate(val)) { + const time = val.getTime(); + const time2 = bigintStats[key].getTime(); + assert( + Math.abs(time - time2) < 2, + `difference of ${key}.getTime() should < 2.\n` + + `Number version ${time}, BigInt version ${time2}n`); + } else if (key === 'mode') { + // eslint-disable-next-line no-undef + assert.strictEqual(bigintStats[key], BigInt(val)); + assert.strictEqual( + bigintStats.isBlockDevice(), + numStats.isBlockDevice() + ); + assert.strictEqual( + bigintStats.isCharacterDevice(), + numStats.isCharacterDevice() + ); + assert.strictEqual( + bigintStats.isDirectory(), + numStats.isDirectory() + ); + assert.strictEqual( + bigintStats.isFIFO(), + numStats.isFIFO() + ); + assert.strictEqual( + bigintStats.isFile(), + numStats.isFile() + ); + assert.strictEqual( + bigintStats.isSocket(), + numStats.isSocket() + ); + assert.strictEqual( + bigintStats.isSymbolicLink(), + numStats.isSymbolicLink() + ); + } else if (common.isWindows && (key === 'blksize' || key === 'blocks')) { + assert.strictEqual(bigintStats[key], undefined); + assert.strictEqual(numStats[key], undefined); + } else if (Number.isSafeInteger(val)) { + // eslint-disable-next-line no-undef + assert.strictEqual(bigintStats[key], BigInt(val)); + } else { + assert( + Math.abs(Number(bigintStats[key]) - val) < 1, + `${key} is not a safe integer, difference should < 1.\n` + + `Number version ${val}, BigInt version ${bigintStats[key]}n`); + } + } +} + +{ + const bigintStats = fs.statSync(fn, { bigint: true }); + const numStats = fs.statSync(fn); + verifyStats(bigintStats, numStats); +} + +if (!common.isWindows) { + const bigintStats = fs.lstatSync(link, { bigint: true }); + const numStats = fs.lstatSync(link); + verifyStats(bigintStats, numStats); +} + +{ + const fd = fs.openSync(fn, 'r'); + const bigintStats = fs.fstatSync(fd, { bigint: true }); + const numStats = fs.fstatSync(fd); + verifyStats(bigintStats, numStats); + fs.closeSync(fd); +} + +{ + fs.stat(fn, { bigint: true }, (err, bigintStats) => { + fs.stat(fn, (err, numStats) => { + verifyStats(bigintStats, numStats); + }); + }); +} + +if (!common.isWindows) { + fs.lstat(link, { bigint: true }, (err, bigintStats) => { + fs.lstat(link, (err, numStats) => { + verifyStats(bigintStats, numStats); + }); + }); +} + +{ + const fd = fs.openSync(fn, 'r'); + fs.fstat(fd, { bigint: true }, (err, bigintStats) => { + fs.fstat(fd, (err, numStats) => { + verifyStats(bigintStats, numStats); + fs.closeSync(fd); + }); + }); +} + +(async function() { + const bigintStats = await promiseFs.stat(fn, { bigint: true }); + const numStats = await promiseFs.stat(fn); + verifyStats(bigintStats, numStats); +})(); + +if (!common.isWindows) { + (async function() { + const bigintStats = await promiseFs.lstat(link, { bigint: true }); + const numStats = await promiseFs.lstat(link); + verifyStats(bigintStats, numStats); + })(); +} + +(async function() { + const handle = await promiseFs.open(fn, 'r'); + const bigintStats = await handle.stat({ bigint: true }); + const numStats = await handle.stat(); + verifyStats(bigintStats, numStats); + await handle.close(); +})(); diff --git a/test/parallel/test-fs-sync-fd-leak.js b/test/parallel/test-fs-sync-fd-leak.js index e7107546e5b217..52e40eb3901f4c 100644 --- a/test/parallel/test-fs-sync-fd-leak.js +++ b/test/parallel/test-fs-sync-fd-leak.js @@ -40,7 +40,7 @@ fs.writeSync = function() { throw new Error('BAM'); }; -process.binding('fs').fstat = function(fd, _, ctx) { +process.binding('fs').fstat = function(fd, bigint, _, ctx) { ctx.errno = uv.UV_EBADF; ctx.syscall = 'fstat'; }; diff --git a/test/parallel/test-fs-watchfile-bigint.js b/test/parallel/test-fs-watchfile-bigint.js new file mode 100644 index 00000000000000..89cefd12e0443f --- /dev/null +++ b/test/parallel/test-fs-watchfile-bigint.js @@ -0,0 +1,63 @@ +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); + +const enoentFile = path.join(tmpdir.path, 'non-existent-file'); +const expectedStatObject = new fs.Stats( + 0n, // dev + 0n, // mode + 0n, // nlink + 0n, // uid + 0n, // gid + 0n, // rdev + common.isWindows ? undefined : 0n, // blksize + 0n, // ino + 0n, // size + common.isWindows ? undefined : 0n, // blocks + 0n, // atim_msec + 0n, // mtim_msec + 0n, // ctim_msec + 0n // birthtim_msec +); + +tmpdir.refresh(); + +// If the file initially didn't exist, and gets created at a later point of +// time, the callback should be invoked again with proper values in stat object +let fileExists = false; +const options = { interval: 0, bigint: true }; + +const watcher = + fs.watchFile(enoentFile, options, common.mustCall((curr, prev) => { + if (!fileExists) { + // If the file does not exist, all the fields should be zero and the date + // fields should be UNIX EPOCH time + assert.deepStrictEqual(curr, expectedStatObject); + assert.deepStrictEqual(prev, expectedStatObject); + // Create the file now, so that the callback will be called back once the + // event loop notices it. + fs.closeSync(fs.openSync(enoentFile, 'w')); + fileExists = true; + } else { + // If the ino (inode) value is greater than zero, it means that the file + // is present in the filesystem and it has a valid inode number. + assert(curr.ino > 0n); + // As the file just got created, previous ino value should be lesser than + // or equal to zero (non-existent file). + assert(prev.ino <= 0n); + // Stop watching the file + fs.unwatchFile(enoentFile); + watcher.stop(); // stopping a stopped watcher should be a noop + } + }, 2)); + +// 'stop' should only be emitted once - stopping a stopped watcher should +// not trigger a 'stop' event. +watcher.on('stop', common.mustCall(function onStop() {})); + +watcher.start(); // starting a started watcher should be a noop