Skip to content

Commit 171ccdd

Browse files
committed
fix(core): preserve large stat u64 fields
1 parent b4a9191 commit 171ccdd

3 files changed

Lines changed: 171 additions & 54 deletions

File tree

packages/build-tools/bridge-src/builtins/fs.ts

Lines changed: 122 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,35 @@ var O_CREAT = 64;
1414
var O_EXCL = 128;
1515
var O_TRUNC = 512;
1616
var O_APPEND = 1024;
17+
function normalizeStatOptions(options) {
18+
if (options === void 0 || options === null) {
19+
return { bigint: false };
20+
}
21+
if (typeof options !== "object") {
22+
throw createInvalidArgTypeError("options", "of type object", options);
23+
}
24+
validateBooleanOption("bigint", options.bigint);
25+
return { bigint: options.bigint === true };
26+
}
27+
function normalizeStatCallbackArgs(optionsOrCallback, callback) {
28+
if (typeof optionsOrCallback === "function") {
29+
return { options: normalizeStatOptions(void 0), callback: optionsOrCallback };
30+
}
31+
validateCallback(callback, "cb");
32+
return { options: normalizeStatOptions(optionsOrCallback), callback };
33+
}
34+
function toStatNumber(value, fallback = 0) {
35+
return Number(value ?? fallback);
36+
}
37+
function toStatBigInt(value, fallback = 0) {
38+
return BigInt(value ?? fallback);
39+
}
40+
function statModeMatches(mode, bits) {
41+
if (typeof mode === "bigint") {
42+
return (mode & 61440n) === BigInt(bits);
43+
}
44+
return (mode & 61440) === bits;
45+
}
1746
var Stats = class {
1847
dev;
1948
ino;
@@ -34,36 +63,73 @@ var Stats = class {
3463
ctime;
3564
birthtime;
3665
constructor(init) {
37-
this.dev = init.dev ?? 0;
38-
this.ino = init.ino ?? 0;
39-
this.mode = init.mode;
40-
this.nlink = init.nlink ?? 1;
41-
this.uid = init.uid ?? 0;
42-
this.gid = init.gid ?? 0;
43-
this.rdev = init.rdev ?? 0;
44-
this.size = init.size;
45-
this.blksize = init.blksize ?? 4096;
46-
this.blocks = init.blocks ?? Math.ceil(init.size / 512);
66+
const options = normalizeStatOptions(arguments[1]);
67+
if (options.bigint) {
68+
this.dev = toStatBigInt(init.dev_u64 ?? init.dev);
69+
this.ino = toStatBigInt(init.ino_u64 ?? init.ino);
70+
this.mode = toStatBigInt(init.mode);
71+
this.nlink = toStatBigInt(init.nlink ?? 1);
72+
this.uid = toStatBigInt(init.uid);
73+
this.gid = toStatBigInt(init.gid);
74+
this.rdev = toStatBigInt(init.rdev_u64 ?? init.rdev);
75+
this.size = toStatBigInt(init.size_u64 ?? init.size);
76+
this.blksize = toStatBigInt(init.blksize ?? 4096);
77+
this.blocks = toStatBigInt(
78+
init.blocks_u64 ?? init.blocks ?? Math.ceil(Number(this.size) / 512)
79+
);
80+
} else {
81+
this.dev = toStatNumber(init.dev);
82+
this.ino = toStatNumber(init.ino);
83+
this.mode = init.mode;
84+
this.nlink = init.nlink ?? 1;
85+
this.uid = init.uid ?? 0;
86+
this.gid = init.gid ?? 0;
87+
this.rdev = toStatNumber(init.rdev);
88+
this.size = toStatNumber(init.size);
89+
this.blksize = init.blksize ?? 4096;
90+
this.blocks = toStatNumber(
91+
init.blocks ?? Math.ceil(toStatNumber(init.size) / 512)
92+
);
93+
}
4794
const atimeMs = init.atimeMs ?? Date.now();
4895
const mtimeMs = init.mtimeMs ?? Date.now();
4996
const ctimeMs = init.ctimeMs ?? Date.now();
50-
this.atimeMs = atimeMs + ((init.atimeNsec ?? 0) % 1e6) / 1e6;
51-
this.mtimeMs = mtimeMs + ((init.mtimeNsec ?? 0) % 1e6) / 1e6;
52-
this.ctimeMs = ctimeMs + ((init.ctimeNsec ?? 0) % 1e6) / 1e6;
53-
this.birthtimeMs = init.birthtimeMs ?? Date.now();
54-
this.atime = new Date(this.atimeMs);
55-
this.mtime = new Date(this.mtimeMs);
56-
this.ctime = new Date(this.ctimeMs);
57-
this.birthtime = new Date(this.birthtimeMs);
97+
const birthtimeMs = init.birthtimeMs ?? Date.now();
98+
if (options.bigint) {
99+
const atimeNsec = toStatBigInt(init.atimeNsec);
100+
const mtimeNsec = toStatBigInt(init.mtimeNsec);
101+
const ctimeNsec = toStatBigInt(init.ctimeNsec);
102+
this.atimeMs = toStatBigInt(atimeMs);
103+
this.mtimeMs = toStatBigInt(mtimeMs);
104+
this.ctimeMs = toStatBigInt(ctimeMs);
105+
this.birthtimeMs = toStatBigInt(birthtimeMs);
106+
this.atimeNs = this.atimeMs * 1000000n + atimeNsec % 1000000n;
107+
this.mtimeNs = this.mtimeMs * 1000000n + mtimeNsec % 1000000n;
108+
this.ctimeNs = this.ctimeMs * 1000000n + ctimeNsec % 1000000n;
109+
this.birthtimeNs = this.birthtimeMs * 1000000n;
110+
this.atime = new Date(Number(this.atimeMs));
111+
this.mtime = new Date(Number(this.mtimeMs));
112+
this.ctime = new Date(Number(this.ctimeMs));
113+
this.birthtime = new Date(Number(this.birthtimeMs));
114+
} else {
115+
this.atimeMs = atimeMs + ((init.atimeNsec ?? 0) % 1e6) / 1e6;
116+
this.mtimeMs = mtimeMs + ((init.mtimeNsec ?? 0) % 1e6) / 1e6;
117+
this.ctimeMs = ctimeMs + ((init.ctimeNsec ?? 0) % 1e6) / 1e6;
118+
this.birthtimeMs = birthtimeMs;
119+
this.atime = new Date(this.atimeMs);
120+
this.mtime = new Date(this.mtimeMs);
121+
this.ctime = new Date(this.ctimeMs);
122+
this.birthtime = new Date(this.birthtimeMs);
123+
}
58124
}
59125
isFile() {
60-
return (this.mode & 61440) === 32768;
126+
return statModeMatches(this.mode, 32768);
61127
}
62128
isDirectory() {
63-
return (this.mode & 61440) === 16384;
129+
return statModeMatches(this.mode, 16384);
64130
}
65131
isSymbolicLink() {
66-
return (this.mode & 61440) === 40960;
132+
return statModeMatches(this.mode, 40960);
67133
}
68134
isBlockDevice() {
69135
return false;
@@ -378,9 +444,9 @@ var FileHandle = class _FileHandle {
378444
handle._closing = false;
379445
}
380446
}
381-
async stat() {
447+
async stat(options) {
382448
const handle = _FileHandle._assertHandle(this);
383-
return fs.fstatSync(handle.fd);
449+
return fs.fstatSync(handle.fd, options);
384450
}
385451
async sync() {
386452
const handle = _FileHandle._assertHandle(this);
@@ -2387,11 +2453,12 @@ async function fsRmdirAsync(path) {
23872453
const pathStr = normalizePathLike(path);
23882454
await _fsAsync.rmdir.apply(void 0, [pathStr]);
23892455
}
2390-
async function fsStatAsync(path) {
2456+
async function fsStatAsync(path, options) {
23912457
const rawPath = normalizePathLike(path);
2458+
const statOptions = normalizeStatOptions(options);
23922459
try {
23932460
const statJson = await _fsAsync.stat.apply(void 0, [rawPath]);
2394-
return new Stats(decodeBridgeJson(statJson));
2461+
return new Stats(decodeBridgeJson(statJson), statOptions);
23952462
} catch (err) {
23962463
if (bridgeErrorCode(err) === "ENOENT") {
23972464
throw createFsError(
@@ -2404,10 +2471,11 @@ async function fsStatAsync(path) {
24042471
throw err;
24052472
}
24062473
}
2407-
async function fsLstatAsync(path) {
2474+
async function fsLstatAsync(path, options) {
24082475
const pathStr = normalizePathLike(path);
2476+
const statOptions = normalizeStatOptions(options);
24092477
const statJson = await _fsAsync.lstat.apply(void 0, [pathStr]);
2410-
return new Stats(decodeBridgeJson(statJson));
2478+
return new Stats(decodeBridgeJson(statJson), statOptions);
24112479
}
24122480
async function fsUnlinkAsync(path) {
24132481
const pathStr = normalizePathLike(path);
@@ -2768,9 +2836,10 @@ var fs = {
27682836
}
27692837
return _fs.exists.applySyncPromise(void 0, [pathStr]);
27702838
},
2771-
statSync(path, _options) {
2839+
statSync(path, options) {
27722840
const rawPath = normalizePathLike(path);
27732841
const pathStr = rawPath;
2842+
const statOptions = normalizeStatOptions(options);
27742843
let statJson;
27752844
try {
27762845
statJson = _fs.stat.applySyncPromise(void 0, [pathStr]);
@@ -2786,13 +2855,14 @@ var fs = {
27862855
throw err;
27872856
}
27882857
const stat = decodeBridgeJson(statJson);
2789-
return new Stats(stat);
2858+
return new Stats(stat, statOptions);
27902859
},
2791-
lstatSync(path, _options) {
2860+
lstatSync(path, options) {
27922861
const pathStr = normalizePathLike(path);
2862+
const statOptions = normalizeStatOptions(options);
27932863
const statJson = bridgeCall(() => _fs.lstat.applySyncPromise(void 0, [pathStr]), "lstat", pathStr);
27942864
const stat = decodeBridgeJson(statJson);
2795-
return new Stats(stat);
2865+
return new Stats(stat, statOptions);
27962866
},
27972867
unlinkSync(path) {
27982868
const pathStr = normalizePathLike(path);
@@ -2972,8 +3042,9 @@ var fs = {
29723042
throw e;
29733043
}
29743044
},
2975-
fstatSync(fd) {
3045+
fstatSync(fd, options) {
29763046
normalizeFdInteger(fd);
3047+
const statOptions = normalizeStatOptions(options);
29773048
let raw;
29783049
try {
29793050
raw = _fdFstat.applySyncPromise(void 0, [fd]);
@@ -2982,7 +3053,7 @@ var fs = {
29823053
if (msg.includes("EBADF")) throw createFsError("EBADF", "EBADF: bad file descriptor, fstat", "fstat");
29833054
throw e;
29843055
}
2985-
return new Stats(decodeBridgeJson(raw));
3056+
return new Stats(decodeBridgeJson(raw), statOptions);
29863057
},
29873058
ftruncateSync(fd, len) {
29883059
normalizeFdInteger(fd);
@@ -3331,22 +3402,23 @@ var fs = {
33313402
}
33323403
queueMicrotask(() => callback(Boolean(tryNormalizeExistsPath(path) && fs.existsSync(path))));
33333404
},
3334-
stat(path, callback) {
3335-
validateCallback(callback, "cb");
3405+
stat(path, optionsOrCallback, callback) {
3406+
const args = normalizeStatCallbackArgs(optionsOrCallback, callback);
33363407
normalizePathLike(path);
3337-
const cb = callback;
3408+
const cb = args.callback;
33383409
try {
3339-
const stats = fs.statSync(path);
3410+
const stats = fs.statSync(path, args.options);
33403411
queueMicrotask(() => cb(null, stats));
33413412
} catch (e) {
33423413
queueMicrotask(() => cb(e));
33433414
}
33443415
},
3345-
lstat(path, callback) {
3346-
if (callback) {
3347-
const cb = callback;
3416+
lstat(path, optionsOrCallback, callback) {
3417+
if (optionsOrCallback || callback) {
3418+
const args = normalizeStatCallbackArgs(optionsOrCallback, callback);
3419+
const cb = args.callback;
33483420
try {
3349-
const stats = fs.lstatSync(path);
3421+
const stats = fs.lstatSync(path, args.options);
33503422
queueMicrotask(() => cb(null, stats));
33513423
} catch (e) {
33523424
queueMicrotask(() => cb(e));
@@ -3611,12 +3683,13 @@ var fs = {
36113683
}
36123684
return totalBytesWritten;
36133685
},
3614-
fstat(fd, callback) {
3615-
if (callback) {
3686+
fstat(fd, optionsOrCallback, callback) {
3687+
if (optionsOrCallback || callback) {
3688+
const args = normalizeStatCallbackArgs(optionsOrCallback, callback);
36163689
try {
3617-
callback(null, fs.fstatSync(fd));
3690+
args.callback(null, fs.fstatSync(fd, args.options));
36183691
} catch (e) {
3619-
callback(e);
3692+
args.callback(e);
36203693
}
36213694
} else {
36223695
return Promise.resolve(fs.fstatSync(fd));
@@ -3723,11 +3796,11 @@ var fs = {
37233796
async rmdir(path) {
37243797
return fsRmdirAsync(path);
37253798
},
3726-
async stat(path) {
3727-
return fsStatAsync(path);
3799+
async stat(path, options) {
3800+
return fsStatAsync(path, options);
37283801
},
3729-
async lstat(path) {
3730-
return fsLstatAsync(path);
3802+
async lstat(path, options) {
3803+
return fsLstatAsync(path, options);
37313804
},
37323805
async unlink(path) {
37333806
return fsUnlinkAsync(path);

packages/core/src/state.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ import { fromGeneratedProcessSnapshotStatus } from "./protocol-maps.js";
55
export interface LiveGuestFilesystemStat {
66
mode: number;
77
size: number;
8+
size_u64: string;
89
blocks: number;
10+
blocks_u64: string;
911
dev: number;
12+
dev_u64: string;
1013
rdev: number;
14+
rdev_u64: string;
1115
is_directory: boolean;
1216
is_symbolic_link: boolean;
1317
atime_ms: number;
1418
mtime_ms: number;
1519
ctime_ms: number;
1620
birthtime_ms: number;
1721
ino: number;
22+
ino_u64: string;
1823
nlink: number;
1924
uid: number;
2025
gid: number;
@@ -46,10 +51,14 @@ export function fromGeneratedGuestFilesystemStat(
4651
): LiveGuestFilesystemStat {
4752
return {
4853
mode: stat.mode,
49-
size: bigIntToSafeNumber(stat.size, "guest filesystem stat size"),
50-
blocks: bigIntToSafeNumber(stat.blocks, "guest filesystem stat blocks"),
51-
dev: bigIntToSafeNumber(stat.dev, "guest filesystem stat dev"),
52-
rdev: bigIntToSafeNumber(stat.rdev, "guest filesystem stat rdev"),
54+
size: Number(stat.size),
55+
size_u64: stat.size.toString(),
56+
blocks: Number(stat.blocks),
57+
blocks_u64: stat.blocks.toString(),
58+
dev: Number(stat.dev),
59+
dev_u64: stat.dev.toString(),
60+
rdev: Number(stat.rdev),
61+
rdev_u64: stat.rdev.toString(),
5362
is_directory: stat.isDirectory,
5463
is_symbolic_link: stat.isSymbolicLink,
5564
atime_ms: bigIntToSafeNumber(stat.atimeMs, "guest filesystem stat atime"),
@@ -59,7 +68,8 @@ export function fromGeneratedGuestFilesystemStat(
5968
stat.birthtimeMs,
6069
"guest filesystem stat birthtime",
6170
),
62-
ino: bigIntToSafeNumber(stat.ino, "guest filesystem stat ino"),
71+
ino: Number(stat.ino),
72+
ino_u64: stat.ino.toString(),
6373
nlink: bigIntToSafeNumber(stat.nlink, "guest filesystem stat nlink"),
6474
uid: stat.uid,
6575
gid: stat.gid,

packages/core/tests/state.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,56 @@ describe("state conversion", () => {
2929
).toEqual({
3030
mode: 0o100644,
3131
size: 42,
32+
size_u64: "42",
3233
blocks: 1,
34+
blocks_u64: "1",
3335
dev: 2,
36+
dev_u64: "2",
3437
rdev: 0,
38+
rdev_u64: "0",
3539
is_directory: false,
3640
is_symbolic_link: false,
3741
atime_ms: 100,
3842
mtime_ms: 200,
3943
ctime_ms: 300,
4044
birthtime_ms: 400,
4145
ino: 10,
46+
ino_u64: "10",
4247
nlink: 1,
4348
uid: 1000,
4449
gid: 1000,
4550
});
4651
});
4752

53+
it("does not reject u64 stat identity fields above the safe integer range", () => {
54+
const unsafe = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
55+
56+
const stat = fromGeneratedGuestFilesystemStat({
57+
mode: 0o100644,
58+
size: unsafe,
59+
blocks: unsafe,
60+
dev: unsafe,
61+
rdev: unsafe,
62+
isDirectory: false,
63+
isSymbolicLink: false,
64+
atimeMs: 100n,
65+
mtimeMs: 200n,
66+
ctimeMs: 300n,
67+
birthtimeMs: 400n,
68+
ino: unsafe,
69+
nlink: 1n,
70+
uid: 1000,
71+
gid: 1000,
72+
});
73+
74+
expect(stat.dev_u64).toBe(unsafe.toString());
75+
expect(stat.rdev_u64).toBe(unsafe.toString());
76+
expect(stat.ino_u64).toBe(unsafe.toString());
77+
expect(stat.size_u64).toBe(unsafe.toString());
78+
expect(stat.blocks_u64).toBe(unsafe.toString());
79+
expect(() => JSON.stringify(stat)).not.toThrow();
80+
});
81+
4882
it("maps generated socket state entries to live socket entries", () => {
4983
expect(
5084
fromGeneratedSocketStateEntry({

0 commit comments

Comments
 (0)