@ai-ecoverse/slicc-kernel 1.8.12 → 1.10.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 +28 -1
- package/dist/index.js +424 -2
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +581 -68
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +308 -16
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +1058 -166
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +308 -16
- package/dist/process-worker.js.map +4 -4
- package/package.json +1 -1
package/dist/kernel-worker.js
CHANGED
|
@@ -193,6 +193,33 @@ function pinScript(url) {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// src/client/protocol.ts
|
|
197
|
+
var PROTOCOL = [1, 1];
|
|
198
|
+
var FS_METHODS = [
|
|
199
|
+
"readFile",
|
|
200
|
+
"writeFile",
|
|
201
|
+
"stat",
|
|
202
|
+
"lstat",
|
|
203
|
+
"readdir",
|
|
204
|
+
"mkdir",
|
|
205
|
+
"rm",
|
|
206
|
+
"rename",
|
|
207
|
+
"realpath",
|
|
208
|
+
"symlink",
|
|
209
|
+
"readlink",
|
|
210
|
+
"exists"
|
|
211
|
+
];
|
|
212
|
+
function locksOf(scope = globalThis) {
|
|
213
|
+
const locks = scope.navigator?.locks;
|
|
214
|
+
return typeof locks?.request === "function" ? locks : void 0;
|
|
215
|
+
}
|
|
216
|
+
function versionError(theirs) {
|
|
217
|
+
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
218
|
+
if (major === PROTOCOL[0]) return void 0;
|
|
219
|
+
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
220
|
+
return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
196
223
|
// src/fs/types.ts
|
|
197
224
|
function fsError(code, path) {
|
|
198
225
|
return Object.assign(new Error(`${code}: ${path}`), { code });
|
|
@@ -222,13 +249,378 @@ function inodeOf(path) {
|
|
|
222
249
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
|
|
223
250
|
}
|
|
224
251
|
|
|
252
|
+
// src/kernel/signals.ts
|
|
253
|
+
var SIG = {
|
|
254
|
+
HUP: 1,
|
|
255
|
+
INT: 2,
|
|
256
|
+
QUIT: 3,
|
|
257
|
+
ILL: 4,
|
|
258
|
+
TRAP: 5,
|
|
259
|
+
ABRT: 6,
|
|
260
|
+
BUS: 7,
|
|
261
|
+
FPE: 8,
|
|
262
|
+
KILL: 9,
|
|
263
|
+
USR1: 10,
|
|
264
|
+
SEGV: 11,
|
|
265
|
+
USR2: 12,
|
|
266
|
+
PIPE: 13,
|
|
267
|
+
ALRM: 14,
|
|
268
|
+
TERM: 15,
|
|
269
|
+
STKFLT: 16,
|
|
270
|
+
CHLD: 17,
|
|
271
|
+
CONT: 18,
|
|
272
|
+
STOP: 19,
|
|
273
|
+
TSTP: 20,
|
|
274
|
+
TTIN: 21,
|
|
275
|
+
TTOU: 22,
|
|
276
|
+
URG: 23,
|
|
277
|
+
XCPU: 24,
|
|
278
|
+
XFSZ: 25,
|
|
279
|
+
VTALRM: 26,
|
|
280
|
+
PROF: 27,
|
|
281
|
+
WINCH: 28,
|
|
282
|
+
POLL: 29,
|
|
283
|
+
PWR: 30,
|
|
284
|
+
SYS: 31
|
|
285
|
+
};
|
|
286
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
287
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
288
|
+
function defaultAction(sig) {
|
|
289
|
+
if (sig === SIG.CONT) return "continue";
|
|
290
|
+
if (STOPPING.has(sig)) return "stop";
|
|
291
|
+
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
292
|
+
}
|
|
293
|
+
function isSignal(sig) {
|
|
294
|
+
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
295
|
+
}
|
|
296
|
+
function sigbit(sig) {
|
|
297
|
+
return 1 << sig;
|
|
298
|
+
}
|
|
299
|
+
var SIGNAL_BY_NAME = {
|
|
300
|
+
SIGINT: SIG.INT,
|
|
301
|
+
SIGTERM: SIG.TERM,
|
|
302
|
+
SIGKILL: SIG.KILL,
|
|
303
|
+
SIGSTOP: SIG.STOP,
|
|
304
|
+
SIGCONT: SIG.CONT
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// src/body-idle.ts
|
|
308
|
+
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
309
|
+
|
|
310
|
+
// src/transport.ts
|
|
311
|
+
var MAX_REQUEST_BODY = 64 * 1024 * 1024;
|
|
312
|
+
function serveTransport(peer, transport) {
|
|
313
|
+
const open = /* @__PURE__ */ new Map();
|
|
314
|
+
const fail = (nid, e) => {
|
|
315
|
+
open.delete(nid);
|
|
316
|
+
const { status, code } = e;
|
|
317
|
+
peer.postMessage({
|
|
318
|
+
net: "error",
|
|
319
|
+
nid,
|
|
320
|
+
message: e instanceof Error ? e.message : String(e),
|
|
321
|
+
...typeof status === "number" ? { status } : {},
|
|
322
|
+
...typeof code === "string" ? { code } : {}
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
const start = async (call) => {
|
|
326
|
+
const abort = new AbortController();
|
|
327
|
+
const entry = { abort };
|
|
328
|
+
open.set(call.nid, entry);
|
|
329
|
+
try {
|
|
330
|
+
const response = await transport.fetch({
|
|
331
|
+
url: call.url,
|
|
332
|
+
method: call.method,
|
|
333
|
+
headers: call.headers,
|
|
334
|
+
...call.body ? { body: call.body } : {},
|
|
335
|
+
signal: abort.signal
|
|
336
|
+
});
|
|
337
|
+
if (!open.has(call.nid)) {
|
|
338
|
+
await response.cancel();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
|
|
342
|
+
peer.postMessage({
|
|
343
|
+
net: "head",
|
|
344
|
+
nid: call.nid,
|
|
345
|
+
status: response.status,
|
|
346
|
+
statusText: response.statusText,
|
|
347
|
+
headers: response.headers
|
|
348
|
+
});
|
|
349
|
+
} catch (e) {
|
|
350
|
+
fail(call.nid, e);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const read = async (nid) => {
|
|
354
|
+
const body = open.get(nid)?.body;
|
|
355
|
+
if (!body) return;
|
|
356
|
+
try {
|
|
357
|
+
const next = await body.next();
|
|
358
|
+
if (next.done) {
|
|
359
|
+
open.delete(nid);
|
|
360
|
+
peer.postMessage({ net: "end", nid });
|
|
361
|
+
} else {
|
|
362
|
+
const bytes = next.value.slice();
|
|
363
|
+
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
364
|
+
}
|
|
365
|
+
} catch (e) {
|
|
366
|
+
fail(nid, e);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const drop = (nid) => {
|
|
370
|
+
const entry = open.get(nid);
|
|
371
|
+
open.delete(nid);
|
|
372
|
+
entry?.abort.abort();
|
|
373
|
+
void entry?.response?.cancel().catch(() => void 0);
|
|
374
|
+
};
|
|
375
|
+
return {
|
|
376
|
+
answer(call) {
|
|
377
|
+
if (call.net === "fetch") void start(call);
|
|
378
|
+
else if (call.net === "read") void read(call.nid);
|
|
379
|
+
else drop(call.nid);
|
|
380
|
+
},
|
|
381
|
+
close() {
|
|
382
|
+
for (const nid of [...open.keys()]) drop(nid);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/client/serve-client.ts
|
|
388
|
+
var MAX_LINKS = 40;
|
|
389
|
+
function act(session, req, signal) {
|
|
390
|
+
if (req.action === "write") session.write(req.bytes);
|
|
391
|
+
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
392
|
+
else if (req.action === "signal") session.signal(signal(req.signal));
|
|
393
|
+
else session.close();
|
|
394
|
+
}
|
|
395
|
+
async function realpath(fs, path) {
|
|
396
|
+
let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
|
|
397
|
+
let resolved = "";
|
|
398
|
+
for (let hops = 0; parts.length > 0; ) {
|
|
399
|
+
const [name2, ...rest] = parts;
|
|
400
|
+
const next = `${resolved}/${name2}`;
|
|
401
|
+
if (!(await fs.lstat(next)).isSymbolicLink) {
|
|
402
|
+
resolved = next;
|
|
403
|
+
parts = rest;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
|
|
407
|
+
const target = await fs.readlink(next);
|
|
408
|
+
const base = target.startsWith("/") ? "/" : resolved || "/";
|
|
409
|
+
parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
|
|
410
|
+
resolved = "";
|
|
411
|
+
}
|
|
412
|
+
return resolved || "/";
|
|
413
|
+
}
|
|
414
|
+
async function fsCall(fs, method, args) {
|
|
415
|
+
const path = (i) => fs.resolvePath("/", String(args[i]));
|
|
416
|
+
switch (method) {
|
|
417
|
+
case "readFile": {
|
|
418
|
+
const bytes = (await fs.readFileBuffer(path(0))).slice();
|
|
419
|
+
return { result: bytes, transfer: [bytes.buffer] };
|
|
420
|
+
}
|
|
421
|
+
case "writeFile":
|
|
422
|
+
await fs.writeFile(path(0), args[1]);
|
|
423
|
+
return { result: true };
|
|
424
|
+
case "stat":
|
|
425
|
+
return { result: await fs.stat(path(0)) };
|
|
426
|
+
case "lstat":
|
|
427
|
+
return { result: await fs.lstat(path(0)) };
|
|
428
|
+
case "readdir":
|
|
429
|
+
return { result: await fs.readdir(path(0)) };
|
|
430
|
+
case "mkdir":
|
|
431
|
+
await fs.mkdir(path(0), { recursive: true });
|
|
432
|
+
return { result: true };
|
|
433
|
+
case "rm":
|
|
434
|
+
await fs.rm(path(0), { recursive: true, force: args[1] === true });
|
|
435
|
+
return { result: true };
|
|
436
|
+
case "rename":
|
|
437
|
+
await fs.rename(path(0), path(1));
|
|
438
|
+
return { result: true };
|
|
439
|
+
case "realpath":
|
|
440
|
+
return { result: await realpath(fs, String(args[0])) };
|
|
441
|
+
case "symlink":
|
|
442
|
+
await fs.symlink(String(args[0]), path(1));
|
|
443
|
+
return { result: true };
|
|
444
|
+
case "readlink":
|
|
445
|
+
return { result: await fs.readlink(path(0)) };
|
|
446
|
+
default:
|
|
447
|
+
return { result: await fs.exists(path(0)) };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function serveClient(port, host) {
|
|
451
|
+
let state = "new";
|
|
452
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
453
|
+
const groups = /* @__PURE__ */ new Set();
|
|
454
|
+
const watches = /* @__PURE__ */ new Map();
|
|
455
|
+
let net;
|
|
456
|
+
const ended = Promise.withResolvers();
|
|
457
|
+
const send = (message2, transfer = []) => {
|
|
458
|
+
if (state !== "closed") port.postMessage(message2, transfer);
|
|
459
|
+
};
|
|
460
|
+
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
461
|
+
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
462
|
+
if (state === "closed") return;
|
|
463
|
+
send({ bye });
|
|
464
|
+
state = "closed";
|
|
465
|
+
net?.close();
|
|
466
|
+
for (const unwatch of watches.values()) unwatch();
|
|
467
|
+
watches.clear();
|
|
468
|
+
for (const session of terminals.values()) session.close();
|
|
469
|
+
terminals.clear();
|
|
470
|
+
const pids = [...groups];
|
|
471
|
+
groups.clear();
|
|
472
|
+
if (kill && pids.length > 0) {
|
|
473
|
+
void host.launcher().then((launcher) => {
|
|
474
|
+
for (const pid of pids) launcher.kill(-pid, SIG.KILL);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
port.close?.();
|
|
478
|
+
ended.resolve();
|
|
479
|
+
}
|
|
480
|
+
async function hello(message2) {
|
|
481
|
+
const refused = versionError(message2.protocol);
|
|
482
|
+
if (refused) {
|
|
483
|
+
send({ hello: { protocol: PROTOCOL, error: refused } });
|
|
484
|
+
detach();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const launcher = await host.launcher();
|
|
488
|
+
const transport = host.transport?.() ?? launcher.transport;
|
|
489
|
+
net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
|
|
490
|
+
state = "open";
|
|
491
|
+
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
492
|
+
send({
|
|
493
|
+
hello: {
|
|
494
|
+
protocol: PROTOCOL,
|
|
495
|
+
traits: transport.traits,
|
|
496
|
+
...host.lock ? { lock: host.lock } : {}
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
async function terminal(req, l) {
|
|
501
|
+
const session = await l.openTerminal(req.argv, {
|
|
502
|
+
...req.options,
|
|
503
|
+
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
504
|
+
});
|
|
505
|
+
if (state === "closed") {
|
|
506
|
+
session.close();
|
|
507
|
+
} else {
|
|
508
|
+
terminals.set(req.id, session);
|
|
509
|
+
reply(req.id, { started: session.pid });
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
return { result: await session.exited };
|
|
513
|
+
} finally {
|
|
514
|
+
terminals.delete(req.id);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async function spawn(req, l) {
|
|
518
|
+
const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
|
|
519
|
+
let leader;
|
|
520
|
+
const result = await l.run(req.argv, {
|
|
521
|
+
...req.options,
|
|
522
|
+
collect: false,
|
|
523
|
+
onStdout: out(1),
|
|
524
|
+
onStderr: out(2),
|
|
525
|
+
onStarted: (pid) => {
|
|
526
|
+
leader = pid;
|
|
527
|
+
if (state === "closed") return;
|
|
528
|
+
groups.add(pid);
|
|
529
|
+
reply(req.id, { started: pid });
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
533
|
+
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
534
|
+
return { result: result.status };
|
|
535
|
+
}
|
|
536
|
+
async function handle(req) {
|
|
537
|
+
const l = await host.launcher();
|
|
538
|
+
switch (req.op) {
|
|
539
|
+
case "spawn":
|
|
540
|
+
return spawn(req, l);
|
|
541
|
+
case "open-terminal":
|
|
542
|
+
return terminal(req, l);
|
|
543
|
+
case "terminal": {
|
|
544
|
+
const session = terminals.get(req.terminal);
|
|
545
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
546
|
+
act(session, req, host.signal);
|
|
547
|
+
return { result: true };
|
|
548
|
+
}
|
|
549
|
+
case "kill":
|
|
550
|
+
if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
551
|
+
return { result: true };
|
|
552
|
+
case "ps":
|
|
553
|
+
return {
|
|
554
|
+
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
555
|
+
};
|
|
556
|
+
case "fs":
|
|
557
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
558
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
559
|
+
}
|
|
560
|
+
return fsCall(l.fs, req.method, req.args);
|
|
561
|
+
case "watch": {
|
|
562
|
+
const unwatch = l.watchers.watch(
|
|
563
|
+
req.paths,
|
|
564
|
+
{ recursive: req.recursive },
|
|
565
|
+
(change) => send({ watch: req.id, change })
|
|
566
|
+
);
|
|
567
|
+
watches.set(req.id, unwatch);
|
|
568
|
+
return { result: req.id };
|
|
569
|
+
}
|
|
570
|
+
case "unwatch":
|
|
571
|
+
watches.get(req.watch)?.();
|
|
572
|
+
watches.delete(req.watch);
|
|
573
|
+
return { result: true };
|
|
574
|
+
case "detach":
|
|
575
|
+
return { result: true };
|
|
576
|
+
default:
|
|
577
|
+
throw new Error(`unknown op ${req.op}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
port.addEventListener("message", (event) => {
|
|
581
|
+
const data = event.data;
|
|
582
|
+
if (state === "closed") return;
|
|
583
|
+
if (data.hello) {
|
|
584
|
+
if (state === "new") {
|
|
585
|
+
hello(data.hello).catch((err) => {
|
|
586
|
+
send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
|
|
587
|
+
detach();
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (state !== "open") return;
|
|
593
|
+
if (data.net !== void 0) {
|
|
594
|
+
net?.answer(data);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const req = data;
|
|
598
|
+
handle(req).then(
|
|
599
|
+
({ result, transfer }) => {
|
|
600
|
+
reply(req.id, { result }, transfer);
|
|
601
|
+
if (req.op === "detach") detach(req.kill === true);
|
|
602
|
+
},
|
|
603
|
+
(err) => {
|
|
604
|
+
const code = err?.code;
|
|
605
|
+
reply(req.id, {
|
|
606
|
+
error: err instanceof Error ? err.message : String(err),
|
|
607
|
+
...typeof code === "string" ? { code } : {}
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
});
|
|
612
|
+
port.addEventListener("close", () => detach());
|
|
613
|
+
port.start?.();
|
|
614
|
+
return { detach, closed: ended.promise };
|
|
615
|
+
}
|
|
616
|
+
|
|
225
617
|
// src/fs/opfs.ts
|
|
226
618
|
var S_IFDIR = 16384;
|
|
227
619
|
var S_IFREG = 32768;
|
|
228
620
|
var S_IFLNK = 40960;
|
|
229
621
|
var PERM_MASK = 4095;
|
|
230
622
|
var READ_ATTEMPTS = 3;
|
|
231
|
-
var
|
|
623
|
+
var MAX_LINKS2 = 40;
|
|
232
624
|
var CHECK_MS = 1e3;
|
|
233
625
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
234
626
|
var ERRNO_BY_DOM_ERROR = {
|
|
@@ -379,7 +771,7 @@ var OpfsFs = class {
|
|
|
379
771
|
}
|
|
380
772
|
async locate(path, follow) {
|
|
381
773
|
let current = path;
|
|
382
|
-
for (let hops = 0; hops <=
|
|
774
|
+
for (let hops = 0; hops <= MAX_LINKS2; hops++) {
|
|
383
775
|
try {
|
|
384
776
|
return { path: current, handle: await this.handle(current) };
|
|
385
777
|
} catch (err) {
|
|
@@ -2412,6 +2804,7 @@ var RemoteTransport = class {
|
|
|
2412
2804
|
port;
|
|
2413
2805
|
waiting = /* @__PURE__ */ new Map();
|
|
2414
2806
|
nextId = 0;
|
|
2807
|
+
failure;
|
|
2415
2808
|
constructor(port, traits) {
|
|
2416
2809
|
this.port = port;
|
|
2417
2810
|
this.traits = traits;
|
|
@@ -2424,7 +2817,13 @@ var RemoteTransport = class {
|
|
|
2424
2817
|
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
2425
2818
|
} else waiter.resolve(reply);
|
|
2426
2819
|
}
|
|
2820
|
+
fail(error) {
|
|
2821
|
+
this.failure = error;
|
|
2822
|
+
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
2823
|
+
this.waiting.clear();
|
|
2824
|
+
}
|
|
2427
2825
|
ask(call) {
|
|
2826
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
2428
2827
|
return new Promise((resolve, reject) => {
|
|
2429
2828
|
this.waiting.set(call.nid, { resolve, reject });
|
|
2430
2829
|
this.port.postMessage(call);
|
|
@@ -2502,61 +2901,6 @@ var RemoteTransport = class {
|
|
|
2502
2901
|
}
|
|
2503
2902
|
};
|
|
2504
2903
|
|
|
2505
|
-
// src/kernel/signals.ts
|
|
2506
|
-
var SIG = {
|
|
2507
|
-
HUP: 1,
|
|
2508
|
-
INT: 2,
|
|
2509
|
-
QUIT: 3,
|
|
2510
|
-
ILL: 4,
|
|
2511
|
-
TRAP: 5,
|
|
2512
|
-
ABRT: 6,
|
|
2513
|
-
BUS: 7,
|
|
2514
|
-
FPE: 8,
|
|
2515
|
-
KILL: 9,
|
|
2516
|
-
USR1: 10,
|
|
2517
|
-
SEGV: 11,
|
|
2518
|
-
USR2: 12,
|
|
2519
|
-
PIPE: 13,
|
|
2520
|
-
ALRM: 14,
|
|
2521
|
-
TERM: 15,
|
|
2522
|
-
STKFLT: 16,
|
|
2523
|
-
CHLD: 17,
|
|
2524
|
-
CONT: 18,
|
|
2525
|
-
STOP: 19,
|
|
2526
|
-
TSTP: 20,
|
|
2527
|
-
TTIN: 21,
|
|
2528
|
-
TTOU: 22,
|
|
2529
|
-
URG: 23,
|
|
2530
|
-
XCPU: 24,
|
|
2531
|
-
XFSZ: 25,
|
|
2532
|
-
VTALRM: 26,
|
|
2533
|
-
PROF: 27,
|
|
2534
|
-
WINCH: 28,
|
|
2535
|
-
POLL: 29,
|
|
2536
|
-
PWR: 30,
|
|
2537
|
-
SYS: 31
|
|
2538
|
-
};
|
|
2539
|
-
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
2540
|
-
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
2541
|
-
function defaultAction(sig) {
|
|
2542
|
-
if (sig === SIG.CONT) return "continue";
|
|
2543
|
-
if (STOPPING.has(sig)) return "stop";
|
|
2544
|
-
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
2545
|
-
}
|
|
2546
|
-
function isSignal(sig) {
|
|
2547
|
-
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
2548
|
-
}
|
|
2549
|
-
function sigbit(sig) {
|
|
2550
|
-
return 1 << sig;
|
|
2551
|
-
}
|
|
2552
|
-
var SIGNAL_BY_NAME = {
|
|
2553
|
-
SIGINT: SIG.INT,
|
|
2554
|
-
SIGTERM: SIG.TERM,
|
|
2555
|
-
SIGKILL: SIG.KILL,
|
|
2556
|
-
SIGSTOP: SIG.STOP,
|
|
2557
|
-
SIGCONT: SIG.CONT
|
|
2558
|
-
};
|
|
2559
|
-
|
|
2560
2904
|
// src/commands.ts
|
|
2561
2905
|
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
2562
2906
|
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -2687,7 +3031,7 @@ async function scanCommands(fs, modules) {
|
|
|
2687
3031
|
var S_IFDIR2 = 16384;
|
|
2688
3032
|
var S_IFREG2 = 32768;
|
|
2689
3033
|
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
2690
|
-
var
|
|
3034
|
+
var MAX_LINKS3 = 40;
|
|
2691
3035
|
function missing2(err) {
|
|
2692
3036
|
return err?.code === "ENOENT";
|
|
2693
3037
|
}
|
|
@@ -2707,7 +3051,7 @@ function synthetic(directory, path) {
|
|
|
2707
3051
|
}
|
|
2708
3052
|
async function followLinks(fs, path) {
|
|
2709
3053
|
let current = path;
|
|
2710
|
-
for (let hops = 0; hops <
|
|
3054
|
+
for (let hops = 0; hops < MAX_LINKS3; hops++) {
|
|
2711
3055
|
const st = await fs.lstat(current).catch(() => void 0);
|
|
2712
3056
|
if (!st?.isSymbolicLink) return current;
|
|
2713
3057
|
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
@@ -2795,6 +3139,89 @@ function withCommandDirs(fs, names) {
|
|
|
2795
3139
|
};
|
|
2796
3140
|
}
|
|
2797
3141
|
|
|
3142
|
+
// src/fs/watch.ts
|
|
3143
|
+
var WATCH_BATCH_LIMIT = 1e3;
|
|
3144
|
+
var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
3145
|
+
var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
3146
|
+
function touches(watcher, changed) {
|
|
3147
|
+
return watcher.paths.some(
|
|
3148
|
+
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
var FsWatchers = class {
|
|
3152
|
+
watchers = /* @__PURE__ */ new Set();
|
|
3153
|
+
watch(paths, options, notify) {
|
|
3154
|
+
const watcher = {
|
|
3155
|
+
paths: paths.map(normalizePath),
|
|
3156
|
+
recursive: options.recursive === true,
|
|
3157
|
+
notify,
|
|
3158
|
+
pending: /* @__PURE__ */ new Set(),
|
|
3159
|
+
scheduled: false
|
|
3160
|
+
};
|
|
3161
|
+
this.watchers.add(watcher);
|
|
3162
|
+
return () => {
|
|
3163
|
+
this.watchers.delete(watcher);
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
changed(...paths) {
|
|
3167
|
+
for (const watcher of this.watchers) {
|
|
3168
|
+
for (const path of paths) {
|
|
3169
|
+
const changed = normalizePath(path);
|
|
3170
|
+
if (touches(watcher, changed)) watcher.pending.add(changed);
|
|
3171
|
+
}
|
|
3172
|
+
if (watcher.pending.size === 0 || watcher.scheduled) continue;
|
|
3173
|
+
watcher.scheduled = true;
|
|
3174
|
+
setTimeout(() => this.flush(watcher), 0);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
flush(watcher) {
|
|
3178
|
+
watcher.scheduled = false;
|
|
3179
|
+
const paths = [...watcher.pending];
|
|
3180
|
+
watcher.pending.clear();
|
|
3181
|
+
if (!this.watchers.has(watcher)) return;
|
|
3182
|
+
watcher.notify(paths.length > WATCH_BATCH_LIMIT ? { overflow: true } : { paths });
|
|
3183
|
+
}
|
|
3184
|
+
wrap(fs) {
|
|
3185
|
+
const after = (run2, changed) => async (...args) => {
|
|
3186
|
+
try {
|
|
3187
|
+
await run2(...args);
|
|
3188
|
+
} finally {
|
|
3189
|
+
this.changed(...changed(...args));
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
const overrides = {
|
|
3193
|
+
writeFile: after(fs.writeFile.bind(fs), (path) => [path]),
|
|
3194
|
+
mkdir: async (path, options) => {
|
|
3195
|
+
const created = [];
|
|
3196
|
+
if (options?.recursive) {
|
|
3197
|
+
const parts = normalizePath(path).split("/").filter(Boolean);
|
|
3198
|
+
for (let at = 1; at < parts.length; at++) {
|
|
3199
|
+
const ancestor = `/${parts.slice(0, at).join("/")}`;
|
|
3200
|
+
if (created.length > 0 || !await fs.exists(ancestor)) created.push(ancestor);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
try {
|
|
3204
|
+
await fs.mkdir(path, options);
|
|
3205
|
+
} finally {
|
|
3206
|
+
this.changed(...created, path);
|
|
3207
|
+
}
|
|
3208
|
+
},
|
|
3209
|
+
rm: after(fs.rm.bind(fs), (path) => [path]),
|
|
3210
|
+
rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
|
|
3211
|
+
symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
|
|
3212
|
+
chmod: after(fs.chmod.bind(fs), (path) => [path]),
|
|
3213
|
+
utimes: after(fs.utimes.bind(fs), (path) => [path])
|
|
3214
|
+
};
|
|
3215
|
+
return new Proxy(fs, {
|
|
3216
|
+
get(target, key, receiver) {
|
|
3217
|
+
if (Object.hasOwn(overrides, key)) return overrides[key];
|
|
3218
|
+
const value = Reflect.get(target, key, receiver);
|
|
3219
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
3220
|
+
}
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
|
|
2798
3225
|
// src/kernel/children.ts
|
|
2799
3226
|
var SpawnError = class extends Error {
|
|
2800
3227
|
code;
|
|
@@ -4712,6 +5139,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4712
5139
|
"proc-captured",
|
|
4713
5140
|
"proc-fork",
|
|
4714
5141
|
"proc-kill",
|
|
5142
|
+
"proc-list",
|
|
4715
5143
|
"proc-exec",
|
|
4716
5144
|
"proc-setpgid",
|
|
4717
5145
|
"proc-getpgid",
|
|
@@ -5160,6 +5588,12 @@ var WasmProcess = class {
|
|
|
5160
5588
|
throw new KernelError("ESRCH");
|
|
5161
5589
|
}
|
|
5162
5590
|
return { ok: true, kind: "void" };
|
|
5591
|
+
case "proc-list":
|
|
5592
|
+
return {
|
|
5593
|
+
ok: true,
|
|
5594
|
+
kind: "json",
|
|
5595
|
+
json: this.options.processes?.() ?? { boot: 0, processes: [] }
|
|
5596
|
+
};
|
|
5163
5597
|
case "proc-alarm":
|
|
5164
5598
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
5165
5599
|
return { ok: true, kind: "void" };
|
|
@@ -5263,6 +5697,7 @@ function spawnWasmProcess(opts) {
|
|
|
5263
5697
|
forker: opts.forker,
|
|
5264
5698
|
fs: opts.fs,
|
|
5265
5699
|
kill: opts.kill,
|
|
5700
|
+
processes: opts.processes,
|
|
5266
5701
|
jobs: opts.jobs,
|
|
5267
5702
|
ptys: opts.ptys,
|
|
5268
5703
|
net: opts.net,
|
|
@@ -5407,6 +5842,9 @@ var JobTable = class {
|
|
|
5407
5842
|
this.members.set(pid, member);
|
|
5408
5843
|
return member;
|
|
5409
5844
|
}
|
|
5845
|
+
list() {
|
|
5846
|
+
return [...this.members.values()];
|
|
5847
|
+
}
|
|
5410
5848
|
remove(pid) {
|
|
5411
5849
|
this.members.delete(pid);
|
|
5412
5850
|
}
|
|
@@ -5737,16 +6175,19 @@ var Launcher = class {
|
|
|
5737
6175
|
processes = /* @__PURE__ */ new Map();
|
|
5738
6176
|
zombies = /* @__PURE__ */ new Set();
|
|
5739
6177
|
orphans = /* @__PURE__ */ new Set();
|
|
6178
|
+
described = /* @__PURE__ */ new Map();
|
|
5740
6179
|
jobs = new JobTable();
|
|
5741
6180
|
locks = new LockTable();
|
|
5742
6181
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
5743
6182
|
net = new LoopbackNet();
|
|
5744
6183
|
ca;
|
|
5745
6184
|
transport;
|
|
6185
|
+
watchers = new FsWatchers();
|
|
5746
6186
|
nextPid = 1e3;
|
|
6187
|
+
boot = Date.now();
|
|
5747
6188
|
terminals = 0;
|
|
5748
6189
|
constructor(options) {
|
|
5749
|
-
this.base = options.fs;
|
|
6190
|
+
this.base = this.watchers.wrap(options.fs);
|
|
5750
6191
|
this.createWorker = options.createWorker;
|
|
5751
6192
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
5752
6193
|
this.env = { ...networkEnv(), ...options.env };
|
|
@@ -5757,7 +6198,7 @@ var Launcher = class {
|
|
|
5757
6198
|
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
5758
6199
|
ca: this.ca
|
|
5759
6200
|
});
|
|
5760
|
-
this.fs = withCommandDirs(
|
|
6201
|
+
this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
5761
6202
|
}
|
|
5762
6203
|
commands() {
|
|
5763
6204
|
this.catalog ??= scanCommands(this.base, this.modulesDir);
|
|
@@ -5915,6 +6356,7 @@ var Launcher = class {
|
|
|
5915
6356
|
spawner: this.spawner(pid, req.report),
|
|
5916
6357
|
forker: this.forker(pid, req),
|
|
5917
6358
|
kill: (target, sig) => this.kill(target, sig),
|
|
6359
|
+
processes: () => ({ boot: this.boot, processes: this.list() }),
|
|
5918
6360
|
jobs: this.jobs,
|
|
5919
6361
|
ptys: this.ptys,
|
|
5920
6362
|
net: this.net,
|
|
@@ -5926,17 +6368,57 @@ var Launcher = class {
|
|
|
5926
6368
|
});
|
|
5927
6369
|
this.processes.set(pid, handle);
|
|
5928
6370
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
6371
|
+
this.described.set(pid, {
|
|
6372
|
+
argv: [req.argv0, ...req.args],
|
|
6373
|
+
tty: terminal?.name ?? null,
|
|
6374
|
+
started: Date.now()
|
|
6375
|
+
});
|
|
5929
6376
|
void handle.exited.then(() => {
|
|
5930
6377
|
this.processes.delete(pid);
|
|
5931
|
-
if (req.ppid === void 0 || this.orphans.delete(pid)) this.
|
|
6378
|
+
if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
|
|
5932
6379
|
else this.zombies.add(pid);
|
|
5933
6380
|
});
|
|
5934
6381
|
return handle;
|
|
5935
6382
|
}
|
|
6383
|
+
forget(pid) {
|
|
6384
|
+
this.jobs.remove(pid);
|
|
6385
|
+
this.described.delete(pid);
|
|
6386
|
+
}
|
|
5936
6387
|
reaped(pid) {
|
|
5937
|
-
if (this.zombies.delete(pid)) this.
|
|
6388
|
+
if (this.zombies.delete(pid)) this.forget(pid);
|
|
5938
6389
|
else this.orphans.add(pid);
|
|
5939
6390
|
}
|
|
6391
|
+
list() {
|
|
6392
|
+
const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
|
|
6393
|
+
const rootOf = (member) => {
|
|
6394
|
+
let root = member;
|
|
6395
|
+
for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
|
|
6396
|
+
root = up;
|
|
6397
|
+
}
|
|
6398
|
+
return root;
|
|
6399
|
+
};
|
|
6400
|
+
const shown = (pid) => {
|
|
6401
|
+
const member = pid === void 0 ? void 0 : members.get(pid);
|
|
6402
|
+
return member ? rootOf(member).pid : pid ?? 0;
|
|
6403
|
+
};
|
|
6404
|
+
const listed = [];
|
|
6405
|
+
for (const member of members.values()) {
|
|
6406
|
+
const described = this.described.get(member.pid);
|
|
6407
|
+
if (member.execed || !described) continue;
|
|
6408
|
+
const root = rootOf(member);
|
|
6409
|
+
listed.push({
|
|
6410
|
+
pid: root.pid,
|
|
6411
|
+
tid: member.pid,
|
|
6412
|
+
ppid: shown(root.ppid),
|
|
6413
|
+
pgid: member.pgid,
|
|
6414
|
+
sid: member.sid,
|
|
6415
|
+
...described,
|
|
6416
|
+
started: (this.described.get(root.pid) ?? described).started,
|
|
6417
|
+
state: this.processes.has(member.pid) ? "S" : "Z"
|
|
6418
|
+
});
|
|
6419
|
+
}
|
|
6420
|
+
return listed;
|
|
6421
|
+
}
|
|
5940
6422
|
spawner(ppid, report) {
|
|
5941
6423
|
return async (req, fds) => {
|
|
5942
6424
|
const planned = await this.plan(req.file, req.argv, req.cwd);
|
|
@@ -6014,8 +6496,9 @@ var Launcher = class {
|
|
|
6014
6496
|
const env = this.environment(cwd, options.env);
|
|
6015
6497
|
const out = [];
|
|
6016
6498
|
const err = [];
|
|
6499
|
+
const keep = options.collect !== false;
|
|
6017
6500
|
const collect = (chunks, tee) => (bytes) => {
|
|
6018
|
-
chunks.push(bytes);
|
|
6501
|
+
if (keep) chunks.push(bytes);
|
|
6019
6502
|
tee?.(bytes);
|
|
6020
6503
|
};
|
|
6021
6504
|
const stdout = collect(out, options.onStdout);
|
|
@@ -6031,6 +6514,7 @@ var Launcher = class {
|
|
|
6031
6514
|
fds.installAt(1, sinkFile(stdout));
|
|
6032
6515
|
fds.installAt(2, sinkFile(stderr));
|
|
6033
6516
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
6517
|
+
options.onStarted?.(handle.pid);
|
|
6034
6518
|
const status = await handle.exited;
|
|
6035
6519
|
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
6036
6520
|
}
|
|
@@ -6043,7 +6527,7 @@ function signalNumber(name2) {
|
|
|
6043
6527
|
throw new Error(`unknown signal ${name2}`);
|
|
6044
6528
|
return SIG[key];
|
|
6045
6529
|
}
|
|
6046
|
-
function
|
|
6530
|
+
function act2(session, req) {
|
|
6047
6531
|
if (req.action === "write") session.write(req.bytes);
|
|
6048
6532
|
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
6049
6533
|
else if (req.action === "signal") session.signal(signalNumber(req.signal));
|
|
@@ -6056,7 +6540,35 @@ function serveKernel(port, deps) {
|
|
|
6056
6540
|
let launcher;
|
|
6057
6541
|
let remote;
|
|
6058
6542
|
const terminals = /* @__PURE__ */ new Map();
|
|
6059
|
-
const reply = (id, body) => port.postMessage({ id, ...body });
|
|
6543
|
+
const reply = (id, body, transfer) => port.postMessage({ id, ...body }, transfer);
|
|
6544
|
+
const clients = /* @__PURE__ */ new Set();
|
|
6545
|
+
const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
|
|
6546
|
+
let holding;
|
|
6547
|
+
function holdLock(locks2) {
|
|
6548
|
+
holding ??= new Promise((resolve) => {
|
|
6549
|
+
const name2 = `slicc-kernel:${crypto.randomUUID()}`;
|
|
6550
|
+
void locks2.request(name2, () => {
|
|
6551
|
+
resolve(name2);
|
|
6552
|
+
return new Promise(() => {
|
|
6553
|
+
});
|
|
6554
|
+
});
|
|
6555
|
+
});
|
|
6556
|
+
return holding;
|
|
6557
|
+
}
|
|
6558
|
+
async function connect() {
|
|
6559
|
+
await ready2();
|
|
6560
|
+
const { port1, port2 } = new MessageChannel();
|
|
6561
|
+
const held = locks ? await holdLock(locks) : void 0;
|
|
6562
|
+
const served = serveClient(port1, {
|
|
6563
|
+
launcher: ready2,
|
|
6564
|
+
signal: signalNumber,
|
|
6565
|
+
...locks ? { locks } : {},
|
|
6566
|
+
...held ? { lock: held } : {}
|
|
6567
|
+
});
|
|
6568
|
+
clients.add(served);
|
|
6569
|
+
void served.closed.then(() => clients.delete(served));
|
|
6570
|
+
return port2;
|
|
6571
|
+
}
|
|
6060
6572
|
async function ready2() {
|
|
6061
6573
|
if (!launcher) throw new Error("the kernel is not initialized");
|
|
6062
6574
|
return launcher;
|
|
@@ -6095,10 +6607,11 @@ function serveKernel(port, deps) {
|
|
|
6095
6607
|
return true;
|
|
6096
6608
|
}
|
|
6097
6609
|
if (req.op === "open-terminal") return terminal(req);
|
|
6610
|
+
if (req.op === "connect") return connect();
|
|
6098
6611
|
if (req.op === "terminal") {
|
|
6099
6612
|
const session = terminals.get(req.terminal);
|
|
6100
6613
|
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
6101
|
-
|
|
6614
|
+
act2(session, req);
|
|
6102
6615
|
return true;
|
|
6103
6616
|
}
|
|
6104
6617
|
return (await ready2()).run(req.argv, {
|
|
@@ -6114,7 +6627,7 @@ function serveKernel(port, deps) {
|
|
|
6114
6627
|
}
|
|
6115
6628
|
const req = event.data;
|
|
6116
6629
|
handle(req).then(
|
|
6117
|
-
(result) => reply(req.id, { result }),
|
|
6630
|
+
(result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
|
|
6118
6631
|
(err) => reply(req.id, { error: err instanceof Error ? err.message : String(err) })
|
|
6119
6632
|
);
|
|
6120
6633
|
});
|