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:
parent
af2a104563
commit
1e7645c39a
56
lib/fs.js
56
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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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());
|
||||
|
@ -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_;
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
145
test/parallel/test-fs-stat-bigint.js
Normal file
145
test/parallel/test-fs-stat-bigint.js
Normal 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();
|
||||
})();
|
@ -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';
|
||||
};
|
||||
|
63
test/parallel/test-fs-watchfile-bigint.js
Normal file
63
test/parallel/test-fs-watchfile-bigint.js
Normal 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
|
Loading…
x
Reference in New Issue
Block a user