@ai-ecoverse/slicc-kernel 1.12.0 → 1.14.0
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/README.md +22 -1
- package/dist/driver-worker.js +574 -0
- package/dist/driver-worker.js.map +7 -0
- package/dist/driver.js +115 -0
- package/dist/driver.js.map +7 -0
- package/dist/index.js +18 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +1018 -41
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +576 -0
- package/dist/node-driver-worker.js.map +7 -0
- package/dist/node-process-worker.js +51 -16
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +1010 -22
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +51 -16
- package/dist/process-worker.js.map +3 -3
- package/package.json +4 -2
package/dist/node.js
CHANGED
|
@@ -252,7 +252,7 @@ function serveTransport(peer, transport) {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
// src/client/protocol.ts
|
|
255
|
-
var PROTOCOL = [1,
|
|
255
|
+
var PROTOCOL = [1, 2];
|
|
256
256
|
var FS_METHODS = [
|
|
257
257
|
"readFile",
|
|
258
258
|
"writeFile",
|
|
@@ -341,6 +341,11 @@ async function fsCall(fs, method, args) {
|
|
|
341
341
|
return { result: await fs.exists(path(0)) };
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
|
+
function signalOf(host) {
|
|
345
|
+
return host.signal ?? ((name2) => {
|
|
346
|
+
throw fsError("EINVAL", `this kernel names no signal ${name2}`);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
344
349
|
function serveClient(port, host) {
|
|
345
350
|
let state = "new";
|
|
346
351
|
const terminals = /* @__PURE__ */ new Map();
|
|
@@ -428,6 +433,9 @@ function serveClient(port, host) {
|
|
|
428
433
|
return { result: result.status };
|
|
429
434
|
}
|
|
430
435
|
async function handle(req) {
|
|
436
|
+
if (host.scope === "transport" && req.op !== "detach") {
|
|
437
|
+
throw fsError("EPERM", `${req.op}: this client may only use the network transport`);
|
|
438
|
+
}
|
|
431
439
|
const l = await host.launcher();
|
|
432
440
|
switch (req.op) {
|
|
433
441
|
case "spawn":
|
|
@@ -437,11 +445,11 @@ function serveClient(port, host) {
|
|
|
437
445
|
case "terminal": {
|
|
438
446
|
const session = terminals.get(req.terminal);
|
|
439
447
|
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
440
|
-
act(session, req, host
|
|
448
|
+
act(session, req, signalOf(host));
|
|
441
449
|
return { result: true };
|
|
442
450
|
}
|
|
443
451
|
case "kill":
|
|
444
|
-
if (!l.kill(req.pid, host
|
|
452
|
+
if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
445
453
|
return { result: true };
|
|
446
454
|
case "ps":
|
|
447
455
|
return {
|
|
@@ -465,6 +473,13 @@ function serveClient(port, host) {
|
|
|
465
473
|
watches.get(req.watch)?.();
|
|
466
474
|
watches.delete(req.watch);
|
|
467
475
|
return { result: true };
|
|
476
|
+
case "mount":
|
|
477
|
+
return { result: await l.mount(req.spec) };
|
|
478
|
+
case "umount":
|
|
479
|
+
l.umount(req.target);
|
|
480
|
+
return { result: true };
|
|
481
|
+
case "mounts":
|
|
482
|
+
return { result: l.mounts.list() };
|
|
468
483
|
case "detach":
|
|
469
484
|
return { result: true };
|
|
470
485
|
default:
|
|
@@ -1153,9 +1168,28 @@ async function packages(fs, modules) {
|
|
|
1153
1168
|
}
|
|
1154
1169
|
return dirs;
|
|
1155
1170
|
}
|
|
1171
|
+
async function pnpmGlobalRoots(fs, home) {
|
|
1172
|
+
const roots = [];
|
|
1173
|
+
const global = `${home}/global`;
|
|
1174
|
+
for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
|
|
1175
|
+
for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
|
|
1176
|
+
const dir = `${global}/${version}/${group}`;
|
|
1177
|
+
const st = await fs.lstat(dir).catch(() => void 0);
|
|
1178
|
+
if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
|
|
1179
|
+
roots.push(`${dir}/node_modules`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return roots;
|
|
1183
|
+
}
|
|
1184
|
+
async function packagesIn(fs, roots) {
|
|
1185
|
+
const dirs = [];
|
|
1186
|
+
for (const root of typeof roots === "string" ? [roots] : roots)
|
|
1187
|
+
dirs.push(...await packages(fs, root));
|
|
1188
|
+
return dirs;
|
|
1189
|
+
}
|
|
1156
1190
|
async function scanCommands(fs, modules) {
|
|
1157
1191
|
const found = /* @__PURE__ */ new Map();
|
|
1158
|
-
for (const pkg of await
|
|
1192
|
+
for (const pkg of await packagesIn(fs, modules)) {
|
|
1159
1193
|
let manifest;
|
|
1160
1194
|
try {
|
|
1161
1195
|
manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
|
|
@@ -1172,6 +1206,24 @@ async function scanCommands(fs, modules) {
|
|
|
1172
1206
|
}
|
|
1173
1207
|
return found;
|
|
1174
1208
|
}
|
|
1209
|
+
async function scanFilesystems(fs, modules) {
|
|
1210
|
+
const found = /* @__PURE__ */ new Map();
|
|
1211
|
+
for (const pkg of await packagesIn(fs, modules)) {
|
|
1212
|
+
let manifest;
|
|
1213
|
+
try {
|
|
1214
|
+
manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
|
|
1215
|
+
} catch {
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
const declared = manifest.slicc?.filesystems;
|
|
1219
|
+
if (!declared || typeof declared !== "object") continue;
|
|
1220
|
+
for (const [type, entry] of Object.entries(declared)) {
|
|
1221
|
+
const module = inside(pkg, entry?.module);
|
|
1222
|
+
if (NAME.test(type) && module && !found.has(type)) found.set(type, module);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return found;
|
|
1226
|
+
}
|
|
1175
1227
|
|
|
1176
1228
|
// src/fs/commands.ts
|
|
1177
1229
|
var S_IFDIR2 = 16384;
|
|
@@ -1934,7 +1986,8 @@ function statJson(s) {
|
|
|
1934
1986
|
mtimeMs: s.mtime.getTime(),
|
|
1935
1987
|
atimeMs: s.atime.getTime(),
|
|
1936
1988
|
ctimeMs: s.ctime.getTime(),
|
|
1937
|
-
ino: s.ino
|
|
1989
|
+
ino: s.ino,
|
|
1990
|
+
...s.dev !== void 0 ? { dev: s.dev } : {}
|
|
1938
1991
|
};
|
|
1939
1992
|
}
|
|
1940
1993
|
async function readdirStat(fs, dir) {
|
|
@@ -1995,8 +2048,6 @@ async function run(fs, path, req, cwd) {
|
|
|
1995
2048
|
case "utimes":
|
|
1996
2049
|
await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
|
|
1997
2050
|
return done;
|
|
1998
|
-
case "statfs":
|
|
1999
|
-
return json(await storageUsage());
|
|
2000
2051
|
default:
|
|
2001
2052
|
return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
|
|
2002
2053
|
}
|
|
@@ -2006,7 +2057,13 @@ async function dispatchSyncFs(req) {
|
|
|
2006
2057
|
if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
|
|
2007
2058
|
const { fs, cwd } = entry;
|
|
2008
2059
|
try {
|
|
2009
|
-
|
|
2060
|
+
const path = fs.resolvePath(cwd, req.path);
|
|
2061
|
+
if (req.op === "statfs") return json(await entry.statfs?.(path) ?? await storageUsage());
|
|
2062
|
+
if (req.op === "hold") {
|
|
2063
|
+
entry.hold?.(path, req.mode === 1);
|
|
2064
|
+
return done;
|
|
2065
|
+
}
|
|
2066
|
+
return await run(fs, path, req, cwd);
|
|
2010
2067
|
} catch (err) {
|
|
2011
2068
|
return toErrno(err);
|
|
2012
2069
|
}
|
|
@@ -2070,7 +2127,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
2070
2127
|
const { header: header2, window } = views;
|
|
2071
2128
|
const pending = /* @__PURE__ */ new Map();
|
|
2072
2129
|
let disposed = false;
|
|
2073
|
-
const { dispatch } = opts;
|
|
2130
|
+
const { dispatch: dispatch2 } = opts;
|
|
2074
2131
|
function drop(id) {
|
|
2075
2132
|
const entry = pending.get(id);
|
|
2076
2133
|
if (!entry) return;
|
|
@@ -2125,7 +2182,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
2125
2182
|
const req = { ...body, token };
|
|
2126
2183
|
let dispatched;
|
|
2127
2184
|
try {
|
|
2128
|
-
dispatched =
|
|
2185
|
+
dispatched = dispatch2(req);
|
|
2129
2186
|
} catch (err) {
|
|
2130
2187
|
settle(id, {
|
|
2131
2188
|
ok: false,
|
|
@@ -4747,6 +4804,10 @@ var VfsNodes = class {
|
|
|
4747
4804
|
node.opens++;
|
|
4748
4805
|
return node;
|
|
4749
4806
|
}
|
|
4807
|
+
holds(prefix) {
|
|
4808
|
+
for (const path of this.byPath.keys()) if (within2(path, prefix)) return true;
|
|
4809
|
+
return false;
|
|
4810
|
+
}
|
|
4750
4811
|
closed(node) {
|
|
4751
4812
|
node.opens--;
|
|
4752
4813
|
if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
|
|
@@ -4924,6 +4985,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4924
4985
|
"proc-fork",
|
|
4925
4986
|
"proc-kill",
|
|
4926
4987
|
"proc-list",
|
|
4988
|
+
"mount-list",
|
|
4927
4989
|
"proc-exec",
|
|
4928
4990
|
"proc-setpgid",
|
|
4929
4991
|
"proc-getpgid",
|
|
@@ -4967,6 +5029,7 @@ var WasmProcess = class {
|
|
|
4967
5029
|
this.options = options;
|
|
4968
5030
|
this.children = new ChildTable(fds, options.spawner, options.forker);
|
|
4969
5031
|
this.nodes = new VfsNodes(options.fs);
|
|
5032
|
+
options.openFiles?.add(this.nodes);
|
|
4970
5033
|
this.children.onChildState = () => this.signal(SIG.CHLD);
|
|
4971
5034
|
this.children.onReap = options.onReap;
|
|
4972
5035
|
}
|
|
@@ -5378,6 +5441,8 @@ var WasmProcess = class {
|
|
|
5378
5441
|
kind: "json",
|
|
5379
5442
|
json: this.options.processes?.() ?? { boot: 0, processes: [] }
|
|
5380
5443
|
};
|
|
5444
|
+
case "mount-list":
|
|
5445
|
+
return { ok: true, kind: "json", json: this.options.mounts?.() ?? [] };
|
|
5381
5446
|
case "proc-alarm":
|
|
5382
5447
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
5383
5448
|
return { ok: true, kind: "void" };
|
|
@@ -5435,6 +5500,7 @@ var WasmProcess = class {
|
|
|
5435
5500
|
this.asyncOps.close();
|
|
5436
5501
|
for (const pid of this.children.pids()) this.options.onReap?.(pid);
|
|
5437
5502
|
await this.fds.closeAll();
|
|
5503
|
+
this.options.openFiles?.delete(this.nodes);
|
|
5438
5504
|
await this.options.http?.closeAll();
|
|
5439
5505
|
}
|
|
5440
5506
|
};
|
|
@@ -5482,6 +5548,8 @@ function spawnWasmProcess(opts) {
|
|
|
5482
5548
|
fs: opts.fs,
|
|
5483
5549
|
kill: opts.kill,
|
|
5484
5550
|
processes: opts.processes,
|
|
5551
|
+
openFiles: opts.openFiles,
|
|
5552
|
+
mounts: opts.mounts,
|
|
5485
5553
|
jobs: opts.jobs,
|
|
5486
5554
|
ptys: opts.ptys,
|
|
5487
5555
|
net: opts.net,
|
|
@@ -5494,13 +5562,24 @@ function spawnWasmProcess(opts) {
|
|
|
5494
5562
|
raise: (sig) => signal(sig),
|
|
5495
5563
|
...opts.program.abi === "wasi" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
|
|
5496
5564
|
});
|
|
5497
|
-
const
|
|
5565
|
+
const holds = /* @__PURE__ */ new Map();
|
|
5566
|
+
opts.held?.add(holds);
|
|
5567
|
+
const token = mintSyncFsToken({
|
|
5568
|
+
fs: opts.fs,
|
|
5569
|
+
cwd: opts.cwd,
|
|
5570
|
+
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
5571
|
+
hold: (path, on) => {
|
|
5572
|
+
const count = (holds.get(path) ?? 0) + (on ? 1 : -1);
|
|
5573
|
+
if (count > 0) holds.set(path, count);
|
|
5574
|
+
else holds.delete(path);
|
|
5575
|
+
}
|
|
5576
|
+
});
|
|
5498
5577
|
const worker = opts.createWorker();
|
|
5499
|
-
const
|
|
5578
|
+
const dispatch2 = async (req) => {
|
|
5500
5579
|
if (isWasmSyscall(req)) return process.syscall(req);
|
|
5501
5580
|
return dispatchSyncFs(req);
|
|
5502
5581
|
};
|
|
5503
|
-
const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
|
|
5582
|
+
const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
|
|
5504
5583
|
let settle;
|
|
5505
5584
|
const exited = new Promise((resolve) => settle = resolve);
|
|
5506
5585
|
let done4 = false;
|
|
@@ -5514,6 +5593,7 @@ function spawnWasmProcess(opts) {
|
|
|
5514
5593
|
for (const tid of [...threads.keys()]) endThread(tid);
|
|
5515
5594
|
responder.dispose();
|
|
5516
5595
|
revokeSyncFsToken(token);
|
|
5596
|
+
opts.held?.delete(holds);
|
|
5517
5597
|
worker.terminate();
|
|
5518
5598
|
void process.exit().then(
|
|
5519
5599
|
() => settle(code),
|
|
@@ -5553,7 +5633,7 @@ function spawnWasmProcess(opts) {
|
|
|
5553
5633
|
}
|
|
5554
5634
|
const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
5555
5635
|
const tw = opts.createWorker();
|
|
5556
|
-
const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch });
|
|
5636
|
+
const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch: dispatch2 });
|
|
5557
5637
|
const onThreadMessage = (event) => {
|
|
5558
5638
|
const data = event.data;
|
|
5559
5639
|
if (data?.type === WASM_THREAD_EXIT) endThread(thread.tid);
|
|
@@ -5719,6 +5799,815 @@ var JobTable = class {
|
|
|
5719
5799
|
}
|
|
5720
5800
|
};
|
|
5721
5801
|
|
|
5802
|
+
// src/mount/protocol.ts
|
|
5803
|
+
var DRIVER_PROTOCOL = [1, 0];
|
|
5804
|
+
function driverVersionError(theirs) {
|
|
5805
|
+
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
5806
|
+
if (major === DRIVER_PROTOCOL[0]) return void 0;
|
|
5807
|
+
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
5808
|
+
return `slicc-kernel driver protocol ${named} is not supported: this side speaks ${DRIVER_PROTOCOL.join(".")}`;
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
// src/mount/driver.ts
|
|
5812
|
+
function fsError2(code, message2 = code) {
|
|
5813
|
+
return Object.assign(new Error(message2), { code });
|
|
5814
|
+
}
|
|
5815
|
+
async function dispatch(h, req) {
|
|
5816
|
+
switch (req.op) {
|
|
5817
|
+
case "getattr":
|
|
5818
|
+
return h.getattr(req.path);
|
|
5819
|
+
case "readdir":
|
|
5820
|
+
return h.readdir(req.path);
|
|
5821
|
+
case "open":
|
|
5822
|
+
return h.open(req.path, {
|
|
5823
|
+
write: req.write,
|
|
5824
|
+
create: req.create,
|
|
5825
|
+
truncate: req.truncate,
|
|
5826
|
+
exclusive: req.exclusive
|
|
5827
|
+
});
|
|
5828
|
+
case "read":
|
|
5829
|
+
return h.read(req.fh, req.offset, req.size);
|
|
5830
|
+
case "write":
|
|
5831
|
+
return h.write(req.fh, req.offset, req.bytes);
|
|
5832
|
+
case "release":
|
|
5833
|
+
return h.release(req.fh);
|
|
5834
|
+
case "mkdir":
|
|
5835
|
+
return h.mkdir(req.path);
|
|
5836
|
+
case "rmdir":
|
|
5837
|
+
return h.rmdir(req.path);
|
|
5838
|
+
case "unlink":
|
|
5839
|
+
return h.unlink(req.path);
|
|
5840
|
+
case "rename":
|
|
5841
|
+
return h.rename(req.from, req.to);
|
|
5842
|
+
case "symlink":
|
|
5843
|
+
if (!h.symlink) throw fsError2("EPERM", "this file system has no symlinks");
|
|
5844
|
+
return h.symlink(req.target, req.path);
|
|
5845
|
+
case "readlink":
|
|
5846
|
+
if (!h.readlink) throw fsError2("EINVAL", `${req.path} is not a symlink`);
|
|
5847
|
+
return h.readlink(req.path);
|
|
5848
|
+
case "setattr":
|
|
5849
|
+
return h.setattr?.(req.path, {
|
|
5850
|
+
...req.mode !== void 0 ? { mode: req.mode } : {},
|
|
5851
|
+
...req.mtime !== void 0 ? { mtime: req.mtime } : {}
|
|
5852
|
+
});
|
|
5853
|
+
case "statfs":
|
|
5854
|
+
return h.statfs?.() ?? null;
|
|
5855
|
+
default:
|
|
5856
|
+
throw fsError2("ENOSYS", `unknown driver op ${req.op}`);
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
function serveFilesystem(port, handlers, capabilities = {}) {
|
|
5860
|
+
let open = false;
|
|
5861
|
+
const reply = (message2, transfer = []) => port.postMessage(message2, transfer);
|
|
5862
|
+
async function hello(message2) {
|
|
5863
|
+
const refused = driverVersionError(message2.protocol);
|
|
5864
|
+
if (refused) {
|
|
5865
|
+
reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused, errno: "EPROTO" } });
|
|
5866
|
+
return;
|
|
5867
|
+
}
|
|
5868
|
+
try {
|
|
5869
|
+
const refined = await handlers.mount?.(message2.mount);
|
|
5870
|
+
open = true;
|
|
5871
|
+
reply({
|
|
5872
|
+
hello: { protocol: DRIVER_PROTOCOL, capabilities: { ...capabilities, ...refined } }
|
|
5873
|
+
});
|
|
5874
|
+
} catch (err) {
|
|
5875
|
+
const code = err?.code;
|
|
5876
|
+
reply({
|
|
5877
|
+
hello: {
|
|
5878
|
+
protocol: DRIVER_PROTOCOL,
|
|
5879
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5880
|
+
errno: typeof code === "string" ? code : "EIO"
|
|
5881
|
+
}
|
|
5882
|
+
});
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
port.addEventListener("message", (event) => {
|
|
5886
|
+
const data = event.data;
|
|
5887
|
+
if (data.hello) {
|
|
5888
|
+
void hello(data.hello);
|
|
5889
|
+
return;
|
|
5890
|
+
}
|
|
5891
|
+
if (!open) return;
|
|
5892
|
+
dispatch(handlers, data).then(
|
|
5893
|
+
(result) => {
|
|
5894
|
+
if (result instanceof Uint8Array) {
|
|
5895
|
+
const bytes = result.slice();
|
|
5896
|
+
reply({ id: data.id, result: bytes }, [bytes.buffer]);
|
|
5897
|
+
} else reply({ id: data.id, result: result ?? null });
|
|
5898
|
+
},
|
|
5899
|
+
(err) => {
|
|
5900
|
+
const code = err?.code;
|
|
5901
|
+
reply({
|
|
5902
|
+
id: data.id,
|
|
5903
|
+
errno: typeof code === "string" ? code : "EIO",
|
|
5904
|
+
message: err instanceof Error ? err.message : String(err)
|
|
5905
|
+
});
|
|
5906
|
+
}
|
|
5907
|
+
);
|
|
5908
|
+
});
|
|
5909
|
+
port.start?.();
|
|
5910
|
+
return { invalidate: (paths) => reply({ invalidate: paths }) };
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5913
|
+
// src/mount/connection.ts
|
|
5914
|
+
var DRIVER_TIMEOUT_MS = 3e4;
|
|
5915
|
+
function unref(timer) {
|
|
5916
|
+
timer.unref?.();
|
|
5917
|
+
return timer;
|
|
5918
|
+
}
|
|
5919
|
+
function errnoError(code, message2) {
|
|
5920
|
+
return Object.assign(new Error(`${code}: ${message2}`), { code });
|
|
5921
|
+
}
|
|
5922
|
+
var DriverConnection = class _DriverConnection {
|
|
5923
|
+
capabilities;
|
|
5924
|
+
failure;
|
|
5925
|
+
port;
|
|
5926
|
+
waiting;
|
|
5927
|
+
timeoutMs;
|
|
5928
|
+
nextId = 0;
|
|
5929
|
+
onFail;
|
|
5930
|
+
constructor(port, capabilities, waiting, timeoutMs, onFail) {
|
|
5931
|
+
this.port = port;
|
|
5932
|
+
this.capabilities = capabilities;
|
|
5933
|
+
this.waiting = waiting;
|
|
5934
|
+
this.timeoutMs = timeoutMs;
|
|
5935
|
+
this.onFail = onFail;
|
|
5936
|
+
}
|
|
5937
|
+
static async open(port, mount, options = {}) {
|
|
5938
|
+
const timeoutMs = options.timeoutMs ?? DRIVER_TIMEOUT_MS;
|
|
5939
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
5940
|
+
const greeted = Promise.withResolvers();
|
|
5941
|
+
port.addEventListener("message", (event) => {
|
|
5942
|
+
const data = event.data;
|
|
5943
|
+
if (data.hello) return greeted.resolve(data.hello);
|
|
5944
|
+
if (data.invalidate !== void 0) return options.onInvalidate?.(data.invalidate);
|
|
5945
|
+
const call = waiting.get(data.id);
|
|
5946
|
+
if (!call) return;
|
|
5947
|
+
waiting.delete(data.id);
|
|
5948
|
+
clearTimeout(call.timer);
|
|
5949
|
+
if (data.errno) call.reject(errnoError(data.errno, data.message ?? data.errno));
|
|
5950
|
+
else call.resolve(data.result);
|
|
5951
|
+
});
|
|
5952
|
+
port.start?.();
|
|
5953
|
+
port.postMessage({ hello: { protocol: DRIVER_PROTOCOL, mount } });
|
|
5954
|
+
const timer = unref(
|
|
5955
|
+
setTimeout(
|
|
5956
|
+
() => greeted.reject(errnoError("EIO", `the driver did not answer within ${timeoutMs} ms`)),
|
|
5957
|
+
timeoutMs
|
|
5958
|
+
)
|
|
5959
|
+
);
|
|
5960
|
+
let hello;
|
|
5961
|
+
try {
|
|
5962
|
+
hello = await greeted.promise;
|
|
5963
|
+
} finally {
|
|
5964
|
+
clearTimeout(timer);
|
|
5965
|
+
}
|
|
5966
|
+
const refused = driverVersionError(hello.protocol);
|
|
5967
|
+
if (refused) throw errnoError("EPROTO", refused);
|
|
5968
|
+
if (hello.error) throw errnoError(hello.errno ?? "EIO", hello.error);
|
|
5969
|
+
return new _DriverConnection(port, hello.capabilities ?? {}, waiting, timeoutMs, options.onFail);
|
|
5970
|
+
}
|
|
5971
|
+
call(call, transfer = []) {
|
|
5972
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
5973
|
+
const id = ++this.nextId;
|
|
5974
|
+
return new Promise((resolve, reject) => {
|
|
5975
|
+
const timer = unref(
|
|
5976
|
+
setTimeout(() => {
|
|
5977
|
+
this.waiting.delete(id);
|
|
5978
|
+
const error = errnoError(
|
|
5979
|
+
"EIO",
|
|
5980
|
+
`the driver did not answer ${call.op} within ${this.timeoutMs} ms`
|
|
5981
|
+
);
|
|
5982
|
+
reject(error);
|
|
5983
|
+
this.fail(error);
|
|
5984
|
+
this.onFail?.(error);
|
|
5985
|
+
}, this.timeoutMs)
|
|
5986
|
+
);
|
|
5987
|
+
this.waiting.set(id, { resolve, reject, timer });
|
|
5988
|
+
this.port.postMessage({ ...call, id }, transfer);
|
|
5989
|
+
});
|
|
5990
|
+
}
|
|
5991
|
+
fail(error) {
|
|
5992
|
+
if (this.failure) return;
|
|
5993
|
+
this.failure = error;
|
|
5994
|
+
for (const call of this.waiting.values()) {
|
|
5995
|
+
clearTimeout(call.timer);
|
|
5996
|
+
call.reject(error);
|
|
5997
|
+
}
|
|
5998
|
+
this.waiting.clear();
|
|
5999
|
+
}
|
|
6000
|
+
close() {
|
|
6001
|
+
this.fail(errnoError("EIO", "the file system is unmounted"));
|
|
6002
|
+
this.port.close?.();
|
|
6003
|
+
}
|
|
6004
|
+
};
|
|
6005
|
+
|
|
6006
|
+
// src/mount/mount-fs.ts
|
|
6007
|
+
var MAX_LINKS4 = 40;
|
|
6008
|
+
var DEFAULT_IO = 8 * 1024 * 1024;
|
|
6009
|
+
var UNITS = { "": 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4 };
|
|
6010
|
+
function parseSize(text) {
|
|
6011
|
+
const match = /^(\d+(?:\.\d+)?)\s*([kmgt]?)i?b?$/i.exec(text.trim());
|
|
6012
|
+
if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
|
|
6013
|
+
return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
|
|
6014
|
+
}
|
|
6015
|
+
var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
6016
|
+
function heldUnder(held, target) {
|
|
6017
|
+
for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
|
|
6018
|
+
return false;
|
|
6019
|
+
}
|
|
6020
|
+
var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
6021
|
+
function statOf(attr, path, dev) {
|
|
6022
|
+
const mtime = new Date(attr.mtime);
|
|
6023
|
+
const fallback = attr.kind === "directory" ? 16877 : attr.kind === "symlink" ? 41471 : 33188;
|
|
6024
|
+
return {
|
|
6025
|
+
isFile: attr.kind === "file",
|
|
6026
|
+
isDirectory: attr.kind === "directory",
|
|
6027
|
+
isSymbolicLink: attr.kind === "symlink",
|
|
6028
|
+
size: attr.size,
|
|
6029
|
+
mode: attr.mode ?? fallback,
|
|
6030
|
+
mtime,
|
|
6031
|
+
atime: mtime,
|
|
6032
|
+
ctime: mtime,
|
|
6033
|
+
ino: attr.ino ?? inodeOf(path),
|
|
6034
|
+
dev
|
|
6035
|
+
};
|
|
6036
|
+
}
|
|
6037
|
+
function concat4(chunks2, total) {
|
|
6038
|
+
if (chunks2.length === 1 && chunks2[0]?.length === total) return chunks2[0];
|
|
6039
|
+
const out = new Uint8Array(total);
|
|
6040
|
+
let at = 0;
|
|
6041
|
+
for (const chunk2 of chunks2) {
|
|
6042
|
+
out.set(chunk2, at);
|
|
6043
|
+
at += chunk2.length;
|
|
6044
|
+
}
|
|
6045
|
+
return out;
|
|
6046
|
+
}
|
|
6047
|
+
var MountTable = class {
|
|
6048
|
+
mounts = /* @__PURE__ */ new Map();
|
|
6049
|
+
devices = 0;
|
|
6050
|
+
pending = /* @__PURE__ */ new Set();
|
|
6051
|
+
deps;
|
|
6052
|
+
constructor(deps) {
|
|
6053
|
+
this.deps = deps;
|
|
6054
|
+
}
|
|
6055
|
+
list() {
|
|
6056
|
+
return [...this.mounts.values()].map(({ type, source, target, options, state, error }) => ({
|
|
6057
|
+
type,
|
|
6058
|
+
source,
|
|
6059
|
+
target,
|
|
6060
|
+
options: { ...options },
|
|
6061
|
+
state,
|
|
6062
|
+
...error ? { error } : {}
|
|
6063
|
+
}));
|
|
6064
|
+
}
|
|
6065
|
+
locate(path) {
|
|
6066
|
+
let best;
|
|
6067
|
+
for (const mount of this.mounts.values()) {
|
|
6068
|
+
if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
|
|
6069
|
+
best = mount;
|
|
6070
|
+
}
|
|
6071
|
+
if (!best) return void 0;
|
|
6072
|
+
const rel = best.target === "/" ? path : path.slice(best.target.length);
|
|
6073
|
+
return { mount: best, rel: rel || "/" };
|
|
6074
|
+
}
|
|
6075
|
+
async mount(spec, fs) {
|
|
6076
|
+
const target = normalizePath(spec.target);
|
|
6077
|
+
if (this.mounts.has(target) || this.pending.has(target)) {
|
|
6078
|
+
throw errnoError("EBUSY", `${target} is already mounted`);
|
|
6079
|
+
}
|
|
6080
|
+
this.pending.add(target);
|
|
6081
|
+
try {
|
|
6082
|
+
return await this.attach(spec, target, fs);
|
|
6083
|
+
} finally {
|
|
6084
|
+
this.pending.delete(target);
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
async attach(spec, target, fs) {
|
|
6088
|
+
const st = await fs.stat(target);
|
|
6089
|
+
if (!st.isDirectory) throw errnoError("ENOTDIR", target);
|
|
6090
|
+
const options = { ...spec.options };
|
|
6091
|
+
const opened2 = await this.deps.open(spec.type, { ...spec, target, options });
|
|
6092
|
+
let mounted;
|
|
6093
|
+
const started = Promise.withResolvers();
|
|
6094
|
+
opened2.onCrash?.((error) => {
|
|
6095
|
+
if (mounted) this.crashed(mounted, error);
|
|
6096
|
+
else
|
|
6097
|
+
started.reject(
|
|
6098
|
+
errnoError("EIO", `the ${spec.type} driver failed to start: ${error.message}`)
|
|
6099
|
+
);
|
|
6100
|
+
});
|
|
6101
|
+
let conn;
|
|
6102
|
+
try {
|
|
6103
|
+
conn = await Promise.race([
|
|
6104
|
+
DriverConnection.open(
|
|
6105
|
+
opened2.port,
|
|
6106
|
+
{ source: spec.source, options },
|
|
6107
|
+
{
|
|
6108
|
+
...this.deps.timeoutMs ? { timeoutMs: this.deps.timeoutMs } : {},
|
|
6109
|
+
onInvalidate: (paths) => this.invalidated(target, paths),
|
|
6110
|
+
onFail: (error) => {
|
|
6111
|
+
if (mounted) this.crashed(mounted, error);
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
),
|
|
6115
|
+
started.promise
|
|
6116
|
+
]);
|
|
6117
|
+
} catch (err) {
|
|
6118
|
+
opened2.dispose();
|
|
6119
|
+
throw err;
|
|
6120
|
+
}
|
|
6121
|
+
const caps = conn.capabilities;
|
|
6122
|
+
const mount = {
|
|
6123
|
+
type: spec.type,
|
|
6124
|
+
source: spec.source,
|
|
6125
|
+
target,
|
|
6126
|
+
options,
|
|
6127
|
+
state: "ok",
|
|
6128
|
+
dev: 256 + ++this.devices,
|
|
6129
|
+
conn,
|
|
6130
|
+
caps,
|
|
6131
|
+
maxFile: options.maxfile !== void 0 ? parseSize(options.maxfile) : caps.maxFile ?? 0,
|
|
6132
|
+
attrs: /* @__PURE__ */ new Map(),
|
|
6133
|
+
lists: /* @__PURE__ */ new Map(),
|
|
6134
|
+
dispose: () => opened2.dispose()
|
|
6135
|
+
};
|
|
6136
|
+
mounted = mount;
|
|
6137
|
+
this.mounts.set(target, mount);
|
|
6138
|
+
return this.list().find((m) => m.target === target);
|
|
6139
|
+
}
|
|
6140
|
+
umount(target) {
|
|
6141
|
+
const at = normalizePath(target);
|
|
6142
|
+
const mount = this.mounts.get(at);
|
|
6143
|
+
if (!mount) throw errnoError("EINVAL", `${at} is not mounted`);
|
|
6144
|
+
if (this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
|
|
6145
|
+
this.mounts.delete(at);
|
|
6146
|
+
mount.conn.close();
|
|
6147
|
+
mount.dispose();
|
|
6148
|
+
}
|
|
6149
|
+
crashed(mount, error) {
|
|
6150
|
+
mount.state = "failed";
|
|
6151
|
+
mount.attrs.clear();
|
|
6152
|
+
mount.lists.clear();
|
|
6153
|
+
mount.error = error.message;
|
|
6154
|
+
mount.conn.fail(
|
|
6155
|
+
errnoError("EIO", `the ${mount.type} driver for ${mount.target} failed: ${error.message}`)
|
|
6156
|
+
);
|
|
6157
|
+
}
|
|
6158
|
+
invalidated(target, paths) {
|
|
6159
|
+
const mount = this.mounts.get(target);
|
|
6160
|
+
if (!mount) return;
|
|
6161
|
+
if (paths === true) {
|
|
6162
|
+
mount.attrs.clear();
|
|
6163
|
+
mount.lists.clear();
|
|
6164
|
+
this.deps.changed?.(target);
|
|
6165
|
+
return;
|
|
6166
|
+
}
|
|
6167
|
+
const full = paths.map((p) => normalizePath(`${target}/${p}`));
|
|
6168
|
+
for (const path of full) this.forget(mount, path);
|
|
6169
|
+
this.deps.changed?.(...full);
|
|
6170
|
+
}
|
|
6171
|
+
forget(mount, path) {
|
|
6172
|
+
for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
|
|
6173
|
+
mount.lists.delete(path);
|
|
6174
|
+
mount.lists.delete(parentOf4(path));
|
|
6175
|
+
for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
|
|
6176
|
+
}
|
|
6177
|
+
async getattr(mount, path, rel) {
|
|
6178
|
+
if (mount.conn.failure) throw mount.conn.failure;
|
|
6179
|
+
const cached = mount.attrs.get(path);
|
|
6180
|
+
if (cached && cached.expires > Date.now()) return cached.value;
|
|
6181
|
+
const attr = await mount.conn.call({ op: "getattr", path: rel });
|
|
6182
|
+
const ttl = mount.caps.attrTtl ?? 1e3;
|
|
6183
|
+
if (ttl > 0) mount.attrs.set(path, { value: attr, expires: Date.now() + ttl });
|
|
6184
|
+
return attr;
|
|
6185
|
+
}
|
|
6186
|
+
async readdir(mount, path, rel) {
|
|
6187
|
+
if (mount.conn.failure) throw mount.conn.failure;
|
|
6188
|
+
const cached = mount.lists.get(path);
|
|
6189
|
+
if (cached && cached.expires > Date.now()) return cached.value;
|
|
6190
|
+
const entries = await mount.conn.call({ op: "readdir", path: rel });
|
|
6191
|
+
const ttl = mount.caps.entryTtl ?? mount.caps.attrTtl ?? 1e3;
|
|
6192
|
+
if (ttl > 0) mount.lists.set(path, { value: entries, expires: Date.now() + ttl });
|
|
6193
|
+
return entries;
|
|
6194
|
+
}
|
|
6195
|
+
async statfs(path) {
|
|
6196
|
+
const found = this.locate(normalizePath(path));
|
|
6197
|
+
if (!found) return void 0;
|
|
6198
|
+
const st = await found.mount.conn.call({ op: "statfs" });
|
|
6199
|
+
if (!st) return { quota: 0, usage: 0 };
|
|
6200
|
+
return { quota: st.blocks * st.bsize, usage: (st.blocks - st.bfree) * st.bsize };
|
|
6201
|
+
}
|
|
6202
|
+
wrap(base) {
|
|
6203
|
+
return new MountFs(this, base);
|
|
6204
|
+
}
|
|
6205
|
+
};
|
|
6206
|
+
var MountFs = class {
|
|
6207
|
+
table;
|
|
6208
|
+
base;
|
|
6209
|
+
constructor(table, base) {
|
|
6210
|
+
this.table = table;
|
|
6211
|
+
this.base = base;
|
|
6212
|
+
}
|
|
6213
|
+
at(path) {
|
|
6214
|
+
return this.table.locate(normalizePath(path));
|
|
6215
|
+
}
|
|
6216
|
+
writable(mount, path) {
|
|
6217
|
+
if (mount.caps.readonly) throw errnoError("EROFS", path);
|
|
6218
|
+
}
|
|
6219
|
+
async follow(path, hops = 0, create = false) {
|
|
6220
|
+
const found = this.at(path);
|
|
6221
|
+
if (!found) return path;
|
|
6222
|
+
const real = normalizePath(path);
|
|
6223
|
+
let attr;
|
|
6224
|
+
try {
|
|
6225
|
+
attr = await this.table.getattr(found.mount, real, found.rel);
|
|
6226
|
+
} catch (err) {
|
|
6227
|
+
if (create && err.code === "ENOENT") return real;
|
|
6228
|
+
throw err;
|
|
6229
|
+
}
|
|
6230
|
+
if (attr.kind !== "symlink") return real;
|
|
6231
|
+
if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
|
|
6232
|
+
const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
6233
|
+
return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
|
|
6234
|
+
}
|
|
6235
|
+
async open(mount, rel, write) {
|
|
6236
|
+
return await mount.conn.call({
|
|
6237
|
+
op: "open",
|
|
6238
|
+
path: rel,
|
|
6239
|
+
write,
|
|
6240
|
+
create: write,
|
|
6241
|
+
truncate: write,
|
|
6242
|
+
exclusive: false
|
|
6243
|
+
});
|
|
6244
|
+
}
|
|
6245
|
+
resolvePath(base, path) {
|
|
6246
|
+
return this.base.resolvePath(base, path);
|
|
6247
|
+
}
|
|
6248
|
+
async readFile(path) {
|
|
6249
|
+
return new TextDecoder().decode(await this.readFileBuffer(path));
|
|
6250
|
+
}
|
|
6251
|
+
async readFileBuffer(path) {
|
|
6252
|
+
const real = await this.follow(path);
|
|
6253
|
+
const found = this.at(real);
|
|
6254
|
+
if (!found) return this.base.readFileBuffer(real);
|
|
6255
|
+
const { mount, rel } = found;
|
|
6256
|
+
const attr = await this.table.getattr(mount, real, rel);
|
|
6257
|
+
if (attr.kind === "directory") throw errnoError("EISDIR", real);
|
|
6258
|
+
if (mount.maxFile > 0 && attr.size > mount.maxFile) {
|
|
6259
|
+
throw errnoError("EFBIG", `${real} is larger than this mount's maxfile (${mount.maxFile})`);
|
|
6260
|
+
}
|
|
6261
|
+
const fh = await this.open(mount, rel, false);
|
|
6262
|
+
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
6263
|
+
const chunks2 = [];
|
|
6264
|
+
let total = 0;
|
|
6265
|
+
try {
|
|
6266
|
+
for (; ; ) {
|
|
6267
|
+
const bytes = await mount.conn.call({
|
|
6268
|
+
op: "read",
|
|
6269
|
+
fh,
|
|
6270
|
+
offset: total,
|
|
6271
|
+
size: chunk2
|
|
6272
|
+
});
|
|
6273
|
+
if (bytes.length === 0) break;
|
|
6274
|
+
chunks2.push(bytes);
|
|
6275
|
+
total += bytes.length;
|
|
6276
|
+
if (bytes.length < chunk2 && total >= attr.size) break;
|
|
6277
|
+
}
|
|
6278
|
+
} finally {
|
|
6279
|
+
await mount.conn.call({ op: "release", fh });
|
|
6280
|
+
}
|
|
6281
|
+
return concat4(chunks2, total);
|
|
6282
|
+
}
|
|
6283
|
+
async writeFile(path, content) {
|
|
6284
|
+
const real = await this.follow(path, 0, true);
|
|
6285
|
+
const found = this.at(real);
|
|
6286
|
+
if (!found) return this.base.writeFile(real, content);
|
|
6287
|
+
const { mount, rel } = found;
|
|
6288
|
+
this.writable(mount, real);
|
|
6289
|
+
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
6290
|
+
if (mount.maxFile > 0 && bytes.length > mount.maxFile) {
|
|
6291
|
+
throw errnoError(
|
|
6292
|
+
"EFBIG",
|
|
6293
|
+
`${real} would be larger than this mount's maxfile (${mount.maxFile})`
|
|
6294
|
+
);
|
|
6295
|
+
}
|
|
6296
|
+
this.table.forget(mount, real);
|
|
6297
|
+
const fh = await this.open(mount, rel, true);
|
|
6298
|
+
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
6299
|
+
try {
|
|
6300
|
+
for (let offset = 0; offset < bytes.length; offset += chunk2) {
|
|
6301
|
+
await mount.conn.call({
|
|
6302
|
+
op: "write",
|
|
6303
|
+
fh,
|
|
6304
|
+
offset,
|
|
6305
|
+
bytes: bytes.slice(offset, offset + chunk2)
|
|
6306
|
+
});
|
|
6307
|
+
}
|
|
6308
|
+
} finally {
|
|
6309
|
+
await mount.conn.call({ op: "release", fh });
|
|
6310
|
+
this.table.forget(mount, real);
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
async exists(path) {
|
|
6314
|
+
if (!this.at(path)) return this.base.exists(path);
|
|
6315
|
+
try {
|
|
6316
|
+
await this.stat(path);
|
|
6317
|
+
return true;
|
|
6318
|
+
} catch (err) {
|
|
6319
|
+
const code = err.code;
|
|
6320
|
+
if (code === "ENOENT" || code === "ENOTDIR") return false;
|
|
6321
|
+
throw err;
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
async lstat(path) {
|
|
6325
|
+
const found = this.at(path);
|
|
6326
|
+
if (!found) return this.base.lstat(path);
|
|
6327
|
+
const real = normalizePath(path);
|
|
6328
|
+
return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount.dev);
|
|
6329
|
+
}
|
|
6330
|
+
async stat(path) {
|
|
6331
|
+
const real = await this.follow(path);
|
|
6332
|
+
return this.at(real) ? this.lstat(real) : this.base.stat(real);
|
|
6333
|
+
}
|
|
6334
|
+
async entries(path) {
|
|
6335
|
+
const real = await this.follow(path);
|
|
6336
|
+
const found = this.at(real);
|
|
6337
|
+
if (!found) throw errnoError("ENOTDIR", real);
|
|
6338
|
+
return {
|
|
6339
|
+
real,
|
|
6340
|
+
mount: found.mount,
|
|
6341
|
+
list: await this.table.readdir(found.mount, real, found.rel)
|
|
6342
|
+
};
|
|
6343
|
+
}
|
|
6344
|
+
async readdir(path) {
|
|
6345
|
+
if (!this.at(path)) return this.base.readdir(path);
|
|
6346
|
+
return (await this.entries(path)).list.map((e) => e.name);
|
|
6347
|
+
}
|
|
6348
|
+
async readdirStat(path) {
|
|
6349
|
+
if (!this.at(path)) return this.base.readdirStat(path);
|
|
6350
|
+
const { real, mount, list } = await this.entries(path);
|
|
6351
|
+
return Promise.all(
|
|
6352
|
+
list.map(async (entry) => {
|
|
6353
|
+
const child = normalizePath(`${real}/${entry.name}`);
|
|
6354
|
+
if (entry.attr && mount.caps.listingStats) {
|
|
6355
|
+
return [entry.name, statOf(entry.attr, child, mount.dev)];
|
|
6356
|
+
}
|
|
6357
|
+
return [entry.name, await this.lstat(child).catch(() => null)];
|
|
6358
|
+
})
|
|
6359
|
+
);
|
|
6360
|
+
}
|
|
6361
|
+
async mkdir(path, options = {}) {
|
|
6362
|
+
const found = this.at(path);
|
|
6363
|
+
if (!found) return this.base.mkdir(path, options);
|
|
6364
|
+
const { mount, rel } = found;
|
|
6365
|
+
const real = normalizePath(path);
|
|
6366
|
+
this.writable(mount, real);
|
|
6367
|
+
if (!options.recursive) {
|
|
6368
|
+
if (rel === "/") throw errnoError("EEXIST", real);
|
|
6369
|
+
this.table.forget(mount, real);
|
|
6370
|
+
await mount.conn.call({ op: "mkdir", path: rel });
|
|
6371
|
+
return;
|
|
6372
|
+
}
|
|
6373
|
+
const parts = rel.split("/").filter(Boolean);
|
|
6374
|
+
for (let n = 1; n <= parts.length; n++) {
|
|
6375
|
+
const sub = `/${parts.slice(0, n).join("/")}`;
|
|
6376
|
+
const full = normalizePath(`${mount.target}${sub}`);
|
|
6377
|
+
const exists = await this.table.getattr(mount, full, sub).catch(() => void 0);
|
|
6378
|
+
if (exists?.kind === "directory") continue;
|
|
6379
|
+
if (exists) throw errnoError("ENOTDIR", full);
|
|
6380
|
+
this.table.forget(mount, full);
|
|
6381
|
+
await mount.conn.call({ op: "mkdir", path: sub });
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
async rm(path, options = {}) {
|
|
6385
|
+
const found = this.at(path);
|
|
6386
|
+
if (!found) return this.base.rm(path, options);
|
|
6387
|
+
const { mount, rel } = found;
|
|
6388
|
+
const real = normalizePath(path);
|
|
6389
|
+
if (rel === "/") throw errnoError("EBUSY", `${real} is a mount point`);
|
|
6390
|
+
this.writable(mount, real);
|
|
6391
|
+
let attr;
|
|
6392
|
+
try {
|
|
6393
|
+
attr = await this.table.getattr(mount, real, rel);
|
|
6394
|
+
} catch (err) {
|
|
6395
|
+
if (options.force && err.code === "ENOENT") return;
|
|
6396
|
+
throw err;
|
|
6397
|
+
}
|
|
6398
|
+
this.table.forget(mount, real);
|
|
6399
|
+
if (attr.kind === "directory" && options.recursive) {
|
|
6400
|
+
for (const entry of await this.table.readdir(mount, real, rel)) {
|
|
6401
|
+
await this.rm(`${real}/${entry.name}`, { recursive: true, force: options.force ?? false });
|
|
6402
|
+
}
|
|
6403
|
+
this.table.forget(mount, real);
|
|
6404
|
+
}
|
|
6405
|
+
await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel });
|
|
6406
|
+
this.table.forget(mount, real);
|
|
6407
|
+
}
|
|
6408
|
+
async rename(from, to) {
|
|
6409
|
+
const a = this.at(from);
|
|
6410
|
+
const b = this.at(to);
|
|
6411
|
+
if (!a && !b) return this.base.rename(from, to);
|
|
6412
|
+
if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
|
|
6413
|
+
this.writable(a.mount, normalizePath(to));
|
|
6414
|
+
this.table.forget(a.mount, normalizePath(from));
|
|
6415
|
+
this.table.forget(a.mount, normalizePath(to));
|
|
6416
|
+
try {
|
|
6417
|
+
await a.mount.conn.call({ op: "rename", from: a.rel, to: b.rel });
|
|
6418
|
+
} finally {
|
|
6419
|
+
this.table.forget(a.mount, normalizePath(from));
|
|
6420
|
+
this.table.forget(a.mount, normalizePath(to));
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
async symlink(target, path) {
|
|
6424
|
+
const found = this.at(path);
|
|
6425
|
+
if (!found) return this.base.symlink(target, path);
|
|
6426
|
+
this.writable(found.mount, normalizePath(path));
|
|
6427
|
+
if (!found.mount.caps.symlinks)
|
|
6428
|
+
throw errnoError("EPERM", `${path}: this file system has no symlinks`);
|
|
6429
|
+
this.table.forget(found.mount, normalizePath(path));
|
|
6430
|
+
await found.mount.conn.call({ op: "symlink", target, path: found.rel });
|
|
6431
|
+
}
|
|
6432
|
+
async readlink(path) {
|
|
6433
|
+
const found = this.at(path);
|
|
6434
|
+
if (!found) return this.base.readlink(path);
|
|
6435
|
+
return await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
6436
|
+
}
|
|
6437
|
+
async chmod(path, mode) {
|
|
6438
|
+
const found = this.at(path);
|
|
6439
|
+
if (!found) return this.base.chmod(path, mode);
|
|
6440
|
+
this.writable(found.mount, normalizePath(path));
|
|
6441
|
+
if (!found.mount.caps.chmod) return;
|
|
6442
|
+
this.table.forget(found.mount, normalizePath(path));
|
|
6443
|
+
await found.mount.conn.call({ op: "setattr", path: found.rel, mode });
|
|
6444
|
+
}
|
|
6445
|
+
async utimes(path, atime, mtime) {
|
|
6446
|
+
const found = this.at(path);
|
|
6447
|
+
if (!found) return this.base.utimes(path, atime, mtime);
|
|
6448
|
+
this.writable(found.mount, normalizePath(path));
|
|
6449
|
+
this.table.forget(found.mount, normalizePath(path));
|
|
6450
|
+
await found.mount.conn.call({ op: "setattr", path: found.rel, mtime: mtime.getTime() });
|
|
6451
|
+
}
|
|
6452
|
+
};
|
|
6453
|
+
|
|
6454
|
+
// src/mount/tmpfs.ts
|
|
6455
|
+
function tmpfs() {
|
|
6456
|
+
let inodes = 0;
|
|
6457
|
+
const make = (kind, mode) => ({
|
|
6458
|
+
kind,
|
|
6459
|
+
ino: ++inodes,
|
|
6460
|
+
mode,
|
|
6461
|
+
mtime: Date.now(),
|
|
6462
|
+
data: new Uint8Array(0),
|
|
6463
|
+
target: "",
|
|
6464
|
+
children: /* @__PURE__ */ new Map()
|
|
6465
|
+
});
|
|
6466
|
+
const root = make("directory", 16877);
|
|
6467
|
+
const handles = /* @__PURE__ */ new Map();
|
|
6468
|
+
let nextFh = 0;
|
|
6469
|
+
const parts = (path) => path.split("/").filter(Boolean);
|
|
6470
|
+
function find(path) {
|
|
6471
|
+
let node = root;
|
|
6472
|
+
for (const name2 of parts(path)) {
|
|
6473
|
+
if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
|
|
6474
|
+
const next = node.children.get(name2);
|
|
6475
|
+
if (!next) throw fsError2("ENOENT", path);
|
|
6476
|
+
node = next;
|
|
6477
|
+
}
|
|
6478
|
+
return node;
|
|
6479
|
+
}
|
|
6480
|
+
function parent(path) {
|
|
6481
|
+
const names = parts(path);
|
|
6482
|
+
const name2 = names.pop();
|
|
6483
|
+
if (name2 === void 0) throw fsError2("EBUSY", "the root of a mount");
|
|
6484
|
+
const dir = find(`/${names.join("/")}`);
|
|
6485
|
+
if (dir.kind !== "directory") throw fsError2("ENOTDIR", path);
|
|
6486
|
+
return { dir, name: name2 };
|
|
6487
|
+
}
|
|
6488
|
+
const attr = (node) => ({
|
|
6489
|
+
kind: node.kind,
|
|
6490
|
+
size: node.kind === "file" ? node.data.length : node.kind === "symlink" ? node.target.length : 4096,
|
|
6491
|
+
mtime: node.mtime,
|
|
6492
|
+
mode: node.mode,
|
|
6493
|
+
ino: node.ino
|
|
6494
|
+
});
|
|
6495
|
+
const handle = (fh) => {
|
|
6496
|
+
const found = handles.get(fh);
|
|
6497
|
+
if (!found) throw fsError2("EBADF", `handle ${fh}`);
|
|
6498
|
+
return found;
|
|
6499
|
+
};
|
|
6500
|
+
function opened2(path, flags) {
|
|
6501
|
+
const { dir, name: name2 } = parent(path);
|
|
6502
|
+
const existing = dir.children.get(name2);
|
|
6503
|
+
if (existing && flags.exclusive) throw fsError2("EEXIST", path);
|
|
6504
|
+
if (existing?.kind === "directory" && flags.write) throw fsError2("EISDIR", path);
|
|
6505
|
+
if (existing) return existing;
|
|
6506
|
+
if (!flags.create) throw fsError2("ENOENT", path);
|
|
6507
|
+
const node = make("file", 33188);
|
|
6508
|
+
dir.children.set(name2, node);
|
|
6509
|
+
dir.mtime = Date.now();
|
|
6510
|
+
return node;
|
|
6511
|
+
}
|
|
6512
|
+
return {
|
|
6513
|
+
async getattr(path) {
|
|
6514
|
+
return attr(find(path));
|
|
6515
|
+
},
|
|
6516
|
+
async readdir(path) {
|
|
6517
|
+
const dir = find(path);
|
|
6518
|
+
if (dir.kind !== "directory") throw fsError2("ENOTDIR", path);
|
|
6519
|
+
return [...dir.children].map(
|
|
6520
|
+
([name2, node]) => ({ name: name2, kind: node.kind, attr: attr(node) })
|
|
6521
|
+
);
|
|
6522
|
+
},
|
|
6523
|
+
async open(path, flags) {
|
|
6524
|
+
const node = opened2(path, flags);
|
|
6525
|
+
const data = flags.truncate ? new Uint8Array(0) : node.data.slice();
|
|
6526
|
+
handles.set(++nextFh, { node, data, length: data.length, dirty: flags.truncate });
|
|
6527
|
+
return nextFh;
|
|
6528
|
+
},
|
|
6529
|
+
async read(fh, offset, size) {
|
|
6530
|
+
const h = handle(fh);
|
|
6531
|
+
return h.data.slice(offset, Math.min(h.length, offset + size));
|
|
6532
|
+
},
|
|
6533
|
+
async write(fh, offset, bytes) {
|
|
6534
|
+
const h = handle(fh);
|
|
6535
|
+
const end = offset + bytes.length;
|
|
6536
|
+
if (end > h.data.length) {
|
|
6537
|
+
const grown = new Uint8Array(Math.max(end, h.data.length * 2));
|
|
6538
|
+
grown.set(h.data.subarray(0, h.length));
|
|
6539
|
+
h.data = grown;
|
|
6540
|
+
}
|
|
6541
|
+
h.data.set(bytes, offset);
|
|
6542
|
+
h.length = Math.max(h.length, end);
|
|
6543
|
+
h.dirty = true;
|
|
6544
|
+
},
|
|
6545
|
+
async release(fh) {
|
|
6546
|
+
const h = handle(fh);
|
|
6547
|
+
handles.delete(fh);
|
|
6548
|
+
if (!h.dirty) return;
|
|
6549
|
+
h.node.data = h.data.slice(0, h.length);
|
|
6550
|
+
h.node.mtime = Date.now();
|
|
6551
|
+
},
|
|
6552
|
+
async mkdir(path) {
|
|
6553
|
+
const { dir, name: name2 } = parent(path);
|
|
6554
|
+
if (dir.children.has(name2)) throw fsError2("EEXIST", path);
|
|
6555
|
+
dir.children.set(name2, make("directory", 16877));
|
|
6556
|
+
},
|
|
6557
|
+
async rmdir(path) {
|
|
6558
|
+
const { dir, name: name2 } = parent(path);
|
|
6559
|
+
const node = dir.children.get(name2);
|
|
6560
|
+
if (!node) throw fsError2("ENOENT", path);
|
|
6561
|
+
if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
|
|
6562
|
+
if (node.children.size > 0) throw fsError2("ENOTEMPTY", path);
|
|
6563
|
+
dir.children.delete(name2);
|
|
6564
|
+
},
|
|
6565
|
+
async unlink(path) {
|
|
6566
|
+
const { dir, name: name2 } = parent(path);
|
|
6567
|
+
const node = dir.children.get(name2);
|
|
6568
|
+
if (!node) throw fsError2("ENOENT", path);
|
|
6569
|
+
if (node.kind === "directory") throw fsError2("EISDIR", path);
|
|
6570
|
+
dir.children.delete(name2);
|
|
6571
|
+
},
|
|
6572
|
+
async rename(from, to) {
|
|
6573
|
+
const a = `/${parts(from).join("/")}`;
|
|
6574
|
+
const b = `/${parts(to).join("/")}`;
|
|
6575
|
+
if (b !== a && b.startsWith(`${a}/`)) throw fsError2("EINVAL", `${to} is inside ${from}`);
|
|
6576
|
+
const source = parent(from);
|
|
6577
|
+
const node = source.dir.children.get(source.name);
|
|
6578
|
+
if (!node) throw fsError2("ENOENT", from);
|
|
6579
|
+
const target = parent(to);
|
|
6580
|
+
const replaced = target.dir.children.get(target.name);
|
|
6581
|
+
if (replaced === node) return;
|
|
6582
|
+
if (replaced?.kind === "directory" && (node.kind !== "directory" || replaced.children.size > 0)) {
|
|
6583
|
+
throw fsError2(node.kind === "directory" ? "ENOTEMPTY" : "EISDIR", to);
|
|
6584
|
+
}
|
|
6585
|
+
source.dir.children.delete(source.name);
|
|
6586
|
+
target.dir.children.set(target.name, node);
|
|
6587
|
+
},
|
|
6588
|
+
async symlink(target, path) {
|
|
6589
|
+
const { dir, name: name2 } = parent(path);
|
|
6590
|
+
if (dir.children.has(name2)) throw fsError2("EEXIST", path);
|
|
6591
|
+
const node = make("symlink", 41471);
|
|
6592
|
+
node.target = target;
|
|
6593
|
+
dir.children.set(name2, node);
|
|
6594
|
+
},
|
|
6595
|
+
async readlink(path) {
|
|
6596
|
+
const node = find(path);
|
|
6597
|
+
if (node.kind !== "symlink") throw fsError2("EINVAL", path);
|
|
6598
|
+
return node.target;
|
|
6599
|
+
},
|
|
6600
|
+
async setattr(path, change) {
|
|
6601
|
+
const node = find(path);
|
|
6602
|
+
if (change.mode !== void 0) node.mode = node.mode & ~4095 | change.mode & 4095;
|
|
6603
|
+
if (change.mtime !== void 0) node.mtime = change.mtime;
|
|
6604
|
+
},
|
|
6605
|
+
async statfs() {
|
|
6606
|
+
return { bsize: 4096, blocks: 262144, bfree: 262144 };
|
|
6607
|
+
}
|
|
6608
|
+
};
|
|
6609
|
+
}
|
|
6610
|
+
|
|
5722
6611
|
// src/process/wasi/wasi-module.ts
|
|
5723
6612
|
function importedMemory(bytes) {
|
|
5724
6613
|
let at = 8;
|
|
@@ -5879,6 +6768,7 @@ function foreignImports(bytes) {
|
|
|
5879
6768
|
}
|
|
5880
6769
|
|
|
5881
6770
|
// src/launcher.ts
|
|
6771
|
+
var PNPM_HOME = "/home/.local/share/pnpm";
|
|
5882
6772
|
var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
5883
6773
|
var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
|
|
5884
6774
|
var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
@@ -5940,7 +6830,7 @@ function childHandle(handle) {
|
|
|
5940
6830
|
onState: (listener) => handle.onState(listener)
|
|
5941
6831
|
};
|
|
5942
6832
|
}
|
|
5943
|
-
function
|
|
6833
|
+
function concat5(chunks2) {
|
|
5944
6834
|
const out = new Uint8Array(chunks2.reduce((n, chunk2) => n + chunk2.length, 0));
|
|
5945
6835
|
let offset = 0;
|
|
5946
6836
|
for (const chunk2 of chunks2) {
|
|
@@ -5968,12 +6858,24 @@ var Launcher = class {
|
|
|
5968
6858
|
ca;
|
|
5969
6859
|
transport;
|
|
5970
6860
|
watchers = new FsWatchers();
|
|
6861
|
+
pnpmHome;
|
|
6862
|
+
mounts;
|
|
6863
|
+
createDriverWorker;
|
|
6864
|
+
openFiles = /* @__PURE__ */ new Set();
|
|
6865
|
+
held = /* @__PURE__ */ new Set();
|
|
5971
6866
|
nextPid = 1e3;
|
|
5972
6867
|
boot = Date.now();
|
|
5973
6868
|
terminals = 0;
|
|
5974
6869
|
constructor(options) {
|
|
5975
|
-
this.
|
|
6870
|
+
this.mounts = new MountTable({
|
|
6871
|
+
open: (type, spec) => this.driver(type, spec),
|
|
6872
|
+
busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
|
|
6873
|
+
changed: this.watchers.changed.bind(this.watchers)
|
|
6874
|
+
});
|
|
6875
|
+
this.base = this.watchers.wrap(this.mounts.wrap(options.fs));
|
|
5976
6876
|
this.createWorker = options.createWorker;
|
|
6877
|
+
this.createDriverWorker = options.createDriverWorker;
|
|
6878
|
+
this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
|
|
5977
6879
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
5978
6880
|
this.env = { ...networkEnv(), ...options.env };
|
|
5979
6881
|
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
@@ -5984,11 +6886,17 @@ var Launcher = class {
|
|
|
5984
6886
|
ca: this.ca
|
|
5985
6887
|
});
|
|
5986
6888
|
this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
6889
|
+
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
6890
|
+
this.catalog = void 0;
|
|
6891
|
+
});
|
|
5987
6892
|
}
|
|
5988
6893
|
commands() {
|
|
5989
|
-
this.catalog ??= scanCommands(this.base,
|
|
6894
|
+
this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
|
|
5990
6895
|
return this.catalog;
|
|
5991
6896
|
}
|
|
6897
|
+
async roots() {
|
|
6898
|
+
return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
|
|
6899
|
+
}
|
|
5992
6900
|
async resolve(file, argv0, cwd) {
|
|
5993
6901
|
const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
|
|
5994
6902
|
if (name2 !== void 0) {
|
|
@@ -6142,6 +7050,10 @@ var Launcher = class {
|
|
|
6142
7050
|
forker: this.forker(pid, req),
|
|
6143
7051
|
kill: (target, sig) => this.kill(target, sig),
|
|
6144
7052
|
processes: () => ({ boot: this.boot, processes: this.list() }),
|
|
7053
|
+
openFiles: this.openFiles,
|
|
7054
|
+
held: this.held,
|
|
7055
|
+
statfs: (path) => this.mounts.statfs(path),
|
|
7056
|
+
mounts: () => this.mounts.list(),
|
|
6145
7057
|
jobs: this.jobs,
|
|
6146
7058
|
ptys: this.ptys,
|
|
6147
7059
|
net: this.net,
|
|
@@ -6226,12 +7138,66 @@ var Launcher = class {
|
|
|
6226
7138
|
if (sig !== 0) handle.signal(sig);
|
|
6227
7139
|
return true;
|
|
6228
7140
|
}
|
|
7141
|
+
async driver(type, _spec) {
|
|
7142
|
+
if (type === "tmpfs") {
|
|
7143
|
+
const { port1, port2 } = new MessageChannel();
|
|
7144
|
+
serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
|
|
7145
|
+
return {
|
|
7146
|
+
port: port1,
|
|
7147
|
+
dispose: () => {
|
|
7148
|
+
port1.close();
|
|
7149
|
+
port2.close();
|
|
7150
|
+
}
|
|
7151
|
+
};
|
|
7152
|
+
}
|
|
7153
|
+
const module = (await scanFilesystems(this.base, await this.roots())).get(type);
|
|
7154
|
+
if (!module) throw fsError("ENODEV", `unknown file system type ${type}`);
|
|
7155
|
+
if (!this.createDriverWorker)
|
|
7156
|
+
throw fsError("ENODEV", `this kernel cannot start ${type} drivers`);
|
|
7157
|
+
const code = await this.base.readFile(module);
|
|
7158
|
+
const worker = this.createDriverWorker();
|
|
7159
|
+
const driver = new MessageChannel();
|
|
7160
|
+
const client = new MessageChannel();
|
|
7161
|
+
const served = serveClient(client.port1, {
|
|
7162
|
+
launcher: async () => this,
|
|
7163
|
+
scope: "transport"
|
|
7164
|
+
});
|
|
7165
|
+
worker.postMessage({ code, driver: driver.port2, client: client.port2 }, [
|
|
7166
|
+
driver.port2,
|
|
7167
|
+
client.port2
|
|
7168
|
+
]);
|
|
7169
|
+
return {
|
|
7170
|
+
port: driver.port1,
|
|
7171
|
+
dispose: () => {
|
|
7172
|
+
worker.terminate();
|
|
7173
|
+
served.detach();
|
|
7174
|
+
driver.port1.close();
|
|
7175
|
+
},
|
|
7176
|
+
onCrash: (listener) => worker.addEventListener("error", (event) => {
|
|
7177
|
+
event.preventDefault?.();
|
|
7178
|
+
listener(new Error(String(event.message)));
|
|
7179
|
+
})
|
|
7180
|
+
};
|
|
7181
|
+
}
|
|
7182
|
+
mount(spec) {
|
|
7183
|
+
return this.mounts.mount(spec, this.fs);
|
|
7184
|
+
}
|
|
7185
|
+
umount(target) {
|
|
7186
|
+
this.mounts.umount(target);
|
|
7187
|
+
}
|
|
6229
7188
|
async prepare() {
|
|
6230
7189
|
for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
|
|
6231
7190
|
await writeCaFile(this.base, this.ca).catch(() => void 0);
|
|
6232
7191
|
}
|
|
6233
7192
|
environment(cwd, extra) {
|
|
6234
|
-
return {
|
|
7193
|
+
return {
|
|
7194
|
+
PATH: `/usr/bin:/bin:${this.pnpmHome}/bin`,
|
|
7195
|
+
HOME: "/home",
|
|
7196
|
+
PNPM_HOME: this.pnpmHome,
|
|
7197
|
+
...this.env,
|
|
7198
|
+
PWD: cwd,
|
|
7199
|
+
...extra
|
|
7200
|
+
};
|
|
6235
7201
|
}
|
|
6236
7202
|
async starting(argv, dir) {
|
|
6237
7203
|
this.catalog = void 0;
|
|
@@ -6293,7 +7259,7 @@ var Launcher = class {
|
|
|
6293
7259
|
`));
|
|
6294
7260
|
if (!planned) {
|
|
6295
7261
|
report(`${file}: command not found`);
|
|
6296
|
-
return { status: NOT_FOUND, stdout:
|
|
7262
|
+
return { status: NOT_FOUND, stdout: concat5(out), stderr: concat5(err) };
|
|
6297
7263
|
}
|
|
6298
7264
|
const fds = new FdTable();
|
|
6299
7265
|
fds.installAt(0, options.stdin ? bytesSource(options.stdin) : nullFile());
|
|
@@ -6302,7 +7268,7 @@ var Launcher = class {
|
|
|
6302
7268
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
6303
7269
|
options.onStarted?.(handle.pid);
|
|
6304
7270
|
const status = await handle.exited;
|
|
6305
|
-
return { status, stdout:
|
|
7271
|
+
return { status, stdout: concat5(out), stderr: concat5(err) };
|
|
6306
7272
|
}
|
|
6307
7273
|
};
|
|
6308
7274
|
|
|
@@ -6598,7 +7564,7 @@ async function holdLock(locks) {
|
|
|
6598
7564
|
await granted.promise;
|
|
6599
7565
|
return { name: name2, release: () => held.resolve() };
|
|
6600
7566
|
}
|
|
6601
|
-
function
|
|
7567
|
+
function concat6(chunks2) {
|
|
6602
7568
|
const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
|
|
6603
7569
|
let at = 0;
|
|
6604
7570
|
for (const chunk2 of chunks2) {
|
|
@@ -6720,7 +7686,7 @@ async function runOn(channel, argv, opts) {
|
|
|
6720
7686
|
onStderr: tee(2, onStderr)
|
|
6721
7687
|
});
|
|
6722
7688
|
const status = await child.exited;
|
|
6723
|
-
const text = (fd) => new TextDecoder().decode(
|
|
7689
|
+
const text = (fd) => new TextDecoder().decode(concat6(chunks2[fd]));
|
|
6724
7690
|
return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
|
|
6725
7691
|
}
|
|
6726
7692
|
async function terminalOn(channel, argv, opts) {
|
|
@@ -6799,6 +7765,17 @@ function fsOn(channel) {
|
|
|
6799
7765
|
}
|
|
6800
7766
|
};
|
|
6801
7767
|
}
|
|
7768
|
+
function since(channel, minor, what, run2) {
|
|
7769
|
+
if (channel.protocol[1] < minor) {
|
|
7770
|
+
return Promise.reject(
|
|
7771
|
+
new KernelCallError(
|
|
7772
|
+
`this kernel speaks protocol ${channel.protocol.join(".")}, which has no ${what}`,
|
|
7773
|
+
"ENOSYS"
|
|
7774
|
+
)
|
|
7775
|
+
);
|
|
7776
|
+
}
|
|
7777
|
+
return run2();
|
|
7778
|
+
}
|
|
6802
7779
|
async function greet(channel, lock, timeoutMs) {
|
|
6803
7780
|
channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
|
|
6804
7781
|
const timer = setTimeout(
|
|
@@ -6841,6 +7818,9 @@ async function attachKernel(port, options = {}) {
|
|
|
6841
7818
|
run: (argv, opts = {}) => runOn(channel, argv, opts),
|
|
6842
7819
|
openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
|
|
6843
7820
|
ps: () => channel.call({ op: "ps" }),
|
|
7821
|
+
mount: (spec) => since(channel, 2, "mount", () => channel.call({ op: "mount", spec })),
|
|
7822
|
+
umount: async (target) => void await since(channel, 2, "umount", () => channel.call({ op: "umount", target })),
|
|
7823
|
+
mounts: () => since(channel, 2, "mounts", () => channel.call({ op: "mounts" })),
|
|
6844
7824
|
kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
|
|
6845
7825
|
fetch: (req) => {
|
|
6846
7826
|
const { signal, ...rest } = req;
|
|
@@ -6919,6 +7899,11 @@ async function createNodeKernel(options = {}) {
|
|
|
6919
7899
|
const launcher = new Launcher({
|
|
6920
7900
|
fs,
|
|
6921
7901
|
createWorker: () => nodeWorker(file, live, terminated),
|
|
7902
|
+
createDriverWorker: () => nodeWorker(
|
|
7903
|
+
options.driverWorker ?? new URL("./node-driver-worker.js", import.meta.url),
|
|
7904
|
+
live,
|
|
7905
|
+
terminated
|
|
7906
|
+
),
|
|
6922
7907
|
...options.modules ? { modules: options.modules } : {},
|
|
6923
7908
|
...options.env ? { env: options.env } : {},
|
|
6924
7909
|
...transport ? { transport } : {}
|
|
@@ -6982,6 +7967,9 @@ async function createNodeKernel(options = {}) {
|
|
|
6982
7967
|
await fs.writeFile(path, data);
|
|
6983
7968
|
},
|
|
6984
7969
|
readFile: async (path) => fs.readFileBuffer(path),
|
|
7970
|
+
mount: (spec) => guard(() => launcher.mount(spec)),
|
|
7971
|
+
umount: (target) => guard(async () => launcher.umount(target)),
|
|
7972
|
+
mounts: async () => launcher.mounts.list(),
|
|
6985
7973
|
async connect() {
|
|
6986
7974
|
if (terminated) throw new Error(TERMINATED);
|
|
6987
7975
|
const { port1, port2 } = new MessageChannel2();
|