Skip to content

Commit 365e062

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 78c9e5f commit 365e062

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
@@ -5129,6 +5191,7 @@ the file contents.
51295191
[`net.Socket`]: net.html#net_class_net_socket
51305192
[`stat()`]: fs.html#fs_fs_stat_path_options_callback
51315193
[`util.promisify()`]: util.html#util_util_promisify_original
5194+
[bigints]: https://tc39.github.io/proposal-bigint
51325195
[Caveats]: #fs_caveats
51335196
[Common System Errors]: errors.html#errors_common_system_errors
51345197
[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 {
@@ -17,7 +17,8 @@ const {
1717
const {
1818
isArrayBufferView,
1919
isUint8Array,
20-
isDate
20+
isDate,
21+
isBigUint64Array
2122
} = require('internal/util/types');
2223
const { once } = require('internal/util');
2324
const { toPathIfFileURL } = require('internal/url');
@@ -231,27 +232,9 @@ function preprocessSymlinkDestination(path, type, linkPath) {
231232
}
232233
}
233234

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

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

290-
Stats.prototype.isFile = function() {
254+
StatsBase.prototype.isFile = function() {
291255
return this._checkModeProperty(S_IFREG);
292256
};
293257

294-
Stats.prototype.isBlockDevice = function() {
258+
StatsBase.prototype.isBlockDevice = function() {
295259
return this._checkModeProperty(S_IFBLK);
296260
};
297261

298-
Stats.prototype.isCharacterDevice = function() {
262+
StatsBase.prototype.isCharacterDevice = function() {
299263
return this._checkModeProperty(S_IFCHR);
300264
};
301265

302-
Stats.prototype.isSymbolicLink = function() {
266+
StatsBase.prototype.isSymbolicLink = function() {
303267
return this._checkModeProperty(S_IFLNK);
304268
};
305269

306-
Stats.prototype.isFIFO = function() {
270+
StatsBase.prototype.isFIFO = function() {
307271
return this._checkModeProperty(S_IFIFO);
308272
};
309273

310-
Stats.prototype.isSocket = function() {
274+
StatsBase.prototype.isSocket = function() {
311275
return this._checkModeProperty(S_IFSOCK);
312276
};
313277

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

324376
function stringToFlags(flags) {
@@ -466,6 +518,7 @@ function warnOnNonPortableTemplate(template) {
466518

467519
module.exports = {
468520
assertEncoding,
521+
BigIntStats, // for testing
469522
copyObject,
470523
Dirent,
471524
getDirents,

src/env.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,32 @@ struct PackageConfig {
105105
};
106106
} // namespace loader
107107

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

113135
// PER_ISOLATE_* macros: We have a lot of per-isolate properties
114136
// 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)