@volter/tabnode 0.5.4 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{runtime-worker-DBtzjAkn.js → runtime-worker-iInsmIAS.js} +83 -13
- package/dist/index.d.ts +6 -2
- package/dist/index.mjs +122 -18
- package/dist/process-registry.d.ts +7 -0
- package/dist/process-tokens.d.ts +15 -2
- package/dist/virtual-fs.d.ts +25 -1
- package/package.json +1 -1
- package/src/index.ts +18 -4
- package/src/node-lib/binding/fs.ts +1 -1
- package/src/process-registry.ts +22 -2
- package/src/process-tokens.ts +30 -3
- package/src/shims/child_process.ts +4 -3
- package/src/virtual-fs.ts +77 -5
|
@@ -6641,6 +6641,14 @@ var asyncHooksShim$1 = /* @__PURE__ */ Object.freeze({
|
|
|
6641
6641
|
executionAsyncResource,
|
|
6642
6642
|
triggerAsyncId
|
|
6643
6643
|
});
|
|
6644
|
+
function describedIdentity(identity) {
|
|
6645
|
+
const { pid, ppid, argv, cwd, startedAt } = identity;
|
|
6646
|
+
const described = { pid, ppid };
|
|
6647
|
+
if (Array.isArray(argv) && argv.length <= 4096 && argv.every((arg) => typeof arg === "string")) described.argv = Object.freeze([...argv]);
|
|
6648
|
+
if (typeof cwd === "string" && cwd.startsWith("/")) described.cwd = cwd;
|
|
6649
|
+
if (typeof startedAt === "number" && Number.isFinite(startedAt)) described.startedAt = startedAt;
|
|
6650
|
+
return Object.freeze(described);
|
|
6651
|
+
}
|
|
6644
6652
|
function createProcessRegistryOwner() {
|
|
6645
6653
|
let nextPid2 = 1e3 + Math.floor(Math.random() * 3e4);
|
|
6646
6654
|
const live = /* @__PURE__ */ new Map();
|
|
@@ -6697,7 +6705,7 @@ function createProcessRegistryOwner() {
|
|
|
6697
6705
|
if (identity.ppid !== 0 && !Array.from(runs2.values()).some((parent) => parent.pid === identity.ppid)) {
|
|
6698
6706
|
throw new Error("Parent process is not owned by this realm.");
|
|
6699
6707
|
}
|
|
6700
|
-
const entry =
|
|
6708
|
+
const entry = describedIdentity(identity);
|
|
6701
6709
|
runs2.set(token, entry);
|
|
6702
6710
|
live.set(entry.pid, entry);
|
|
6703
6711
|
receivers.set(entry.pid, receiver);
|
|
@@ -6739,6 +6747,10 @@ function createProcessRegistryOwner() {
|
|
|
6739
6747
|
};
|
|
6740
6748
|
scopes.set(scope, { allocated, runs: runs2, active: active2 });
|
|
6741
6749
|
return scope;
|
|
6750
|
+
},
|
|
6751
|
+
/** Every live process of the container, in pid order: what `/proc` lists. */
|
|
6752
|
+
table() {
|
|
6753
|
+
return [...live.values()].sort((a, b) => a.pid - b.pid);
|
|
6742
6754
|
}
|
|
6743
6755
|
};
|
|
6744
6756
|
}
|
|
@@ -6779,8 +6791,8 @@ const pidsOfRuns = /* @__PURE__ */ new Map();
|
|
|
6779
6791
|
function mintPid() {
|
|
6780
6792
|
return processRegistry.allocate();
|
|
6781
6793
|
}
|
|
6782
|
-
function setRunPid(token, pid, ppid) {
|
|
6783
|
-
processRegistry.publish(token, { pid, ppid });
|
|
6794
|
+
function setRunPid(token, pid, ppid, started) {
|
|
6795
|
+
processRegistry.publish(token, { pid, ppid, ...started, startedAt: Date.now() });
|
|
6784
6796
|
pidsOfRuns.set(token, { pid, ppid });
|
|
6785
6797
|
}
|
|
6786
6798
|
function runPid(token) {
|
|
@@ -9657,7 +9669,7 @@ function createUtilBinding(require2) {
|
|
|
9657
9669
|
const bufferModule = lazyModule("buffer");
|
|
9658
9670
|
const Buffer$9 = lazyExport("buffer", "Buffer");
|
|
9659
9671
|
function createNodeError$1(code2, syscall, path2) {
|
|
9660
|
-
const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP"];
|
|
9672
|
+
const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP", "EROFS"];
|
|
9661
9673
|
if (known.includes(code2)) return createNodeError(code2, syscall, path2);
|
|
9662
9674
|
return Object.assign(new Error(`${code2}: ${syscall} '${path2}'`), { code: code2, syscall, path: path2, errno: -9 });
|
|
9663
9675
|
}
|
|
@@ -27571,14 +27583,16 @@ function createNodeError(code2, syscall, path2, message) {
|
|
|
27571
27583
|
ENOTDIR: -20,
|
|
27572
27584
|
EISDIR: -21,
|
|
27573
27585
|
EEXIST: -17,
|
|
27574
|
-
ENOTEMPTY: -39
|
|
27586
|
+
ENOTEMPTY: -39,
|
|
27587
|
+
EROFS: -30
|
|
27575
27588
|
};
|
|
27576
27589
|
const messages2 = {
|
|
27577
27590
|
ENOENT: "no such file or directory",
|
|
27578
27591
|
ENOTDIR: "not a directory",
|
|
27579
27592
|
EISDIR: "is a directory",
|
|
27580
27593
|
EEXIST: "file already exists",
|
|
27581
|
-
ENOTEMPTY: "directory not empty"
|
|
27594
|
+
ENOTEMPTY: "directory not empty",
|
|
27595
|
+
EROFS: "read-only file system"
|
|
27582
27596
|
};
|
|
27583
27597
|
const err2 = new Error(
|
|
27584
27598
|
`${code2}: ${messages2[code2]}, ${syscall} '${path2}'`
|
|
@@ -27637,6 +27651,7 @@ class VirtualFS {
|
|
|
27637
27651
|
return { files };
|
|
27638
27652
|
}
|
|
27639
27653
|
serializeNode(path2, node, files) {
|
|
27654
|
+
if (node.mount) return;
|
|
27640
27655
|
if (node.type === "file") {
|
|
27641
27656
|
let content = "";
|
|
27642
27657
|
if (node.content && node.content.length > 0) {
|
|
@@ -27692,6 +27707,7 @@ class VirtualFS {
|
|
|
27692
27707
|
*/
|
|
27693
27708
|
writeFileSyncInternal(path2, data2, emitEvent) {
|
|
27694
27709
|
const normalized = this.normalizePath(path2);
|
|
27710
|
+
this.assertWritable(normalized, "open");
|
|
27695
27711
|
const parentPath = this.getParentPath(normalized);
|
|
27696
27712
|
const basename = this.getBasename(normalized);
|
|
27697
27713
|
if (!basename) {
|
|
@@ -27767,11 +27783,12 @@ class VirtualFS {
|
|
|
27767
27783
|
if (depth > 40) throw createNodeError("ELOOP", "stat", path2);
|
|
27768
27784
|
const segments = this.getPathSegments(path2);
|
|
27769
27785
|
let current3 = this.root;
|
|
27786
|
+
let mounted;
|
|
27770
27787
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
27771
|
-
if (current3.type !== "directory" || !current3.children) {
|
|
27788
|
+
if (current3.type !== "directory" || !mounted && !current3.children) {
|
|
27772
27789
|
return void 0;
|
|
27773
27790
|
}
|
|
27774
|
-
const child = current3.children.get(segments[index2]);
|
|
27791
|
+
const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3.children.get(segments[index2]);
|
|
27775
27792
|
if (!child) {
|
|
27776
27793
|
return void 0;
|
|
27777
27794
|
}
|
|
@@ -27781,16 +27798,53 @@ class VirtualFS {
|
|
|
27781
27798
|
const target = this.__substrateLinkTarget(linkPath, child.target);
|
|
27782
27799
|
return this.__substrateNode(rest ? target + "/" + rest : target, follow, depth + 1);
|
|
27783
27800
|
}
|
|
27784
|
-
|
|
27801
|
+
if (!mounted && child.mount) {
|
|
27802
|
+
mounted = { tree: child.mount, at: index2 };
|
|
27803
|
+
current3 = child.mount.node("/") ?? child;
|
|
27804
|
+
} else {
|
|
27805
|
+
current3 = child;
|
|
27806
|
+
}
|
|
27785
27807
|
}
|
|
27786
27808
|
return current3;
|
|
27787
27809
|
}
|
|
27810
|
+
/**
|
|
27811
|
+
* Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
|
|
27812
|
+
* below it are the tree's answers, writes fail with `EROFS`, and a snapshot
|
|
27813
|
+
* leaves it out. Answers the unmount.
|
|
27814
|
+
*/
|
|
27815
|
+
mount(path2, tree2) {
|
|
27816
|
+
var _a3;
|
|
27817
|
+
const normalized = this.normalizePath(path2);
|
|
27818
|
+
const basename = this.getBasename(normalized);
|
|
27819
|
+
if (!basename) throw createNodeError("EINVAL", "mount", path2);
|
|
27820
|
+
const parent = this.ensureDirectory(this.getParentPath(normalized));
|
|
27821
|
+
const existing = parent.children.get(basename);
|
|
27822
|
+
if (existing && (existing.type !== "directory" || existing.mount || ((_a3 = existing.children) == null ? void 0 : _a3.size))) {
|
|
27823
|
+
throw createNodeError("EEXIST", "mount", path2);
|
|
27824
|
+
}
|
|
27825
|
+
const node = { type: "directory", children: /* @__PURE__ */ new Map(), mtime: Date.now(), mode: 365, mount: tree2 };
|
|
27826
|
+
parent.children.set(basename, node);
|
|
27827
|
+
return () => {
|
|
27828
|
+
if (parent.children.get(basename) === node) parent.children.delete(basename);
|
|
27829
|
+
};
|
|
27830
|
+
}
|
|
27831
|
+
/** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
|
|
27832
|
+
assertWritable(path2, syscall) {
|
|
27833
|
+
var _a3;
|
|
27834
|
+
let current3 = this.root;
|
|
27835
|
+
for (const segment of this.getPathSegments(path2)) {
|
|
27836
|
+
current3 = (_a3 = current3 == null ? void 0 : current3.children) == null ? void 0 : _a3.get(segment);
|
|
27837
|
+
if (!current3) return;
|
|
27838
|
+
if (current3.mount) throw createNodeError("EROFS", syscall, path2);
|
|
27839
|
+
}
|
|
27840
|
+
}
|
|
27788
27841
|
/** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
|
|
27789
27842
|
__substrateLinkTarget(linkPath, target) {
|
|
27790
27843
|
return this.normalizePath(target.startsWith("/") ? target : this.getParentPath(linkPath) + "/" + target);
|
|
27791
27844
|
}
|
|
27792
27845
|
symlinkSync(target, path2) {
|
|
27793
27846
|
const normalized = this.normalizePath(path2);
|
|
27847
|
+
this.assertWritable(normalized, "symlink");
|
|
27794
27848
|
const parentPath = this.getParentPath(normalized);
|
|
27795
27849
|
const basename = this.getBasename(normalized);
|
|
27796
27850
|
const parent = this.getNode(parentPath);
|
|
@@ -27851,11 +27905,13 @@ class VirtualFS {
|
|
|
27851
27905
|
* Get stats for path
|
|
27852
27906
|
*/
|
|
27853
27907
|
chmodSync(path2, mode) {
|
|
27908
|
+
this.assertWritable(path2, "chmod");
|
|
27854
27909
|
const node = this.getNode(path2);
|
|
27855
27910
|
if (!node) throw createNodeError("ENOENT", "chmod", path2);
|
|
27856
27911
|
node.mode = mode & 4095;
|
|
27857
27912
|
}
|
|
27858
27913
|
utimesSync(path2, atime, mtime) {
|
|
27914
|
+
this.assertWritable(path2, "utime");
|
|
27859
27915
|
const node = this.getNode(path2);
|
|
27860
27916
|
if (!node) throw createNodeError("ENOENT", "utimes", path2);
|
|
27861
27917
|
node.atime = atime instanceof Date ? atime.getTime() : atime * 1e3;
|
|
@@ -27961,7 +28017,10 @@ class VirtualFS {
|
|
|
27961
28017
|
* Create directory, optionally with recursive parent creation
|
|
27962
28018
|
*/
|
|
27963
28019
|
mkdirSync(path2, options) {
|
|
28020
|
+
var _a3;
|
|
27964
28021
|
const normalized = this.normalizePath(path2);
|
|
28022
|
+
if ((options == null ? void 0 : options.recursive) && ((_a3 = this.getNode(normalized)) == null ? void 0 : _a3.type) === "directory") return;
|
|
28023
|
+
this.assertWritable(normalized, "mkdir");
|
|
27965
28024
|
if (options == null ? void 0 : options.recursive) {
|
|
27966
28025
|
this.ensureDirectory(normalized);
|
|
27967
28026
|
return;
|
|
@@ -28006,6 +28065,7 @@ class VirtualFS {
|
|
|
28006
28065
|
*/
|
|
28007
28066
|
unlinkSync(path2) {
|
|
28008
28067
|
const normalized = this.normalizePath(path2);
|
|
28068
|
+
this.assertWritable(normalized, "unlink");
|
|
28009
28069
|
const parentPath = this.getParentPath(normalized);
|
|
28010
28070
|
const basename = this.getBasename(normalized);
|
|
28011
28071
|
const parent = this.getNode(parentPath);
|
|
@@ -28028,6 +28088,7 @@ class VirtualFS {
|
|
|
28028
28088
|
*/
|
|
28029
28089
|
rmdirSync(path2) {
|
|
28030
28090
|
const normalized = this.normalizePath(path2);
|
|
28091
|
+
this.assertWritable(normalized, "rmdir");
|
|
28031
28092
|
const parentPath = this.getParentPath(normalized);
|
|
28032
28093
|
const basename = this.getBasename(normalized);
|
|
28033
28094
|
if (!basename) {
|
|
@@ -28056,6 +28117,8 @@ class VirtualFS {
|
|
|
28056
28117
|
renameSync(oldPath, newPath) {
|
|
28057
28118
|
const normalizedOld = this.normalizePath(oldPath);
|
|
28058
28119
|
const normalizedNew = this.normalizePath(newPath);
|
|
28120
|
+
this.assertWritable(normalizedOld, "rename");
|
|
28121
|
+
this.assertWritable(normalizedNew, "rename");
|
|
28059
28122
|
const oldParentPath = this.getParentPath(normalizedOld);
|
|
28060
28123
|
const oldBasename = this.getBasename(normalizedOld);
|
|
28061
28124
|
const newParentPath = this.getParentPath(normalizedNew);
|
|
@@ -28154,10 +28217,12 @@ class VirtualFS {
|
|
|
28154
28217
|
for (let hops = 0; hops <= 40; hops += 1) {
|
|
28155
28218
|
const segments = resolved.split("/").filter(Boolean);
|
|
28156
28219
|
let current3 = this.root;
|
|
28220
|
+
let mounted;
|
|
28157
28221
|
let linked = false;
|
|
28158
28222
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
28159
|
-
const child = current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
|
|
28223
|
+
const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
|
|
28160
28224
|
if (!child) throw createNodeError("ENOENT", "realpath", path2);
|
|
28225
|
+
if (!mounted && child.mount) mounted = { tree: child.mount, at: index2 };
|
|
28161
28226
|
if (child.type === "symlink") {
|
|
28162
28227
|
const linkPath = "/" + segments.slice(0, index2 + 1).join("/");
|
|
28163
28228
|
const rest = segments.slice(index2 + 1).join("/");
|
|
@@ -69753,7 +69818,7 @@ const _runStreams = /* @__PURE__ */ new Map();
|
|
|
69753
69818
|
async function runHostedNode(host, launch) {
|
|
69754
69819
|
var _a3;
|
|
69755
69820
|
const { token, streams } = launch;
|
|
69756
|
-
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
69821
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0, { argv: ["node", ...launch.argv], cwd: launch.cwd });
|
|
69757
69822
|
let input;
|
|
69758
69823
|
try {
|
|
69759
69824
|
(_a3 = streams == null ? void 0 : streams.signal) == null ? void 0 : _a3.throwIfAborted();
|
|
@@ -69989,7 +70054,7 @@ function initChildProcess(vfs2) {
|
|
|
69989
70054
|
...runPid(runToken) ? { pid: runPid(runToken).pid, ppid: runPid(runToken).ppid } : {}
|
|
69990
70055
|
});
|
|
69991
70056
|
const proc = runtime2.getProcess();
|
|
69992
|
-
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
|
|
70057
|
+
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid, { argv: ["node", ...args], cwd: ctx.cwd });
|
|
69993
70058
|
const releaseRun = runToken === null ? null : __recordRun(runToken, {
|
|
69994
70059
|
process: proc,
|
|
69995
70060
|
stdout: appendStdout,
|
|
@@ -70715,7 +70780,12 @@ function startChildRun(request) {
|
|
|
70715
70780
|
var _a3;
|
|
70716
70781
|
const token = `child-${__nextChildRun++}`;
|
|
70717
70782
|
const pid = mintPid();
|
|
70718
|
-
setRunPid(
|
|
70783
|
+
setRunPid(
|
|
70784
|
+
token,
|
|
70785
|
+
pid,
|
|
70786
|
+
((_a3 = runPid(__currentProcessToken() ?? __lastLaunchedToken)) == null ? void 0 : _a3.pid) ?? 0,
|
|
70787
|
+
{ argv: request.args.length ? request.args : [request.file], ...request.cwd ? { cwd: request.cwd } : {} }
|
|
70788
|
+
);
|
|
70719
70789
|
const controller = new AbortController();
|
|
70720
70790
|
const pendingStdin = [];
|
|
70721
70791
|
const admittedNode = nodeProcessHostInstalled();
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export { restoreHostGlobals, guestRealmInstalled } from './host-globals';
|
|
11
11
|
export { VirtualFS } from './virtual-fs';
|
|
12
|
-
export type { FSNode, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
|
|
12
|
+
export type { FSNode, MountedTree, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
|
|
13
13
|
export { Runtime, execute } from './runtime';
|
|
14
14
|
export type { Module, RuntimeOptions, RequireFunction } from './runtime';
|
|
15
15
|
export { createRuntime, WorkerRuntime } from './create-runtime';
|
|
@@ -46,7 +46,7 @@ import { ServerBridge } from './server-bridge';
|
|
|
46
46
|
import { type Socket as NetSocket } from './node-lib/net-module';
|
|
47
47
|
import { runPid, processByPid } from './process-tokens';
|
|
48
48
|
export { runPid, processByPid };
|
|
49
|
-
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope } from './process-tokens';
|
|
49
|
+
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope, ownerProcessTable } from './process-tokens';
|
|
50
50
|
export { installNodeProcessHost, nodeProcessHostInstalled } from './node-process-host';
|
|
51
51
|
export type { NodeProcessLaunch, NodeProcessHost } from './node-process-host';
|
|
52
52
|
export type { ProcessIdentity, ProcessRegistry, ProcessRegistryScope, InitialProcessRegistration } from './process-registry';
|
|
@@ -112,6 +112,10 @@ export declare function createContainer(options?: ContainerOptions): {
|
|
|
112
112
|
run: (command: string, options?: RunOptions) => Promise<RunResult>;
|
|
113
113
|
pendingTimers: (token: string) => number;
|
|
114
114
|
processPorts: (token: string) => number[];
|
|
115
|
+
/** The pid of the process listening on a port of this engine, where a guest process is: `/proc`'s socket owner. */
|
|
116
|
+
portPid: (port: number) => number | undefined;
|
|
117
|
+
/** Deliver a signal to the named run as another process's `kill(pid)` does; false when the run is gone. */
|
|
118
|
+
signalProcess: (token: string, signal: string) => boolean;
|
|
115
119
|
stopProcess: (token: string) => boolean;
|
|
116
120
|
/**
|
|
117
121
|
* Input for one run's guest, by its process token; without a token, the most
|
package/dist/index.mjs
CHANGED
|
@@ -6350,6 +6350,14 @@ const asyncHooksShim$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
|
|
|
6350
6350
|
executionAsyncResource,
|
|
6351
6351
|
triggerAsyncId
|
|
6352
6352
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
6353
|
+
function describedIdentity(identity) {
|
|
6354
|
+
const { pid, ppid, argv, cwd, startedAt } = identity;
|
|
6355
|
+
const described = { pid, ppid };
|
|
6356
|
+
if (Array.isArray(argv) && argv.length <= 4096 && argv.every((arg) => typeof arg === "string")) described.argv = Object.freeze([...argv]);
|
|
6357
|
+
if (typeof cwd === "string" && cwd.startsWith("/")) described.cwd = cwd;
|
|
6358
|
+
if (typeof startedAt === "number" && Number.isFinite(startedAt)) described.startedAt = startedAt;
|
|
6359
|
+
return Object.freeze(described);
|
|
6360
|
+
}
|
|
6353
6361
|
function createProcessRegistryOwner() {
|
|
6354
6362
|
let nextPid2 = 1e3 + Math.floor(Math.random() * 3e4);
|
|
6355
6363
|
const live = /* @__PURE__ */ new Map();
|
|
@@ -6406,7 +6414,7 @@ function createProcessRegistryOwner() {
|
|
|
6406
6414
|
if (identity.ppid !== 0 && !Array.from(runs2.values()).some((parent) => parent.pid === identity.ppid)) {
|
|
6407
6415
|
throw new Error("Parent process is not owned by this realm.");
|
|
6408
6416
|
}
|
|
6409
|
-
const entry =
|
|
6417
|
+
const entry = describedIdentity(identity);
|
|
6410
6418
|
runs2.set(token, entry);
|
|
6411
6419
|
live.set(entry.pid, entry);
|
|
6412
6420
|
receivers.set(entry.pid, receiver);
|
|
@@ -6448,6 +6456,10 @@ function createProcessRegistryOwner() {
|
|
|
6448
6456
|
};
|
|
6449
6457
|
scopes2.set(scope, { allocated: allocated2, runs: runs2, active: active2 });
|
|
6450
6458
|
return scope;
|
|
6459
|
+
},
|
|
6460
|
+
/** Every live process of the container, in pid order: what `/proc` lists. */
|
|
6461
|
+
table() {
|
|
6462
|
+
return [...live.values()].sort((a, b) => a.pid - b.pid);
|
|
6451
6463
|
}
|
|
6452
6464
|
};
|
|
6453
6465
|
}
|
|
@@ -6485,6 +6497,18 @@ function __stopOwnedProcess(token) {
|
|
|
6485
6497
|
var _a2;
|
|
6486
6498
|
(_a2 = runs.get(token)) == null ? void 0 : _a2.stopTimers();
|
|
6487
6499
|
}
|
|
6500
|
+
function __signalOwnedProcess(token, signal) {
|
|
6501
|
+
const run = runs.get(token);
|
|
6502
|
+
if (!run) return false;
|
|
6503
|
+
const guest = run.process;
|
|
6504
|
+
try {
|
|
6505
|
+
enterRun(token, () => guest.kill(guest.pid, signal));
|
|
6506
|
+
} catch (error) {
|
|
6507
|
+
if (error instanceof Error && error.message.startsWith("Process exited with code")) return true;
|
|
6508
|
+
return false;
|
|
6509
|
+
}
|
|
6510
|
+
return true;
|
|
6511
|
+
}
|
|
6488
6512
|
const processRegistryOwner = createProcessRegistryOwner();
|
|
6489
6513
|
let processRegistry = processRegistryOwner.createScope();
|
|
6490
6514
|
let registryInstalled = false;
|
|
@@ -6494,6 +6518,10 @@ function createProcessRegistryScope() {
|
|
|
6494
6518
|
if (registryInstalled) throw new Error("Only the container owner can create process registry scopes.");
|
|
6495
6519
|
return processRegistryOwner.createScope();
|
|
6496
6520
|
}
|
|
6521
|
+
function ownerProcessTable() {
|
|
6522
|
+
if (registryInstalled) throw new Error("This realm is a process registry client, not the container owner.");
|
|
6523
|
+
return processRegistryOwner.table();
|
|
6524
|
+
}
|
|
6497
6525
|
function ownerProcessRegistryScope() {
|
|
6498
6526
|
if (registryInstalled) throw new Error("This realm is a process registry client, not the container owner.");
|
|
6499
6527
|
return processRegistry;
|
|
@@ -6513,9 +6541,9 @@ function mintPid() {
|
|
|
6513
6541
|
registryUsed = true;
|
|
6514
6542
|
return processRegistry.allocate();
|
|
6515
6543
|
}
|
|
6516
|
-
function setRunPid(token, pid, ppid) {
|
|
6544
|
+
function setRunPid(token, pid, ppid, started2) {
|
|
6517
6545
|
registryUsed = true;
|
|
6518
|
-
processRegistry.publish(token, { pid, ppid });
|
|
6546
|
+
processRegistry.publish(token, { pid, ppid, ...started2, startedAt: Date.now() });
|
|
6519
6547
|
pidsOfRuns.set(token, { pid, ppid });
|
|
6520
6548
|
}
|
|
6521
6549
|
function runPid(token) {
|
|
@@ -9804,7 +9832,7 @@ function createUtilBinding(require2) {
|
|
|
9804
9832
|
const bufferModule = lazyModule("buffer");
|
|
9805
9833
|
const Buffer$9 = lazyExport("buffer", "Buffer");
|
|
9806
9834
|
function createNodeError$1(code2, syscall, path2) {
|
|
9807
|
-
const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP"];
|
|
9835
|
+
const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP", "EROFS"];
|
|
9808
9836
|
if (known.includes(code2)) return createNodeError(code2, syscall, path2);
|
|
9809
9837
|
return Object.assign(new Error(`${code2}: ${syscall} '${path2}'`), { code: code2, syscall, path: path2, errno: -9 });
|
|
9810
9838
|
}
|
|
@@ -17762,10 +17790,17 @@ var implementation$1 = function bind2(that) {
|
|
|
17762
17790
|
var implementation = implementation$1;
|
|
17763
17791
|
var functionBind = Function.prototype.bind || implementation;
|
|
17764
17792
|
var functionCall = Function.prototype.call;
|
|
17765
|
-
var functionApply
|
|
17793
|
+
var functionApply;
|
|
17794
|
+
var hasRequiredFunctionApply;
|
|
17795
|
+
function requireFunctionApply() {
|
|
17796
|
+
if (hasRequiredFunctionApply) return functionApply;
|
|
17797
|
+
hasRequiredFunctionApply = 1;
|
|
17798
|
+
functionApply = Function.prototype.apply;
|
|
17799
|
+
return functionApply;
|
|
17800
|
+
}
|
|
17766
17801
|
var reflectApply = typeof Reflect !== "undefined" && Reflect && Reflect.apply;
|
|
17767
17802
|
var bind$2 = functionBind;
|
|
17768
|
-
var $apply$1 =
|
|
17803
|
+
var $apply$1 = requireFunctionApply();
|
|
17769
17804
|
var $call$2 = functionCall;
|
|
17770
17805
|
var $reflectApply = reflectApply;
|
|
17771
17806
|
var actualApply = $reflectApply || bind$2.call($call$2, $apply$1);
|
|
@@ -17885,7 +17920,7 @@ var hasSymbols = requireHasSymbols()();
|
|
|
17885
17920
|
var getProto = requireGetProto();
|
|
17886
17921
|
var $ObjectGPO = requireObject_getPrototypeOf();
|
|
17887
17922
|
var $ReflectGPO = requireReflect_getPrototypeOf();
|
|
17888
|
-
var $apply =
|
|
17923
|
+
var $apply = requireFunctionApply();
|
|
17889
17924
|
var $call = functionCall;
|
|
17890
17925
|
var needsEval = {};
|
|
17891
17926
|
var TypedArray = typeof Uint8Array === "undefined" || !getProto ? undefined$1 : getProto(Uint8Array);
|
|
@@ -18526,7 +18561,7 @@ function requireApplyBind() {
|
|
|
18526
18561
|
if (hasRequiredApplyBind) return applyBind;
|
|
18527
18562
|
hasRequiredApplyBind = 1;
|
|
18528
18563
|
var bind3 = functionBind;
|
|
18529
|
-
var $apply2 =
|
|
18564
|
+
var $apply2 = requireFunctionApply();
|
|
18530
18565
|
var actualApply$1 = actualApply;
|
|
18531
18566
|
applyBind = function applyBind2() {
|
|
18532
18567
|
return actualApply$1(bind3, $apply2, arguments);
|
|
@@ -27737,14 +27772,16 @@ function createNodeError(code2, syscall, path2, message) {
|
|
|
27737
27772
|
ENOTDIR: -20,
|
|
27738
27773
|
EISDIR: -21,
|
|
27739
27774
|
EEXIST: -17,
|
|
27740
|
-
ENOTEMPTY: -39
|
|
27775
|
+
ENOTEMPTY: -39,
|
|
27776
|
+
EROFS: -30
|
|
27741
27777
|
};
|
|
27742
27778
|
const messages2 = {
|
|
27743
27779
|
ENOENT: "no such file or directory",
|
|
27744
27780
|
ENOTDIR: "not a directory",
|
|
27745
27781
|
EISDIR: "is a directory",
|
|
27746
27782
|
EEXIST: "file already exists",
|
|
27747
|
-
ENOTEMPTY: "directory not empty"
|
|
27783
|
+
ENOTEMPTY: "directory not empty",
|
|
27784
|
+
EROFS: "read-only file system"
|
|
27748
27785
|
};
|
|
27749
27786
|
const err2 = new Error(
|
|
27750
27787
|
`${code2}: ${messages2[code2]}, ${syscall} '${path2}'`
|
|
@@ -27803,6 +27840,7 @@ class VirtualFS {
|
|
|
27803
27840
|
return { files };
|
|
27804
27841
|
}
|
|
27805
27842
|
serializeNode(path2, node, files) {
|
|
27843
|
+
if (node.mount) return;
|
|
27806
27844
|
if (node.type === "file") {
|
|
27807
27845
|
let content = "";
|
|
27808
27846
|
if (node.content && node.content.length > 0) {
|
|
@@ -27858,6 +27896,7 @@ class VirtualFS {
|
|
|
27858
27896
|
*/
|
|
27859
27897
|
writeFileSyncInternal(path2, data2, emitEvent) {
|
|
27860
27898
|
const normalized = this.normalizePath(path2);
|
|
27899
|
+
this.assertWritable(normalized, "open");
|
|
27861
27900
|
const parentPath = this.getParentPath(normalized);
|
|
27862
27901
|
const basename2 = this.getBasename(normalized);
|
|
27863
27902
|
if (!basename2) {
|
|
@@ -27933,11 +27972,12 @@ class VirtualFS {
|
|
|
27933
27972
|
if (depth > 40) throw createNodeError("ELOOP", "stat", path2);
|
|
27934
27973
|
const segments = this.getPathSegments(path2);
|
|
27935
27974
|
let current3 = this.root;
|
|
27975
|
+
let mounted;
|
|
27936
27976
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
27937
|
-
if (current3.type !== "directory" || !current3.children) {
|
|
27977
|
+
if (current3.type !== "directory" || !mounted && !current3.children) {
|
|
27938
27978
|
return void 0;
|
|
27939
27979
|
}
|
|
27940
|
-
const child = current3.children.get(segments[index2]);
|
|
27980
|
+
const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3.children.get(segments[index2]);
|
|
27941
27981
|
if (!child) {
|
|
27942
27982
|
return void 0;
|
|
27943
27983
|
}
|
|
@@ -27947,16 +27987,53 @@ class VirtualFS {
|
|
|
27947
27987
|
const target = this.__substrateLinkTarget(linkPath, child.target);
|
|
27948
27988
|
return this.__substrateNode(rest ? target + "/" + rest : target, follow, depth + 1);
|
|
27949
27989
|
}
|
|
27950
|
-
|
|
27990
|
+
if (!mounted && child.mount) {
|
|
27991
|
+
mounted = { tree: child.mount, at: index2 };
|
|
27992
|
+
current3 = child.mount.node("/") ?? child;
|
|
27993
|
+
} else {
|
|
27994
|
+
current3 = child;
|
|
27995
|
+
}
|
|
27951
27996
|
}
|
|
27952
27997
|
return current3;
|
|
27953
27998
|
}
|
|
27999
|
+
/**
|
|
28000
|
+
* Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
|
|
28001
|
+
* below it are the tree's answers, writes fail with `EROFS`, and a snapshot
|
|
28002
|
+
* leaves it out. Answers the unmount.
|
|
28003
|
+
*/
|
|
28004
|
+
mount(path2, tree2) {
|
|
28005
|
+
var _a2;
|
|
28006
|
+
const normalized = this.normalizePath(path2);
|
|
28007
|
+
const basename2 = this.getBasename(normalized);
|
|
28008
|
+
if (!basename2) throw createNodeError("EINVAL", "mount", path2);
|
|
28009
|
+
const parent = this.ensureDirectory(this.getParentPath(normalized));
|
|
28010
|
+
const existing = parent.children.get(basename2);
|
|
28011
|
+
if (existing && (existing.type !== "directory" || existing.mount || ((_a2 = existing.children) == null ? void 0 : _a2.size))) {
|
|
28012
|
+
throw createNodeError("EEXIST", "mount", path2);
|
|
28013
|
+
}
|
|
28014
|
+
const node = { type: "directory", children: /* @__PURE__ */ new Map(), mtime: Date.now(), mode: 365, mount: tree2 };
|
|
28015
|
+
parent.children.set(basename2, node);
|
|
28016
|
+
return () => {
|
|
28017
|
+
if (parent.children.get(basename2) === node) parent.children.delete(basename2);
|
|
28018
|
+
};
|
|
28019
|
+
}
|
|
28020
|
+
/** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
|
|
28021
|
+
assertWritable(path2, syscall) {
|
|
28022
|
+
var _a2;
|
|
28023
|
+
let current3 = this.root;
|
|
28024
|
+
for (const segment of this.getPathSegments(path2)) {
|
|
28025
|
+
current3 = (_a2 = current3 == null ? void 0 : current3.children) == null ? void 0 : _a2.get(segment);
|
|
28026
|
+
if (!current3) return;
|
|
28027
|
+
if (current3.mount) throw createNodeError("EROFS", syscall, path2);
|
|
28028
|
+
}
|
|
28029
|
+
}
|
|
27954
28030
|
/** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
|
|
27955
28031
|
__substrateLinkTarget(linkPath, target) {
|
|
27956
28032
|
return this.normalizePath(target.startsWith("/") ? target : this.getParentPath(linkPath) + "/" + target);
|
|
27957
28033
|
}
|
|
27958
28034
|
symlinkSync(target, path2) {
|
|
27959
28035
|
const normalized = this.normalizePath(path2);
|
|
28036
|
+
this.assertWritable(normalized, "symlink");
|
|
27960
28037
|
const parentPath = this.getParentPath(normalized);
|
|
27961
28038
|
const basename2 = this.getBasename(normalized);
|
|
27962
28039
|
const parent = this.getNode(parentPath);
|
|
@@ -28017,11 +28094,13 @@ class VirtualFS {
|
|
|
28017
28094
|
* Get stats for path
|
|
28018
28095
|
*/
|
|
28019
28096
|
chmodSync(path2, mode) {
|
|
28097
|
+
this.assertWritable(path2, "chmod");
|
|
28020
28098
|
const node = this.getNode(path2);
|
|
28021
28099
|
if (!node) throw createNodeError("ENOENT", "chmod", path2);
|
|
28022
28100
|
node.mode = mode & 4095;
|
|
28023
28101
|
}
|
|
28024
28102
|
utimesSync(path2, atime, mtime) {
|
|
28103
|
+
this.assertWritable(path2, "utime");
|
|
28025
28104
|
const node = this.getNode(path2);
|
|
28026
28105
|
if (!node) throw createNodeError("ENOENT", "utimes", path2);
|
|
28027
28106
|
node.atime = atime instanceof Date ? atime.getTime() : atime * 1e3;
|
|
@@ -28127,7 +28206,10 @@ class VirtualFS {
|
|
|
28127
28206
|
* Create directory, optionally with recursive parent creation
|
|
28128
28207
|
*/
|
|
28129
28208
|
mkdirSync(path2, options) {
|
|
28209
|
+
var _a2;
|
|
28130
28210
|
const normalized = this.normalizePath(path2);
|
|
28211
|
+
if ((options == null ? void 0 : options.recursive) && ((_a2 = this.getNode(normalized)) == null ? void 0 : _a2.type) === "directory") return;
|
|
28212
|
+
this.assertWritable(normalized, "mkdir");
|
|
28131
28213
|
if (options == null ? void 0 : options.recursive) {
|
|
28132
28214
|
this.ensureDirectory(normalized);
|
|
28133
28215
|
return;
|
|
@@ -28172,6 +28254,7 @@ class VirtualFS {
|
|
|
28172
28254
|
*/
|
|
28173
28255
|
unlinkSync(path2) {
|
|
28174
28256
|
const normalized = this.normalizePath(path2);
|
|
28257
|
+
this.assertWritable(normalized, "unlink");
|
|
28175
28258
|
const parentPath = this.getParentPath(normalized);
|
|
28176
28259
|
const basename2 = this.getBasename(normalized);
|
|
28177
28260
|
const parent = this.getNode(parentPath);
|
|
@@ -28194,6 +28277,7 @@ class VirtualFS {
|
|
|
28194
28277
|
*/
|
|
28195
28278
|
rmdirSync(path2) {
|
|
28196
28279
|
const normalized = this.normalizePath(path2);
|
|
28280
|
+
this.assertWritable(normalized, "rmdir");
|
|
28197
28281
|
const parentPath = this.getParentPath(normalized);
|
|
28198
28282
|
const basename2 = this.getBasename(normalized);
|
|
28199
28283
|
if (!basename2) {
|
|
@@ -28222,6 +28306,8 @@ class VirtualFS {
|
|
|
28222
28306
|
renameSync(oldPath, newPath) {
|
|
28223
28307
|
const normalizedOld = this.normalizePath(oldPath);
|
|
28224
28308
|
const normalizedNew = this.normalizePath(newPath);
|
|
28309
|
+
this.assertWritable(normalizedOld, "rename");
|
|
28310
|
+
this.assertWritable(normalizedNew, "rename");
|
|
28225
28311
|
const oldParentPath = this.getParentPath(normalizedOld);
|
|
28226
28312
|
const oldBasename = this.getBasename(normalizedOld);
|
|
28227
28313
|
const newParentPath = this.getParentPath(normalizedNew);
|
|
@@ -28320,10 +28406,12 @@ class VirtualFS {
|
|
|
28320
28406
|
for (let hops = 0; hops <= 40; hops += 1) {
|
|
28321
28407
|
const segments = resolved.split("/").filter(Boolean);
|
|
28322
28408
|
let current3 = this.root;
|
|
28409
|
+
let mounted;
|
|
28323
28410
|
let linked = false;
|
|
28324
28411
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
28325
|
-
const child = current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
|
|
28412
|
+
const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
|
|
28326
28413
|
if (!child) throw createNodeError("ENOENT", "realpath", path2);
|
|
28414
|
+
if (!mounted && child.mount) mounted = { tree: child.mount, at: index2 };
|
|
28327
28415
|
if (child.type === "symlink") {
|
|
28328
28416
|
const linkPath = "/" + segments.slice(0, index2 + 1).join("/");
|
|
28329
28417
|
const rest = segments.slice(index2 + 1).join("/");
|
|
@@ -29379,7 +29467,7 @@ const _runStreams = /* @__PURE__ */ new Map();
|
|
|
29379
29467
|
async function runHostedNode(host2, launch) {
|
|
29380
29468
|
var _a2;
|
|
29381
29469
|
const { token, streams } = launch;
|
|
29382
|
-
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
29470
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0, { argv: ["node", ...launch.argv], cwd: launch.cwd });
|
|
29383
29471
|
let input;
|
|
29384
29472
|
try {
|
|
29385
29473
|
(_a2 = streams == null ? void 0 : streams.signal) == null ? void 0 : _a2.throwIfAborted();
|
|
@@ -29638,7 +29726,7 @@ function initChildProcess(vfs2) {
|
|
|
29638
29726
|
...runPid(runToken) ? { pid: runPid(runToken).pid, ppid: runPid(runToken).ppid } : {}
|
|
29639
29727
|
});
|
|
29640
29728
|
const proc = runtime.getProcess();
|
|
29641
|
-
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
|
|
29729
|
+
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid, { argv: ["node", ...args], cwd: ctx.cwd });
|
|
29642
29730
|
const releaseRun = runToken === null ? null : __recordRun(runToken, {
|
|
29643
29731
|
process: proc,
|
|
29644
29732
|
stdout: appendStdout,
|
|
@@ -30384,7 +30472,12 @@ function startChildRun(request) {
|
|
|
30384
30472
|
var _a2;
|
|
30385
30473
|
const token = `child-${__nextChildRun++}`;
|
|
30386
30474
|
const pid = mintPid();
|
|
30387
|
-
setRunPid(
|
|
30475
|
+
setRunPid(
|
|
30476
|
+
token,
|
|
30477
|
+
pid,
|
|
30478
|
+
((_a2 = runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)) == null ? void 0 : _a2.pid) ?? 0,
|
|
30479
|
+
{ argv: request.args.length ? request.args : [request.file], ...request.cwd ? { cwd: request.cwd } : {} }
|
|
30480
|
+
);
|
|
30388
30481
|
const controller = new AbortController();
|
|
30389
30482
|
const pendingStdin = [];
|
|
30390
30483
|
const admittedNode = nodeProcessHostInstalled() && engineProgramFor(request.file, request.cwd) === "node";
|
|
@@ -35562,7 +35655,7 @@ class WorkerRuntime {
|
|
|
35562
35655
|
this.worker = new Worker(
|
|
35563
35656
|
new URL(
|
|
35564
35657
|
/* @vite-ignore */
|
|
35565
|
-
"/assets/runtime-worker-
|
|
35658
|
+
"/assets/runtime-worker-iInsmIAS.js",
|
|
35566
35659
|
import.meta.url
|
|
35567
35660
|
),
|
|
35568
35661
|
{ type: "module" }
|
|
@@ -38278,6 +38371,16 @@ function createContainer(options) {
|
|
|
38278
38371
|
},
|
|
38279
38372
|
/** The ports the servers the named run opened are listening on. */
|
|
38280
38373
|
processPorts: (token) => __ownedServerPorts(token),
|
|
38374
|
+
portPid: (port) => {
|
|
38375
|
+
var _a2;
|
|
38376
|
+
const listener = listenerOnPort(port);
|
|
38377
|
+
return listener ? (_a2 = runPid(ownerOf(listener))) == null ? void 0 : _a2.pid : void 0;
|
|
38378
|
+
},
|
|
38379
|
+
/**
|
|
38380
|
+
* Deliver a signal to the named run as another process's `kill(pid)` does:
|
|
38381
|
+
* the guest's listeners for it run, else its default action ends the run.
|
|
38382
|
+
*/
|
|
38383
|
+
signalProcess: (token, signal) => __signalOwnedProcess(token, signal),
|
|
38281
38384
|
/** End the named run: its timers stop and its servers are released. */
|
|
38282
38385
|
stopProcess: (token) => {
|
|
38283
38386
|
const known = __runFor(token) !== void 0 || __ownedServerPorts(token).length > 0;
|
|
@@ -38350,6 +38453,7 @@ export {
|
|
|
38350
38453
|
nodeProcessHostInstalled,
|
|
38351
38454
|
index as npm,
|
|
38352
38455
|
ownerProcessRegistryScope,
|
|
38456
|
+
ownerProcessTable,
|
|
38353
38457
|
path,
|
|
38354
38458
|
perfHooksShim as perf_hooks,
|
|
38355
38459
|
processByPid,
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
export interface ProcessIdentity {
|
|
3
3
|
readonly pid: number;
|
|
4
4
|
readonly ppid: number;
|
|
5
|
+
/** What the process was started as, where its starter said: `/proc/<pid>/cmdline`. */
|
|
6
|
+
readonly argv?: readonly string[];
|
|
7
|
+
/** The directory it was started in: `/proc/<pid>/cwd`. */
|
|
8
|
+
readonly cwd?: string;
|
|
9
|
+
/** When it started, in milliseconds since the epoch. */
|
|
10
|
+
readonly startedAt?: number;
|
|
5
11
|
}
|
|
6
12
|
/** A run already admitted by the container owner into this realm's scope. */
|
|
7
13
|
export interface InitialProcessRegistration {
|
|
@@ -36,4 +42,5 @@ export interface ProcessRegistryScope extends ProcessRegistry {
|
|
|
36
42
|
*/
|
|
37
43
|
export declare function createProcessRegistryOwner(): {
|
|
38
44
|
createScope(): ProcessRegistryScope;
|
|
45
|
+
table(): ProcessIdentity[];
|
|
39
46
|
};
|
package/dist/process-tokens.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* command gives its process has it removed.
|
|
16
16
|
*/
|
|
17
17
|
import type { Process } from './shims/process';
|
|
18
|
-
import { type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
|
|
18
|
+
import { type ProcessIdentity, type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
|
|
19
19
|
/** Whatever the embedding runtime uses to name one guest process. */
|
|
20
20
|
export type ProcessToken = string;
|
|
21
21
|
/** The environment variable a run's name travels in, from `exec` to `node`. */
|
|
@@ -64,8 +64,18 @@ export declare function __currentProcessToken(): ProcessToken | null;
|
|
|
64
64
|
* half, `__releaseOwnedServers`, which calls this for the timers.
|
|
65
65
|
*/
|
|
66
66
|
export declare function __stopOwnedProcess(token: ProcessToken): void;
|
|
67
|
+
/**
|
|
68
|
+
* Another process's `kill(pid, signal)`, delivered to a named run's guest.
|
|
69
|
+
* On a machine the target's handlers run, or its default action ends it; the
|
|
70
|
+
* guest's own `process.kill(process.pid, signal)` is exactly that, so the
|
|
71
|
+
* signal is raised through it, as the run. False when the run is gone or the
|
|
72
|
+
* signal is not one Node names.
|
|
73
|
+
*/
|
|
74
|
+
export declare function __signalOwnedProcess(token: ProcessToken, signal: string): boolean;
|
|
67
75
|
/** Each child realm gets its own registration authority in this container. */
|
|
68
76
|
export declare function createProcessRegistryScope(): ProcessRegistryScope;
|
|
77
|
+
/** Trusted container owner only: every live process of the container, from every realm. */
|
|
78
|
+
export declare function ownerProcessTable(): ProcessIdentity[];
|
|
69
79
|
/** Trusted container owner only; worker clients cannot expose their authority. */
|
|
70
80
|
export declare function ownerProcessRegistryScope(): ProcessRegistryScope;
|
|
71
81
|
/** Startup-only host seam; never swap identity authorities under live runs. */
|
|
@@ -73,7 +83,10 @@ export declare function installProcessRegistry(registry: ProcessRegistry, initia
|
|
|
73
83
|
/** The next process number this engine hands out. */
|
|
74
84
|
export declare function mintPid(): number;
|
|
75
85
|
/** Record the numbers a named run was started with, for the run to read back. */
|
|
76
|
-
export declare function setRunPid(token: ProcessToken, pid: number, ppid: number
|
|
86
|
+
export declare function setRunPid(token: ProcessToken, pid: number, ppid: number, started?: {
|
|
87
|
+
argv?: readonly string[];
|
|
88
|
+
cwd?: string;
|
|
89
|
+
}): void;
|
|
77
90
|
/** The numbers a named run was started with, where one was recorded. */
|
|
78
91
|
export declare function runPid(token: ProcessToken | null | undefined): {
|
|
79
92
|
pid: number;
|
package/dist/virtual-fs.d.ts
CHANGED
|
@@ -37,6 +37,22 @@ export interface FSNode {
|
|
|
37
37
|
* apart). Absent until something sets it, which is the write time.
|
|
38
38
|
*/
|
|
39
39
|
atime?: number;
|
|
40
|
+
/**
|
|
41
|
+
* A tree mounted here: the paths below this node are the tree's, answered
|
|
42
|
+
* when they are read, and never this filesystem's to write. Linux's
|
|
43
|
+
* `/proc` is one.
|
|
44
|
+
*/
|
|
45
|
+
mount?: MountedTree;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A read-only tree another party answers, mounted at a directory: `node`
|
|
49
|
+
* returns what is at a path relative to the mount point (`/` is the mount
|
|
50
|
+
* point itself), computed when it is asked, or undefined where nothing is.
|
|
51
|
+
* A directory's `children` names what a listing shows; a link's `target` is
|
|
52
|
+
* followed as any link is.
|
|
53
|
+
*/
|
|
54
|
+
export interface MountedTree {
|
|
55
|
+
node(path: string): FSNode | undefined;
|
|
40
56
|
}
|
|
41
57
|
type VFSChangeListener = (path: string, content: string) => void;
|
|
42
58
|
type VFSDeleteListener = (path: string) => void;
|
|
@@ -83,7 +99,7 @@ export interface NodeError extends Error {
|
|
|
83
99
|
syscall: string;
|
|
84
100
|
path?: string;
|
|
85
101
|
}
|
|
86
|
-
export declare function createNodeError(code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL', syscall: string, path: string, message?: string): NodeError;
|
|
102
|
+
export declare function createNodeError(code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL' | 'EROFS', syscall: string, path: string, message?: string): NodeError;
|
|
87
103
|
export declare class VirtualFS {
|
|
88
104
|
private root;
|
|
89
105
|
private encoder;
|
|
@@ -140,6 +156,14 @@ export declare class VirtualFS {
|
|
|
140
156
|
getNode(path: string): FSNode | undefined;
|
|
141
157
|
/** The node at a path, following every link on the way and, when `follow`, the last one too. */
|
|
142
158
|
__substrateNode(path: string, follow: boolean, depth: number): FSNode | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
|
|
161
|
+
* below it are the tree's answers, writes fail with `EROFS`, and a snapshot
|
|
162
|
+
* leaves it out. Answers the unmount.
|
|
163
|
+
*/
|
|
164
|
+
mount(path: string, tree: MountedTree): () => void;
|
|
165
|
+
/** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
|
|
166
|
+
private assertWritable;
|
|
143
167
|
/** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
|
|
144
168
|
__substrateLinkTarget(linkPath: string, target: string): string;
|
|
145
169
|
symlinkSync(target: string, path: string): void;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// hands the host's own globals back. See `host-globals.ts`.
|
|
14
14
|
export { restoreHostGlobals, guestRealmInstalled } from './host-globals';
|
|
15
15
|
export { VirtualFS } from './virtual-fs';
|
|
16
|
-
export type { FSNode, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
|
|
16
|
+
export type { FSNode, MountedTree, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
|
|
17
17
|
export { Runtime, execute } from './runtime';
|
|
18
18
|
export type { Module, RuntimeOptions, RequireFunction } from './runtime';
|
|
19
19
|
export { createRuntime, WorkerRuntime } from './create-runtime';
|
|
@@ -54,11 +54,12 @@ import { PackageManager } from './npm';
|
|
|
54
54
|
import { ServerBridge, getServerBridge } from './server-bridge';
|
|
55
55
|
import { runCommand, registerRunStreams, releaseRunStreams, sendStdin } from './shims/child_process';
|
|
56
56
|
import { Server as NetServer, __releaseOwnedHandles, type Socket as NetSocket } from './node-lib/net-module';
|
|
57
|
-
import { __adoptHandle, type OwnedHandle } from './node-lib/binding/handles';
|
|
57
|
+
import { __adoptHandle, ownerOf, type OwnedHandle } from './node-lib/binding/handles';
|
|
58
|
+
import { listenerOnPort } from './node-lib/binding/tcp_wrap';
|
|
58
59
|
|
|
59
|
-
import { __currentProcessToken, __runFor, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
|
|
60
|
+
import { __currentProcessToken, __runFor, __signalOwnedProcess, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
|
|
60
61
|
export { runPid, processByPid };
|
|
61
|
-
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope } from './process-tokens';
|
|
62
|
+
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope, ownerProcessTable } from './process-tokens';
|
|
62
63
|
export { installNodeProcessHost, nodeProcessHostInstalled } from './node-process-host';
|
|
63
64
|
export type { NodeProcessLaunch, NodeProcessHost } from './node-process-host';
|
|
64
65
|
export type { ProcessIdentity, ProcessRegistry, ProcessRegistryScope, InitialProcessRegistration } from './process-registry';
|
|
@@ -123,6 +124,10 @@ export function createContainer(options?: ContainerOptions): {
|
|
|
123
124
|
run: (command: string, options?: RunOptions) => Promise<RunResult>;
|
|
124
125
|
pendingTimers: (token: string) => number;
|
|
125
126
|
processPorts: (token: string) => number[];
|
|
127
|
+
/** The pid of the process listening on a port of this engine, where a guest process is: `/proc`'s socket owner. */
|
|
128
|
+
portPid: (port: number) => number | undefined;
|
|
129
|
+
/** Deliver a signal to the named run as another process's `kill(pid)` does; false when the run is gone. */
|
|
130
|
+
signalProcess: (token: string, signal: string) => boolean;
|
|
126
131
|
stopProcess: (token: string) => boolean;
|
|
127
132
|
/**
|
|
128
133
|
* Input for one run's guest, by its process token; without a token, the most
|
|
@@ -208,6 +213,15 @@ export function createContainer(options?: ContainerOptions): {
|
|
|
208
213
|
pendingTimers: (token: string): number => __runFor(token)?.pendingTimers() ?? 0,
|
|
209
214
|
/** The ports the servers the named run opened are listening on. */
|
|
210
215
|
processPorts: (token: string): number[] => __ownedServerPorts(token),
|
|
216
|
+
portPid: (port: number): number | undefined => {
|
|
217
|
+
const listener = listenerOnPort(port);
|
|
218
|
+
return listener ? runPid(ownerOf(listener as unknown as OwnedHandle))?.pid : undefined;
|
|
219
|
+
},
|
|
220
|
+
/**
|
|
221
|
+
* Deliver a signal to the named run as another process's `kill(pid)` does:
|
|
222
|
+
* the guest's listeners for it run, else its default action ends the run.
|
|
223
|
+
*/
|
|
224
|
+
signalProcess: (token: string, signal: string): boolean => __signalOwnedProcess(token, signal),
|
|
211
225
|
/** End the named run: its timers stop and its servers are released. */
|
|
212
226
|
stopProcess: (token: string): boolean => {
|
|
213
227
|
const known = __runFor(token) !== undefined || __ownedServerPorts(token).length > 0;
|
|
@@ -40,7 +40,7 @@ import { errname } from './uv';
|
|
|
40
40
|
* the same shape here rather than widened there.
|
|
41
41
|
*/
|
|
42
42
|
function createNodeError(code: string, syscall: string, path: string): Error {
|
|
43
|
-
const known = ['EEXIST', 'EINVAL', 'EISDIR', 'ENOENT', 'ENOTDIR', 'ENOTEMPTY', 'ELOOP'];
|
|
43
|
+
const known = ['EEXIST', 'EINVAL', 'EISDIR', 'ENOENT', 'ENOTDIR', 'ENOTEMPTY', 'ELOOP', 'EROFS'];
|
|
44
44
|
if (known.includes(code)) return vfsError(code as 'ENOENT', syscall, path);
|
|
45
45
|
return Object.assign(new Error(`${code}: ${syscall} '${path}'`), { code, syscall, path, errno: -9 });
|
|
46
46
|
}
|
package/src/process-registry.ts
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
export interface ProcessIdentity {
|
|
3
3
|
readonly pid: number;
|
|
4
4
|
readonly ppid: number;
|
|
5
|
+
/** What the process was started as, where its starter said: `/proc/<pid>/cmdline`. */
|
|
6
|
+
readonly argv?: readonly string[];
|
|
7
|
+
/** The directory it was started in: `/proc/<pid>/cwd`. */
|
|
8
|
+
readonly cwd?: string;
|
|
9
|
+
/** When it started, in milliseconds since the epoch. */
|
|
10
|
+
readonly startedAt?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The parts of an identity a starter may add, checked before they are kept. */
|
|
14
|
+
function describedIdentity(identity: ProcessIdentity): ProcessIdentity {
|
|
15
|
+
const { pid, ppid, argv, cwd, startedAt } = identity;
|
|
16
|
+
const described: { pid: number; ppid: number; argv?: readonly string[]; cwd?: string; startedAt?: number } = { pid, ppid };
|
|
17
|
+
if (Array.isArray(argv) && argv.length <= 4096 && argv.every(arg => typeof arg === 'string')) described.argv = Object.freeze([...argv]);
|
|
18
|
+
if (typeof cwd === 'string' && cwd.startsWith('/')) described.cwd = cwd;
|
|
19
|
+
if (typeof startedAt === 'number' && Number.isFinite(startedAt)) described.startedAt = startedAt;
|
|
20
|
+
return Object.freeze(described);
|
|
5
21
|
}
|
|
6
22
|
|
|
7
23
|
/** A run already admitted by the container owner into this realm's scope. */
|
|
@@ -38,7 +54,7 @@ export interface ProcessRegistryScope extends ProcessRegistry {
|
|
|
38
54
|
* so identical local run tokens cannot overwrite or release another realm's
|
|
39
55
|
* process. Allocation alone does not announce a live process.
|
|
40
56
|
*/
|
|
41
|
-
export function createProcessRegistryOwner(): { createScope(): ProcessRegistryScope } {
|
|
57
|
+
export function createProcessRegistryOwner(): { createScope(): ProcessRegistryScope; table(): ProcessIdentity[] } {
|
|
42
58
|
let nextPid = 1000 + Math.floor(Math.random() * 30000);
|
|
43
59
|
const live = new Map<number, ProcessIdentity>();
|
|
44
60
|
// Which realm's scope answers for each live pid, including after handoff.
|
|
@@ -104,7 +120,7 @@ export function createProcessRegistryOwner(): { createScope(): ProcessRegistrySc
|
|
|
104
120
|
if (identity.ppid !== 0 && !Array.from(runs.values()).some(parent => parent.pid === identity.ppid)) {
|
|
105
121
|
throw new Error('Parent process is not owned by this realm.');
|
|
106
122
|
}
|
|
107
|
-
const entry =
|
|
123
|
+
const entry = describedIdentity(identity);
|
|
108
124
|
runs.set(token, entry);
|
|
109
125
|
live.set(entry.pid, entry);
|
|
110
126
|
receivers.set(entry.pid, receiver);
|
|
@@ -140,5 +156,9 @@ export function createProcessRegistryOwner(): { createScope(): ProcessRegistrySc
|
|
|
140
156
|
scopes.set(scope, { allocated, runs, active });
|
|
141
157
|
return scope;
|
|
142
158
|
},
|
|
159
|
+
/** Every live process of the container, in pid order: what `/proc` lists. */
|
|
160
|
+
table() {
|
|
161
|
+
return [...live.values()].sort((a, b) => a.pid - b.pid);
|
|
162
|
+
},
|
|
143
163
|
};
|
|
144
164
|
}
|
package/src/process-tokens.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import type { Process } from './shims/process';
|
|
19
19
|
import { AsyncLocalStorage } from './shims/async_hooks';
|
|
20
|
-
import { createProcessRegistryOwner, type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
|
|
20
|
+
import { createProcessRegistryOwner, type ProcessIdentity, type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
|
|
21
21
|
|
|
22
22
|
/** Whatever the embedding runtime uses to name one guest process. */
|
|
23
23
|
export type ProcessToken = string;
|
|
@@ -113,6 +113,27 @@ export function __stopOwnedProcess(token: ProcessToken): void {
|
|
|
113
113
|
runs.get(token)?.stopTimers();
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Another process's `kill(pid, signal)`, delivered to a named run's guest.
|
|
118
|
+
* On a machine the target's handlers run, or its default action ends it; the
|
|
119
|
+
* guest's own `process.kill(process.pid, signal)` is exactly that, so the
|
|
120
|
+
* signal is raised through it, as the run. False when the run is gone or the
|
|
121
|
+
* signal is not one Node names.
|
|
122
|
+
*/
|
|
123
|
+
export function __signalOwnedProcess(token: ProcessToken, signal: string): boolean {
|
|
124
|
+
const run = runs.get(token);
|
|
125
|
+
if (!run) return false;
|
|
126
|
+
const guest = run.process as unknown as { pid: number; kill(pid: number, signal: string): unknown };
|
|
127
|
+
try { enterRun(token, () => guest.kill(guest.pid, signal)); }
|
|
128
|
+
catch (error) {
|
|
129
|
+
// A default action ends the process by `exit`, which a synchronous guest
|
|
130
|
+
// frame reports by throwing; that is the signal delivered.
|
|
131
|
+
if (error instanceof Error && error.message.startsWith("Process exited with code")) return true;
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
/**
|
|
117
138
|
* A process's own number.
|
|
118
139
|
*
|
|
@@ -147,6 +168,12 @@ export function createProcessRegistryScope(): ProcessRegistryScope {
|
|
|
147
168
|
return processRegistryOwner.createScope();
|
|
148
169
|
}
|
|
149
170
|
|
|
171
|
+
/** Trusted container owner only: every live process of the container, from every realm. */
|
|
172
|
+
export function ownerProcessTable(): ProcessIdentity[] {
|
|
173
|
+
if (registryInstalled) throw new Error('This realm is a process registry client, not the container owner.');
|
|
174
|
+
return processRegistryOwner.table();
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
/** Trusted container owner only; worker clients cannot expose their authority. */
|
|
151
178
|
export function ownerProcessRegistryScope(): ProcessRegistryScope {
|
|
152
179
|
if (registryInstalled) throw new Error('This realm is a process registry client, not the container owner.');
|
|
@@ -177,9 +204,9 @@ export function mintPid(): number {
|
|
|
177
204
|
}
|
|
178
205
|
|
|
179
206
|
/** Record the numbers a named run was started with, for the run to read back. */
|
|
180
|
-
export function setRunPid(token: ProcessToken, pid: number, ppid: number): void {
|
|
207
|
+
export function setRunPid(token: ProcessToken, pid: number, ppid: number, started?: { argv?: readonly string[]; cwd?: string }): void {
|
|
181
208
|
registryUsed = true;
|
|
182
|
-
processRegistry.publish(token, { pid, ppid });
|
|
209
|
+
processRegistry.publish(token, { pid, ppid, ...started, startedAt: Date.now() });
|
|
183
210
|
pidsOfRuns.set(token, { pid, ppid });
|
|
184
211
|
}
|
|
185
212
|
|
|
@@ -199,7 +199,7 @@ const _runStreams = new Map<ProcessToken, RunStreams>();
|
|
|
199
199
|
/** Submit before returning to a spawning parent; completion remains async. */
|
|
200
200
|
async function runHostedNode(host: NodeProcessHost, launch: Omit<NodeProcessLaunch, 'identity' | 'inherited' | 'stdinStream'>): Promise<CommandOutcome> {
|
|
201
201
|
const { token, streams } = launch;
|
|
202
|
-
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
202
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0, { argv: ['node', ...launch.argv], cwd: launch.cwd });
|
|
203
203
|
let input: ReturnType<typeof nodeProcessInput> | undefined;
|
|
204
204
|
try {
|
|
205
205
|
streams?.signal?.throwIfAborted();
|
|
@@ -578,7 +578,7 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
578
578
|
// This run's numbers, recorded under its name: a child it spawns reads
|
|
579
579
|
// them for its own `ppid`, and `process.kill(pid, 0)` asks this registry
|
|
580
580
|
// whether a pid is a live process.
|
|
581
|
-
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
|
|
581
|
+
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid, { argv: ['node', ...args], cwd: ctx.cwd });
|
|
582
582
|
const releaseRun = runToken === null ? null : __recordRun(runToken, {
|
|
583
583
|
process: proc,
|
|
584
584
|
stdout: appendStdout,
|
|
@@ -1680,7 +1680,8 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1680
1680
|
const pid = mintPid();
|
|
1681
1681
|
// In a process realm the admitted guest is always the spawning parent.
|
|
1682
1682
|
// Async bookkeeping for a routed child must not make that child its parent.
|
|
1683
|
-
setRunPid(token, pid, runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)?.pid ?? 0
|
|
1683
|
+
setRunPid(token, pid, runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)?.pid ?? 0,
|
|
1684
|
+
{ argv: request.args.length ? request.args : [request.file], ...(request.cwd ? { cwd: request.cwd } : {}) });
|
|
1684
1685
|
const controller = new AbortController();
|
|
1685
1686
|
const pendingStdin: Array<Uint8Array | null> = [];
|
|
1686
1687
|
// A host terminal consumes input incrementally. The old string-only
|
package/src/virtual-fs.ts
CHANGED
|
@@ -44,6 +44,23 @@ export interface FSNode {
|
|
|
44
44
|
* apart). Absent until something sets it, which is the write time.
|
|
45
45
|
*/
|
|
46
46
|
atime?: number;
|
|
47
|
+
/**
|
|
48
|
+
* A tree mounted here: the paths below this node are the tree's, answered
|
|
49
|
+
* when they are read, and never this filesystem's to write. Linux's
|
|
50
|
+
* `/proc` is one.
|
|
51
|
+
*/
|
|
52
|
+
mount?: MountedTree;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A read-only tree another party answers, mounted at a directory: `node`
|
|
57
|
+
* returns what is at a path relative to the mount point (`/` is the mount
|
|
58
|
+
* point itself), computed when it is asked, or undefined where nothing is.
|
|
59
|
+
* A directory's `children` names what a listing shows; a link's `target` is
|
|
60
|
+
* followed as any link is.
|
|
61
|
+
*/
|
|
62
|
+
export interface MountedTree {
|
|
63
|
+
node(path: string): FSNode | undefined;
|
|
47
64
|
}
|
|
48
65
|
|
|
49
66
|
// Identity belongs to the node, so writes and renames preserve it while an
|
|
@@ -116,7 +133,7 @@ export interface NodeError extends Error {
|
|
|
116
133
|
}
|
|
117
134
|
|
|
118
135
|
export function createNodeError(
|
|
119
|
-
code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL',
|
|
136
|
+
code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL' | 'EROFS',
|
|
120
137
|
syscall: string,
|
|
121
138
|
path: string,
|
|
122
139
|
message?: string
|
|
@@ -127,6 +144,7 @@ export function createNodeError(
|
|
|
127
144
|
EISDIR: -21,
|
|
128
145
|
EEXIST: -17,
|
|
129
146
|
ENOTEMPTY: -39,
|
|
147
|
+
EROFS: -30,
|
|
130
148
|
};
|
|
131
149
|
|
|
132
150
|
const messages: Record<string, string> = {
|
|
@@ -135,6 +153,7 @@ export function createNodeError(
|
|
|
135
153
|
EISDIR: 'is a directory',
|
|
136
154
|
EEXIST: 'file already exists',
|
|
137
155
|
ENOTEMPTY: 'directory not empty',
|
|
156
|
+
EROFS: 'read-only file system',
|
|
138
157
|
};
|
|
139
158
|
|
|
140
159
|
const err = new Error(
|
|
@@ -216,6 +235,7 @@ export class VirtualFS {
|
|
|
216
235
|
}
|
|
217
236
|
|
|
218
237
|
private serializeNode(path: string, node: FSNode, files: VFSFileEntry[]): void {
|
|
238
|
+
if (node.mount) return; // a mounted tree is its answerer's, never the filesystem's contents
|
|
219
239
|
if (node.type === 'file') {
|
|
220
240
|
// Encode binary content as base64
|
|
221
241
|
let content = '';
|
|
@@ -284,6 +304,7 @@ export class VirtualFS {
|
|
|
284
304
|
*/
|
|
285
305
|
private writeFileSyncInternal(path: string, data: string | Uint8Array, emitEvent: boolean): void {
|
|
286
306
|
const normalized = this.normalizePath(path);
|
|
307
|
+
this.assertWritable(normalized, 'open');
|
|
287
308
|
const parentPath = this.getParentPath(normalized);
|
|
288
309
|
const basename = this.getBasename(normalized);
|
|
289
310
|
|
|
@@ -377,11 +398,14 @@ export class VirtualFS {
|
|
|
377
398
|
if (depth > 40) throw createNodeError('ELOOP', 'stat', path);
|
|
378
399
|
const segments = this.getPathSegments(path);
|
|
379
400
|
let current = this.root;
|
|
401
|
+
let mounted: { tree: MountedTree; at: number } | undefined;
|
|
380
402
|
for (let index = 0; index < segments.length; index += 1) {
|
|
381
|
-
if (current.type !== 'directory' || !current.children) {
|
|
403
|
+
if (current.type !== 'directory' || (!mounted && !current.children)) {
|
|
382
404
|
return undefined;
|
|
383
405
|
}
|
|
384
|
-
const child =
|
|
406
|
+
const child = mounted
|
|
407
|
+
? mounted.tree.node('/' + segments.slice(mounted.at + 1, index + 1).join('/'))
|
|
408
|
+
: current.children!.get(segments[index]);
|
|
385
409
|
if (!child) {
|
|
386
410
|
return undefined;
|
|
387
411
|
}
|
|
@@ -391,16 +415,51 @@ export class VirtualFS {
|
|
|
391
415
|
const target = this.__substrateLinkTarget(linkPath, child.target!);
|
|
392
416
|
return this.__substrateNode(rest ? target + '/' + rest : target, follow, depth + 1);
|
|
393
417
|
}
|
|
394
|
-
|
|
418
|
+
if (!mounted && child.mount) {
|
|
419
|
+
mounted = { tree: child.mount, at: index };
|
|
420
|
+
current = child.mount.node('/') ?? child;
|
|
421
|
+
} else {
|
|
422
|
+
current = child;
|
|
423
|
+
}
|
|
395
424
|
}
|
|
396
425
|
return current;
|
|
397
426
|
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
|
|
430
|
+
* below it are the tree's answers, writes fail with `EROFS`, and a snapshot
|
|
431
|
+
* leaves it out. Answers the unmount.
|
|
432
|
+
*/
|
|
433
|
+
mount(path: string, tree: MountedTree): () => void {
|
|
434
|
+
const normalized = this.normalizePath(path);
|
|
435
|
+
const basename = this.getBasename(normalized);
|
|
436
|
+
if (!basename) throw createNodeError('EINVAL', 'mount', path);
|
|
437
|
+
const parent = this.ensureDirectory(this.getParentPath(normalized));
|
|
438
|
+
const existing = parent.children!.get(basename);
|
|
439
|
+
if (existing && (existing.type !== 'directory' || existing.mount || existing.children?.size)) {
|
|
440
|
+
throw createNodeError('EEXIST', 'mount', path);
|
|
441
|
+
}
|
|
442
|
+
const node: FSNode = { type: 'directory', children: new Map(), mtime: Date.now(), mode: 0o555, mount: tree };
|
|
443
|
+
parent.children!.set(basename, node);
|
|
444
|
+
return () => { if (parent.children!.get(basename) === node) parent.children!.delete(basename); };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
|
|
448
|
+
private assertWritable(path: string, syscall: string): void {
|
|
449
|
+
let current: FSNode | undefined = this.root;
|
|
450
|
+
for (const segment of this.getPathSegments(path)) {
|
|
451
|
+
current = current?.children?.get(segment);
|
|
452
|
+
if (!current) return;
|
|
453
|
+
if (current.mount) throw createNodeError('EROFS', syscall, path);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
398
456
|
/** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
|
|
399
457
|
__substrateLinkTarget(linkPath: string, target: string): string {
|
|
400
458
|
return this.normalizePath(target.startsWith('/') ? target : this.getParentPath(linkPath) + '/' + target);
|
|
401
459
|
}
|
|
402
460
|
symlinkSync(target: string, path: string): void {
|
|
403
461
|
const normalized = this.normalizePath(path);
|
|
462
|
+
this.assertWritable(normalized, 'symlink');
|
|
404
463
|
const parentPath = this.getParentPath(normalized);
|
|
405
464
|
const basename = this.getBasename(normalized);
|
|
406
465
|
const parent = this.getNode(parentPath);
|
|
@@ -472,12 +531,14 @@ export class VirtualFS {
|
|
|
472
531
|
* Get stats for path
|
|
473
532
|
*/
|
|
474
533
|
chmodSync(path: string, mode: number): void {
|
|
534
|
+
this.assertWritable(path, 'chmod');
|
|
475
535
|
const node = this.getNode(path);
|
|
476
536
|
if (!node) throw createNodeError('ENOENT', 'chmod', path);
|
|
477
537
|
node.mode = mode & 0o7777;
|
|
478
538
|
}
|
|
479
539
|
|
|
480
540
|
utimesSync(path: string, atime: number | Date, mtime: number | Date): void {
|
|
541
|
+
this.assertWritable(path, 'utime');
|
|
481
542
|
const node = this.getNode(path);
|
|
482
543
|
if (!node) throw createNodeError('ENOENT', 'utimes', path);
|
|
483
544
|
node.atime = atime instanceof Date ? atime.getTime() : atime * 1000;
|
|
@@ -600,6 +661,9 @@ export class VirtualFS {
|
|
|
600
661
|
*/
|
|
601
662
|
mkdirSync(path: string, options?: { recursive?: boolean }): void {
|
|
602
663
|
const normalized = this.normalizePath(path);
|
|
664
|
+
// `mkdir -p` of a directory that is there, a mount point among them, is done
|
|
665
|
+
if (options?.recursive && this.getNode(normalized)?.type === 'directory') return;
|
|
666
|
+
this.assertWritable(normalized, 'mkdir');
|
|
603
667
|
|
|
604
668
|
if (options?.recursive) {
|
|
605
669
|
this.ensureDirectory(normalized);
|
|
@@ -657,6 +721,7 @@ export class VirtualFS {
|
|
|
657
721
|
*/
|
|
658
722
|
unlinkSync(path: string): void {
|
|
659
723
|
const normalized = this.normalizePath(path);
|
|
724
|
+
this.assertWritable(normalized, 'unlink');
|
|
660
725
|
const parentPath = this.getParentPath(normalized);
|
|
661
726
|
const basename = this.getBasename(normalized);
|
|
662
727
|
|
|
@@ -689,6 +754,7 @@ export class VirtualFS {
|
|
|
689
754
|
*/
|
|
690
755
|
rmdirSync(path: string): void {
|
|
691
756
|
const normalized = this.normalizePath(path);
|
|
757
|
+
this.assertWritable(normalized, 'rmdir');
|
|
692
758
|
const parentPath = this.getParentPath(normalized);
|
|
693
759
|
const basename = this.getBasename(normalized);
|
|
694
760
|
|
|
@@ -727,6 +793,8 @@ export class VirtualFS {
|
|
|
727
793
|
renameSync(oldPath: string, newPath: string): void {
|
|
728
794
|
const normalizedOld = this.normalizePath(oldPath);
|
|
729
795
|
const normalizedNew = this.normalizePath(newPath);
|
|
796
|
+
this.assertWritable(normalizedOld, 'rename');
|
|
797
|
+
this.assertWritable(normalizedNew, 'rename');
|
|
730
798
|
|
|
731
799
|
const oldParentPath = this.getParentPath(normalizedOld);
|
|
732
800
|
const oldBasename = this.getBasename(normalizedOld);
|
|
@@ -846,10 +914,14 @@ export class VirtualFS {
|
|
|
846
914
|
for (let hops = 0; hops <= 40; hops += 1) {
|
|
847
915
|
const segments = resolved.split('/').filter(Boolean);
|
|
848
916
|
let current: FSNode | undefined = this.root;
|
|
917
|
+
let mounted: { tree: MountedTree; at: number } | undefined;
|
|
849
918
|
let linked = false;
|
|
850
919
|
for (let index = 0; index < segments.length; index += 1) {
|
|
851
|
-
const child: FSNode | undefined =
|
|
920
|
+
const child: FSNode | undefined = mounted
|
|
921
|
+
? mounted.tree.node('/' + segments.slice(mounted.at + 1, index + 1).join('/'))
|
|
922
|
+
: current && current.children ? current.children.get(segments[index]) : void 0;
|
|
852
923
|
if (!child) throw createNodeError('ENOENT', 'realpath', path);
|
|
924
|
+
if (!mounted && child.mount) mounted = { tree: child.mount, at: index };
|
|
853
925
|
if (child.type === 'symlink') {
|
|
854
926
|
const linkPath = '/' + segments.slice(0, index + 1).join('/');
|
|
855
927
|
const rest = segments.slice(index + 1).join('/');
|