Skip to content

Commit d36adcd

Browse files
joyeecheungBridgeAR
authored andcommitted
fs: add *timeNs properties to BigInt Stats objects
- Extend the aliased buffer for stats objects to contain the entire time spec (seconds and nanoseconds) for the time values instead of calculating the milliseconds in C++ and lose precision there. - Calculate the nanosecond-precision time values in JS and expose them in BigInt Stats objects as `*timeNs`. The millisecond-precision values are now calculated from the nanosecond-precision values. PR-URL: #21387 Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Gus Caplan <[email protected]>
1 parent 5a7154e commit d36adcd

File tree

7 files changed

+275
-129
lines changed

7 files changed

+275
-129
lines changed

doc/api/fs.md

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ A `fs.Stats` object provides information about a file.
512512
Objects returned from [`fs.stat()`][], [`fs.lstat()`][] and [`fs.fstat()`][] and
513513
their synchronous counterparts are of this type.
514514
If `bigint` in the `options` passed to those methods is true, the numeric values
515-
will be `bigint` instead of `number`.
515+
will be `bigint` instead of `number`, and the object will contain additional
516+
nanosecond-precision properties suffixed with `Ns`.
516517

517518
```console
518519
Stats {
@@ -539,7 +540,7 @@ Stats {
539540
`bigint` version:
540541

541542
```console
542-
Stats {
543+
BigIntStats {
543544
dev: 2114n,
544545
ino: 48064969n,
545546
mode: 33188n,
@@ -554,6 +555,10 @@ Stats {
554555
mtimeMs: 1318289051000n,
555556
ctimeMs: 1318289051000n,
556557
birthtimeMs: 1318289051000n,
558+
atimeNs: 1318289051000000000n,
559+
mtimeNs: 1318289051000000000n,
560+
ctimeNs: 1318289051000000000n,
561+
birthtimeNs: 1318289051000000000n,
557562
atime: Mon, 10 Oct 2011 23:24:11 GMT,
558563
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
559564
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
@@ -726,6 +731,54 @@ added: v8.1.0
726731
The timestamp indicating the creation time of this file expressed in
727732
milliseconds since the POSIX Epoch.
728733

734+
### stats.atimeNs
735+
<!-- YAML
736+
added: REPLACEME
737+
-->
738+
739+
* {bigint}
740+
741+
Only present when `bigint: true` is passed into the method that generates
742+
the object.
743+
The timestamp indicating the last time this file was accessed expressed in
744+
nanoseconds since the POSIX Epoch.
745+
746+
### stats.mtimeNs
747+
<!-- YAML
748+
added: REPLACEME
749+
-->
750+
751+
* {bigint}
752+
753+
Only present when `bigint: true` is passed into the method that generates
754+
the object.
755+
The timestamp indicating the last time this file was modified expressed in
756+
nanoseconds since the POSIX Epoch.
757+
758+
### stats.ctimeNs
759+
<!-- YAML
760+
added: REPLACEME
761+
-->
762+
763+
* {bigint}
764+
765+
Only present when `bigint: true` is passed into the method that generates
766+
the object.
767+
The timestamp indicating the last time the file status was changed expressed
768+
in nanoseconds since the POSIX Epoch.
769+
770+
### stats.birthtimeNs
771+
<!-- YAML
772+
added: REPLACEME
773+
-->
774+
775+
* {bigint}
776+
777+
Only present when `bigint: true` is passed into the method that generates
778+
the object.
779+
The timestamp indicating the creation time of this file expressed in
780+
nanoseconds since the POSIX Epoch.
781+
729782
### stats.atime
730783
<!-- YAML
731784
added: v0.11.13
@@ -765,8 +818,17 @@ The timestamp indicating the creation time of this file.
765818
### Stat Time Values
766819

767820
The `atimeMs`, `mtimeMs`, `ctimeMs`, `birthtimeMs` properties are
768-
[numbers][MDN-Number] that hold the corresponding times in milliseconds. Their
769-
precision is platform specific. `atime`, `mtime`, `ctime`, and `birthtime` are
821+
numeric values that hold the corresponding times in milliseconds. Their
822+
precision is platform specific. When `bigint: true` is passed into the
823+
method that generates the object, the properties will be [bigints][],
824+
otherwise they will be [numbers][MDN-Number].
825+
826+
The `atimeNs`, `mtimeNs`, `ctimeNs`, `birthtimeNs` properties are
827+
[bigints][] that hold the corresponding times in nanoseconds. They are
828+
only present when `bigint: true` is passed into the method that generates
829+
the object. Their precision is platform specific.
830+
831+
`atime`, `mtime`, `ctime`, and `birthtime` are
770832
[`Date`][MDN-Date] object alternate representations of the various times. The
771833
`Date` and number values are not connected. Assigning a new number value, or
772834
mutating the `Date` value, will not be reflected in the corresponding alternate
@@ -4976,6 +5038,7 @@ the file contents.
49765038
[`net.Socket`]: net.html#net_class_net_socket
49775039
[`stat()`]: fs.html#fs_fs_stat_path_options_callback
49785040
[`util.promisify()`]: util.html#util_util_promisify_original
5041+
[bigints]: https://tc39.github.io/proposal-bigint
49795042
[Caveats]: #fs_caveats
49805043
[Common System Errors]: errors.html#errors_common_system_errors
49815044
[FS Constants]: #fs_fs_constants_1

lib/internal/fs/utils.js

Lines changed: 108 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { Reflect } = primordials;
3+
const { Object, Reflect } = primordials;
44

55
const { Buffer, kMaxLength } = require('buffer');
66
const {
@@ -16,7 +16,8 @@ const {
1616
} = require('internal/errors');
1717
const {
1818
isUint8Array,
19-
isDate
19+
isDate,
20+
isBigUint64Array
2021
} = require('internal/util/types');
2122
const { once } = require('internal/util');
2223
const { toPathIfFileURL } = require('internal/url');
@@ -230,27 +231,9 @@ function preprocessSymlinkDestination(path, type, linkPath) {
230231
}
231232
}
232233

233-
function dateFromNumeric(num) {
234-
return new Date(Number(num) + 0.5);
235-
}
236-
237234
// Constructor for file stats.
238-
function Stats(
239-
dev,
240-
mode,
241-
nlink,
242-
uid,
243-
gid,
244-
rdev,
245-
blksize,
246-
ino,
247-
size,
248-
blocks,
249-
atim_msec,
250-
mtim_msec,
251-
ctim_msec,
252-
birthtim_msec
253-
) {
235+
function StatsBase(dev, mode, nlink, uid, gid, rdev, blksize,
236+
ino, size, blocks) {
254237
this.dev = dev;
255238
this.mode = mode;
256239
this.nlink = nlink;
@@ -261,63 +244,132 @@ function Stats(
261244
this.ino = ino;
262245
this.size = size;
263246
this.blocks = blocks;
264-
this.atimeMs = atim_msec;
265-
this.mtimeMs = mtim_msec;
266-
this.ctimeMs = ctim_msec;
267-
this.birthtimeMs = birthtim_msec;
268-
this.atime = dateFromNumeric(atim_msec);
269-
this.mtime = dateFromNumeric(mtim_msec);
270-
this.ctime = dateFromNumeric(ctim_msec);
271-
this.birthtime = dateFromNumeric(birthtim_msec);
272247
}
273248

274-
Stats.prototype._checkModeProperty = function(property) {
275-
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
276-
property === S_IFSOCK)) {
277-
return false; // Some types are not available on Windows
278-
}
279-
if (typeof this.mode === 'bigint') { // eslint-disable-line valid-typeof
280-
return (this.mode & BigInt(S_IFMT)) === BigInt(property);
281-
}
282-
return (this.mode & S_IFMT) === property;
283-
};
284-
285-
Stats.prototype.isDirectory = function() {
249+
StatsBase.prototype.isDirectory = function() {
286250
return this._checkModeProperty(S_IFDIR);
287251
};
288252

289-
Stats.prototype.isFile = function() {
253+
StatsBase.prototype.isFile = function() {
290254
return this._checkModeProperty(S_IFREG);
291255
};
292256

293-
Stats.prototype.isBlockDevice = function() {
257+
StatsBase.prototype.isBlockDevice = function() {
294258
return this._checkModeProperty(S_IFBLK);
295259
};
296260

297-
Stats.prototype.isCharacterDevice = function() {
261+
StatsBase.prototype.isCharacterDevice = function() {
298262
return this._checkModeProperty(S_IFCHR);
299263
};
300264

301-
Stats.prototype.isSymbolicLink = function() {
265+
StatsBase.prototype.isSymbolicLink = function() {
302266
return this._checkModeProperty(S_IFLNK);
303267
};
304268

305-
Stats.prototype.isFIFO = function() {
269+
StatsBase.prototype.isFIFO = function() {
306270
return this._checkModeProperty(S_IFIFO);
307271
};
308272

309-
Stats.prototype.isSocket = function() {
273+
StatsBase.prototype.isSocket = function() {
310274
return this._checkModeProperty(S_IFSOCK);
311275
};
312276

277+
const kNsPerMsBigInt = 10n ** 6n;
278+
const kNsPerSecBigInt = 10n ** 9n;
279+
const kMsPerSec = 10 ** 3;
280+
const kNsPerMs = 10 ** 6;
281+
function msFromTimeSpec(sec, nsec) {
282+
return sec * kMsPerSec + nsec / kNsPerMs;
283+
}
284+
285+
function nsFromTimeSpecBigInt(sec, nsec) {
286+
return sec * kNsPerSecBigInt + nsec;
287+
}
288+
289+
function dateFromMs(ms) {
290+
return new Date(Number(ms) + 0.5);
291+
}
292+
293+
function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize,
294+
ino, size, blocks,
295+
atimeNs, mtimeNs, ctimeNs, birthtimeNs) {
296+
StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize,
297+
ino, size, blocks);
298+
299+
this.atimeMs = atimeNs / kNsPerMsBigInt;
300+
this.mtimeMs = mtimeNs / kNsPerMsBigInt;
301+
this.ctimeMs = ctimeNs / kNsPerMsBigInt;
302+
this.birthtimeMs = birthtimeNs / kNsPerMsBigInt;
303+
this.atimeNs = atimeNs;
304+
this.mtimeNs = mtimeNs;
305+
this.ctimeNs = ctimeNs;
306+
this.birthtimeNs = birthtimeNs;
307+
this.atime = dateFromMs(this.atimeMs);
308+
this.mtime = dateFromMs(this.mtimeMs);
309+
this.ctime = dateFromMs(this.ctimeMs);
310+
this.birthtime = dateFromMs(this.birthtimeMs);
311+
}
312+
313+
Object.setPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
314+
Object.setPrototypeOf(BigIntStats, StatsBase);
315+
316+
BigIntStats.prototype._checkModeProperty = function(property) {
317+
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
318+
property === S_IFSOCK)) {
319+
return false; // Some types are not available on Windows
320+
}
321+
return (this.mode & BigInt(S_IFMT)) === BigInt(property);
322+
};
323+
324+
function Stats(dev, mode, nlink, uid, gid, rdev, blksize,
325+
ino, size, blocks,
326+
atimeMs, mtimeMs, ctimeMs, birthtimeMs) {
327+
StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize,
328+
ino, size, blocks);
329+
this.atimeMs = atimeMs;
330+
this.mtimeMs = mtimeMs;
331+
this.ctimeMs = ctimeMs;
332+
this.birthtimeMs = birthtimeMs;
333+
this.atime = dateFromMs(atimeMs);
334+
this.mtime = dateFromMs(mtimeMs);
335+
this.ctime = dateFromMs(ctimeMs);
336+
this.birthtime = dateFromMs(birthtimeMs);
337+
}
338+
339+
Object.setPrototypeOf(Stats.prototype, StatsBase.prototype);
340+
Object.setPrototypeOf(Stats, StatsBase);
341+
342+
Stats.prototype._checkModeProperty = function(property) {
343+
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
344+
property === S_IFSOCK)) {
345+
return false; // Some types are not available on Windows
346+
}
347+
return (this.mode & S_IFMT) === property;
348+
};
349+
313350
function getStatsFromBinding(stats, offset = 0) {
314-
return new Stats(stats[0 + offset], stats[1 + offset], stats[2 + offset],
315-
stats[3 + offset], stats[4 + offset], stats[5 + offset],
316-
stats[6 + offset], // blksize
317-
stats[7 + offset], stats[8 + offset],
318-
stats[9 + offset], // blocks
319-
stats[10 + offset], stats[11 + offset],
320-
stats[12 + offset], stats[13 + offset]);
351+
if (isBigUint64Array(stats)) {
352+
return new BigIntStats(
353+
stats[0 + offset], stats[1 + offset], stats[2 + offset],
354+
stats[3 + offset], stats[4 + offset], stats[5 + offset],
355+
stats[6 + offset], stats[7 + offset], stats[8 + offset],
356+
stats[9 + offset],
357+
nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]),
358+
nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]),
359+
nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]),
360+
nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset])
361+
);
362+
}
363+
return new Stats(
364+
stats[0 + offset], stats[1 + offset], stats[2 + offset],
365+
stats[3 + offset], stats[4 + offset], stats[5 + offset],
366+
stats[6 + offset], stats[7 + offset], stats[8 + offset],
367+
stats[9 + offset],
368+
msFromTimeSpec(stats[10 + offset], stats[11 + offset]),
369+
msFromTimeSpec(stats[12 + offset], stats[13 + offset]),
370+
msFromTimeSpec(stats[14 + offset], stats[15 + offset]),
371+
msFromTimeSpec(stats[16 + offset], stats[17 + offset])
372+
);
321373
}
322374

