@ai-ecoverse/slicc-kernel 1.8.12 → 1.9.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 +27 -1
- package/dist/index.js +391 -2
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +479 -66
- 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 +939 -180
- 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, 0];
|
|
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,362 @@ 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
|
+
let net;
|
|
455
|
+
const ended = Promise.withResolvers();
|
|
456
|
+
const send = (message2, transfer = []) => {
|
|
457
|
+
if (state !== "closed") port.postMessage(message2, transfer);
|
|
458
|
+
};
|
|
459
|
+
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
460
|
+
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
461
|
+
if (state === "closed") return;
|
|
462
|
+
send({ bye });
|
|
463
|
+
state = "closed";
|
|
464
|
+
net?.close();
|
|
465
|
+
for (const session of terminals.values()) session.close();
|
|
466
|
+
terminals.clear();
|
|
467
|
+
const pids = [...groups];
|
|
468
|
+
groups.clear();
|
|
469
|
+
if (kill && pids.length > 0) {
|
|
470
|
+
void host.launcher().then((launcher) => {
|
|
471
|
+
for (const pid of pids) launcher.kill(-pid, SIG.KILL);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
port.close?.();
|
|
475
|
+
ended.resolve();
|
|
476
|
+
}
|
|
477
|
+
async function hello(message2) {
|
|
478
|
+
const refused = versionError(message2.protocol);
|
|
479
|
+
if (refused) {
|
|
480
|
+
send({ hello: { protocol: PROTOCOL, error: refused } });
|
|
481
|
+
detach();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const launcher = await host.launcher();
|
|
485
|
+
const transport = host.transport?.() ?? launcher.transport;
|
|
486
|
+
net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
|
|
487
|
+
state = "open";
|
|
488
|
+
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
489
|
+
send({
|
|
490
|
+
hello: {
|
|
491
|
+
protocol: PROTOCOL,
|
|
492
|
+
traits: transport.traits,
|
|
493
|
+
...host.lock ? { lock: host.lock } : {}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async function terminal(req, l) {
|
|
498
|
+
const session = await l.openTerminal(req.argv, {
|
|
499
|
+
...req.options,
|
|
500
|
+
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
501
|
+
});
|
|
502
|
+
if (state === "closed") {
|
|
503
|
+
session.close();
|
|
504
|
+
} else {
|
|
505
|
+
terminals.set(req.id, session);
|
|
506
|
+
reply(req.id, { started: session.pid });
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
return { result: await session.exited };
|
|
510
|
+
} finally {
|
|
511
|
+
terminals.delete(req.id);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async function spawn(req, l) {
|
|
515
|
+
const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
|
|
516
|
+
let leader;
|
|
517
|
+
const result = await l.run(req.argv, {
|
|
518
|
+
...req.options,
|
|
519
|
+
collect: false,
|
|
520
|
+
onStdout: out(1),
|
|
521
|
+
onStderr: out(2),
|
|
522
|
+
onStarted: (pid) => {
|
|
523
|
+
leader = pid;
|
|
524
|
+
if (state === "closed") return;
|
|
525
|
+
groups.add(pid);
|
|
526
|
+
reply(req.id, { started: pid });
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
530
|
+
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
531
|
+
return { result: result.status };
|
|
532
|
+
}
|
|
533
|
+
async function handle(req) {
|
|
534
|
+
const l = await host.launcher();
|
|
535
|
+
switch (req.op) {
|
|
536
|
+
case "spawn":
|
|
537
|
+
return spawn(req, l);
|
|
538
|
+
case "open-terminal":
|
|
539
|
+
return terminal(req, l);
|
|
540
|
+
case "terminal": {
|
|
541
|
+
const session = terminals.get(req.terminal);
|
|
542
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
543
|
+
act(session, req, host.signal);
|
|
544
|
+
return { result: true };
|
|
545
|
+
}
|
|
546
|
+
case "kill":
|
|
547
|
+
if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
548
|
+
return { result: true };
|
|
549
|
+
case "ps":
|
|
550
|
+
return {
|
|
551
|
+
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
552
|
+
};
|
|
553
|
+
case "fs":
|
|
554
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
555
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
556
|
+
}
|
|
557
|
+
return fsCall(l.fs, req.method, req.args);
|
|
558
|
+
case "detach":
|
|
559
|
+
return { result: true };
|
|
560
|
+
default:
|
|
561
|
+
throw new Error(`unknown op ${req.op}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
port.addEventListener("message", (event) => {
|
|
565
|
+
const data = event.data;
|
|
566
|
+
if (state === "closed") return;
|
|
567
|
+
if (data.hello) {
|
|
568
|
+
if (state === "new") {
|
|
569
|
+
hello(data.hello).catch((err) => {
|
|
570
|
+
send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
|
|
571
|
+
detach();
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (state !== "open") return;
|
|
577
|
+
if (data.net !== void 0) {
|
|
578
|
+
net?.answer(data);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const req = data;
|
|
582
|
+
handle(req).then(
|
|
583
|
+
({ result, transfer }) => {
|
|
584
|
+
reply(req.id, { result }, transfer);
|
|
585
|
+
if (req.op === "detach") detach(req.kill === true);
|
|
586
|
+
},
|
|
587
|
+
(err) => {
|
|
588
|
+
const code = err?.code;
|
|
589
|
+
reply(req.id, {
|
|
590
|
+
error: err instanceof Error ? err.message : String(err),
|
|
591
|
+
...typeof code === "string" ? { code } : {}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
});
|
|
596
|
+
port.addEventListener("close", () => detach());
|
|
597
|
+
port.start?.();
|
|
598
|
+
return { detach, closed: ended.promise };
|
|
599
|
+
}
|
|
600
|
+
|
|
225
601
|
// src/fs/opfs.ts
|
|
226
602
|
var S_IFDIR = 16384;
|
|
227
603
|
var S_IFREG = 32768;
|
|
228
604
|
var S_IFLNK = 40960;
|
|
229
605
|
var PERM_MASK = 4095;
|
|
230
606
|
var READ_ATTEMPTS = 3;
|
|
231
|
-
var
|
|
607
|
+
var MAX_LINKS2 = 40;
|
|
232
608
|
var CHECK_MS = 1e3;
|
|
233
609
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
234
610
|
var ERRNO_BY_DOM_ERROR = {
|
|
@@ -379,7 +755,7 @@ var OpfsFs = class {
|
|
|
379
755
|
}
|
|
380
756
|
async locate(path, follow) {
|
|
381
757
|
let current = path;
|
|
382
|
-
for (let hops = 0; hops <=
|
|
758
|
+
for (let hops = 0; hops <= MAX_LINKS2; hops++) {
|
|
383
759
|
try {
|
|
384
760
|
return { path: current, handle: await this.handle(current) };
|
|
385
761
|
} catch (err) {
|
|
@@ -2412,6 +2788,7 @@ var RemoteTransport = class {
|
|
|
2412
2788
|
port;
|
|
2413
2789
|
waiting = /* @__PURE__ */ new Map();
|
|
2414
2790
|
nextId = 0;
|
|
2791
|
+
failure;
|
|
2415
2792
|
constructor(port, traits) {
|
|
2416
2793
|
this.port = port;
|
|
2417
2794
|
this.traits = traits;
|
|
@@ -2424,7 +2801,13 @@ var RemoteTransport = class {
|
|
|
2424
2801
|
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
2425
2802
|
} else waiter.resolve(reply);
|
|
2426
2803
|
}
|
|
2804
|
+
fail(error) {
|
|
2805
|
+
this.failure = error;
|
|
2806
|
+
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
2807
|
+
this.waiting.clear();
|
|
2808
|
+
}
|
|
2427
2809
|
ask(call) {
|
|
2810
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
2428
2811
|
return new Promise((resolve, reject) => {
|
|
2429
2812
|
this.waiting.set(call.nid, { resolve, reject });
|
|
2430
2813
|
this.port.postMessage(call);
|
|
@@ -2502,61 +2885,6 @@ var RemoteTransport = class {
|
|
|
2502
2885
|
}
|
|
2503
2886
|
};
|
|
2504
2887
|
|
|
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
2888
|
// src/commands.ts
|
|
2561
2889
|
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
2562
2890
|
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -2687,7 +3015,7 @@ async function scanCommands(fs, modules) {
|
|
|
2687
3015
|
var S_IFDIR2 = 16384;
|
|
2688
3016
|
var S_IFREG2 = 32768;
|
|
2689
3017
|
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
2690
|
-
var
|
|
3018
|
+
var MAX_LINKS3 = 40;
|
|
2691
3019
|
function missing2(err) {
|
|
2692
3020
|
return err?.code === "ENOENT";
|
|
2693
3021
|
}
|
|
@@ -2707,7 +3035,7 @@ function synthetic(directory, path) {
|
|
|
2707
3035
|
}
|
|
2708
3036
|
async function followLinks(fs, path) {
|
|
2709
3037
|
let current = path;
|
|
2710
|
-
for (let hops = 0; hops <
|
|
3038
|
+
for (let hops = 0; hops < MAX_LINKS3; hops++) {
|
|
2711
3039
|
const st = await fs.lstat(current).catch(() => void 0);
|
|
2712
3040
|
if (!st?.isSymbolicLink) return current;
|
|
2713
3041
|
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
@@ -4712,6 +5040,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4712
5040
|
"proc-captured",
|
|
4713
5041
|
"proc-fork",
|
|
4714
5042
|
"proc-kill",
|
|
5043
|
+
"proc-list",
|
|
4715
5044
|
"proc-exec",
|
|
4716
5045
|
"proc-setpgid",
|
|
4717
5046
|
"proc-getpgid",
|
|
@@ -5160,6 +5489,12 @@ var WasmProcess = class {
|
|
|
5160
5489
|
throw new KernelError("ESRCH");
|
|
5161
5490
|
}
|
|
5162
5491
|
return { ok: true, kind: "void" };
|
|
5492
|
+
case "proc-list":
|
|
5493
|
+
return {
|
|
5494
|
+
ok: true,
|
|
5495
|
+
kind: "json",
|
|
5496
|
+
json: this.options.processes?.() ?? { boot: 0, processes: [] }
|
|
5497
|
+
};
|
|
5163
5498
|
case "proc-alarm":
|
|
5164
5499
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
5165
5500
|
return { ok: true, kind: "void" };
|
|
@@ -5263,6 +5598,7 @@ function spawnWasmProcess(opts) {
|
|
|
5263
5598
|
forker: opts.forker,
|
|
5264
5599
|
fs: opts.fs,
|
|
5265
5600
|
kill: opts.kill,
|
|
5601
|
+
processes: opts.processes,
|
|
5266
5602
|
jobs: opts.jobs,
|
|
5267
5603
|
ptys: opts.ptys,
|
|
5268
5604
|
net: opts.net,
|
|
@@ -5407,6 +5743,9 @@ var JobTable = class {
|
|
|
5407
5743
|
this.members.set(pid, member);
|
|
5408
5744
|
return member;
|
|
5409
5745
|
}
|
|
5746
|
+
list() {
|
|
5747
|
+
return [...this.members.values()];
|
|
5748
|
+
}
|
|
5410
5749
|
remove(pid) {
|
|
5411
5750
|
this.members.delete(pid);
|
|
5412
5751
|
}
|
|
@@ -5737,6 +6076,7 @@ var Launcher = class {
|
|
|
5737
6076
|
processes = /* @__PURE__ */ new Map();
|
|
5738
6077
|
zombies = /* @__PURE__ */ new Set();
|
|
5739
6078
|
orphans = /* @__PURE__ */ new Set();
|
|
6079
|
+
described = /* @__PURE__ */ new Map();
|
|
5740
6080
|
jobs = new JobTable();
|
|
5741
6081
|
locks = new LockTable();
|
|
5742
6082
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
@@ -5744,6 +6084,7 @@ var Launcher = class {
|
|
|
5744
6084
|
ca;
|
|
5745
6085
|
transport;
|
|
5746
6086
|
nextPid = 1e3;
|
|
6087
|
+
boot = Date.now();
|
|
5747
6088
|
terminals = 0;
|
|
5748
6089
|
constructor(options) {
|
|
5749
6090
|
this.base = options.fs;
|
|
@@ -5915,6 +6256,7 @@ var Launcher = class {
|
|
|
5915
6256
|
spawner: this.spawner(pid, req.report),
|
|
5916
6257
|
forker: this.forker(pid, req),
|
|
5917
6258
|
kill: (target, sig) => this.kill(target, sig),
|
|
6259
|
+
processes: () => ({ boot: this.boot, processes: this.list() }),
|
|
5918
6260
|
jobs: this.jobs,
|
|
5919
6261
|
ptys: this.ptys,
|
|
5920
6262
|
net: this.net,
|
|
@@ -5926,17 +6268,57 @@ var Launcher = class {
|
|
|
5926
6268
|
});
|
|
5927
6269
|
this.processes.set(pid, handle);
|
|
5928
6270
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
6271
|
+
this.described.set(pid, {
|
|
6272
|
+
argv: [req.argv0, ...req.args],
|
|
6273
|
+
tty: terminal?.name ?? null,
|
|
6274
|
+
started: Date.now()
|
|
6275
|
+
});
|
|
5929
6276
|
void handle.exited.then(() => {
|
|
5930
6277
|
this.processes.delete(pid);
|
|
5931
|
-
if (req.ppid === void 0 || this.orphans.delete(pid)) this.
|
|
6278
|
+
if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
|
|
5932
6279
|
else this.zombies.add(pid);
|
|
5933
6280
|
});
|
|
5934
6281
|
return handle;
|
|
5935
6282
|
}
|
|
6283
|
+
forget(pid) {
|
|
6284
|
+
this.jobs.remove(pid);
|
|
6285
|
+
this.described.delete(pid);
|
|
6286
|
+
}
|
|
5936
6287
|
reaped(pid) {
|
|
5937
|
-
if (this.zombies.delete(pid)) this.
|
|
6288
|
+
if (this.zombies.delete(pid)) this.forget(pid);
|
|
5938
6289
|
else this.orphans.add(pid);
|
|
5939
6290
|
}
|
|
6291
|
+
list() {
|
|
6292
|
+
const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
|
|
6293
|
+
const rootOf = (member) => {
|
|
6294
|
+
let root = member;
|
|
6295
|
+
for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
|
|
6296
|
+
root = up;
|
|
6297
|
+
}
|
|
6298
|
+
return root;
|
|
6299
|
+
};
|
|
6300
|
+
const shown = (pid) => {
|
|
6301
|
+
const member = pid === void 0 ? void 0 : members.get(pid);
|
|
6302
|
+
return member ? rootOf(member).pid : pid ?? 0;
|
|
6303
|
+
};
|
|
6304
|
+
const listed = [];
|
|
6305
|
+
for (const member of members.values()) {
|
|
6306
|
+
const described = this.described.get(member.pid);
|
|
6307
|
+
if (member.execed || !described) continue;
|
|
6308
|
+
const root = rootOf(member);
|
|
6309
|
+
listed.push({
|
|
6310
|
+
pid: root.pid,
|
|
6311
|
+
tid: member.pid,
|
|
6312
|
+
ppid: shown(root.ppid),
|
|
6313
|
+
pgid: member.pgid,
|
|
6314
|
+
sid: member.sid,
|
|
6315
|
+
...described,
|
|
6316
|
+
started: (this.described.get(root.pid) ?? described).started,
|
|
6317
|
+
state: this.processes.has(member.pid) ? "S" : "Z"
|
|
6318
|
+
});
|
|
6319
|
+
}
|
|
6320
|
+
return listed;
|
|
6321
|
+
}
|
|
5940
6322
|
spawner(ppid, report) {
|
|
5941
6323
|
return async (req, fds) => {
|
|
5942
6324
|
const planned = await this.plan(req.file, req.argv, req.cwd);
|
|
@@ -6014,8 +6396,9 @@ var Launcher = class {
|
|
|
6014
6396
|
const env = this.environment(cwd, options.env);
|
|
6015
6397
|
const out = [];
|
|
6016
6398
|
const err = [];
|
|
6399
|
+
const keep = options.collect !== false;
|
|
6017
6400
|
const collect = (chunks, tee) => (bytes) => {
|
|
6018
|
-
chunks.push(bytes);
|
|
6401
|
+
if (keep) chunks.push(bytes);
|
|
6019
6402
|
tee?.(bytes);
|
|
6020
6403
|
};
|
|
6021
6404
|
const stdout = collect(out, options.onStdout);
|
|
@@ -6031,6 +6414,7 @@ var Launcher = class {
|
|
|
6031
6414
|
fds.installAt(1, sinkFile(stdout));
|
|
6032
6415
|
fds.installAt(2, sinkFile(stderr));
|
|
6033
6416
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
6417
|
+
options.onStarted?.(handle.pid);
|
|
6034
6418
|
const status = await handle.exited;
|
|
6035
6419
|
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
6036
6420
|
}
|
|
@@ -6043,7 +6427,7 @@ function signalNumber(name2) {
|
|
|
6043
6427
|
throw new Error(`unknown signal ${name2}`);
|
|
6044
6428
|
return SIG[key];
|
|
6045
6429
|
}
|
|
6046
|
-
function
|
|
6430
|
+
function act2(session, req) {
|
|
6047
6431
|
if (req.action === "write") session.write(req.bytes);
|
|
6048
6432
|
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
6049
6433
|
else if (req.action === "signal") session.signal(signalNumber(req.signal));
|
|
@@ -6056,7 +6440,35 @@ function serveKernel(port, deps) {
|
|
|
6056
6440
|
let launcher;
|
|
6057
6441
|
let remote;
|
|
6058
6442
|
const terminals = /* @__PURE__ */ new Map();
|
|
6059
|
-
const reply = (id, body) => port.postMessage({ id, ...body });
|
|
6443
|
+
const reply = (id, body, transfer) => port.postMessage({ id, ...body }, transfer);
|
|
6444
|
+
const clients = /* @__PURE__ */ new Set();
|
|
6445
|
+
const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
|
|
6446
|
+
let holding;
|
|
6447
|
+
function holdLock(locks2) {
|
|
6448
|
+
holding ??= new Promise((resolve) => {
|
|
6449
|
+
const name2 = `slicc-kernel:${crypto.randomUUID()}`;
|
|
6450
|
+
void locks2.request(name2, () => {
|
|
6451
|
+
resolve(name2);
|
|
6452
|
+
return new Promise(() => {
|
|
6453
|
+
});
|
|
6454
|
+
});
|
|
6455
|
+
});
|
|
6456
|
+
return holding;
|
|
6457
|
+
}
|
|
6458
|
+
async function connect() {
|
|
6459
|
+
await ready2();
|
|
6460
|
+
const { port1, port2 } = new MessageChannel();
|
|
6461
|
+
const held = locks ? await holdLock(locks) : void 0;
|
|
6462
|
+
const served = serveClient(port1, {
|
|
6463
|
+
launcher: ready2,
|
|
6464
|
+
signal: signalNumber,
|
|
6465
|
+
...locks ? { locks } : {},
|
|
6466
|
+
...held ? { lock: held } : {}
|
|
6467
|
+
});
|
|
6468
|
+
clients.add(served);
|
|
6469
|
+
void served.closed.then(() => clients.delete(served));
|
|
6470
|
+
return port2;
|
|
6471
|
+
}
|
|
6060
6472
|
async function ready2() {
|
|
6061
6473
|
if (!launcher) throw new Error("the kernel is not initialized");
|
|
6062
6474
|
return launcher;
|
|
@@ -6095,10 +6507,11 @@ function serveKernel(port, deps) {
|
|
|
6095
6507
|
return true;
|
|
6096
6508
|
}
|
|
6097
6509
|
if (req.op === "open-terminal") return terminal(req);
|
|
6510
|
+
if (req.op === "connect") return connect();
|
|
6098
6511
|
if (req.op === "terminal") {
|
|
6099
6512
|
const session = terminals.get(req.terminal);
|
|
6100
6513
|
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
6101
|
-
|
|
6514
|
+
act2(session, req);
|
|
6102
6515
|
return true;
|
|
6103
6516
|
}
|
|
6104
6517
|
return (await ready2()).run(req.argv, {
|
|
@@ -6114,7 +6527,7 @@ function serveKernel(port, deps) {
|
|
|
6114
6527
|
}
|
|
6115
6528
|
const req = event.data;
|
|
6116
6529
|
handle(req).then(
|
|
6117
|
-
(result) => reply(req.id, { result }),
|
|
6530
|
+
(result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
|
|
6118
6531
|
(err) => reply(req.id, { error: err instanceof Error ? err.message : String(err) })
|
|
6119
6532
|
);
|
|
6120
6533
|
});
|