@ai-ecoverse/slicc-kernel 1.8.11 → 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 +486 -66
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +382 -28
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +945 -179
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +382 -28
- package/dist/process-worker.js.map +4 -4
- package/package.json +2 -2
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("/")) || "/";
|
|
@@ -3023,6 +3351,11 @@ function statJson(s) {
|
|
|
3023
3351
|
async function readdirStat(fs, dir) {
|
|
3024
3352
|
return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
|
|
3025
3353
|
}
|
|
3354
|
+
async function storageUsage() {
|
|
3355
|
+
const estimate = await globalThis.navigator?.storage?.estimate?.();
|
|
3356
|
+
if (typeof estimate?.quota !== "number") return null;
|
|
3357
|
+
return { quota: estimate.quota, usage: estimate.usage ?? 0 };
|
|
3358
|
+
}
|
|
3026
3359
|
var done = { ok: true, kind: "void" };
|
|
3027
3360
|
function json(value) {
|
|
3028
3361
|
return { ok: true, kind: "json", json: value };
|
|
@@ -3073,6 +3406,8 @@ async function run(fs, path, req, cwd) {
|
|
|
3073
3406
|
case "utimes":
|
|
3074
3407
|
await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
|
|
3075
3408
|
return done;
|
|
3409
|
+
case "statfs":
|
|
3410
|
+
return json(await storageUsage());
|
|
3076
3411
|
default:
|
|
3077
3412
|
return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
|
|
3078
3413
|
}
|
|
@@ -4705,6 +5040,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4705
5040
|
"proc-captured",
|
|
4706
5041
|
"proc-fork",
|
|
4707
5042
|
"proc-kill",
|
|
5043
|
+
"proc-list",
|
|
4708
5044
|
"proc-exec",
|
|
4709
5045
|
"proc-setpgid",
|
|
4710
5046
|
"proc-getpgid",
|
|
@@ -5153,6 +5489,12 @@ var WasmProcess = class {
|
|
|
5153
5489
|
throw new KernelError("ESRCH");
|
|
5154
5490
|
}
|
|
5155
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
|
+
};
|
|
5156
5498
|
case "proc-alarm":
|
|
5157
5499
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
5158
5500
|
return { ok: true, kind: "void" };
|
|
@@ -5256,6 +5598,7 @@ function spawnWasmProcess(opts) {
|
|
|
5256
5598
|
forker: opts.forker,
|
|
5257
5599
|
fs: opts.fs,
|
|
5258
5600
|
kill: opts.kill,
|
|
5601
|
+
processes: opts.processes,
|
|
5259
5602
|
jobs: opts.jobs,
|
|
5260
5603
|
ptys: opts.ptys,
|
|
5261
5604
|
net: opts.net,
|
|
@@ -5400,6 +5743,9 @@ var JobTable = class {
|
|
|
5400
5743
|
this.members.set(pid, member);
|
|
5401
5744
|
return member;
|
|
5402
5745
|
}
|
|
5746
|
+
list() {
|
|
5747
|
+
return [...this.members.values()];
|
|
5748
|
+
}
|
|
5403
5749
|
remove(pid) {
|
|
5404
5750
|
this.members.delete(pid);
|
|
5405
5751
|
}
|
|
@@ -5730,6 +6076,7 @@ var Launcher = class {
|
|
|
5730
6076
|
processes = /* @__PURE__ */ new Map();
|
|
5731
6077
|
zombies = /* @__PURE__ */ new Set();
|
|
5732
6078
|
orphans = /* @__PURE__ */ new Set();
|
|
6079
|
+
described = /* @__PURE__ */ new Map();
|
|
5733
6080
|
jobs = new JobTable();
|
|
5734
6081
|
locks = new LockTable();
|
|
5735
6082
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
@@ -5737,6 +6084,7 @@ var Launcher = class {
|
|
|
5737
6084
|
ca;
|
|
5738
6085
|
transport;
|
|
5739
6086
|
nextPid = 1e3;
|
|
6087
|
+
boot = Date.now();
|
|
5740
6088
|
terminals = 0;
|
|
5741
6089
|
constructor(options) {
|
|
5742
6090
|
this.base = options.fs;
|
|
@@ -5908,6 +6256,7 @@ var Launcher = class {
|
|
|
5908
6256
|
spawner: this.spawner(pid, req.report),
|
|
5909
6257
|
forker: this.forker(pid, req),
|
|
5910
6258
|
kill: (target, sig) => this.kill(target, sig),
|
|
6259
|
+
processes: () => ({ boot: this.boot, processes: this.list() }),
|
|
5911
6260
|
jobs: this.jobs,
|
|
5912
6261
|
ptys: this.ptys,
|
|
5913
6262
|
net: this.net,
|
|
@@ -5919,17 +6268,57 @@ var Launcher = class {
|
|
|
5919
6268
|
});
|
|
5920
6269
|
this.processes.set(pid, handle);
|
|
5921
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
|
+
});
|
|
5922
6276
|
void handle.exited.then(() => {
|
|
5923
6277
|
this.processes.delete(pid);
|
|
5924
|
-
if (req.ppid === void 0 || this.orphans.delete(pid)) this.
|
|
6278
|
+
if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
|
|
5925
6279
|
else this.zombies.add(pid);
|
|
5926
6280
|
});
|
|
5927
6281
|
return handle;
|
|
5928
6282
|
}
|
|
6283
|
+
forget(pid) {
|
|
6284
|
+
this.jobs.remove(pid);
|
|
6285
|
+
this.described.delete(pid);
|
|
6286
|
+
}
|
|
5929
6287
|
reaped(pid) {
|
|
5930
|
-
if (this.zombies.delete(pid)) this.
|
|
6288
|
+
if (this.zombies.delete(pid)) this.forget(pid);
|
|
5931
6289
|
else this.orphans.add(pid);
|
|
5932
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
|
+
}
|
|
5933
6322
|
spawner(ppid, report) {
|
|
5934
6323
|
return async (req, fds) => {
|
|
5935
6324
|
const planned = await this.plan(req.file, req.argv, req.cwd);
|
|
@@ -6007,8 +6396,9 @@ var Launcher = class {
|
|
|
6007
6396
|
const env = this.environment(cwd, options.env);
|
|
6008
6397
|
const out = [];
|
|
6009
6398
|
const err = [];
|
|
6399
|
+
const keep = options.collect !== false;
|
|
6010
6400
|
const collect = (chunks, tee) => (bytes) => {
|
|
6011
|
-
chunks.push(bytes);
|
|
6401
|
+
if (keep) chunks.push(bytes);
|
|
6012
6402
|
tee?.(bytes);
|
|
6013
6403
|
};
|
|
6014
6404
|
const stdout = collect(out, options.onStdout);
|
|
@@ -6024,6 +6414,7 @@ var Launcher = class {
|
|
|
6024
6414
|
fds.installAt(1, sinkFile(stdout));
|
|
6025
6415
|
fds.installAt(2, sinkFile(stderr));
|
|
6026
6416
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
6417
|
+
options.onStarted?.(handle.pid);
|
|
6027
6418
|
const status = await handle.exited;
|
|
6028
6419
|
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
6029
6420
|
}
|
|
@@ -6036,7 +6427,7 @@ function signalNumber(name2) {
|
|
|
6036
6427
|
throw new Error(`unknown signal ${name2}`);
|
|
6037
6428
|
return SIG[key];
|
|
6038
6429
|
}
|
|
6039
|
-
function
|
|
6430
|
+
function act2(session, req) {
|
|
6040
6431
|
if (req.action === "write") session.write(req.bytes);
|
|
6041
6432
|
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
6042
6433
|
else if (req.action === "signal") session.signal(signalNumber(req.signal));
|
|
@@ -6049,7 +6440,35 @@ function serveKernel(port, deps) {
|
|
|
6049
6440
|
let launcher;
|
|
6050
6441
|
let remote;
|
|
6051
6442
|
const terminals = /* @__PURE__ */ new Map();
|
|
6052
|
-
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
|
+
}
|
|
6053
6472
|
async function ready2() {
|
|
6054
6473
|
if (!launcher) throw new Error("the kernel is not initialized");
|
|
6055
6474
|
return launcher;
|
|
@@ -6088,10 +6507,11 @@ function serveKernel(port, deps) {
|
|
|
6088
6507
|
return true;
|
|
6089
6508
|
}
|
|
6090
6509
|
if (req.op === "open-terminal") return terminal(req);
|
|
6510
|
+
if (req.op === "connect") return connect();
|
|
6091
6511
|
if (req.op === "terminal") {
|
|
6092
6512
|
const session = terminals.get(req.terminal);
|
|
6093
6513
|
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
6094
|
-
|
|
6514
|
+
act2(session, req);
|
|
6095
6515
|
return true;
|
|
6096
6516
|
}
|
|
6097
6517
|
return (await ready2()).run(req.argv, {
|
|
@@ -6107,7 +6527,7 @@ function serveKernel(port, deps) {
|
|
|
6107
6527
|
}
|
|
6108
6528
|
const req = event.data;
|
|
6109
6529
|
handle(req).then(
|
|
6110
|
-
(result) => reply(req.id, { result }),
|
|
6530
|
+
(result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
|
|
6111
6531
|
(err) => reply(req.id, { error: err instanceof Error ? err.message : String(err) })
|
|
6112
6532
|
);
|
|
6113
6533
|
});
|