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 <info@bnoordhuis.nl>
This commit is contained in:
Joyee Cheung 2018-04-07 17:01:06 +08:00
parent af2a104563
commit 1e7645c39a
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
15 changed files with 341 additions and 79 deletions

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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]);
}

View File

@ -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

View File

@ -546,6 +546,11 @@ Environment::fs_stats_field_array() {
return &fs_stats_field_array_;
}
inline AliasedBuffer<uint64_t, v8::BigUint64Array>*
Environment::fs_stats_field_bigint_array() {
return &fs_stats_field_bigint_array_;
}
inline std::vector<std::unique_ptr<fs::FileHandleReadWrap>>&
Environment::file_handle_read_wrap_freelist() {
return file_handle_read_wrap_freelist_;

View File

@ -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());

View File

@ -694,6 +694,8 @@ class Environment {
void set_debug_categories(const std::string& cats, bool enabled);
inline AliasedBuffer<double, v8::Float64Array>* fs_stats_field_array();
inline AliasedBuffer<uint64_t, v8::BigUint64Array>*
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<int>(DebugCategory::CATEGORY_COUNT)] = {0};
AliasedBuffer<double, v8::Float64Array> fs_stats_field_array_;
AliasedBuffer<uint64_t, v8::BigUint64Array> fs_stats_field_bigint_array_;
std::vector<std::unique_ptr<fs::FileHandleReadWrap>>
file_handle_read_wrap_freelist_;

View File

@ -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<Value> 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> value) {
@ -433,7 +434,7 @@ void FSReqWrap::SetReturnValue(const FunctionCallbackInfo<Value>& args) {
void NewFSReqWrap(const FunctionCallbackInfo<Value>& 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<Value> ctx, FSReqWrapSync* req_wrap,
return err;
}
inline FSReqBase* GetReqWrap(Environment* env, Local<Value> value) {
inline FSReqBase* GetReqWrap(Environment* env, Local<Value> value,
bool use_bigint = false) {
if (value->IsObject()) {
return Unwrap<FSReqBase>(value.As<Object>());
} else if (value->StrictEquals(env->fs_use_promises_symbol())) {
return new FSReqPromise<double, Float64Array>(env);
if (use_bigint) {
return new FSReqPromise<uint64_t, BigUint64Array>(env, use_bigint);
} else {
return new FSReqPromise<double, Float64Array>(env, use_bigint);
}
}
return nullptr;
}
@ -825,22 +831,23 @@ static void Stat(const FunctionCallbackInfo<Value>& 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<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr), use_bigint);
args.GetReturnValue().Set(arr);
}
}
@ -849,20 +856,21 @@ static void LStat(const FunctionCallbackInfo<Value>& 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<Value>& args) {
}
Local<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr), use_bigint);
args.GetReturnValue().Set(arr);
}
}
@ -884,22 +892,23 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsInt32());
int fd = args[0].As<Int32>()->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<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr), use_bigint);
args.GetReturnValue().Set(arr);
}
}
@ -1911,6 +1920,10 @@ void Initialize(Local<Object> 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

View File

@ -26,8 +26,9 @@ class FSReqBase : public ReqWrap<uv_fs_t> {
public:
typedef MaybeStackBuffer<char, 64> FSReqBuffer;
FSReqBase(Environment* env, Local<Object> req, AsyncWrap::ProviderType type)
: ReqWrap(env, req, type) {
FSReqBase(Environment* env, Local<Object> 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<uv_fs_t> {
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<uv_fs_t> {
class FSReqWrap : public FSReqBase {
public:
FSReqWrap(Environment* env, Local<Object> req)
: FSReqBase(env, req, AsyncWrap::PROVIDER_FSREQWRAP) { }
FSReqWrap(Environment* env, Local<Object> req, bool use_bigint)
: FSReqBase(env, req, AsyncWrap::PROVIDER_FSREQWRAP, use_bigint) { }
void Reject(Local<Value> reject) override;
void Resolve(Local<Value> value) override;
@ -96,11 +99,12 @@ class FSReqWrap : public FSReqBase {
template <typename NativeT = double, typename V8T = v8::Float64Array>
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<Value>& args) override {

View File

@ -337,14 +337,14 @@ v8::Local<v8::Value> FillStatsArray(AliasedBuffer<NativeT, V8T>* 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<v8::Value> FillStatsArray(AliasedBuffer<NativeT, V8T>* fields_ptr,
inline v8::Local<v8::Value> 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,

View File

@ -75,9 +75,10 @@ void StatWatcher::Initialize(Environment* env, Local<Object> target) {
}
StatWatcher::StatWatcher(Environment* env, Local<Object> wrap)
StatWatcher::StatWatcher(Environment* env, Local<Object> 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<void*>(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<Value> arr = node::FillGlobalStatsArray(env, curr);
node::FillGlobalStatsArray(env, prev, env->kFsStatsFieldsLength);
Local<Value> arr = node::FillGlobalStatsArray(env, curr,
wrap->use_bigint_);
node::FillGlobalStatsArray(env, prev, wrap->use_bigint_,
env->kFsStatsFieldsLength);
Local<Value> argv[2] {
Integer::New(env->isolate(), status),
@ -116,7 +119,7 @@ void StatWatcher::Callback(uv_fs_poll_t* handle,
void StatWatcher::New(const FunctionCallbackInfo<Value>& 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() {

View File

@ -39,7 +39,7 @@ class StatWatcher : public AsyncWrap {
static void Initialize(Environment* env, v8::Local<v8::Object> target);
protected:
StatWatcher(Environment* env, v8::Local<v8::Object> wrap);
StatWatcher(Environment* env, v8::Local<v8::Object> wrap, bool use_bigint);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
@ -57,6 +57,7 @@ class StatWatcher : public AsyncWrap {
bool IsActive();
uv_fs_poll_t* watcher_;
const bool use_bigint_;
};
} // namespace node

View File

@ -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();
})();

View File

@ -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';
};

View File

@ -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