323375
function stringToFlags(flags) {
@@ -453,6 +505,7 @@ function warnOnNonPortableTemplate(template) {
453505

454506
module.exports = {
455507
assertEncoding,
508+
BigIntStats, // for testing
456509
copyObject,
457510
Dirent,
458511
getDirents,

src/env.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,32 @@ struct PackageConfig {
102102
};
103103
} // namespace loader
104104

105+
enum class FsStatsOffset {
106+
kDev = 0,
107+
kMode,
108+
kNlink,
109+
kUid,
110+
kGid,
111+
kRdev,
112+
kBlkSize,
113+
kIno,
114+
kSize,
115+
kBlocks,
116+
kATimeSec,
117+
kATimeNsec,
118+
kMTimeSec,
119+
kMTimeNsec,
120+
kCTimeSec,
121+
kCTimeNsec,
122+
kBirthTimeSec,
123+
kBirthTimeNsec,
124+
kFsStatsFieldsNumber
125+
};
126+
105127
// Stat fields buffers contain twice the number of entries in an uv_stat_t
106128
// because `fs.StatWatcher` needs room to store 2 `fs.Stats` instances.
107-
constexpr size_t kFsStatsFieldsNumber = 14;
108-
constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
129+
constexpr size_t kFsStatsBufferLength =
130+
static_cast<size_t>(FsStatsOffset::kFsStatsFieldsNumber) * 2;
109131

110132
// PER_ISOLATE_* macros: We have a lot of per-isolate properties
111133
// and adding and maintaining their getters and setters by hand would be

src/node_file.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,10 +2197,13 @@ void Initialize(Local<Object> target,
21972197

21982198
env->SetMethod(target, "mkdtemp", Mkdtemp);
21992199

2200-
target->Set(context,
2201-
FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"),
2202-
Integer::New(isolate, kFsStatsFieldsNumber))
2203-
.Check();
2200+
target
2201+
->Set(context,
2202+
FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"),
2203+
Integer::New(
2204+
isolate,
2205+
static_cast<int32_t>(FsStatsOffset::kFsStatsFieldsNumber)))
2206+
.Check();
22042207

22052208
target->Set(context,
22062209
FIXED_ONE_BYTE_STRING(isolate, "statValues"),

0 commit comments

Comments
 (0)