@ai-ecoverse/slicc-kernel 1.19.0 → 1.20.1
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 +14 -3
- package/dist/driver-worker.js +248 -3
- package/dist/driver-worker.js.map +4 -4
- package/dist/index.js +275 -4
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +2024 -1897
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +248 -3
- package/dist/node-driver-worker.js.map +4 -4
- package/dist/node-process-worker.js +23 -0
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +2536 -2180
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +23 -0
- package/dist/process-worker.js.map +3 -3
- package/package.json +2 -2
package/dist/kernel-worker.js
CHANGED
|
@@ -225,7 +225,7 @@ function pinScript(url) {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// src/client/protocol.ts
|
|
228
|
-
var PROTOCOL = [1,
|
|
228
|
+
var PROTOCOL = [1, 4];
|
|
229
229
|
var FS_METHODS = [
|
|
230
230
|
"readFile",
|
|
231
231
|
"writeFile",
|
|
@@ -280,1232 +280,1638 @@ function inodeOf(path) {
|
|
|
280
280
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
// src/kernel/
|
|
284
|
-
var
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
BUS: 7,
|
|
292
|
-
FPE: 8,
|
|
293
|
-
KILL: 9,
|
|
294
|
-
USR1: 10,
|
|
295
|
-
SEGV: 11,
|
|
296
|
-
USR2: 12,
|
|
297
|
-
PIPE: 13,
|
|
298
|
-
ALRM: 14,
|
|
299
|
-
TERM: 15,
|
|
300
|
-
STKFLT: 16,
|
|
301
|
-
CHLD: 17,
|
|
302
|
-
CONT: 18,
|
|
303
|
-
STOP: 19,
|
|
304
|
-
TSTP: 20,
|
|
305
|
-
TTIN: 21,
|
|
306
|
-
TTOU: 22,
|
|
307
|
-
URG: 23,
|
|
308
|
-
XCPU: 24,
|
|
309
|
-
XFSZ: 25,
|
|
310
|
-
VTALRM: 26,
|
|
311
|
-
PROF: 27,
|
|
312
|
-
WINCH: 28,
|
|
313
|
-
POLL: 29,
|
|
314
|
-
PWR: 30,
|
|
315
|
-
SYS: 31
|
|
316
|
-
};
|
|
317
|
-
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
318
|
-
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
319
|
-
function defaultAction(sig) {
|
|
320
|
-
if (sig === SIG.CONT) return "continue";
|
|
321
|
-
if (STOPPING.has(sig)) return "stop";
|
|
322
|
-
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
323
|
-
}
|
|
324
|
-
function isSignal(sig) {
|
|
325
|
-
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
326
|
-
}
|
|
327
|
-
function sigbit(sig) {
|
|
328
|
-
return 1 << sig;
|
|
329
|
-
}
|
|
330
|
-
var SIGNAL_BY_NAME = {
|
|
331
|
-
SIGINT: SIG.INT,
|
|
332
|
-
SIGTERM: SIG.TERM,
|
|
333
|
-
SIGKILL: SIG.KILL,
|
|
334
|
-
SIGSTOP: SIG.STOP,
|
|
335
|
-
SIGCONT: SIG.CONT
|
|
283
|
+
// src/kernel/pipe.ts
|
|
284
|
+
var PIPE_CAPACITY = 64 * 1024;
|
|
285
|
+
var PipeError = class extends Error {
|
|
286
|
+
code;
|
|
287
|
+
constructor(code) {
|
|
288
|
+
super(code);
|
|
289
|
+
this.code = code;
|
|
290
|
+
}
|
|
336
291
|
};
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
open.delete(nid);
|
|
347
|
-
const { status, code } = e;
|
|
348
|
-
peer.postMessage({
|
|
349
|
-
net: "error",
|
|
350
|
-
nid,
|
|
351
|
-
message: e instanceof Error ? e.message : String(e),
|
|
352
|
-
...typeof status === "number" ? { status } : {},
|
|
353
|
-
...typeof code === "string" ? { code } : {}
|
|
354
|
-
});
|
|
355
|
-
};
|
|
356
|
-
const start = async (call) => {
|
|
357
|
-
const abort = new AbortController();
|
|
358
|
-
const entry = { abort };
|
|
359
|
-
open.set(call.nid, entry);
|
|
360
|
-
try {
|
|
361
|
-
const response = await transport.fetch({
|
|
362
|
-
url: call.url,
|
|
363
|
-
method: call.method,
|
|
364
|
-
headers: call.headers,
|
|
365
|
-
...call.body ? { body: call.body } : {},
|
|
366
|
-
signal: abort.signal
|
|
367
|
-
});
|
|
368
|
-
if (!open.has(call.nid)) {
|
|
369
|
-
await response.cancel();
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
|
|
373
|
-
peer.postMessage({
|
|
374
|
-
net: "head",
|
|
375
|
-
nid: call.nid,
|
|
376
|
-
status: response.status,
|
|
377
|
-
statusText: response.statusText,
|
|
378
|
-
headers: response.headers
|
|
379
|
-
});
|
|
380
|
-
} catch (e) {
|
|
381
|
-
fail(call.nid, e);
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
const read = async (nid) => {
|
|
385
|
-
const body = open.get(nid)?.body;
|
|
386
|
-
if (!body) return;
|
|
387
|
-
try {
|
|
388
|
-
const next = await body.next();
|
|
389
|
-
if (next.done) {
|
|
390
|
-
open.delete(nid);
|
|
391
|
-
peer.postMessage({ net: "end", nid });
|
|
392
|
-
} else {
|
|
393
|
-
const bytes = next.value.slice();
|
|
394
|
-
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
395
|
-
}
|
|
396
|
-
} catch (e) {
|
|
397
|
-
fail(nid, e);
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
const drop = (nid) => {
|
|
401
|
-
const entry = open.get(nid);
|
|
402
|
-
open.delete(nid);
|
|
403
|
-
entry?.abort.abort();
|
|
404
|
-
void entry?.response?.cancel().catch(() => void 0);
|
|
405
|
-
};
|
|
406
|
-
return {
|
|
407
|
-
answer(call) {
|
|
408
|
-
if (call.net === "fetch") void start(call);
|
|
409
|
-
else if (call.net === "read") void read(call.nid);
|
|
410
|
-
else drop(call.nid);
|
|
411
|
-
},
|
|
412
|
-
close() {
|
|
413
|
-
for (const nid of [...open.keys()]) drop(nid);
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// src/client/serve-client.ts
|
|
419
|
-
var MAX_LINKS = 40;
|
|
420
|
-
function act(session, req, signal) {
|
|
421
|
-
if (req.action === "write") session.write(req.bytes);
|
|
422
|
-
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
423
|
-
else if (req.action === "signal") session.signal(signal(req.signal));
|
|
424
|
-
else session.close();
|
|
425
|
-
}
|
|
426
|
-
async function realpath(fs, path) {
|
|
427
|
-
let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
|
|
428
|
-
let resolved = "";
|
|
429
|
-
for (let hops = 0; parts.length > 0; ) {
|
|
430
|
-
const [name2, ...rest] = parts;
|
|
431
|
-
const next = `${resolved}/${name2}`;
|
|
432
|
-
if (!(await fs.lstat(next)).isSymbolicLink) {
|
|
433
|
-
resolved = next;
|
|
434
|
-
parts = rest;
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
|
|
438
|
-
const target = await fs.readlink(next);
|
|
439
|
-
const base = target.startsWith("/") ? "/" : resolved || "/";
|
|
440
|
-
parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
|
|
441
|
-
resolved = "";
|
|
292
|
+
var KernelPipe = class {
|
|
293
|
+
chunks = [];
|
|
294
|
+
size = 0;
|
|
295
|
+
readers = 0;
|
|
296
|
+
writers = 0;
|
|
297
|
+
waiters = [];
|
|
298
|
+
capacity;
|
|
299
|
+
constructor(capacity = PIPE_CAPACITY) {
|
|
300
|
+
this.capacity = capacity;
|
|
442
301
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
302
|
+
get buffered() {
|
|
303
|
+
return this.size;
|
|
304
|
+
}
|
|
305
|
+
get readReady() {
|
|
306
|
+
return this.size > 0 || this.writers === 0;
|
|
307
|
+
}
|
|
308
|
+
get writeReady() {
|
|
309
|
+
return this.size < this.capacity || this.readers === 0;
|
|
310
|
+
}
|
|
311
|
+
get writersGone() {
|
|
312
|
+
return this.writers === 0;
|
|
313
|
+
}
|
|
314
|
+
get readersGone() {
|
|
315
|
+
return this.readers === 0;
|
|
316
|
+
}
|
|
317
|
+
openRead() {
|
|
318
|
+
this.readers += 1;
|
|
319
|
+
}
|
|
320
|
+
openWrite() {
|
|
321
|
+
this.writers += 1;
|
|
322
|
+
}
|
|
323
|
+
closeRead() {
|
|
324
|
+
if (this.readers > 0) this.readers -= 1;
|
|
325
|
+
if (this.readers === 0) {
|
|
326
|
+
this.chunks = [];
|
|
327
|
+
this.size = 0;
|
|
451
328
|
}
|
|
452
|
-
|
|
453
|
-
await fs.writeFile(path(0), args[1]);
|
|
454
|
-
return { result: true };
|
|
455
|
-
case "stat":
|
|
456
|
-
return { result: await fs.stat(path(0)) };
|
|
457
|
-
case "lstat":
|
|
458
|
-
return { result: await fs.lstat(path(0)) };
|
|
459
|
-
case "readdir":
|
|
460
|
-
return { result: await fs.readdir(path(0)) };
|
|
461
|
-
case "mkdir":
|
|
462
|
-
await fs.mkdir(path(0), { recursive: true });
|
|
463
|
-
return { result: true };
|
|
464
|
-
case "rm":
|
|
465
|
-
await fs.rm(path(0), { recursive: true, force: args[1] === true });
|
|
466
|
-
return { result: true };
|
|
467
|
-
case "rename":
|
|
468
|
-
await fs.rename(path(0), path(1));
|
|
469
|
-
return { result: true };
|
|
470
|
-
case "realpath":
|
|
471
|
-
return { result: await realpath(fs, String(args[0])) };
|
|
472
|
-
case "symlink":
|
|
473
|
-
await fs.symlink(String(args[0]), path(1));
|
|
474
|
-
return { result: true };
|
|
475
|
-
case "readlink":
|
|
476
|
-
return { result: await fs.readlink(path(0)) };
|
|
477
|
-
default:
|
|
478
|
-
return { result: await fs.exists(path(0)) };
|
|
329
|
+
this.wake();
|
|
479
330
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
throw fsError("EINVAL", `this kernel names no signal ${name2}`);
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
function killGroups(host, pgids) {
|
|
487
|
-
if (pgids.length === 0) return;
|
|
488
|
-
void host.launcher().then((launcher) => {
|
|
489
|
-
for (const pgid of pgids) launcher.kill(-pgid, SIG.KILL);
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
function joinable(l, pgid, ours) {
|
|
493
|
-
const sid = l.groupSession(pgid);
|
|
494
|
-
if (sid === void 0) throw fsError("ESRCH", `no process group ${pgid}`);
|
|
495
|
-
if (!ours(sid)) throw fsError("EPERM", `process group ${pgid} is in another session`);
|
|
496
|
-
}
|
|
497
|
-
function serveClient(port, host) {
|
|
498
|
-
let state = "new";
|
|
499
|
-
const terminals = /* @__PURE__ */ new Map();
|
|
500
|
-
const groups = /* @__PURE__ */ new Set();
|
|
501
|
-
const sessions = /* @__PURE__ */ new Set();
|
|
502
|
-
const watches = /* @__PURE__ */ new Map();
|
|
503
|
-
let net;
|
|
504
|
-
const ended = Promise.withResolvers();
|
|
505
|
-
const send = (message2, transfer = []) => {
|
|
506
|
-
if (state !== "closed") port.postMessage(message2, transfer);
|
|
507
|
-
};
|
|
508
|
-
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
509
|
-
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
510
|
-
if (state === "closed") return;
|
|
511
|
-
send({ bye });
|
|
512
|
-
state = "closed";
|
|
513
|
-
net?.close();
|
|
514
|
-
for (const unwatch of watches.values()) unwatch();
|
|
515
|
-
watches.clear();
|
|
516
|
-
for (const session of terminals.values()) session.close();
|
|
517
|
-
terminals.clear();
|
|
518
|
-
if (kill) killGroups(host, [...groups]);
|
|
519
|
-
groups.clear();
|
|
520
|
-
port.close?.();
|
|
521
|
-
ended.resolve();
|
|
331
|
+
closeWrite() {
|
|
332
|
+
if (this.writers > 0) this.writers -= 1;
|
|
333
|
+
this.wake();
|
|
522
334
|
}
|
|
523
|
-
async
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
detach();
|
|
528
|
-
return;
|
|
335
|
+
async read(max, signal) {
|
|
336
|
+
while (this.size === 0) {
|
|
337
|
+
if (this.writers === 0) return new Uint8Array(0);
|
|
338
|
+
await this.changed(signal);
|
|
529
339
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
state = "open";
|
|
534
|
-
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
535
|
-
send({
|
|
536
|
-
hello: {
|
|
537
|
-
protocol: PROTOCOL,
|
|
538
|
-
traits: transport.traits,
|
|
539
|
-
...host.lock ? { lock: host.lock } : {}
|
|
540
|
-
}
|
|
541
|
-
});
|
|
340
|
+
const out = this.take(Math.min(max, this.size));
|
|
341
|
+
this.wake();
|
|
342
|
+
return out;
|
|
542
343
|
}
|
|
543
|
-
async
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
});
|
|
548
|
-
if (state === "closed") {
|
|
549
|
-
session.close();
|
|
550
|
-
} else {
|
|
551
|
-
terminals.set(req.id, session);
|
|
552
|
-
sessions.add(session.pid);
|
|
553
|
-
reply(req.id, { started: session.pid });
|
|
344
|
+
async peek(max, signal) {
|
|
345
|
+
while (this.size === 0) {
|
|
346
|
+
if (this.writers === 0) return new Uint8Array(0);
|
|
347
|
+
await this.changed(signal);
|
|
554
348
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
349
|
+
const out = new Uint8Array(Math.min(max, this.size));
|
|
350
|
+
let filled = 0;
|
|
351
|
+
for (const chunk2 of this.chunks) {
|
|
352
|
+
if (filled === out.length) break;
|
|
353
|
+
const count = Math.min(chunk2.length, out.length - filled);
|
|
354
|
+
out.set(chunk2.subarray(0, count), filled);
|
|
355
|
+
filled += count;
|
|
559
356
|
}
|
|
357
|
+
return out;
|
|
560
358
|
}
|
|
561
|
-
async
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (state === "closed") return;
|
|
573
|
-
groups.add(req.options.pgid ?? pid);
|
|
574
|
-
if (req.options.pgid === void 0) sessions.add(pid);
|
|
575
|
-
reply(req.id, { started: pid });
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
579
|
-
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
580
|
-
return { result: result.status };
|
|
581
|
-
}
|
|
582
|
-
async function handle(req) {
|
|
583
|
-
if (host.scope === "transport" && req.op !== "detach") {
|
|
584
|
-
throw fsError("EPERM", `${req.op}: this client may only use the network transport`);
|
|
585
|
-
}
|
|
586
|
-
const l = await host.launcher();
|
|
587
|
-
switch (req.op) {
|
|
588
|
-
case "spawn":
|
|
589
|
-
return spawn(req, l);
|
|
590
|
-
case "open-terminal":
|
|
591
|
-
return terminal(req, l);
|
|
592
|
-
case "terminal": {
|
|
593
|
-
const session = terminals.get(req.terminal);
|
|
594
|
-
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
595
|
-
act(session, req, signalOf(host));
|
|
596
|
-
return { result: true };
|
|
597
|
-
}
|
|
598
|
-
case "kill":
|
|
599
|
-
if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
600
|
-
return { result: true };
|
|
601
|
-
case "ps":
|
|
602
|
-
return {
|
|
603
|
-
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
604
|
-
};
|
|
605
|
-
case "fs":
|
|
606
|
-
if (!FS_METHODS.includes(req.method)) {
|
|
607
|
-
throw new Error(`unknown file system call ${req.method}`);
|
|
359
|
+
async write(bytes, signal) {
|
|
360
|
+
let offset = 0;
|
|
361
|
+
while (offset < bytes.length) {
|
|
362
|
+
if (this.readers === 0) throw new PipeError("EPIPE");
|
|
363
|
+
const room = this.capacity - this.size;
|
|
364
|
+
if (room === 0) {
|
|
365
|
+
try {
|
|
366
|
+
await this.changed(signal);
|
|
367
|
+
} catch (e) {
|
|
368
|
+
if (offset > 0) return offset;
|
|
369
|
+
throw e;
|
|
608
370
|
}
|
|
609
|
-
|
|
610
|
-
case "watch": {
|
|
611
|
-
const unwatch = l.watchers.watch(
|
|
612
|
-
req.paths,
|
|
613
|
-
{ recursive: req.recursive },
|
|
614
|
-
(change) => send({ watch: req.id, change })
|
|
615
|
-
);
|
|
616
|
-
watches.set(req.id, unwatch);
|
|
617
|
-
return { result: req.id };
|
|
371
|
+
continue;
|
|
618
372
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
return { result: await l.mount(req.spec) };
|
|
625
|
-
case "umount":
|
|
626
|
-
l.umount(req.target);
|
|
627
|
-
return { result: true };
|
|
628
|
-
case "mounts":
|
|
629
|
-
return { result: l.mounts.list() };
|
|
630
|
-
case "detach":
|
|
631
|
-
return { result: true };
|
|
632
|
-
default:
|
|
633
|
-
throw new Error(`unknown op ${req.op}`);
|
|
373
|
+
const n = Math.min(room, bytes.length - offset);
|
|
374
|
+
this.chunks.push(bytes.slice(offset, offset + n));
|
|
375
|
+
this.size += n;
|
|
376
|
+
offset += n;
|
|
377
|
+
this.wake();
|
|
634
378
|
}
|
|
379
|
+
return bytes.length;
|
|
635
380
|
}
|
|
636
|
-
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
381
|
+
take(n) {
|
|
382
|
+
const out = new Uint8Array(n);
|
|
383
|
+
let filled = 0;
|
|
384
|
+
while (filled < n) {
|
|
385
|
+
const head = this.chunks[0];
|
|
386
|
+
const count = Math.min(head.length, n - filled);
|
|
387
|
+
out.set(head.subarray(0, count), filled);
|
|
388
|
+
filled += count;
|
|
389
|
+
if (count === head.length) this.chunks.shift();
|
|
390
|
+
else this.chunks[0] = head.subarray(count);
|
|
391
|
+
}
|
|
392
|
+
this.size -= n;
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
changed(signal) {
|
|
396
|
+
if (signal?.aborted) return Promise.reject(new PipeError("EINTR"));
|
|
397
|
+
return new Promise((resolve, reject) => {
|
|
398
|
+
const onAbort = () => {
|
|
399
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
400
|
+
reject(new PipeError("EINTR"));
|
|
401
|
+
};
|
|
402
|
+
const waiter = () => {
|
|
403
|
+
signal?.removeEventListener("abort", onAbort);
|
|
404
|
+
resolve();
|
|
405
|
+
};
|
|
406
|
+
this.waiters.push(waiter);
|
|
407
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
wake() {
|
|
411
|
+
const waiters = this.waiters;
|
|
412
|
+
this.waiters = [];
|
|
413
|
+
for (const resolve of waiters) resolve();
|
|
414
|
+
}
|
|
415
|
+
};
|
|
672
416
|
|
|
673
|
-
// src/
|
|
674
|
-
var
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
var CHECK_MS = 1e3;
|
|
681
|
-
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
682
|
-
var ERRNO_BY_DOM_ERROR = {
|
|
683
|
-
NotFoundError: "ENOENT",
|
|
684
|
-
TypeMismatchError: "ENOTDIR",
|
|
685
|
-
InvalidModificationError: "ENOTEMPTY",
|
|
686
|
-
NoModificationAllowedError: "EBUSY",
|
|
687
|
-
TypeError: "EINVAL",
|
|
688
|
-
NotAllowedError: "EACCES"
|
|
417
|
+
// src/kernel/fd-table.ts
|
|
418
|
+
var KernelError = class extends Error {
|
|
419
|
+
code;
|
|
420
|
+
constructor(code) {
|
|
421
|
+
super(code);
|
|
422
|
+
this.code = code;
|
|
423
|
+
}
|
|
689
424
|
};
|
|
690
|
-
function
|
|
691
|
-
|
|
692
|
-
const name2 = err?.name;
|
|
693
|
-
return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
|
|
425
|
+
function pollFile(file) {
|
|
426
|
+
return file.poll?.() ?? { readable: !!file.read, writable: !!file.write, hangup: false };
|
|
694
427
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
428
|
+
var OpenFile = class {
|
|
429
|
+
refs = 1;
|
|
430
|
+
file;
|
|
431
|
+
constructor(file) {
|
|
432
|
+
this.file = file;
|
|
433
|
+
}
|
|
434
|
+
retain() {
|
|
435
|
+
this.refs += 1;
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
release() {
|
|
439
|
+
this.refs -= 1;
|
|
440
|
+
if (this.refs === 0) return this.file.close();
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
function openPipe(capacity) {
|
|
444
|
+
const pipe = new KernelPipe(capacity);
|
|
445
|
+
pipe.openRead();
|
|
446
|
+
pipe.openWrite();
|
|
447
|
+
return {
|
|
448
|
+
read: new OpenFile({
|
|
449
|
+
read: async (max, signal) => {
|
|
450
|
+
try {
|
|
451
|
+
return await pipe.read(max, signal);
|
|
452
|
+
} catch (e) {
|
|
453
|
+
throw new KernelError(e.code);
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
close: () => pipe.closeRead(),
|
|
457
|
+
poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
|
|
458
|
+
changed: (signal) => pipe.changed(signal)
|
|
459
|
+
}),
|
|
460
|
+
write: new OpenFile({
|
|
461
|
+
write: async (bytes, signal) => {
|
|
462
|
+
try {
|
|
463
|
+
return await pipe.write(bytes, signal);
|
|
464
|
+
} catch (e) {
|
|
465
|
+
throw new KernelError(e.code);
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
close: () => pipe.closeWrite(),
|
|
469
|
+
poll: () => ({ readable: false, writable: pipe.writeReady, hangup: pipe.readersGone }),
|
|
470
|
+
changed: (signal) => pipe.changed(signal)
|
|
471
|
+
})
|
|
472
|
+
};
|
|
698
473
|
}
|
|
699
|
-
function
|
|
700
|
-
|
|
474
|
+
function bytesSource(data) {
|
|
475
|
+
let offset = 0;
|
|
476
|
+
return new OpenFile({
|
|
477
|
+
read: async (max) => {
|
|
478
|
+
const out = data.subarray(offset, offset + max);
|
|
479
|
+
offset += out.length;
|
|
480
|
+
return out;
|
|
481
|
+
},
|
|
482
|
+
close: () => {
|
|
483
|
+
}
|
|
484
|
+
});
|
|
701
485
|
}
|
|
702
|
-
function
|
|
703
|
-
return
|
|
486
|
+
function sinkFile(onData) {
|
|
487
|
+
return new OpenFile({
|
|
488
|
+
write: async (bytes) => {
|
|
489
|
+
onData(bytes.slice());
|
|
490
|
+
return bytes.length;
|
|
491
|
+
},
|
|
492
|
+
close: () => {
|
|
493
|
+
}
|
|
494
|
+
});
|
|
704
495
|
}
|
|
705
|
-
function
|
|
706
|
-
return
|
|
496
|
+
function nullFile() {
|
|
497
|
+
return new OpenFile({
|
|
498
|
+
read: async () => new Uint8Array(0),
|
|
499
|
+
write: async (bytes) => bytes.length,
|
|
500
|
+
close: () => {
|
|
501
|
+
}
|
|
502
|
+
});
|
|
707
503
|
}
|
|
708
|
-
function
|
|
709
|
-
const
|
|
710
|
-
|
|
504
|
+
function deviceFile(device, access) {
|
|
505
|
+
const read = async (max) => {
|
|
506
|
+
if (device === "null") return new Uint8Array(0);
|
|
507
|
+
const out = new Uint8Array(max);
|
|
508
|
+
if (device === "urandom") {
|
|
509
|
+
for (let at = 0; at < max; at += 65536) {
|
|
510
|
+
crypto.getRandomValues(out.subarray(at, Math.min(max, at + 65536)));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
};
|
|
515
|
+
return new OpenFile({
|
|
516
|
+
...access !== "write" ? { read } : {},
|
|
517
|
+
...access !== "read" ? { write: async (bytes) => bytes.length } : {},
|
|
518
|
+
seek: async () => 0,
|
|
519
|
+
heldMeta: { device, ...access ? { access } : {} },
|
|
520
|
+
close: () => {
|
|
521
|
+
}
|
|
522
|
+
});
|
|
711
523
|
}
|
|
712
|
-
function
|
|
713
|
-
return
|
|
524
|
+
function heldFile(meta) {
|
|
525
|
+
return new OpenFile({ held: true, ...meta ? { heldMeta: meta } : {}, close: () => {
|
|
526
|
+
} });
|
|
714
527
|
}
|
|
715
|
-
function
|
|
716
|
-
return "
|
|
528
|
+
function kernelFdKind(file) {
|
|
529
|
+
if (file.held) return "held";
|
|
530
|
+
if (file.heldMeta && "device" in file.heldMeta) return "device";
|
|
531
|
+
if (file.tty) return "tty";
|
|
532
|
+
return file.seek ? "file" : "stream";
|
|
717
533
|
}
|
|
718
|
-
var
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
534
|
+
var FdTable = class _FdTable {
|
|
535
|
+
static MAX_FDS = 1024;
|
|
536
|
+
fds = /* @__PURE__ */ new Map();
|
|
537
|
+
cloexec = /* @__PURE__ */ new Set();
|
|
538
|
+
status = /* @__PURE__ */ new Map();
|
|
539
|
+
get(fd) {
|
|
540
|
+
const file = this.fds.get(fd);
|
|
541
|
+
if (!file) throw new KernelError("EBADF");
|
|
542
|
+
return file;
|
|
543
|
+
}
|
|
544
|
+
stdioTerminal() {
|
|
545
|
+
for (const fd of [0, 1, 2]) {
|
|
546
|
+
const tty = this.fds.get(fd)?.file.tty;
|
|
547
|
+
if (tty) return tty;
|
|
548
|
+
}
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
numbers() {
|
|
552
|
+
return [...this.fds.keys()].sort((a, b) => a - b);
|
|
553
|
+
}
|
|
554
|
+
has(fd) {
|
|
555
|
+
return this.fds.has(fd);
|
|
556
|
+
}
|
|
557
|
+
setCloseOnExec(fd) {
|
|
558
|
+
this.get(fd);
|
|
559
|
+
this.cloexec.add(fd);
|
|
560
|
+
}
|
|
561
|
+
clearCloseOnExec(fd) {
|
|
562
|
+
this.get(fd);
|
|
563
|
+
this.cloexec.delete(fd);
|
|
564
|
+
}
|
|
565
|
+
closesOnExec(fd) {
|
|
566
|
+
return this.cloexec.has(fd);
|
|
567
|
+
}
|
|
568
|
+
setStatusFlags(fd, flags) {
|
|
569
|
+
this.get(fd);
|
|
570
|
+
this.status.set(fd, flags);
|
|
571
|
+
}
|
|
572
|
+
statusFlags(fd) {
|
|
573
|
+
return this.status.get(fd);
|
|
574
|
+
}
|
|
575
|
+
install(file, min = 0) {
|
|
576
|
+
for (let fd = min; fd < _FdTable.MAX_FDS; fd++) {
|
|
577
|
+
if (!this.fds.has(fd)) {
|
|
578
|
+
this.fds.set(fd, file);
|
|
579
|
+
return fd;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
void Promise.resolve(file.release()).catch(() => void 0);
|
|
583
|
+
throw new KernelError("EMFILE");
|
|
584
|
+
}
|
|
585
|
+
installAt(fd, file) {
|
|
586
|
+
if (fd < 0 || fd >= _FdTable.MAX_FDS) {
|
|
587
|
+
void Promise.resolve(file.release()).catch(() => void 0);
|
|
588
|
+
throw new KernelError("EBADF");
|
|
733
589
|
}
|
|
590
|
+
const previous = this.fds.get(fd);
|
|
591
|
+
this.fds.set(fd, file);
|
|
592
|
+
this.cloexec.delete(fd);
|
|
593
|
+
this.status.delete(fd);
|
|
594
|
+
void Promise.resolve(previous?.release()).catch(() => void 0);
|
|
595
|
+
}
|
|
596
|
+
dup(fd, min = 0) {
|
|
597
|
+
return this.install(this.get(fd).retain(), min);
|
|
598
|
+
}
|
|
599
|
+
dup2(oldFd, newFd) {
|
|
600
|
+
const file = this.get(oldFd);
|
|
601
|
+
if (oldFd !== newFd) this.installAt(newFd, file.retain());
|
|
602
|
+
return newFd;
|
|
734
603
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
this.
|
|
604
|
+
close(fd) {
|
|
605
|
+
const file = this.get(fd);
|
|
606
|
+
this.fds.delete(fd);
|
|
607
|
+
this.cloexec.delete(fd);
|
|
608
|
+
this.status.delete(fd);
|
|
609
|
+
return file.release();
|
|
738
610
|
}
|
|
739
|
-
|
|
740
|
-
|
|
611
|
+
fork() {
|
|
612
|
+
const child = new _FdTable();
|
|
613
|
+
for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
|
|
614
|
+
for (const fd of this.cloexec) child.cloexec.add(fd);
|
|
615
|
+
for (const [fd, flags] of this.status) child.status.set(fd, flags);
|
|
616
|
+
return child;
|
|
741
617
|
}
|
|
742
|
-
async
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
}
|
|
749
|
-
return dir;
|
|
618
|
+
async closeAll() {
|
|
619
|
+
const files = [...this.fds.values()];
|
|
620
|
+
this.fds.clear();
|
|
621
|
+
this.cloexec.clear();
|
|
622
|
+
this.status.clear();
|
|
623
|
+
await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
|
|
750
624
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// src/kernel/socket.ts
|
|
628
|
+
var SOMAXCONN = 4096;
|
|
629
|
+
var EPHEMERAL_FIRST = 32768;
|
|
630
|
+
var EPHEMERAL_LAST = 60999;
|
|
631
|
+
var SOL_SOCKET = 1;
|
|
632
|
+
var SO_TYPE = 3;
|
|
633
|
+
var SO_ERROR = 4;
|
|
634
|
+
var SO_SNDBUF = 7;
|
|
635
|
+
var SO_RCVBUF = 8;
|
|
636
|
+
var SO_ACCEPTCONN = 30;
|
|
637
|
+
var SO_DOMAIN = 39;
|
|
638
|
+
var SOCK_STREAM = 1;
|
|
639
|
+
var AF_UNIX = 1;
|
|
640
|
+
var AF_INET = 2;
|
|
641
|
+
var SHUT_RD = 0;
|
|
642
|
+
var SHUT_WR = 1;
|
|
643
|
+
var SHUT_RDWR = 2;
|
|
644
|
+
function isLoopback(host) {
|
|
645
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
646
|
+
}
|
|
647
|
+
async function pipeCall(op) {
|
|
648
|
+
try {
|
|
649
|
+
return await op();
|
|
650
|
+
} catch (e) {
|
|
651
|
+
if (e instanceof PipeError) throw new KernelError(e.code);
|
|
652
|
+
throw e;
|
|
769
653
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
return
|
|
654
|
+
}
|
|
655
|
+
var Changes = class {
|
|
656
|
+
waiters = [];
|
|
657
|
+
wait(signal) {
|
|
658
|
+
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
659
|
+
return new Promise((resolve, reject) => {
|
|
660
|
+
const onAbort = () => {
|
|
661
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
662
|
+
reject(new KernelError("EINTR"));
|
|
663
|
+
};
|
|
664
|
+
const waiter = () => {
|
|
665
|
+
signal?.removeEventListener("abort", onAbort);
|
|
666
|
+
resolve();
|
|
667
|
+
};
|
|
668
|
+
this.waiters.push(waiter);
|
|
669
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
670
|
+
});
|
|
776
671
|
}
|
|
777
|
-
|
|
778
|
-
|
|
672
|
+
wake() {
|
|
673
|
+
const waiters = this.waiters;
|
|
674
|
+
this.waiters = [];
|
|
675
|
+
for (const resolve of waiters) resolve();
|
|
779
676
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
this.forget(path);
|
|
788
|
-
return op(await this.dir(path));
|
|
789
|
-
}
|
|
677
|
+
};
|
|
678
|
+
function link(a, b) {
|
|
679
|
+
const ab = new KernelPipe();
|
|
680
|
+
const ba = new KernelPipe();
|
|
681
|
+
for (const pipe of [ab, ba]) {
|
|
682
|
+
pipe.openRead();
|
|
683
|
+
pipe.openWrite();
|
|
790
684
|
}
|
|
791
|
-
|
|
792
|
-
|
|
685
|
+
a.attach({ rx: ba, tx: ab });
|
|
686
|
+
b.attach({ rx: ab, tx: ba });
|
|
687
|
+
}
|
|
688
|
+
var KernelSocket = class _KernelSocket {
|
|
689
|
+
net;
|
|
690
|
+
domain;
|
|
691
|
+
state = "open";
|
|
692
|
+
local;
|
|
693
|
+
peer;
|
|
694
|
+
link;
|
|
695
|
+
readShut = false;
|
|
696
|
+
writeShut = false;
|
|
697
|
+
queue = [];
|
|
698
|
+
backlog = 0;
|
|
699
|
+
bound = false;
|
|
700
|
+
changes = new Changes();
|
|
701
|
+
options = /* @__PURE__ */ new Map();
|
|
702
|
+
constructor(net, domain) {
|
|
703
|
+
this.net = net;
|
|
704
|
+
this.domain = domain;
|
|
793
705
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
return [`/${parts.join("/")}`, name2];
|
|
706
|
+
attach(link2) {
|
|
707
|
+
this.link = link2;
|
|
708
|
+
this.state = "connected";
|
|
709
|
+
this.changes.wake();
|
|
799
710
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const [dir, name2] = await this.parent(path);
|
|
803
|
-
const known = this.dirs.has(path);
|
|
804
|
-
return this.inDir(dir, async (parent) => {
|
|
805
|
-
const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
|
|
806
|
-
try {
|
|
807
|
-
return await this.child(parent, name2, first);
|
|
808
|
-
} catch (err) {
|
|
809
|
-
if (err?.name !== "TypeMismatchError") throw err;
|
|
810
|
-
}
|
|
811
|
-
return this.child(parent, name2, second);
|
|
812
|
-
}).catch((err) => {
|
|
813
|
-
throw translate(err, path);
|
|
814
|
-
});
|
|
711
|
+
get listening() {
|
|
712
|
+
return this.state === "listening";
|
|
815
713
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
() => false
|
|
820
|
-
);
|
|
714
|
+
connection() {
|
|
715
|
+
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
716
|
+
return this.link;
|
|
821
717
|
}
|
|
822
|
-
async
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
return void 0;
|
|
718
|
+
async read(max, signal) {
|
|
719
|
+
const { rx } = this.connection();
|
|
720
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
721
|
+
return pipeCall(() => rx.read(max, signal));
|
|
828
722
|
}
|
|
829
|
-
async
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return { path: current, handle: await this.handle(current) };
|
|
834
|
-
} catch (err) {
|
|
835
|
-
if (!missing(err)) throw err;
|
|
836
|
-
const link2 = await this.linkAlong(current);
|
|
837
|
-
if (!link2) throw err;
|
|
838
|
-
if (link2.path === current && !follow) return { path: current, link: link2 };
|
|
839
|
-
const rest = current.slice(link2.path.length);
|
|
840
|
-
current = resolvePath(parentOf2(link2.path), `${link2.link}${rest}`);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
throw fsError("ELOOP", path);
|
|
723
|
+
async peek(max, signal) {
|
|
724
|
+
const { rx } = this.connection();
|
|
725
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
726
|
+
return pipeCall(() => rx.peek(max, signal));
|
|
844
727
|
}
|
|
845
|
-
|
|
846
|
-
const
|
|
847
|
-
|
|
728
|
+
async write(bytes, signal) {
|
|
729
|
+
const { tx } = this.connection();
|
|
730
|
+
if (this.writeShut) throw new KernelError("EPIPE");
|
|
731
|
+
return pipeCall(() => tx.write(bytes, signal));
|
|
848
732
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
733
|
+
poll() {
|
|
734
|
+
if (this.state === "listening") {
|
|
735
|
+
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
736
|
+
}
|
|
737
|
+
if (this.state !== "connected" || !this.link) {
|
|
738
|
+
return { readable: false, writable: false, hangup: true };
|
|
739
|
+
}
|
|
740
|
+
const { rx, tx } = this.link;
|
|
854
741
|
return {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
size: file?.size ?? 0,
|
|
859
|
-
mode: this.mode(path, directory, meta),
|
|
860
|
-
mtime: new Date(mtime),
|
|
861
|
-
atime: new Date(meta?.atimeMs ?? mtime),
|
|
862
|
-
ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
|
|
863
|
-
ino: meta?.ino ?? inodeOf(path)
|
|
742
|
+
readable: this.readShut || rx.readReady,
|
|
743
|
+
writable: this.writeShut || tx.writeReady,
|
|
744
|
+
hangup: rx.writersGone && tx.readersGone
|
|
864
745
|
};
|
|
865
746
|
}
|
|
866
|
-
|
|
867
|
-
const
|
|
868
|
-
return
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
size: new TextEncoder().encode(entry.link).length,
|
|
873
|
-
mode: S_IFLNK | 511,
|
|
874
|
-
mtime: time2,
|
|
875
|
-
atime: time2,
|
|
876
|
-
ctime: new Date(entry.ctimeMs ?? 0),
|
|
877
|
-
ino: entry.ino ?? inodeOf(entry.path)
|
|
878
|
-
};
|
|
747
|
+
changed(signal) {
|
|
748
|
+
const own = this.changes.wait(signal);
|
|
749
|
+
if (this.state !== "connected" || !this.link) return own;
|
|
750
|
+
const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
|
|
751
|
+
for (const wait of waits) wait.catch(() => void 0);
|
|
752
|
+
return pipeCall(() => Promise.race(waits));
|
|
879
753
|
}
|
|
880
|
-
|
|
881
|
-
if (
|
|
882
|
-
|
|
883
|
-
|
|
754
|
+
bind(addr) {
|
|
755
|
+
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
756
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
757
|
+
this.local = this.net.bind(this, addr);
|
|
758
|
+
this.bound = true;
|
|
884
759
|
}
|
|
885
|
-
|
|
886
|
-
|
|
760
|
+
listen(backlog) {
|
|
761
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
762
|
+
if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
|
|
763
|
+
if (!this.local) {
|
|
764
|
+
if (this.domain === "unix") throw new KernelError("EINVAL");
|
|
765
|
+
this.bind({ family: "inet", host: "0.0.0.0", port: 0 });
|
|
766
|
+
}
|
|
767
|
+
this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
|
|
768
|
+
this.state = "listening";
|
|
887
769
|
}
|
|
888
|
-
async
|
|
889
|
-
|
|
770
|
+
async accept(signal, nonblock = false) {
|
|
771
|
+
for (; ; ) {
|
|
772
|
+
if (this.state !== "listening") throw new KernelError("EINVAL");
|
|
773
|
+
const next = this.queue.shift();
|
|
774
|
+
if (next) return next;
|
|
775
|
+
if (nonblock) throw new KernelError("EAGAIN");
|
|
776
|
+
await this.changes.wait(signal);
|
|
777
|
+
}
|
|
890
778
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
779
|
+
offer(server) {
|
|
780
|
+
if (this.state !== "listening" || this.queue.length >= this.backlog) return false;
|
|
781
|
+
this.queue.push(server);
|
|
782
|
+
this.changes.wake();
|
|
783
|
+
return true;
|
|
896
784
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
if (
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
785
|
+
connect(addr) {
|
|
786
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
787
|
+
if (this.state !== "open") throw new KernelError("EINVAL");
|
|
788
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
789
|
+
const target = this.net.target(addr);
|
|
790
|
+
if (!this.local) {
|
|
791
|
+
if (addr.family === "inet") {
|
|
792
|
+
this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
|
|
793
|
+
this.bound = true;
|
|
794
|
+
} else this.local = { family: "unix", path: "" };
|
|
795
|
+
}
|
|
796
|
+
const server = new _KernelSocket(this.net, this.domain);
|
|
797
|
+
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
|
|
798
|
+
server.peer = this.local;
|
|
799
|
+
this.peer = server.local;
|
|
800
|
+
link(this, server);
|
|
801
|
+
if (!target.offer(server)) {
|
|
802
|
+
this.unlink();
|
|
803
|
+
server.close();
|
|
804
|
+
throw new KernelError("ECONNREFUSED");
|
|
909
805
|
}
|
|
910
806
|
}
|
|
911
|
-
|
|
912
|
-
|
|
807
|
+
unlink() {
|
|
808
|
+
const link2 = this.link;
|
|
809
|
+
if (link2) {
|
|
810
|
+
link2.rx.closeRead();
|
|
811
|
+
link2.tx.closeWrite();
|
|
812
|
+
}
|
|
813
|
+
this.link = void 0;
|
|
814
|
+
this.peer = void 0;
|
|
815
|
+
this.state = "open";
|
|
913
816
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
817
|
+
shutdown(how) {
|
|
818
|
+
const { rx, tx } = this.connection();
|
|
819
|
+
if (how !== SHUT_RD && how !== SHUT_WR && how !== SHUT_RDWR) throw new KernelError("EINVAL");
|
|
820
|
+
if (how !== SHUT_WR && !this.readShut) {
|
|
821
|
+
this.readShut = true;
|
|
822
|
+
rx.closeRead();
|
|
919
823
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
824
|
+
if (how !== SHUT_RD && !this.writeShut) {
|
|
825
|
+
this.writeShut = true;
|
|
826
|
+
tx.closeWrite();
|
|
827
|
+
}
|
|
828
|
+
this.changes.wake();
|
|
923
829
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
830
|
+
getOption(level, name2) {
|
|
831
|
+
if (level === SOL_SOCKET) {
|
|
832
|
+
switch (name2) {
|
|
833
|
+
case SO_TYPE:
|
|
834
|
+
return SOCK_STREAM;
|
|
835
|
+
case SO_ERROR:
|
|
836
|
+
return 0;
|
|
837
|
+
case SO_ACCEPTCONN:
|
|
838
|
+
return this.state === "listening" ? 1 : 0;
|
|
839
|
+
case SO_DOMAIN:
|
|
840
|
+
return this.domain === "inet" ? AF_INET : AF_UNIX;
|
|
841
|
+
case SO_SNDBUF:
|
|
842
|
+
case SO_RCVBUF:
|
|
843
|
+
return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
|
|
844
|
+
}
|
|
933
845
|
}
|
|
934
|
-
|
|
935
|
-
const writable = await file.createWritable();
|
|
936
|
-
await writable.write(content);
|
|
937
|
-
await writable.close();
|
|
846
|
+
return this.options.get(`${level}:${name2}`) ?? 0;
|
|
938
847
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const
|
|
947
|
-
if (!children2.has(name2)) children2.set(name2, link2);
|
|
848
|
+
setOption(level, name2, value) {
|
|
849
|
+
this.options.set(`${level}:${name2}`, value);
|
|
850
|
+
}
|
|
851
|
+
close() {
|
|
852
|
+
const was = this.state;
|
|
853
|
+
this.state = "closed";
|
|
854
|
+
if (was === "listening") {
|
|
855
|
+
for (const pending of this.queue.splice(0)) pending.close();
|
|
948
856
|
}
|
|
949
|
-
|
|
857
|
+
if (this.link) {
|
|
858
|
+
if (!this.readShut) this.link.rx.closeRead();
|
|
859
|
+
if (!this.writeShut) this.link.tx.closeWrite();
|
|
860
|
+
this.readShut = true;
|
|
861
|
+
this.writeShut = true;
|
|
862
|
+
}
|
|
863
|
+
if (this.bound && this.local) this.net.release(this.local, this);
|
|
864
|
+
this.bound = false;
|
|
865
|
+
this.changes.wake();
|
|
950
866
|
}
|
|
951
|
-
|
|
952
|
-
|
|
867
|
+
static pair(net, domain) {
|
|
868
|
+
const a = new _KernelSocket(net, domain);
|
|
869
|
+
const b = new _KernelSocket(net, domain);
|
|
870
|
+
const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
|
|
871
|
+
a.local = b.local = a.peer = b.peer = name2;
|
|
872
|
+
link(a, b);
|
|
873
|
+
return [a, b];
|
|
953
874
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
875
|
+
};
|
|
876
|
+
function canonicalHost(host) {
|
|
877
|
+
return host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
878
|
+
}
|
|
879
|
+
function keyOf(addr) {
|
|
880
|
+
return addr.family === "inet" ? `inet:${addr.port}` : `unix:${addr.path}`;
|
|
881
|
+
}
|
|
882
|
+
var LoopbackNet = class {
|
|
883
|
+
bound = /* @__PURE__ */ new Map();
|
|
884
|
+
activators = /* @__PURE__ */ new Map();
|
|
885
|
+
nextEphemeral = EPHEMERAL_FIRST;
|
|
886
|
+
activate(addr, start) {
|
|
887
|
+
this.activators.set(keyOf(addr), start);
|
|
965
888
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
889
|
+
socket(domain) {
|
|
890
|
+
return new KernelSocket(this, domain);
|
|
891
|
+
}
|
|
892
|
+
listen(addr, backlog = 128) {
|
|
893
|
+
const socket = this.socket(addr.family);
|
|
894
|
+
socket.bind(addr);
|
|
895
|
+
socket.listen(backlog);
|
|
896
|
+
return socket;
|
|
897
|
+
}
|
|
898
|
+
connect(addr) {
|
|
899
|
+
const socket = this.socket(addr.family);
|
|
900
|
+
socket.connect(addr);
|
|
901
|
+
return socket;
|
|
902
|
+
}
|
|
903
|
+
bind(socket, addr) {
|
|
904
|
+
if (addr.family === "unix") {
|
|
905
|
+
if (addr.path === "") throw new KernelError("EINVAL");
|
|
906
|
+
} else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
|
|
907
|
+
throw new KernelError("EADDRNOTAVAIL");
|
|
972
908
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
909
|
+
const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
|
|
910
|
+
const key = keyOf(named);
|
|
911
|
+
if (this.bound.has(key)) throw new KernelError("EADDRINUSE");
|
|
912
|
+
this.bound.set(key, socket);
|
|
913
|
+
return named;
|
|
914
|
+
}
|
|
915
|
+
release(addr, socket) {
|
|
916
|
+
const key = keyOf(addr);
|
|
917
|
+
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
918
|
+
}
|
|
919
|
+
target(addr) {
|
|
920
|
+
if (addr.family === "inet" && !isLoopback(canonicalHost(addr.host))) {
|
|
921
|
+
throw new KernelError("ENETUNREACH");
|
|
977
922
|
}
|
|
978
|
-
const
|
|
979
|
-
if (
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
|
|
923
|
+
const key = keyOf(addr);
|
|
924
|
+
if (!this.bound.has(key)) this.activators.get(key)?.();
|
|
925
|
+
const socket = this.bound.get(key);
|
|
926
|
+
if (!socket?.listening) throw new KernelError("ECONNREFUSED");
|
|
927
|
+
return socket;
|
|
984
928
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
if (options.force && error.code === "ENOENT") return;
|
|
992
|
-
throw error;
|
|
929
|
+
ephemeral() {
|
|
930
|
+
const span = EPHEMERAL_LAST - EPHEMERAL_FIRST + 1;
|
|
931
|
+
for (let i = 0; i < span; i++) {
|
|
932
|
+
const port = this.nextEphemeral;
|
|
933
|
+
this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
|
|
934
|
+
if (!this.bound.has(`inet:${port}`)) return port;
|
|
993
935
|
}
|
|
994
|
-
|
|
995
|
-
|
|
936
|
+
throw new KernelError("EADDRINUSE");
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// src/kernel/dial.ts
|
|
941
|
+
var CHUNK = 64 * 1024;
|
|
942
|
+
var LOOPBACK_NAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
943
|
+
function dialSocket(net, port, host = "127.0.0.1") {
|
|
944
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new KernelError("EINVAL");
|
|
945
|
+
const target = LOOPBACK_NAMES.has(host.toLowerCase()) ? "127.0.0.1" : host;
|
|
946
|
+
return net.connect({ family: "inet", host: target, port });
|
|
947
|
+
}
|
|
948
|
+
var codeOf = (err) => {
|
|
949
|
+
const code = err?.code;
|
|
950
|
+
return typeof code === "string" ? code : "EIO";
|
|
951
|
+
};
|
|
952
|
+
function serveSocket(socket, port, onClose) {
|
|
953
|
+
let closed = false;
|
|
954
|
+
let writes = Promise.resolve();
|
|
955
|
+
let credit = 0;
|
|
956
|
+
let granted2;
|
|
957
|
+
const send = (message2, transfer) => {
|
|
958
|
+
if (!closed) port.postMessage(message2, transfer);
|
|
959
|
+
};
|
|
960
|
+
const close = (reset = false) => {
|
|
961
|
+
if (closed) return;
|
|
962
|
+
if (reset) send({ error: "ECONNRESET" });
|
|
963
|
+
closed = true;
|
|
964
|
+
granted2?.();
|
|
965
|
+
socket.close();
|
|
966
|
+
port.close();
|
|
967
|
+
onClose?.();
|
|
968
|
+
};
|
|
969
|
+
const write = (data) => {
|
|
970
|
+
writes = writes.then(async () => {
|
|
971
|
+
if (closed) return;
|
|
996
972
|
try {
|
|
997
|
-
await
|
|
998
|
-
|
|
999
|
-
(parent) => parent.removeEntry(name2, { recursive: options.recursive === true })
|
|
1000
|
-
);
|
|
973
|
+
await socket.write(data);
|
|
974
|
+
send({ ack: true });
|
|
1001
975
|
} catch (err) {
|
|
1002
|
-
|
|
976
|
+
send({ error: codeOf(err) });
|
|
1003
977
|
}
|
|
1004
|
-
|
|
978
|
+
});
|
|
979
|
+
};
|
|
980
|
+
port.addEventListener("message", ({ data }) => {
|
|
981
|
+
if (data instanceof Uint8Array) write(data);
|
|
982
|
+
else if ("more" in data) {
|
|
983
|
+
credit++;
|
|
984
|
+
granted2?.();
|
|
985
|
+
} else if ("end" in data) {
|
|
986
|
+
writes = writes.then(() => {
|
|
987
|
+
if (!closed) socket.shutdown(SHUT_WR);
|
|
988
|
+
});
|
|
989
|
+
} else if ("close" in data) close();
|
|
990
|
+
});
|
|
991
|
+
port.start?.();
|
|
992
|
+
const turn = () => credit > 0 || closed ? Promise.resolve() : new Promise((r) => granted2 = r);
|
|
993
|
+
void (async () => {
|
|
994
|
+
try {
|
|
995
|
+
for (; ; ) {
|
|
996
|
+
await turn();
|
|
997
|
+
if (closed) return;
|
|
998
|
+
const chunk2 = await socket.read(CHUNK);
|
|
999
|
+
if (chunk2.length === 0) break;
|
|
1000
|
+
credit--;
|
|
1001
|
+
send(chunk2, [chunk2.buffer]);
|
|
1002
|
+
}
|
|
1003
|
+
send({ end: true });
|
|
1004
|
+
} catch (err) {
|
|
1005
|
+
send({ error: codeOf(err) });
|
|
1005
1006
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1007
|
+
})();
|
|
1008
|
+
return () => close(true);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/kernel/signals.ts
|
|
1012
|
+
var SIG = {
|
|
1013
|
+
HUP: 1,
|
|
1014
|
+
INT: 2,
|
|
1015
|
+
QUIT: 3,
|
|
1016
|
+
ILL: 4,
|
|
1017
|
+
TRAP: 5,
|
|
1018
|
+
ABRT: 6,
|
|
1019
|
+
BUS: 7,
|
|
1020
|
+
FPE: 8,
|
|
1021
|
+
KILL: 9,
|
|
1022
|
+
USR1: 10,
|
|
1023
|
+
SEGV: 11,
|
|
1024
|
+
USR2: 12,
|
|
1025
|
+
PIPE: 13,
|
|
1026
|
+
ALRM: 14,
|
|
1027
|
+
TERM: 15,
|
|
1028
|
+
STKFLT: 16,
|
|
1029
|
+
CHLD: 17,
|
|
1030
|
+
CONT: 18,
|
|
1031
|
+
STOP: 19,
|
|
1032
|
+
TSTP: 20,
|
|
1033
|
+
TTIN: 21,
|
|
1034
|
+
TTOU: 22,
|
|
1035
|
+
URG: 23,
|
|
1036
|
+
XCPU: 24,
|
|
1037
|
+
XFSZ: 25,
|
|
1038
|
+
VTALRM: 26,
|
|
1039
|
+
PROF: 27,
|
|
1040
|
+
WINCH: 28,
|
|
1041
|
+
POLL: 29,
|
|
1042
|
+
PWR: 30,
|
|
1043
|
+
SYS: 31
|
|
1044
|
+
};
|
|
1045
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
1046
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
1047
|
+
function defaultAction(sig) {
|
|
1048
|
+
if (sig === SIG.CONT) return "continue";
|
|
1049
|
+
if (STOPPING.has(sig)) return "stop";
|
|
1050
|
+
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
1051
|
+
}
|
|
1052
|
+
function isSignal(sig) {
|
|
1053
|
+
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
1054
|
+
}
|
|
1055
|
+
function sigbit(sig) {
|
|
1056
|
+
return 1 << sig;
|
|
1057
|
+
}
|
|
1058
|
+
var SIGNAL_BY_NAME = {
|
|
1059
|
+
SIGINT: SIG.INT,
|
|
1060
|
+
SIGTERM: SIG.TERM,
|
|
1061
|
+
SIGKILL: SIG.KILL,
|
|
1062
|
+
SIGSTOP: SIG.STOP,
|
|
1063
|
+
SIGCONT: SIG.CONT
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
// src/body-idle.ts
|
|
1067
|
+
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
1068
|
+
|
|
1069
|
+
// src/transport.ts
|
|
1070
|
+
var MAX_REQUEST_BODY = 64 * 1024 * 1024;
|
|
1071
|
+
function serveTransport(peer, transport) {
|
|
1072
|
+
const open = /* @__PURE__ */ new Map();
|
|
1073
|
+
const fail = (nid, e) => {
|
|
1074
|
+
open.delete(nid);
|
|
1075
|
+
const { status, code } = e;
|
|
1076
|
+
peer.postMessage({
|
|
1077
|
+
net: "error",
|
|
1078
|
+
nid,
|
|
1079
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1080
|
+
...typeof status === "number" ? { status } : {},
|
|
1081
|
+
...typeof code === "string" ? { code } : {}
|
|
1019
1082
|
});
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1083
|
+
};
|
|
1084
|
+
const start = async (call) => {
|
|
1085
|
+
const abort = new AbortController();
|
|
1086
|
+
const entry = { abort };
|
|
1087
|
+
open.set(call.nid, entry);
|
|
1088
|
+
try {
|
|
1089
|
+
const response = await transport.fetch({
|
|
1090
|
+
url: call.url,
|
|
1091
|
+
method: call.method,
|
|
1092
|
+
headers: call.headers,
|
|
1093
|
+
...call.body ? { body: call.body } : {},
|
|
1094
|
+
signal: abort.signal
|
|
1095
|
+
});
|
|
1096
|
+
if (!open.has(call.nid)) {
|
|
1097
|
+
await response.cancel();
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
|
|
1101
|
+
peer.postMessage({
|
|
1102
|
+
net: "head",
|
|
1103
|
+
nid: call.nid,
|
|
1104
|
+
status: response.status,
|
|
1105
|
+
statusText: response.statusText,
|
|
1106
|
+
headers: response.headers
|
|
1107
|
+
});
|
|
1108
|
+
} catch (e) {
|
|
1109
|
+
fail(call.nid, e);
|
|
1024
1110
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1111
|
+
};
|
|
1112
|
+
const read = async (nid) => {
|
|
1113
|
+
const body = open.get(nid)?.body;
|
|
1114
|
+
if (!body) return;
|
|
1115
|
+
try {
|
|
1116
|
+
const next = await body.next();
|
|
1117
|
+
if (next.done) {
|
|
1118
|
+
open.delete(nid);
|
|
1119
|
+
peer.postMessage({ net: "end", nid });
|
|
1120
|
+
} else {
|
|
1121
|
+
const bytes = next.value.slice();
|
|
1122
|
+
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
1123
|
+
}
|
|
1124
|
+
} catch (e) {
|
|
1125
|
+
fail(nid, e);
|
|
1027
1126
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1127
|
+
};
|
|
1128
|
+
const drop = (nid) => {
|
|
1129
|
+
const entry = open.get(nid);
|
|
1130
|
+
open.delete(nid);
|
|
1131
|
+
entry?.abort.abort();
|
|
1132
|
+
void entry?.response?.cancel().catch(() => void 0);
|
|
1133
|
+
};
|
|
1134
|
+
return {
|
|
1135
|
+
answer(call) {
|
|
1136
|
+
if (call.net === "fetch") void start(call);
|
|
1137
|
+
else if (call.net === "read") void read(call.nid);
|
|
1138
|
+
else drop(call.nid);
|
|
1139
|
+
},
|
|
1140
|
+
close() {
|
|
1141
|
+
for (const nid of [...open.keys()]) drop(nid);
|
|
1039
1142
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/client/serve-client.ts
|
|
1147
|
+
var MAX_LINKS = 40;
|
|
1148
|
+
function act(session, req, signal) {
|
|
1149
|
+
if (req.action === "write") session.write(req.bytes);
|
|
1150
|
+
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
1151
|
+
else if (req.action === "signal") session.signal(signal(req.signal));
|
|
1152
|
+
else session.close();
|
|
1153
|
+
}
|
|
1154
|
+
async function realpath(fs, path) {
|
|
1155
|
+
let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
|
|
1156
|
+
let resolved = "";
|
|
1157
|
+
for (let hops = 0; parts.length > 0; ) {
|
|
1158
|
+
const [name2, ...rest] = parts;
|
|
1159
|
+
const next = `${resolved}/${name2}`;
|
|
1160
|
+
if (!(await fs.lstat(next)).isSymbolicLink) {
|
|
1161
|
+
resolved = next;
|
|
1162
|
+
parts = rest;
|
|
1163
|
+
continue;
|
|
1056
1164
|
}
|
|
1057
|
-
if (
|
|
1058
|
-
|
|
1165
|
+
if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
|
|
1166
|
+
const target = await fs.readlink(next);
|
|
1167
|
+
const base = target.startsWith("/") ? "/" : resolved || "/";
|
|
1168
|
+
parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
|
|
1169
|
+
resolved = "";
|
|
1170
|
+
}
|
|
1171
|
+
return resolved || "/";
|
|
1172
|
+
}
|
|
1173
|
+
async function fsCall(fs, method, args) {
|
|
1174
|
+
const path = (i) => fs.resolvePath("/", String(args[i]));
|
|
1175
|
+
switch (method) {
|
|
1176
|
+
case "readFile": {
|
|
1177
|
+
const bytes = (await fs.readFileBuffer(path(0))).slice();
|
|
1178
|
+
return { result: bytes, transfer: [bytes.buffer] };
|
|
1179
|
+
}
|
|
1180
|
+
case "writeFile":
|
|
1181
|
+
await fs.writeFile(path(0), args[1]);
|
|
1182
|
+
return { result: true };
|
|
1183
|
+
case "stat":
|
|
1184
|
+
return { result: await fs.stat(path(0)) };
|
|
1185
|
+
case "lstat":
|
|
1186
|
+
return { result: await fs.lstat(path(0)) };
|
|
1187
|
+
case "readdir":
|
|
1188
|
+
return { result: await fs.readdir(path(0)) };
|
|
1189
|
+
case "mkdir":
|
|
1190
|
+
await fs.mkdir(path(0), { recursive: true });
|
|
1191
|
+
return { result: true };
|
|
1192
|
+
case "rm":
|
|
1193
|
+
await fs.rm(path(0), { recursive: true, force: args[1] === true });
|
|
1194
|
+
return { result: true };
|
|
1195
|
+
case "rename":
|
|
1196
|
+
await fs.rename(path(0), path(1));
|
|
1197
|
+
return { result: true };
|
|
1198
|
+
case "realpath":
|
|
1199
|
+
return { result: await realpath(fs, String(args[0])) };
|
|
1200
|
+
case "symlink":
|
|
1201
|
+
await fs.symlink(String(args[0]), path(1));
|
|
1202
|
+
return { result: true };
|
|
1203
|
+
case "readlink":
|
|
1204
|
+
return { result: await fs.readlink(path(0)) };
|
|
1205
|
+
default:
|
|
1206
|
+
return { result: await fs.exists(path(0)) };
|
|
1059
1207
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1208
|
+
}
|
|
1209
|
+
function signalOf(host) {
|
|
1210
|
+
return host.signal ?? ((name2) => {
|
|
1211
|
+
throw fsError("EINVAL", `this kernel names no signal ${name2}`);
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
function dialFor(l, req, open) {
|
|
1215
|
+
const socket = dialSocket(l.net, req.port, req.host);
|
|
1216
|
+
const { port1, port2 } = new MessageChannel();
|
|
1217
|
+
open.set(
|
|
1218
|
+
req.id,
|
|
1219
|
+
serveSocket(socket, port1, () => open.delete(req.id))
|
|
1220
|
+
);
|
|
1221
|
+
return { result: port2, transfer: [port2] };
|
|
1222
|
+
}
|
|
1223
|
+
function killGroups(host, pgids) {
|
|
1224
|
+
if (pgids.length === 0) return;
|
|
1225
|
+
void host.launcher().then((launcher) => {
|
|
1226
|
+
for (const pgid of pgids) launcher.kill(-pgid, SIG.KILL);
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
function joinable(l, pgid, ours) {
|
|
1230
|
+
const sid = l.groupSession(pgid);
|
|
1231
|
+
if (sid === void 0) throw fsError("ESRCH", `no process group ${pgid}`);
|
|
1232
|
+
if (!ours(sid)) throw fsError("EPERM", `process group ${pgid} is in another session`);
|
|
1233
|
+
}
|
|
1234
|
+
function serveClient(port, host) {
|
|
1235
|
+
let state = "new";
|
|
1236
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
1237
|
+
const groups = /* @__PURE__ */ new Set();
|
|
1238
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
1239
|
+
const watches = /* @__PURE__ */ new Map();
|
|
1240
|
+
let net;
|
|
1241
|
+
const ended = Promise.withResolvers();
|
|
1242
|
+
const send = (message2, transfer = []) => {
|
|
1243
|
+
if (state !== "closed") port.postMessage(message2, transfer);
|
|
1244
|
+
};
|
|
1245
|
+
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
1246
|
+
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
1247
|
+
if (state === "closed") return;
|
|
1248
|
+
send({ bye });
|
|
1249
|
+
state = "closed";
|
|
1250
|
+
net?.close();
|
|
1251
|
+
for (const unwatch of watches.values()) unwatch();
|
|
1252
|
+
watches.clear();
|
|
1253
|
+
for (const session of terminals.values()) session.close();
|
|
1254
|
+
terminals.clear();
|
|
1255
|
+
if (kill) killGroups(host, [...groups]);
|
|
1256
|
+
groups.clear();
|
|
1257
|
+
port.close?.();
|
|
1258
|
+
ended.resolve();
|
|
1063
1259
|
}
|
|
1064
|
-
async
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1260
|
+
async function hello(message2) {
|
|
1261
|
+
const refused = versionError(message2.protocol);
|
|
1262
|
+
if (refused) {
|
|
1263
|
+
send({ hello: { protocol: PROTOCOL, error: refused } });
|
|
1264
|
+
detach();
|
|
1265
|
+
return;
|
|
1070
1266
|
}
|
|
1071
|
-
|
|
1267
|
+
const launcher = await host.launcher();
|
|
1268
|
+
const transport = host.transport?.() ?? launcher.transport;
|
|
1269
|
+
net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
|
|
1270
|
+
state = "open";
|
|
1271
|
+
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
1272
|
+
send({
|
|
1273
|
+
hello: {
|
|
1274
|
+
protocol: PROTOCOL,
|
|
1275
|
+
traits: transport.traits,
|
|
1276
|
+
...host.lock ? { lock: host.lock } : {}
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1072
1279
|
}
|
|
1073
|
-
async
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1280
|
+
async function terminal(req, l) {
|
|
1281
|
+
const session = await l.openTerminal(req.argv, {
|
|
1282
|
+
...req.options,
|
|
1283
|
+
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
1284
|
+
});
|
|
1285
|
+
if (state === "closed") {
|
|
1286
|
+
session.close();
|
|
1287
|
+
} else {
|
|
1288
|
+
terminals.set(req.id, session);
|
|
1289
|
+
sessions.add(session.pid);
|
|
1290
|
+
reply(req.id, { started: session.pid });
|
|
1077
1291
|
}
|
|
1078
|
-
}
|
|
1079
|
-
async place(handle, dir, name2) {
|
|
1080
|
-
const moved2 = await handle.move?.(dir, name2).then(
|
|
1081
|
-
() => true,
|
|
1082
|
-
() => false
|
|
1083
|
-
);
|
|
1084
|
-
if (moved2) return true;
|
|
1085
1292
|
try {
|
|
1086
|
-
await
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
throw translate(err, name2);
|
|
1293
|
+
return { result: await session.exited };
|
|
1294
|
+
} finally {
|
|
1295
|
+
terminals.delete(req.id);
|
|
1090
1296
|
}
|
|
1091
|
-
return false;
|
|
1092
1297
|
}
|
|
1093
|
-
async
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1298
|
+
async function spawn(req, l) {
|
|
1299
|
+
const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
|
|
1300
|
+
if (req.options.pgid !== void 0) joinable(l, req.options.pgid, (sid) => sessions.has(sid));
|
|
1301
|
+
let leader;
|
|
1302
|
+
const result = await l.run(req.argv, {
|
|
1303
|
+
...req.options,
|
|
1304
|
+
collect: false,
|
|
1305
|
+
onStdout: out(1),
|
|
1306
|
+
onStderr: out(2),
|
|
1307
|
+
onStarted: (pid) => {
|
|
1308
|
+
leader = pid;
|
|
1309
|
+
if (state === "closed") return;
|
|
1310
|
+
groups.add(req.options.pgid ?? pid);
|
|
1311
|
+
if (req.options.pgid === void 0) sessions.add(pid);
|
|
1312
|
+
reply(req.id, { started: pid });
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
1316
|
+
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
1317
|
+
return { result: result.status };
|
|
1102
1318
|
}
|
|
1103
|
-
async
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const at = resolvePath(parent.path, baseOf(path));
|
|
1107
|
-
if (await this.locate(at, false).then(
|
|
1108
|
-
() => true,
|
|
1109
|
-
(err) => !missing(err)
|
|
1110
|
-
)) {
|
|
1111
|
-
throw fsError("EEXIST", path);
|
|
1319
|
+
async function handle(req) {
|
|
1320
|
+
if (host.scope === "transport" && req.op !== "detach") {
|
|
1321
|
+
throw fsError("EPERM", `${req.op}: this client may only use the network transport`);
|
|
1112
1322
|
}
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1323
|
+
const l = await host.launcher();
|
|
1324
|
+
switch (req.op) {
|
|
1325
|
+
case "spawn":
|
|
1326
|
+
return spawn(req, l);
|
|
1327
|
+
case "open-terminal":
|
|
1328
|
+
return terminal(req, l);
|
|
1329
|
+
case "terminal": {
|
|
1330
|
+
const session = terminals.get(req.terminal);
|
|
1331
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
1332
|
+
act(session, req, signalOf(host));
|
|
1333
|
+
return { result: true };
|
|
1334
|
+
}
|
|
1335
|
+
case "kill":
|
|
1336
|
+
if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
1337
|
+
return { result: true };
|
|
1338
|
+
case "ps":
|
|
1339
|
+
return {
|
|
1340
|
+
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
1341
|
+
};
|
|
1342
|
+
case "fs":
|
|
1343
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
1344
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
1345
|
+
}
|
|
1346
|
+
return fsCall(l.fs, req.method, req.args);
|
|
1347
|
+
case "watch": {
|
|
1348
|
+
const unwatch = l.watchers.watch(
|
|
1349
|
+
req.paths,
|
|
1350
|
+
{ recursive: req.recursive },
|
|
1351
|
+
(change) => send({ watch: req.id, change })
|
|
1352
|
+
);
|
|
1353
|
+
watches.set(req.id, unwatch);
|
|
1354
|
+
return { result: req.id };
|
|
1355
|
+
}
|
|
1356
|
+
case "unwatch":
|
|
1357
|
+
watches.get(req.watch)?.();
|
|
1358
|
+
watches.delete(req.watch);
|
|
1359
|
+
return { result: true };
|
|
1360
|
+
case "mount":
|
|
1361
|
+
return { result: await l.mount(req.spec) };
|
|
1362
|
+
case "umount":
|
|
1363
|
+
l.umount(req.target);
|
|
1364
|
+
return { result: true };
|
|
1365
|
+
case "mounts":
|
|
1366
|
+
return { result: l.mounts.list() };
|
|
1367
|
+
case "dial":
|
|
1368
|
+
return dialFor(l, req, watches);
|
|
1369
|
+
case "detach":
|
|
1370
|
+
return { result: true };
|
|
1371
|
+
default:
|
|
1372
|
+
throw new Error(`unknown op ${req.op}`);
|
|
1160
1373
|
}
|
|
1161
|
-
return removed;
|
|
1162
1374
|
}
|
|
1163
|
-
|
|
1375
|
+
port.addEventListener("message", (event) => {
|
|
1376
|
+
const data = event.data;
|
|
1377
|
+
if (state === "closed") return;
|
|
1378
|
+
if (data.hello) {
|
|
1379
|
+
if (state === "new") {
|
|
1380
|
+
hello(data.hello).catch((err) => {
|
|
1381
|
+
send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
|
|
1382
|
+
detach();
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (state !== "open") return;
|
|
1388
|
+
if (data.net !== void 0) {
|
|
1389
|
+
net?.answer(data);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const req = data;
|
|
1393
|
+
handle(req).then(
|
|
1394
|
+
({ result, transfer }) => {
|
|
1395
|
+
reply(req.id, { result }, transfer);
|
|
1396
|
+
if (req.op === "detach") detach(req.kill === true);
|
|
1397
|
+
},
|
|
1398
|
+
(err) => {
|
|
1399
|
+
const code = err?.code;
|
|
1400
|
+
reply(req.id, {
|
|
1401
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1402
|
+
...typeof code === "string" ? { code } : {}
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
});
|
|
1407
|
+
port.addEventListener("close", () => detach());
|
|
1408
|
+
port.start?.();
|
|
1409
|
+
return { detach, closed: ended.promise };
|
|
1410
|
+
}
|
|
1164
1411
|
|
|
1165
|
-
// src/
|
|
1166
|
-
var
|
|
1167
|
-
var
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1412
|
+
// src/fs/opfs.ts
|
|
1413
|
+
var S_IFDIR = 16384;
|
|
1414
|
+
var S_IFREG = 32768;
|
|
1415
|
+
var S_IFLNK = 40960;
|
|
1416
|
+
var PERM_MASK = 4095;
|
|
1417
|
+
var READ_ATTEMPTS = 3;
|
|
1418
|
+
var MAX_LINKS2 = 40;
|
|
1419
|
+
var CHECK_MS = 1e3;
|
|
1420
|
+
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
1421
|
+
var ERRNO_BY_DOM_ERROR = {
|
|
1422
|
+
NotFoundError: "ENOENT",
|
|
1423
|
+
TypeMismatchError: "ENOTDIR",
|
|
1424
|
+
InvalidModificationError: "ENOTEMPTY",
|
|
1425
|
+
NoModificationAllowedError: "EBUSY",
|
|
1426
|
+
TypeError: "EINVAL",
|
|
1427
|
+
NotAllowedError: "EACCES"
|
|
1173
1428
|
};
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1429
|
+
function translate(err, path) {
|
|
1430
|
+
if (typeof err?.code === "string") return err;
|
|
1431
|
+
const name2 = err?.name;
|
|
1432
|
+
return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
|
|
1433
|
+
}
|
|
1434
|
+
function missing(err) {
|
|
1435
|
+
const code = err?.code;
|
|
1436
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
1437
|
+
}
|
|
1438
|
+
function split(path) {
|
|
1439
|
+
return path.split("/").filter(Boolean);
|
|
1440
|
+
}
|
|
1441
|
+
function parentOf2(path) {
|
|
1442
|
+
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
1443
|
+
}
|
|
1444
|
+
function baseOf(path) {
|
|
1445
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
1446
|
+
}
|
|
1447
|
+
function prefixes(path) {
|
|
1448
|
+
const parts = split(path);
|
|
1449
|
+
return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
|
|
1450
|
+
}
|
|
1451
|
+
function within(path, root) {
|
|
1452
|
+
return path === root || path.startsWith(`${root}/`);
|
|
1453
|
+
}
|
|
1454
|
+
function isHandle(child) {
|
|
1455
|
+
return "kind" in child;
|
|
1456
|
+
}
|
|
1457
|
+
var OpfsFs = class {
|
|
1458
|
+
root;
|
|
1459
|
+
meta;
|
|
1460
|
+
dirs = /* @__PURE__ */ new Map();
|
|
1461
|
+
checked = /* @__PURE__ */ new WeakMap();
|
|
1462
|
+
channel;
|
|
1463
|
+
constructor(root, meta = new MemoryMeta(), channel) {
|
|
1464
|
+
this.root = root;
|
|
1465
|
+
this.meta = meta;
|
|
1466
|
+
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
1467
|
+
if (this.channel) {
|
|
1468
|
+
this.channel.onmessage = ({ data }) => {
|
|
1469
|
+
for (const path of data) this.forget(path);
|
|
1470
|
+
};
|
|
1471
|
+
this.channel.unref?.();
|
|
1472
|
+
}
|
|
1186
1473
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1474
|
+
changed(...paths) {
|
|
1475
|
+
for (const path of paths) this.forget(path);
|
|
1476
|
+
this.channel?.postMessage(paths);
|
|
1189
1477
|
}
|
|
1190
|
-
|
|
1191
|
-
return
|
|
1478
|
+
resolvePath(base, path) {
|
|
1479
|
+
return resolvePath(base, path);
|
|
1192
1480
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1481
|
+
async walk(parts, from, start) {
|
|
1482
|
+
let dir = start;
|
|
1483
|
+
for (let at = from; at < parts.length; at++) {
|
|
1484
|
+
dir = await dir.getDirectoryHandle(parts[at]);
|
|
1485
|
+
this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
|
|
1486
|
+
this.checked.set(dir, performance.now());
|
|
1487
|
+
}
|
|
1488
|
+
return dir;
|
|
1195
1489
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1490
|
+
async dir(path) {
|
|
1491
|
+
const parts = split(path);
|
|
1492
|
+
let from = parts.length;
|
|
1493
|
+
let cached;
|
|
1494
|
+
for (; from > 0 && !cached; from--)
|
|
1495
|
+
cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
|
|
1496
|
+
if (cached) from++;
|
|
1497
|
+
const at = `/${parts.slice(0, from).join("/")}`;
|
|
1498
|
+
if (cached && !await this.current(at, cached)) {
|
|
1499
|
+
this.forget(at);
|
|
1500
|
+
cached = void 0;
|
|
1501
|
+
from = 0;
|
|
1502
|
+
}
|
|
1503
|
+
try {
|
|
1504
|
+
return await this.walk(parts, from, cached ?? this.root);
|
|
1505
|
+
} catch (err) {
|
|
1506
|
+
throw translate(err, path);
|
|
1507
|
+
}
|
|
1198
1508
|
}
|
|
1199
|
-
|
|
1200
|
-
this.
|
|
1509
|
+
async current(path, dir) {
|
|
1510
|
+
if (performance.now() - (this.checked.get(dir) ?? -CHECK_MS) < CHECK_MS) return true;
|
|
1511
|
+
const where = await this.root.resolve(dir).catch(() => null);
|
|
1512
|
+
if (where === null || `/${where.join("/")}` !== path) return false;
|
|
1513
|
+
this.checked.set(dir, performance.now());
|
|
1514
|
+
return true;
|
|
1201
1515
|
}
|
|
1202
|
-
|
|
1203
|
-
this.
|
|
1516
|
+
forget(path) {
|
|
1517
|
+
for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
|
|
1204
1518
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1519
|
+
async inDir(path, op) {
|
|
1520
|
+
const dir = await this.dir(path);
|
|
1521
|
+
try {
|
|
1522
|
+
return await op(dir);
|
|
1523
|
+
} catch (err) {
|
|
1524
|
+
if (err?.name !== "NotFoundError" || await this.current(path, dir))
|
|
1525
|
+
throw err;
|
|
1526
|
+
this.forget(path);
|
|
1527
|
+
return op(await this.dir(path));
|
|
1210
1528
|
}
|
|
1211
|
-
this.wake();
|
|
1212
|
-
}
|
|
1213
|
-
closeWrite() {
|
|
1214
|
-
if (this.writers > 0) this.writers -= 1;
|
|
1215
|
-
this.wake();
|
|
1216
1529
|
}
|
|
1217
|
-
async
|
|
1218
|
-
|
|
1219
|
-
if (this.writers === 0) return new Uint8Array(0);
|
|
1220
|
-
await this.changed(signal);
|
|
1221
|
-
}
|
|
1222
|
-
const out = this.take(Math.min(max, this.size));
|
|
1223
|
-
this.wake();
|
|
1224
|
-
return out;
|
|
1530
|
+
async child(parent, name2, kind) {
|
|
1531
|
+
return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
|
|
1225
1532
|
}
|
|
1226
|
-
async
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
const out = new Uint8Array(Math.min(max, this.size));
|
|
1232
|
-
let filled = 0;
|
|
1233
|
-
for (const chunk2 of this.chunks) {
|
|
1234
|
-
if (filled === out.length) break;
|
|
1235
|
-
const count = Math.min(chunk2.length, out.length - filled);
|
|
1236
|
-
out.set(chunk2.subarray(0, count), filled);
|
|
1237
|
-
filled += count;
|
|
1238
|
-
}
|
|
1239
|
-
return out;
|
|
1533
|
+
async parent(path) {
|
|
1534
|
+
const parts = split(path);
|
|
1535
|
+
const name2 = parts.pop();
|
|
1536
|
+
if (name2 === void 0) throw fsError("EBUSY", path);
|
|
1537
|
+
return [`/${parts.join("/")}`, name2];
|
|
1240
1538
|
}
|
|
1241
|
-
async
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
throw e;
|
|
1252
|
-
}
|
|
1253
|
-
continue;
|
|
1539
|
+
async handle(path) {
|
|
1540
|
+
if (split(path).length === 0) return this.root;
|
|
1541
|
+
const [dir, name2] = await this.parent(path);
|
|
1542
|
+
const known = this.dirs.has(path);
|
|
1543
|
+
return this.inDir(dir, async (parent) => {
|
|
1544
|
+
const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
|
|
1545
|
+
try {
|
|
1546
|
+
return await this.child(parent, name2, first);
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
if (err?.name !== "TypeMismatchError") throw err;
|
|
1254
1549
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1550
|
+
return this.child(parent, name2, second);
|
|
1551
|
+
}).catch((err) => {
|
|
1552
|
+
throw translate(err, path);
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
real(path) {
|
|
1556
|
+
return this.handle(path).then(
|
|
1557
|
+
() => true,
|
|
1558
|
+
() => false
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
async linkAlong(path) {
|
|
1562
|
+
for (const entry of await this.meta.get(prefixes(path))) {
|
|
1563
|
+
if (entry?.link === void 0) continue;
|
|
1564
|
+
if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
|
|
1260
1565
|
}
|
|
1261
|
-
return
|
|
1566
|
+
return void 0;
|
|
1262
1567
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
let
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1568
|
+
async locate(path, follow) {
|
|
1569
|
+
let current = path;
|
|
1570
|
+
for (let hops = 0; hops <= MAX_LINKS2; hops++) {
|
|
1571
|
+
try {
|
|
1572
|
+
return { path: current, handle: await this.handle(current) };
|
|
1573
|
+
} catch (err) {
|
|
1574
|
+
if (!missing(err)) throw err;
|
|
1575
|
+
const link2 = await this.linkAlong(current);
|
|
1576
|
+
if (!link2) throw err;
|
|
1577
|
+
if (link2.path === current && !follow) return { path: current, link: link2 };
|
|
1578
|
+
const rest = current.slice(link2.path.length);
|
|
1579
|
+
current = resolvePath(parentOf2(link2.path), `${link2.link}${rest}`);
|
|
1580
|
+
}
|
|
1273
1581
|
}
|
|
1274
|
-
|
|
1275
|
-
return out;
|
|
1582
|
+
throw fsError("ELOOP", path);
|
|
1276
1583
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
return
|
|
1280
|
-
const onAbort = () => {
|
|
1281
|
-
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
1282
|
-
reject(new PipeError("EINTR"));
|
|
1283
|
-
};
|
|
1284
|
-
const waiter = () => {
|
|
1285
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1286
|
-
resolve();
|
|
1287
|
-
};
|
|
1288
|
-
this.waiters.push(waiter);
|
|
1289
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1290
|
-
});
|
|
1584
|
+
mode(path, directory, meta) {
|
|
1585
|
+
const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
|
|
1586
|
+
return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
|
|
1291
1587
|
}
|
|
1292
|
-
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1588
|
+
async statOf(path, handle, meta) {
|
|
1589
|
+
const directory = handle.kind === "directory";
|
|
1590
|
+
const file = directory ? void 0 : await handle.getFile();
|
|
1591
|
+
const modified = file?.lastModified ?? 0;
|
|
1592
|
+
const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
|
|
1593
|
+
return {
|
|
1594
|
+
isFile: !directory,
|
|
1595
|
+
isDirectory: directory,
|
|
1596
|
+
isSymbolicLink: false,
|
|
1597
|
+
size: file?.size ?? 0,
|
|
1598
|
+
mode: this.mode(path, directory, meta),
|
|
1599
|
+
mtime: new Date(mtime),
|
|
1600
|
+
atime: new Date(meta?.atimeMs ?? mtime),
|
|
1601
|
+
ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
|
|
1602
|
+
ino: meta?.ino ?? inodeOf(path)
|
|
1603
|
+
};
|
|
1296
1604
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1605
|
+
linkStat(entry) {
|
|
1606
|
+
const time2 = new Date(entry.mtimeMs ?? 0);
|
|
1607
|
+
return {
|
|
1608
|
+
isFile: false,
|
|
1609
|
+
isDirectory: false,
|
|
1610
|
+
isSymbolicLink: true,
|
|
1611
|
+
size: new TextEncoder().encode(entry.link).length,
|
|
1612
|
+
mode: S_IFLNK | 511,
|
|
1613
|
+
mtime: time2,
|
|
1614
|
+
atime: time2,
|
|
1615
|
+
ctime: new Date(entry.ctimeMs ?? 0),
|
|
1616
|
+
ino: entry.ino ?? inodeOf(entry.path)
|
|
1617
|
+
};
|
|
1305
1618
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
var OpenFile = class {
|
|
1311
|
-
refs = 1;
|
|
1312
|
-
file;
|
|
1313
|
-
constructor(file) {
|
|
1314
|
-
this.file = file;
|
|
1619
|
+
async describe(found) {
|
|
1620
|
+
if (found.link) return this.linkStat(found.link);
|
|
1621
|
+
const [meta] = await this.meta.get([found.path]);
|
|
1622
|
+
return this.statOf(found.path, found.handle, meta);
|
|
1315
1623
|
}
|
|
1316
|
-
|
|
1317
|
-
this.
|
|
1318
|
-
return this;
|
|
1624
|
+
async stat(path) {
|
|
1625
|
+
return this.describe(await this.locate(path, true));
|
|
1319
1626
|
}
|
|
1320
|
-
|
|
1321
|
-
this.
|
|
1322
|
-
if (this.refs === 0) return this.file.close();
|
|
1627
|
+
async lstat(path) {
|
|
1628
|
+
return this.describe(await this.locate(path, false));
|
|
1323
1629
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
|
|
1340
|
-
changed: (signal) => pipe.changed(signal)
|
|
1341
|
-
}),
|
|
1342
|
-
write: new OpenFile({
|
|
1343
|
-
write: async (bytes, signal) => {
|
|
1344
|
-
try {
|
|
1345
|
-
return await pipe.write(bytes, signal);
|
|
1346
|
-
} catch (e) {
|
|
1347
|
-
throw new KernelError(e.code);
|
|
1630
|
+
async exists(path) {
|
|
1631
|
+
return this.locate(path, true).then(
|
|
1632
|
+
() => true,
|
|
1633
|
+
() => false
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
async readFileBuffer(path) {
|
|
1637
|
+
const handle = (await this.locate(path, true)).handle;
|
|
1638
|
+
if (handle.kind === "directory") throw fsError("EISDIR", path);
|
|
1639
|
+
for (let attempt = 1; ; attempt++) {
|
|
1640
|
+
try {
|
|
1641
|
+
return new Uint8Array(await (await handle.getFile()).arrayBuffer());
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
if (err?.name !== "NotReadableError" || attempt >= READ_ATTEMPTS) {
|
|
1644
|
+
throw translate(err, path);
|
|
1348
1645
|
}
|
|
1349
|
-
|
|
1350
|
-
close: () => pipe.closeWrite(),
|
|
1351
|
-
poll: () => ({ readable: false, writable: pipe.writeReady, hangup: pipe.readersGone }),
|
|
1352
|
-
changed: (signal) => pipe.changed(signal)
|
|
1353
|
-
})
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
function bytesSource(data) {
|
|
1357
|
-
let offset = 0;
|
|
1358
|
-
return new OpenFile({
|
|
1359
|
-
read: async (max) => {
|
|
1360
|
-
const out = data.subarray(offset, offset + max);
|
|
1361
|
-
offset += out.length;
|
|
1362
|
-
return out;
|
|
1363
|
-
},
|
|
1364
|
-
close: () => {
|
|
1365
|
-
}
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
function sinkFile(onData) {
|
|
1369
|
-
return new OpenFile({
|
|
1370
|
-
write: async (bytes) => {
|
|
1371
|
-
onData(bytes.slice());
|
|
1372
|
-
return bytes.length;
|
|
1373
|
-
},
|
|
1374
|
-
close: () => {
|
|
1375
|
-
}
|
|
1376
|
-
});
|
|
1377
|
-
}
|
|
1378
|
-
function nullFile() {
|
|
1379
|
-
return new OpenFile({
|
|
1380
|
-
read: async () => new Uint8Array(0),
|
|
1381
|
-
write: async (bytes) => bytes.length,
|
|
1382
|
-
close: () => {
|
|
1383
|
-
}
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
function deviceFile(device, access) {
|
|
1387
|
-
const read = async (max) => {
|
|
1388
|
-
if (device === "null") return new Uint8Array(0);
|
|
1389
|
-
const out = new Uint8Array(max);
|
|
1390
|
-
if (device === "urandom") {
|
|
1391
|
-
for (let at = 0; at < max; at += 65536) {
|
|
1392
|
-
crypto.getRandomValues(out.subarray(at, Math.min(max, at + 65536)));
|
|
1646
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 10));
|
|
1393
1647
|
}
|
|
1394
1648
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1649
|
+
}
|
|
1650
|
+
async readFile(path) {
|
|
1651
|
+
return new TextDecoder().decode(await this.readFileBuffer(path));
|
|
1652
|
+
}
|
|
1653
|
+
async writeTarget(path) {
|
|
1654
|
+
try {
|
|
1655
|
+
return [(await this.locate(path, true)).path, true];
|
|
1656
|
+
} catch (err) {
|
|
1657
|
+
if (!missing(err)) throw err;
|
|
1403
1658
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
return new OpenFile({ held: true, ...meta ? { heldMeta: meta } : {}, close: () => {
|
|
1408
|
-
} });
|
|
1409
|
-
}
|
|
1410
|
-
function kernelFdKind(file) {
|
|
1411
|
-
if (file.held) return "held";
|
|
1412
|
-
if (file.heldMeta && "device" in file.heldMeta) return "device";
|
|
1413
|
-
if (file.tty) return "tty";
|
|
1414
|
-
return file.seek ? "file" : "stream";
|
|
1415
|
-
}
|
|
1416
|
-
var FdTable = class _FdTable {
|
|
1417
|
-
static MAX_FDS = 1024;
|
|
1418
|
-
fds = /* @__PURE__ */ new Map();
|
|
1419
|
-
cloexec = /* @__PURE__ */ new Set();
|
|
1420
|
-
status = /* @__PURE__ */ new Map();
|
|
1421
|
-
get(fd) {
|
|
1422
|
-
const file = this.fds.get(fd);
|
|
1423
|
-
if (!file) throw new KernelError("EBADF");
|
|
1424
|
-
return file;
|
|
1659
|
+
const [entry] = await this.meta.get([path]);
|
|
1660
|
+
const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
|
|
1661
|
+
return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
|
|
1425
1662
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1663
|
+
async writeFile(path, content) {
|
|
1664
|
+
const [target, existed] = await this.writeTarget(path);
|
|
1665
|
+
const [dir, name2] = await this.parent(target);
|
|
1666
|
+
let file;
|
|
1667
|
+
try {
|
|
1668
|
+
file = await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true }));
|
|
1669
|
+
} catch (err) {
|
|
1670
|
+
const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
|
|
1671
|
+
throw code ? fsError(code, path) : translate(err, path);
|
|
1672
|
+
}
|
|
1673
|
+
if (!existed && content.length === 0) return;
|
|
1674
|
+
const writable = await file.createWritable();
|
|
1675
|
+
await writable.write(content);
|
|
1676
|
+
await writable.close();
|
|
1677
|
+
}
|
|
1678
|
+
async listing(path) {
|
|
1679
|
+
const found = await this.locate(path, true);
|
|
1680
|
+
const handle = found.handle;
|
|
1681
|
+
if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
1682
|
+
const children2 = /* @__PURE__ */ new Map();
|
|
1683
|
+
for await (const child of handle.values()) children2.set(child.name, child);
|
|
1684
|
+
for (const link2 of await this.meta.links(found.path)) {
|
|
1685
|
+
const name2 = baseOf(link2.path);
|
|
1686
|
+
if (!children2.has(name2)) children2.set(name2, link2);
|
|
1430
1687
|
}
|
|
1431
|
-
return
|
|
1688
|
+
return [found.path, [...children2].sort(([a], [b]) => a < b ? -1 : 1)];
|
|
1432
1689
|
}
|
|
1433
|
-
|
|
1434
|
-
return
|
|
1690
|
+
async readdir(path) {
|
|
1691
|
+
return (await this.listing(path))[1].map(([name2]) => name2);
|
|
1435
1692
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1693
|
+
async readdirStat(path) {
|
|
1694
|
+
const [dir, children2] = await this.listing(path);
|
|
1695
|
+
const base = dir === "/" ? "" : dir;
|
|
1696
|
+
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
1697
|
+
return Promise.all(
|
|
1698
|
+
children2.map(async ([name2, child], i) => {
|
|
1699
|
+
if (!isHandle(child)) return [name2, this.linkStat(child)];
|
|
1700
|
+
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
1701
|
+
return [name2, stat];
|
|
1702
|
+
})
|
|
1703
|
+
);
|
|
1438
1704
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1705
|
+
async mkdir(path, options = {}) {
|
|
1706
|
+
let found;
|
|
1707
|
+
try {
|
|
1708
|
+
found = await this.locate(path, true);
|
|
1709
|
+
} catch (err) {
|
|
1710
|
+
if (!missing(err)) throw err;
|
|
1711
|
+
}
|
|
1712
|
+
if (found) {
|
|
1713
|
+
const directory = found.handle?.kind === "directory";
|
|
1714
|
+
if (options.recursive && directory) return;
|
|
1715
|
+
throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
|
|
1716
|
+
}
|
|
1717
|
+
const [entry] = await this.meta.get([path]);
|
|
1718
|
+
if (entry?.link !== void 0) throw fsError("EEXIST", path);
|
|
1719
|
+
if (options.recursive) await this.mkdir(parentOf2(path), options);
|
|
1720
|
+
const parent = await this.locate(parentOf2(path), true);
|
|
1721
|
+
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
1722
|
+
await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
|
|
1442
1723
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1724
|
+
async rm(path, options = {}) {
|
|
1725
|
+
let found;
|
|
1726
|
+
try {
|
|
1727
|
+
found = await this.locate(path, false);
|
|
1728
|
+
} catch (err) {
|
|
1729
|
+
const error = translate(err, path);
|
|
1730
|
+
if (options.force && error.code === "ENOENT") return;
|
|
1731
|
+
throw error;
|
|
1732
|
+
}
|
|
1733
|
+
if (found.handle) {
|
|
1734
|
+
const [dir, name2] = await this.parent(found.path);
|
|
1735
|
+
try {
|
|
1736
|
+
await this.inDir(
|
|
1737
|
+
dir,
|
|
1738
|
+
(parent) => parent.removeEntry(name2, { recursive: options.recursive === true })
|
|
1739
|
+
);
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
throw translate(err, path);
|
|
1742
|
+
}
|
|
1743
|
+
this.changed(found.path);
|
|
1744
|
+
}
|
|
1745
|
+
await this.meta.remove([found.path]);
|
|
1446
1746
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1747
|
+
async rename(from, to) {
|
|
1748
|
+
if (from === to) return;
|
|
1749
|
+
const source = await this.locate(from, false);
|
|
1750
|
+
this.forget(source.path);
|
|
1751
|
+
const parent = await this.locate(parentOf2(to), true);
|
|
1752
|
+
const destination = resolvePath(parent.path, baseOf(to));
|
|
1753
|
+
const sourceStat = await this.describe(source);
|
|
1754
|
+
if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
|
|
1755
|
+
const target = await this.locate(destination, false).catch((err) => {
|
|
1756
|
+
if (missing(err)) return null;
|
|
1757
|
+
throw err;
|
|
1758
|
+
});
|
|
1759
|
+
const targetStat = target && await this.describe(target);
|
|
1760
|
+
if (targetStat?.isDirectory && !sourceStat.isDirectory) throw fsError("EISDIR", to);
|
|
1761
|
+
if (targetStat && !targetStat.isDirectory && sourceStat.isDirectory) {
|
|
1762
|
+
throw fsError("ENOTDIR", to);
|
|
1763
|
+
}
|
|
1764
|
+
if (targetStat?.isDirectory && (await this.readdir(destination)).length > 0) {
|
|
1765
|
+
throw fsError("ENOTEMPTY", to);
|
|
1766
|
+
}
|
|
1767
|
+
const now = Date.now();
|
|
1768
|
+
const carry = (entry) => ({
|
|
1769
|
+
ino: inodeOf(source.path),
|
|
1770
|
+
...entry,
|
|
1771
|
+
path: destination,
|
|
1772
|
+
ctimeMs: now
|
|
1773
|
+
});
|
|
1774
|
+
if (source.link) {
|
|
1775
|
+
if (target?.handle) await this.rm(destination);
|
|
1776
|
+
await this.meta.move(source.path, destination, carry);
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
const dir = parent.handle;
|
|
1780
|
+
const name2 = baseOf(destination);
|
|
1781
|
+
const swap = targetStat?.isDirectory === true;
|
|
1782
|
+
const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
|
|
1783
|
+
this.forget(destination);
|
|
1784
|
+
const timed = await this.explicitTimes(source.path);
|
|
1785
|
+
const moved2 = await this.place(source.handle, dir, staging);
|
|
1786
|
+
if (swap) await this.rm(destination, { recursive: true });
|
|
1787
|
+
await this.meta.move(source.path, destination, carry);
|
|
1788
|
+
if (!moved2) await this.rm(source.path, { recursive: true });
|
|
1789
|
+
let copied = !moved2;
|
|
1790
|
+
if (swap) {
|
|
1791
|
+
const staged = await dir.getDirectoryHandle(staging);
|
|
1792
|
+
const placed = await this.place(staged, dir, name2);
|
|
1793
|
+
if (!placed) await dir.removeEntry(staging, { recursive: true });
|
|
1794
|
+
copied ||= !placed;
|
|
1795
|
+
}
|
|
1796
|
+
if (copied) await this.restamp(destination, timed);
|
|
1797
|
+
this.changed(source.path, destination);
|
|
1449
1798
|
}
|
|
1450
|
-
|
|
1451
|
-
this.
|
|
1452
|
-
|
|
1799
|
+
async modified(path) {
|
|
1800
|
+
const handle = await this.handle(path);
|
|
1801
|
+
return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
|
|
1453
1802
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1803
|
+
async explicitTimes(path) {
|
|
1804
|
+
const timed = [];
|
|
1805
|
+
for (const entry of await this.meta.under(path)) {
|
|
1806
|
+
if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
|
|
1807
|
+
const current = await this.modified(entry.path).catch(() => void 0);
|
|
1808
|
+
if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
|
|
1809
|
+
}
|
|
1810
|
+
return timed;
|
|
1456
1811
|
}
|
|
1457
|
-
|
|
1458
|
-
for (
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
return fd;
|
|
1462
|
-
}
|
|
1812
|
+
async restamp(path, timed) {
|
|
1813
|
+
for (const rest of timed) {
|
|
1814
|
+
const mtimeFor = await this.modified(path + rest);
|
|
1815
|
+
await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
|
|
1463
1816
|
}
|
|
1464
|
-
void Promise.resolve(file.release()).catch(() => void 0);
|
|
1465
|
-
throw new KernelError("EMFILE");
|
|
1466
1817
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1818
|
+
async place(handle, dir, name2) {
|
|
1819
|
+
const moved2 = await handle.move?.(dir, name2).then(
|
|
1820
|
+
() => true,
|
|
1821
|
+
() => false
|
|
1822
|
+
);
|
|
1823
|
+
if (moved2) return true;
|
|
1824
|
+
try {
|
|
1825
|
+
await this.copy(handle, dir, name2);
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
if (handle.kind === "directory") await dir.removeEntry(name2, { recursive: true });
|
|
1828
|
+
throw translate(err, name2);
|
|
1471
1829
|
}
|
|
1472
|
-
|
|
1473
|
-
this.fds.set(fd, file);
|
|
1474
|
-
this.cloexec.delete(fd);
|
|
1475
|
-
this.status.delete(fd);
|
|
1476
|
-
void Promise.resolve(previous?.release()).catch(() => void 0);
|
|
1830
|
+
return false;
|
|
1477
1831
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1832
|
+
async copy(handle, dir, name2) {
|
|
1833
|
+
if (handle.kind === "file") {
|
|
1834
|
+
const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
|
|
1835
|
+
await writable.write(await handle.getFile());
|
|
1836
|
+
await writable.close();
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
const target = await dir.getDirectoryHandle(name2, { create: true });
|
|
1840
|
+
for await (const child of handle.values()) await this.copy(child, target, child.name);
|
|
1480
1841
|
}
|
|
1481
|
-
|
|
1482
|
-
const
|
|
1483
|
-
if (
|
|
1484
|
-
|
|
1842
|
+
async symlink(target, path) {
|
|
1843
|
+
const parent = await this.locate(parentOf2(path), true);
|
|
1844
|
+
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
1845
|
+
const at = resolvePath(parent.path, baseOf(path));
|
|
1846
|
+
if (await this.locate(at, false).then(
|
|
1847
|
+
() => true,
|
|
1848
|
+
(err) => !missing(err)
|
|
1849
|
+
)) {
|
|
1850
|
+
throw fsError("EEXIST", path);
|
|
1851
|
+
}
|
|
1852
|
+
const now = Date.now();
|
|
1853
|
+
await this.meta.update(at, () => ({
|
|
1854
|
+
path: at,
|
|
1855
|
+
link: target,
|
|
1856
|
+
dir: parent.path,
|
|
1857
|
+
ino: inodeOf(at),
|
|
1858
|
+
mtimeMs: now,
|
|
1859
|
+
ctimeMs: now
|
|
1860
|
+
}));
|
|
1485
1861
|
}
|
|
1486
|
-
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
this.status.delete(fd);
|
|
1491
|
-
return file.release();
|
|
1862
|
+
async readlink(path) {
|
|
1863
|
+
const found = await this.locate(path, false);
|
|
1864
|
+
if (found.link) return found.link.link;
|
|
1865
|
+
throw fsError("EINVAL", path);
|
|
1492
1866
|
}
|
|
1493
|
-
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1867
|
+
async chmod(path, mode) {
|
|
1868
|
+
const found = await this.locate(path, true);
|
|
1869
|
+
const ctimeMs = Date.now();
|
|
1870
|
+
await this.meta.update(found.path, (entry) => ({
|
|
1871
|
+
...entry,
|
|
1872
|
+
path: found.path,
|
|
1873
|
+
mode: mode & PERM_MASK,
|
|
1874
|
+
ctimeMs
|
|
1875
|
+
}));
|
|
1499
1876
|
}
|
|
1500
|
-
async
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
await
|
|
1877
|
+
async utimes(path, atime, mtime) {
|
|
1878
|
+
const found = await this.locate(path, true);
|
|
1879
|
+
const handle = found.handle;
|
|
1880
|
+
const file = handle.kind === "file" ? await handle.getFile() : void 0;
|
|
1881
|
+
const ctimeMs = Date.now();
|
|
1882
|
+
await this.meta.update(found.path, (entry) => ({
|
|
1883
|
+
...entry,
|
|
1884
|
+
path: found.path,
|
|
1885
|
+
atimeMs: atime.getTime(),
|
|
1886
|
+
mtimeMs: mtime.getTime(),
|
|
1887
|
+
mtimeFor: file?.lastModified ?? 0,
|
|
1888
|
+
ctimeMs
|
|
1889
|
+
}));
|
|
1890
|
+
}
|
|
1891
|
+
async reconcile() {
|
|
1892
|
+
let removed = 0;
|
|
1893
|
+
for (const entry of await this.meta.all()) {
|
|
1894
|
+
const real = await this.real(entry.path);
|
|
1895
|
+
const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
|
|
1896
|
+
if (!stale) continue;
|
|
1897
|
+
await this.meta.update(entry.path, () => void 0);
|
|
1898
|
+
removed++;
|
|
1899
|
+
}
|
|
1900
|
+
return removed;
|
|
1506
1901
|
}
|
|
1507
1902
|
};
|
|
1508
1903
|
|
|
1904
|
+
// src/kernel/net/loopback-names.ts
|
|
1905
|
+
var HOST_LOOPBACK = "host.slicc.internal";
|
|
1906
|
+
var PAGE_LOOPBACK = "kernel.localhost";
|
|
1907
|
+
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
1908
|
+
function toHostLoopback(href) {
|
|
1909
|
+
const url = new URL(href);
|
|
1910
|
+
if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
|
|
1911
|
+
url.hostname = "127.0.0.1";
|
|
1912
|
+
return url.href;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1509
1915
|
// src/kernel/net/http1.ts
|
|
1510
1916
|
var HttpError = class extends Error {
|
|
1511
1917
|
status;
|
|
@@ -1790,7 +2196,7 @@ var Budget = class {
|
|
|
1790
2196
|
for (const wake of waiters) wake();
|
|
1791
2197
|
}
|
|
1792
2198
|
};
|
|
1793
|
-
function
|
|
2199
|
+
function canonicalHost2(host) {
|
|
1794
2200
|
const bare = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
1795
2201
|
try {
|
|
1796
2202
|
return new URL(`http://${bare}/`).hostname;
|
|
@@ -1820,7 +2226,7 @@ function localV6(address) {
|
|
|
1820
2226
|
return g[5] === 0 && g[6] === 0;
|
|
1821
2227
|
}
|
|
1822
2228
|
function isLoopbackHost(hostname) {
|
|
1823
|
-
const canonical =
|
|
2229
|
+
const canonical = canonicalHost2(hostname);
|
|
1824
2230
|
if (canonical === void 0) return false;
|
|
1825
2231
|
const host = canonical.replace(/\.+$/, "");
|
|
1826
2232
|
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
@@ -1893,7 +2299,7 @@ function tunnelTarget(req) {
|
|
|
1893
2299
|
if (!match || port < 1 || port > 65535) {
|
|
1894
2300
|
throw new HttpError(400, "CONNECT needs host:port");
|
|
1895
2301
|
}
|
|
1896
|
-
const host =
|
|
2302
|
+
const host = canonicalHost2(match[1]);
|
|
1897
2303
|
if (host === void 0) throw new HttpError(400, "CONNECT needs host:port");
|
|
1898
2304
|
if (isLoopbackHost(host)) {
|
|
1899
2305
|
throw new HttpError(
|
|
@@ -2079,7 +2485,7 @@ var RealmProxy = class {
|
|
|
2079
2485
|
async upstream(req, url, body, signal) {
|
|
2080
2486
|
try {
|
|
2081
2487
|
const fetching = this.options.transport.fetch({
|
|
2082
|
-
url,
|
|
2488
|
+
url: toHostLoopback(url),
|
|
2083
2489
|
method: req.method.toUpperCase(),
|
|
2084
2490
|
headers: forwardRequestHeaders(req.headers),
|
|
2085
2491
|
body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
|
|
@@ -2749,6 +3155,7 @@ var CA_PATH = "/etc/ssl/certs/slicc-kernel-ca.pem";
|
|
|
2749
3155
|
var PROXY_URL = `http://127.0.0.1:${REALM_PROXY_PORT}`;
|
|
2750
3156
|
var NO_PROXY = "localhost,.localhost,127.0.0.1,127.0.0.0/8";
|
|
2751
3157
|
var CA_OWNER = "slicc-kernel";
|
|
3158
|
+
var CA_DB = "slicc-kernel-ca";
|
|
2752
3159
|
function networkEnv() {
|
|
2753
3160
|
return {
|
|
2754
3161
|
http_proxy: PROXY_URL,
|
|
@@ -2759,7 +3166,10 @@ function networkEnv() {
|
|
|
2759
3166
|
NO_PROXY,
|
|
2760
3167
|
SSL_CERT_FILE: CA_PATH,
|
|
2761
3168
|
CURL_CA_BUNDLE: CA_PATH,
|
|
2762
|
-
GIT_SSL_CAINFO: CA_PATH
|
|
3169
|
+
GIT_SSL_CAINFO: CA_PATH,
|
|
3170
|
+
SLICC_HOST_LOOPBACK: HOST_LOOPBACK,
|
|
3171
|
+
SLICC_PAGE_LOOPBACK: PAGE_LOOPBACK,
|
|
3172
|
+
SLICC_PAGE_LOOPBACK_URL: PAGE_LOOPBACK_URL
|
|
2763
3173
|
};
|
|
2764
3174
|
}
|
|
2765
3175
|
function missingTransport() {
|
|
@@ -2779,8 +3189,8 @@ function memoryCaStore() {
|
|
|
2779
3189
|
}
|
|
2780
3190
|
};
|
|
2781
3191
|
}
|
|
2782
|
-
function caStore(
|
|
2783
|
-
return
|
|
3192
|
+
function caStore(name2) {
|
|
3193
|
+
return name2 === false ? memoryCaStore() : indexedDbCaStore(name2);
|
|
2784
3194
|
}
|
|
2785
3195
|
function base64(bytes) {
|
|
2786
3196
|
let text = "";
|
|
@@ -2813,12 +3223,27 @@ function kernelCa(store) {
|
|
|
2813
3223
|
function kernelTlsEngine(load) {
|
|
2814
3224
|
return () => loadTlsEngine(load);
|
|
2815
3225
|
}
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
3226
|
+
var CERTIFICATE = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
|
|
3227
|
+
var CA_BUNDLE_MAX = 16;
|
|
3228
|
+
var CA_FILE_LOCK = "slicc-kernel-ca-file";
|
|
3229
|
+
var CA_FILE_TRIES = 3;
|
|
3230
|
+
async function addToBundle(fs, own) {
|
|
3231
|
+
const current = await fs.exists(CA_PATH) ? await fs.readFile(CA_PATH) : "";
|
|
3232
|
+
const all = current.match(CERTIFICATE) ?? [];
|
|
3233
|
+
const others = all.filter((cert) => cert !== own);
|
|
3234
|
+
if (others.length < all.length) return true;
|
|
3235
|
+
const bundle = [...others, own].slice(-CA_BUNDLE_MAX);
|
|
2820
3236
|
await fs.mkdir(CA_PATH.slice(0, CA_PATH.lastIndexOf("/")), { recursive: true });
|
|
2821
|
-
await fs.writeFile(CA_PATH,
|
|
3237
|
+
await fs.writeFile(CA_PATH, `${bundle.join("\n")}
|
|
3238
|
+
`);
|
|
3239
|
+
return false;
|
|
3240
|
+
}
|
|
3241
|
+
async function writeCaFile(fs, ca, locks = globalThis.navigator?.locks) {
|
|
3242
|
+
const own = (await ca()).pem.trim();
|
|
3243
|
+
const update = async () => {
|
|
3244
|
+
for (let i = 0; i < CA_FILE_TRIES && !await addToBundle(fs, own); i++) ;
|
|
3245
|
+
};
|
|
3246
|
+
await (locks ? locks.request(CA_FILE_LOCK, update) : update());
|
|
2822
3247
|
}
|
|
2823
3248
|
function enableNetwork(net, options) {
|
|
2824
3249
|
let proxy;
|
|
@@ -4592,586 +5017,290 @@ var HttpHandles = class {
|
|
|
4592
5017
|
statusText: response.statusText,
|
|
4593
5018
|
url: req.url,
|
|
4594
5019
|
headers: response.headers
|
|
4595
|
-
};
|
|
4596
|
-
return { ok: true, kind: "json", json: head };
|
|
4597
|
-
} catch (e) {
|
|
4598
|
-
const text = message(e);
|
|
4599
|
-
if (text === NO_TRANSPORT) return { ok: false, errno: "ENETUNREACH", message: text };
|
|
4600
|
-
const status = e.status;
|
|
4601
|
-
if (typeof status !== "number" || status === 502) {
|
|
4602
|
-
return { ok: false, errno: "ECONNREFUSED", message: text };
|
|
4603
|
-
}
|
|
4604
|
-
const body = new TextEncoder().encode(text);
|
|
4605
|
-
const handle = this.add(abort, once(body), async () => void 0);
|
|
4606
|
-
const head = {
|
|
4607
|
-
handle,
|
|
4608
|
-
status,
|
|
4609
|
-
statusText: "",
|
|
4610
|
-
url: req.url,
|
|
4611
|
-
headers: [["content-type", "text/plain; charset=utf-8"]]
|
|
4612
|
-
};
|
|
4613
|
-
return { ok: true, kind: "json", json: head };
|
|
4614
|
-
}
|
|
4615
|
-
}
|
|
4616
|
-
async read(handle, max) {
|
|
4617
|
-
const entry = this.open.get(handle);
|
|
4618
|
-
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
4619
|
-
const turn = entry.reading.then(() => this.take(entry, max));
|
|
4620
|
-
entry.reading = turn.then(() => void 0);
|
|
4621
|
-
return turn;
|
|
4622
|
-
}
|
|
4623
|
-
async take(entry, max) {
|
|
4624
|
-
if (entry.left.length === 0 && !entry.done) {
|
|
4625
|
-
try {
|
|
4626
|
-
const next = await entry.body.next();
|
|
4627
|
-
if (next.done) entry.done = true;
|
|
4628
|
-
else entry.left = next.value;
|
|
4629
|
-
} catch (e) {
|
|
4630
|
-
entry.done = true;
|
|
4631
|
-
const timedOut = e?.code === "ETIMEDOUT";
|
|
4632
|
-
return { ok: false, errno: timedOut ? "ETIMEDOUT" : "EIO", message: message(e) };
|
|
4633
|
-
}
|
|
4634
|
-
}
|
|
4635
|
-
const bytes = entry.left.subarray(0, Math.max(0, max));
|
|
4636
|
-
entry.left = entry.left.subarray(bytes.length);
|
|
4637
|
-
return { ok: true, kind: "bytes", bytes };
|
|
4638
|
-
}
|
|
4639
|
-
async close(handle) {
|
|
4640
|
-
const entry = this.open.get(handle);
|
|
4641
|
-
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
4642
|
-
this.open.delete(handle);
|
|
4643
|
-
entry.abort.abort();
|
|
4644
|
-
await entry.cancel().catch(() => void 0);
|
|
4645
|
-
return { ok: true, kind: "void" };
|
|
4646
|
-
}
|
|
4647
|
-
async closeAll() {
|
|
4648
|
-
await Promise.all([...this.open.keys()].map((handle) => this.close(handle)));
|
|
4649
|
-
}
|
|
4650
|
-
};
|
|
4651
|
-
|
|
4652
|
-
// src/kernel/tty.ts
|
|
4653
|
-
var ICRNL = 256;
|
|
4654
|
-
var IUTF8 = 16384;
|
|
4655
|
-
var OPOST = 1;
|
|
4656
|
-
var ONLCR = 4;
|
|
4657
|
-
var ISIG = 1;
|
|
4658
|
-
var ICANON = 2;
|
|
4659
|
-
var ECHO = 8;
|
|
4660
|
-
var ECHOE = 16;
|
|
4661
|
-
var ECHOCTL = 512;
|
|
4662
|
-
var NOFLSH = 128;
|
|
4663
|
-
var IEXTEN = 32768;
|
|
4664
|
-
var VINTR = 0;
|
|
4665
|
-
var VQUIT = 1;
|
|
4666
|
-
var VERASE = 2;
|
|
4667
|
-
var VKILL = 3;
|
|
4668
|
-
var VEOF = 4;
|
|
4669
|
-
var VSUSP = 10;
|
|
4670
|
-
var VWERASE = 14;
|
|
4671
|
-
function defaultTermios() {
|
|
4672
|
-
const cCc = new Array(32).fill(0);
|
|
4673
|
-
Object.assign(cCc, { [VINTR]: 3, [VQUIT]: 28, [VERASE]: 127, [VKILL]: 21 });
|
|
4674
|
-
Object.assign(cCc, { [VEOF]: 4, 6: 1, 8: 17, 9: 19, [VSUSP]: 26 });
|
|
4675
|
-
Object.assign(cCc, { 12: 18, 13: 15, [VWERASE]: 23, 15: 22 });
|
|
4676
|
-
return {
|
|
4677
|
-
c_iflag: ICRNL | 1024 | 8192 | IUTF8,
|
|
4678
|
-
c_oflag: OPOST | ONLCR,
|
|
4679
|
-
c_cflag: 15 | 48 | 128,
|
|
4680
|
-
c_lflag: ISIG | ICANON | ECHO | ECHOE | 32 | ECHOCTL | 2048 | IEXTEN,
|
|
4681
|
-
c_cc: cCc
|
|
4682
|
-
};
|
|
4683
|
-
}
|
|
4684
|
-
var encoder2 = new TextEncoder();
|
|
4685
|
-
function isContinuation(byte) {
|
|
4686
|
-
return (byte & 192) === 128;
|
|
4687
|
-
}
|
|
4688
|
-
var KernelTty = class {
|
|
4689
|
-
termios = defaultTermios();
|
|
4690
|
-
cols = 80;
|
|
4691
|
-
rows = 24;
|
|
4692
|
-
readable = [];
|
|
4693
|
-
line = [];
|
|
4694
|
-
waiters = [];
|
|
4695
|
-
hungUp = false;
|
|
4696
|
-
name;
|
|
4697
|
-
screen;
|
|
4698
|
-
signal;
|
|
4699
|
-
constructor(screen, signal) {
|
|
4700
|
-
this.screen = screen;
|
|
4701
|
-
this.signal = signal;
|
|
4702
|
-
}
|
|
4703
|
-
receive(bytes) {
|
|
4704
|
-
for (const byte of bytes) this.key(byte);
|
|
4705
|
-
this.wake();
|
|
4706
|
-
}
|
|
4707
|
-
resize(cols, rows) {
|
|
4708
|
-
this.setSize(cols, rows);
|
|
4709
|
-
this.signal(SIG.WINCH);
|
|
4710
|
-
}
|
|
4711
|
-
hangup() {
|
|
4712
|
-
this.hungUp = true;
|
|
4713
|
-
this.wake();
|
|
4714
|
-
}
|
|
4715
|
-
signalHangup() {
|
|
4716
|
-
this.signal(SIG.HUP);
|
|
4717
|
-
}
|
|
4718
|
-
setSize(cols, rows) {
|
|
4719
|
-
this.cols = cols;
|
|
4720
|
-
this.rows = rows;
|
|
4721
|
-
}
|
|
4722
|
-
tcgets() {
|
|
4723
|
-
return { ...this.termios, c_cc: [...this.termios.c_cc] };
|
|
4724
|
-
}
|
|
4725
|
-
tcsets(termios) {
|
|
4726
|
-
const wasCanonical = this.canonical;
|
|
4727
|
-
this.termios = { ...termios, c_cc: [...termios.c_cc] };
|
|
4728
|
-
if (wasCanonical && !this.canonical && this.line.length > 0) {
|
|
4729
|
-
this.readable.push(Uint8Array.from(this.line));
|
|
4730
|
-
this.line = [];
|
|
4731
|
-
this.wake();
|
|
4732
|
-
}
|
|
4733
|
-
}
|
|
4734
|
-
winsize() {
|
|
4735
|
-
return [this.rows, this.cols];
|
|
4736
|
-
}
|
|
4737
|
-
file() {
|
|
4738
|
-
return new OpenFile({
|
|
4739
|
-
read: (max, signal) => this.read(max, signal),
|
|
4740
|
-
write: async (bytes) => {
|
|
4741
|
-
if (this.hungUp) throw new KernelError("EIO");
|
|
4742
|
-
this.output(bytes);
|
|
4743
|
-
return bytes.length;
|
|
4744
|
-
},
|
|
4745
|
-
poll: () => ({
|
|
4746
|
-
readable: this.readable.length > 0 || this.hungUp,
|
|
4747
|
-
writable: true,
|
|
4748
|
-
hangup: this.hungUp
|
|
4749
|
-
}),
|
|
4750
|
-
changed: (signal) => this.changed(signal),
|
|
4751
|
-
close: () => {
|
|
4752
|
-
},
|
|
4753
|
-
tty: this
|
|
4754
|
-
});
|
|
4755
|
-
}
|
|
4756
|
-
get canonical() {
|
|
4757
|
-
return (this.termios.c_lflag & ICANON) !== 0;
|
|
4758
|
-
}
|
|
4759
|
-
cc(slot) {
|
|
4760
|
-
return this.termios.c_cc[slot] ?? 0;
|
|
4761
|
-
}
|
|
4762
|
-
key(input) {
|
|
4763
|
-
const { c_iflag, c_lflag } = this.termios;
|
|
4764
|
-
const byte = input === 13 && c_iflag & ICRNL ? 10 : input;
|
|
4765
|
-
if (c_lflag & ISIG && this.signalKey(byte)) return;
|
|
4766
|
-
if (!this.canonical) {
|
|
4767
|
-
this.readable.push(Uint8Array.of(byte));
|
|
4768
|
-
if (c_lflag & ECHO) this.echo(byte);
|
|
4769
|
-
return;
|
|
4770
|
-
}
|
|
4771
|
-
this.edit(byte);
|
|
4772
|
-
}
|
|
4773
|
-
signalKey(byte) {
|
|
4774
|
-
const sig = byte === this.cc(VINTR) ? SIG.INT : byte === this.cc(VQUIT) ? SIG.QUIT : byte === this.cc(VSUSP) ? SIG.TSTP : 0;
|
|
4775
|
-
if (!sig || byte === 0) return false;
|
|
4776
|
-
if (this.termios.c_lflag & ECHO) this.echo(byte);
|
|
4777
|
-
if (!(this.termios.c_lflag & NOFLSH)) {
|
|
4778
|
-
this.line = [];
|
|
4779
|
-
this.readable = [];
|
|
4780
|
-
}
|
|
4781
|
-
this.signal(sig);
|
|
4782
|
-
return true;
|
|
4783
|
-
}
|
|
4784
|
-
edit(byte) {
|
|
4785
|
-
const echo = (this.termios.c_lflag & ECHO) !== 0;
|
|
4786
|
-
if (byte === this.cc(VERASE) || byte === 8) {
|
|
4787
|
-
this.rubOut(this.lastCharLength());
|
|
4788
|
-
} else if (byte === this.cc(VKILL)) {
|
|
4789
|
-
this.rubOut(this.line.length);
|
|
4790
|
-
} else if (byte === this.cc(VWERASE)) {
|
|
4791
|
-
this.rubOut(this.lastWordLength());
|
|
4792
|
-
} else if (byte === this.cc(VEOF)) {
|
|
4793
|
-
this.readable.push(this.line.length > 0 ? Uint8Array.from(this.line) : null);
|
|
4794
|
-
this.line = [];
|
|
4795
|
-
} else if (byte === 10) {
|
|
4796
|
-
this.line.push(byte);
|
|
4797
|
-
if (echo) this.output(encoder2.encode("\n"));
|
|
4798
|
-
this.readable.push(Uint8Array.from(this.line));
|
|
4799
|
-
this.line = [];
|
|
4800
|
-
} else {
|
|
4801
|
-
this.line.push(byte);
|
|
4802
|
-
if (echo) this.echo(byte);
|
|
4803
|
-
}
|
|
4804
|
-
}
|
|
4805
|
-
lastWordLength() {
|
|
4806
|
-
let end = this.line.length;
|
|
4807
|
-
while (end > 0 && this.line[end - 1] === 32) end--;
|
|
4808
|
-
while (end > 0 && this.line[end - 1] !== 32) end--;
|
|
4809
|
-
return this.line.length - end;
|
|
4810
|
-
}
|
|
4811
|
-
lastCharLength() {
|
|
4812
|
-
const { line } = this;
|
|
4813
|
-
let n = Math.min(1, line.length);
|
|
4814
|
-
if (this.termios.c_iflag & IUTF8) {
|
|
4815
|
-
while (n < line.length && isContinuation(line[line.length - n] ?? 0)) n++;
|
|
4816
|
-
}
|
|
4817
|
-
return n;
|
|
4818
|
-
}
|
|
4819
|
-
rubOut(n) {
|
|
4820
|
-
const dropped2 = this.line.splice(this.line.length - n, n);
|
|
4821
|
-
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
4822
|
-
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
4823
|
-
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
4824
|
-
this.output(encoder2.encode("\b \b".repeat(cells)), false);
|
|
4825
|
-
}
|
|
4826
|
-
}
|
|
4827
|
-
echo(byte) {
|
|
4828
|
-
if (byte < 32 && byte !== 10 && byte !== 9 && this.termios.c_lflag & ECHOCTL) {
|
|
4829
|
-
this.output(Uint8Array.of(94, byte + 64), false);
|
|
4830
|
-
return;
|
|
5020
|
+
};
|
|
5021
|
+
return { ok: true, kind: "json", json: head };
|
|
5022
|
+
} catch (e) {
|
|
5023
|
+
const text = message(e);
|
|
5024
|
+
if (text === NO_TRANSPORT) return { ok: false, errno: "ENETUNREACH", message: text };
|
|
5025
|
+
const status = e.status;
|
|
5026
|
+
if (typeof status !== "number" || status === 502) {
|
|
5027
|
+
return { ok: false, errno: "ECONNREFUSED", message: text };
|
|
5028
|
+
}
|
|
5029
|
+
const body = new TextEncoder().encode(text);
|
|
5030
|
+
const handle = this.add(abort, once(body), async () => void 0);
|
|
5031
|
+
const head = {
|
|
5032
|
+
handle,
|
|
5033
|
+
status,
|
|
5034
|
+
statusText: "",
|
|
5035
|
+
url: req.url,
|
|
5036
|
+
headers: [["content-type", "text/plain; charset=utf-8"]]
|
|
5037
|
+
};
|
|
5038
|
+
return { ok: true, kind: "json", json: head };
|
|
4831
5039
|
}
|
|
4832
|
-
this.output(Uint8Array.of(byte));
|
|
4833
5040
|
}
|
|
4834
|
-
|
|
4835
|
-
const
|
|
4836
|
-
if (!
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
const out = [];
|
|
4841
|
-
for (const byte of bytes) {
|
|
4842
|
-
if (byte === 10) out.push(13);
|
|
4843
|
-
out.push(byte);
|
|
4844
|
-
}
|
|
4845
|
-
this.screen.write(Uint8Array.from(out));
|
|
5041
|
+
async read(handle, max) {
|
|
5042
|
+
const entry = this.open.get(handle);
|
|
5043
|
+
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
5044
|
+
const turn = entry.reading.then(() => this.take(entry, max));
|
|
5045
|
+
entry.reading = turn.then(() => void 0);
|
|
5046
|
+
return turn;
|
|
4846
5047
|
}
|
|
4847
|
-
async
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
const chunks = [];
|
|
4859
|
-
let n = 0;
|
|
4860
|
-
while (this.readable.length > 0 && n < max) {
|
|
4861
|
-
const next = this.readable[0];
|
|
4862
|
-
if (next === null || next === void 0) break;
|
|
4863
|
-
const take = next.subarray(0, max - n);
|
|
4864
|
-
chunks.push(take);
|
|
4865
|
-
n += take.length;
|
|
4866
|
-
if (take.length < next.length) this.readable[0] = next.subarray(take.length);
|
|
4867
|
-
else this.readable.shift();
|
|
4868
|
-
if (this.canonical) break;
|
|
4869
|
-
}
|
|
4870
|
-
const out = new Uint8Array(n);
|
|
4871
|
-
let at = 0;
|
|
4872
|
-
for (const chunk2 of chunks) {
|
|
4873
|
-
out.set(chunk2, at);
|
|
4874
|
-
at += chunk2.length;
|
|
5048
|
+
async take(entry, max) {
|
|
5049
|
+
if (entry.left.length === 0 && !entry.done) {
|
|
5050
|
+
try {
|
|
5051
|
+
const next = await entry.body.next();
|
|
5052
|
+
if (next.done) entry.done = true;
|
|
5053
|
+
else entry.left = next.value;
|
|
5054
|
+
} catch (e) {
|
|
5055
|
+
entry.done = true;
|
|
5056
|
+
const timedOut = e?.code === "ETIMEDOUT";
|
|
5057
|
+
return { ok: false, errno: timedOut ? "ETIMEDOUT" : "EIO", message: message(e) };
|
|
5058
|
+
}
|
|
4875
5059
|
}
|
|
4876
|
-
|
|
5060
|
+
const bytes = entry.left.subarray(0, Math.max(0, max));
|
|
5061
|
+
entry.left = entry.left.subarray(bytes.length);
|
|
5062
|
+
return { ok: true, kind: "bytes", bytes };
|
|
4877
5063
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
return
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
const waiter = () => {
|
|
4886
|
-
signal?.removeEventListener("abort", onAbort);
|
|
4887
|
-
resolve();
|
|
4888
|
-
};
|
|
4889
|
-
this.waiters.push(waiter);
|
|
4890
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4891
|
-
});
|
|
5064
|
+
async close(handle) {
|
|
5065
|
+
const entry = this.open.get(handle);
|
|
5066
|
+
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
5067
|
+
this.open.delete(handle);
|
|
5068
|
+
entry.abort.abort();
|
|
5069
|
+
await entry.cancel().catch(() => void 0);
|
|
5070
|
+
return { ok: true, kind: "void" };
|
|
4892
5071
|
}
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
this.waiters = [];
|
|
4896
|
-
for (const resolve of waiters) resolve();
|
|
5072
|
+
async closeAll() {
|
|
5073
|
+
await Promise.all([...this.open.keys()].map((handle) => this.close(handle)));
|
|
4897
5074
|
}
|
|
4898
5075
|
};
|
|
4899
5076
|
|
|
4900
|
-
// src/kernel/
|
|
4901
|
-
var
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
5077
|
+
// src/kernel/tty.ts
|
|
5078
|
+
var ICRNL = 256;
|
|
5079
|
+
var IUTF8 = 16384;
|
|
5080
|
+
var OPOST = 1;
|
|
5081
|
+
var ONLCR = 4;
|
|
5082
|
+
var ISIG = 1;
|
|
5083
|
+
var ICANON = 2;
|
|
5084
|
+
var ECHO = 8;
|
|
5085
|
+
var ECHOE = 16;
|
|
5086
|
+
var ECHOCTL = 512;
|
|
5087
|
+
var NOFLSH = 128;
|
|
5088
|
+
var IEXTEN = 32768;
|
|
5089
|
+
var VINTR = 0;
|
|
5090
|
+
var VQUIT = 1;
|
|
5091
|
+
var VERASE = 2;
|
|
5092
|
+
var VKILL = 3;
|
|
5093
|
+
var VEOF = 4;
|
|
5094
|
+
var VSUSP = 10;
|
|
5095
|
+
var VWERASE = 14;
|
|
5096
|
+
function defaultTermios() {
|
|
5097
|
+
const cCc = new Array(32).fill(0);
|
|
5098
|
+
Object.assign(cCc, { [VINTR]: 3, [VQUIT]: 28, [VERASE]: 127, [VKILL]: 21 });
|
|
5099
|
+
Object.assign(cCc, { [VEOF]: 4, 6: 1, 8: 17, 9: 19, [VSUSP]: 26 });
|
|
5100
|
+
Object.assign(cCc, { 12: 18, 13: 15, [VWERASE]: 23, 15: 22 });
|
|
5101
|
+
return {
|
|
5102
|
+
c_iflag: ICRNL | 1024 | 8192 | IUTF8,
|
|
5103
|
+
c_oflag: OPOST | ONLCR,
|
|
5104
|
+
c_cflag: 15 | 48 | 128,
|
|
5105
|
+
c_lflag: ISIG | ICANON | ECHO | ECHOE | 32 | ECHOCTL | 2048 | IEXTEN,
|
|
5106
|
+
c_cc: cCc
|
|
5107
|
+
};
|
|
5108
|
+
}
|
|
5109
|
+
var encoder2 = new TextEncoder();
|
|
5110
|
+
function isContinuation(byte) {
|
|
5111
|
+
return (byte & 192) === 128;
|
|
5112
|
+
}
|
|
5113
|
+
var KernelTty = class {
|
|
5114
|
+
termios = defaultTermios();
|
|
5115
|
+
cols = 80;
|
|
5116
|
+
rows = 24;
|
|
5117
|
+
readable = [];
|
|
5118
|
+
line = [];
|
|
4906
5119
|
waiters = [];
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
this.
|
|
4914
|
-
this.onFree = onFree;
|
|
4915
|
-
this.slave = new KernelTty(
|
|
4916
|
-
{ write: (bytes) => this.fromSlave(bytes) },
|
|
4917
|
-
(sig) => signal(this.slave, sig)
|
|
4918
|
-
);
|
|
4919
|
-
this.slave.name = `/dev/pts/${index}`;
|
|
4920
|
-
}
|
|
4921
|
-
master() {
|
|
4922
|
-
return new OpenFile({
|
|
4923
|
-
read: (max, signal) => this.readMaster(max, signal),
|
|
4924
|
-
write: async (bytes) => {
|
|
4925
|
-
this.slave.receive(bytes);
|
|
4926
|
-
return bytes.length;
|
|
4927
|
-
},
|
|
4928
|
-
poll: () => ({
|
|
4929
|
-
readable: this.queue.length > 0 || this.slaveGone,
|
|
4930
|
-
writable: true,
|
|
4931
|
-
hangup: this.slaveGone
|
|
4932
|
-
}),
|
|
4933
|
-
changed: (signal) => this.changed(signal),
|
|
4934
|
-
close: () => this.closeMaster(),
|
|
4935
|
-
pty: this
|
|
4936
|
-
});
|
|
5120
|
+
hungUp = false;
|
|
5121
|
+
name;
|
|
5122
|
+
screen;
|
|
5123
|
+
signal;
|
|
5124
|
+
constructor(screen, signal) {
|
|
5125
|
+
this.screen = screen;
|
|
5126
|
+
this.signal = signal;
|
|
4937
5127
|
}
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
this.slaveRefs += 1;
|
|
4942
|
-
this.slaveSeen = true;
|
|
4943
|
-
return new OpenFile({ ...file, close: () => this.closeSlave() });
|
|
5128
|
+
receive(bytes) {
|
|
5129
|
+
for (const byte of bytes) this.key(byte);
|
|
5130
|
+
this.wake();
|
|
4944
5131
|
}
|
|
4945
|
-
|
|
4946
|
-
|
|
5132
|
+
resize(cols, rows) {
|
|
5133
|
+
this.setSize(cols, rows);
|
|
5134
|
+
this.signal(SIG.WINCH);
|
|
4947
5135
|
}
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
this.queue.push(bytes.slice());
|
|
5136
|
+
hangup() {
|
|
5137
|
+
this.hungUp = true;
|
|
4951
5138
|
this.wake();
|
|
4952
5139
|
}
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
if (this.slaveGone) throw new KernelError("EIO");
|
|
4956
|
-
await this.changed(signal);
|
|
4957
|
-
}
|
|
4958
|
-
const status = this.packet ? 1 : 0;
|
|
4959
|
-
const chunks = [];
|
|
4960
|
-
let n = 0;
|
|
4961
|
-
while (this.queue.length > 0 && n < max - status) {
|
|
4962
|
-
const next = this.queue[0];
|
|
4963
|
-
const take = next.subarray(0, max - status - n);
|
|
4964
|
-
chunks.push(take);
|
|
4965
|
-
n += take.length;
|
|
4966
|
-
if (take.length < next.length) this.queue[0] = next.subarray(take.length);
|
|
4967
|
-
else this.queue.shift();
|
|
4968
|
-
}
|
|
4969
|
-
const out = new Uint8Array(status + n);
|
|
4970
|
-
let at = status;
|
|
4971
|
-
for (const chunk2 of chunks) {
|
|
4972
|
-
out.set(chunk2, at);
|
|
4973
|
-
at += chunk2.length;
|
|
4974
|
-
}
|
|
4975
|
-
return out;
|
|
5140
|
+
signalHangup() {
|
|
5141
|
+
this.signal(SIG.HUP);
|
|
4976
5142
|
}
|
|
4977
|
-
|
|
4978
|
-
this.
|
|
4979
|
-
this.
|
|
4980
|
-
this.slave.hangup();
|
|
4981
|
-
this.slave.signalHangup();
|
|
4982
|
-
this.freeIfUnused();
|
|
5143
|
+
setSize(cols, rows) {
|
|
5144
|
+
this.cols = cols;
|
|
5145
|
+
this.rows = rows;
|
|
4983
5146
|
}
|
|
4984
|
-
|
|
4985
|
-
this.
|
|
4986
|
-
|
|
4987
|
-
|
|
5147
|
+
tcgets() {
|
|
5148
|
+
return { ...this.termios, c_cc: [...this.termios.c_cc] };
|
|
5149
|
+
}
|
|
5150
|
+
tcsets(termios) {
|
|
5151
|
+
const wasCanonical = this.canonical;
|
|
5152
|
+
this.termios = { ...termios, c_cc: [...termios.c_cc] };
|
|
5153
|
+
if (wasCanonical && !this.canonical && this.line.length > 0) {
|
|
5154
|
+
this.readable.push(Uint8Array.from(this.line));
|
|
5155
|
+
this.line = [];
|
|
5156
|
+
this.wake();
|
|
5157
|
+
}
|
|
4988
5158
|
}
|
|
4989
|
-
|
|
4990
|
-
|
|
5159
|
+
winsize() {
|
|
5160
|
+
return [this.rows, this.cols];
|
|
4991
5161
|
}
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5162
|
+
file() {
|
|
5163
|
+
return new OpenFile({
|
|
5164
|
+
read: (max, signal) => this.read(max, signal),
|
|
5165
|
+
write: async (bytes) => {
|
|
5166
|
+
if (this.hungUp) throw new KernelError("EIO");
|
|
5167
|
+
this.output(bytes);
|
|
5168
|
+
return bytes.length;
|
|
5169
|
+
},
|
|
5170
|
+
poll: () => ({
|
|
5171
|
+
readable: this.readable.length > 0 || this.hungUp,
|
|
5172
|
+
writable: true,
|
|
5173
|
+
hangup: this.hungUp
|
|
5174
|
+
}),
|
|
5175
|
+
changed: (signal) => this.changed(signal),
|
|
5176
|
+
close: () => {
|
|
5177
|
+
},
|
|
5178
|
+
tty: this
|
|
5005
5179
|
});
|
|
5006
5180
|
}
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
this.waiters = [];
|
|
5010
|
-
for (const resolve of waiters) resolve();
|
|
5181
|
+
get canonical() {
|
|
5182
|
+
return (this.termios.c_lflag & ICANON) !== 0;
|
|
5011
5183
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
pairs = /* @__PURE__ */ new Map();
|
|
5015
|
-
signal;
|
|
5016
|
-
constructor(signal) {
|
|
5017
|
-
this.signal = signal;
|
|
5184
|
+
cc(slot) {
|
|
5185
|
+
return this.termios.c_cc[slot] ?? 0;
|
|
5018
5186
|
}
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
this.
|
|
5024
|
-
|
|
5187
|
+
key(input) {
|
|
5188
|
+
const { c_iflag, c_lflag } = this.termios;
|
|
5189
|
+
const byte = input === 13 && c_iflag & ICRNL ? 10 : input;
|
|
5190
|
+
if (c_lflag & ISIG && this.signalKey(byte)) return;
|
|
5191
|
+
if (!this.canonical) {
|
|
5192
|
+
this.readable.push(Uint8Array.of(byte));
|
|
5193
|
+
if (c_lflag & ECHO) this.echo(byte);
|
|
5194
|
+
return;
|
|
5195
|
+
}
|
|
5196
|
+
this.edit(byte);
|
|
5025
5197
|
}
|
|
5026
|
-
|
|
5027
|
-
const
|
|
5028
|
-
if (!
|
|
5029
|
-
|
|
5198
|
+
signalKey(byte) {
|
|
5199
|
+
const sig = byte === this.cc(VINTR) ? SIG.INT : byte === this.cc(VQUIT) ? SIG.QUIT : byte === this.cc(VSUSP) ? SIG.TSTP : 0;
|
|
5200
|
+
if (!sig || byte === 0) return false;
|
|
5201
|
+
if (this.termios.c_lflag & ECHO) this.echo(byte);
|
|
5202
|
+
if (!(this.termios.c_lflag & NOFLSH)) {
|
|
5203
|
+
this.line = [];
|
|
5204
|
+
this.readable = [];
|
|
5205
|
+
}
|
|
5206
|
+
this.signal(sig);
|
|
5207
|
+
return true;
|
|
5030
5208
|
}
|
|
5031
|
-
|
|
5032
|
-
|
|
5209
|
+
edit(byte) {
|
|
5210
|
+
const echo = (this.termios.c_lflag & ECHO) !== 0;
|
|
5211
|
+
if (byte === this.cc(VERASE) || byte === 8) {
|
|
5212
|
+
this.rubOut(this.lastCharLength());
|
|
5213
|
+
} else if (byte === this.cc(VKILL)) {
|
|
5214
|
+
this.rubOut(this.line.length);
|
|
5215
|
+
} else if (byte === this.cc(VWERASE)) {
|
|
5216
|
+
this.rubOut(this.lastWordLength());
|
|
5217
|
+
} else if (byte === this.cc(VEOF)) {
|
|
5218
|
+
this.readable.push(this.line.length > 0 ? Uint8Array.from(this.line) : null);
|
|
5219
|
+
this.line = [];
|
|
5220
|
+
} else if (byte === 10) {
|
|
5221
|
+
this.line.push(byte);
|
|
5222
|
+
if (echo) this.output(encoder2.encode("\n"));
|
|
5223
|
+
this.readable.push(Uint8Array.from(this.line));
|
|
5224
|
+
this.line = [];
|
|
5225
|
+
} else {
|
|
5226
|
+
this.line.push(byte);
|
|
5227
|
+
if (echo) this.echo(byte);
|
|
5228
|
+
}
|
|
5033
5229
|
}
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
]
|
|
5045
|
-
function masterOf(ctx, fd) {
|
|
5046
|
-
const pair = ctx.fds.get(fd).file.pty;
|
|
5047
|
-
if (!pair) throw new KernelError("ENOTTY");
|
|
5048
|
-
return pair;
|
|
5049
|
-
}
|
|
5050
|
-
function terminalOf(ctx, fd) {
|
|
5051
|
-
const file = ctx.fds.get(fd).file;
|
|
5052
|
-
const tty = file.tty ?? file.pty?.slave;
|
|
5053
|
-
if (!tty) throw new KernelError("ENOTTY");
|
|
5054
|
-
return tty;
|
|
5055
|
-
}
|
|
5056
|
-
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5057
|
-
var done3 = { ok: true, kind: "void" };
|
|
5058
|
-
function ptySyscall(req, ctx) {
|
|
5059
|
-
switch (req.op) {
|
|
5060
|
-
case "pty-open": {
|
|
5061
|
-
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5062
|
-
return json3(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5230
|
+
lastWordLength() {
|
|
5231
|
+
let end = this.line.length;
|
|
5232
|
+
while (end > 0 && this.line[end - 1] === 32) end--;
|
|
5233
|
+
while (end > 0 && this.line[end - 1] !== 32) end--;
|
|
5234
|
+
return this.line.length - end;
|
|
5235
|
+
}
|
|
5236
|
+
lastCharLength() {
|
|
5237
|
+
const { line } = this;
|
|
5238
|
+
let n = Math.min(1, line.length);
|
|
5239
|
+
if (this.termios.c_iflag & IUTF8) {
|
|
5240
|
+
while (n < line.length && isContinuation(line[line.length - n] ?? 0)) n++;
|
|
5063
5241
|
}
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5242
|
+
return n;
|
|
5243
|
+
}
|
|
5244
|
+
rubOut(n) {
|
|
5245
|
+
const dropped2 = this.line.splice(this.line.length - n, n);
|
|
5246
|
+
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
5247
|
+
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
5248
|
+
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
5249
|
+
this.output(encoder2.encode("\b \b".repeat(cells)), false);
|
|
5070
5250
|
}
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
return
|
|
5076
|
-
case "pty-ctty": {
|
|
5077
|
-
const tty = terminalOf(ctx, req.fd);
|
|
5078
|
-
const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
|
|
5079
|
-
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
|
|
5080
|
-
if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
|
|
5081
|
-
return done3;
|
|
5251
|
+
}
|
|
5252
|
+
echo(byte) {
|
|
5253
|
+
if (byte < 32 && byte !== 10 && byte !== 9 && this.termios.c_lflag & ECHOCTL) {
|
|
5254
|
+
this.output(Uint8Array.of(94, byte + 64), false);
|
|
5255
|
+
return;
|
|
5082
5256
|
}
|
|
5083
|
-
|
|
5084
|
-
return json3(ctx.ptys?.numbers() ?? []);
|
|
5085
|
-
case "pty-packet":
|
|
5086
|
-
masterOf(ctx, req.fd).packet = req.on;
|
|
5087
|
-
return done3;
|
|
5088
|
-
case "pty-winsz-set":
|
|
5089
|
-
terminalOf(ctx, req.fd).resize(req.cols, req.rows);
|
|
5090
|
-
return done3;
|
|
5257
|
+
this.output(Uint8Array.of(byte));
|
|
5091
5258
|
}
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
const
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
return out;
|
|
5105
|
-
}
|
|
5106
|
-
function interruption(signal) {
|
|
5107
|
-
const { promise, reject } = Promise.withResolvers();
|
|
5108
|
-
const fail = () => reject(new KernelError("EINTR"));
|
|
5109
|
-
if (signal.aborted) fail();
|
|
5110
|
-
else signal.addEventListener("abort", fail, { once: true });
|
|
5111
|
-
return { promise, done: () => signal.removeEventListener("abort", fail) };
|
|
5112
|
-
}
|
|
5113
|
-
async function selectFds(fds, read, write, timeoutMs, interrupt) {
|
|
5114
|
-
const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
|
|
5115
|
-
const interrupted2 = interruption(interrupt);
|
|
5116
|
-
interrupted2.promise.catch(() => {
|
|
5117
|
-
});
|
|
5118
|
-
try {
|
|
5119
|
-
return await waitReady(fds, read, write, deadline, interrupted2.promise);
|
|
5120
|
-
} finally {
|
|
5121
|
-
interrupted2.done();
|
|
5259
|
+
output(bytes, translate2 = true) {
|
|
5260
|
+
const { c_oflag } = this.termios;
|
|
5261
|
+
if (!translate2 || !(c_oflag & OPOST && c_oflag & ONLCR) || !bytes.includes(10)) {
|
|
5262
|
+
this.screen.write(bytes);
|
|
5263
|
+
return;
|
|
5264
|
+
}
|
|
5265
|
+
const out = [];
|
|
5266
|
+
for (const byte of bytes) {
|
|
5267
|
+
if (byte === 10) out.push(13);
|
|
5268
|
+
out.push(byte);
|
|
5269
|
+
}
|
|
5270
|
+
this.screen.write(Uint8Array.from(out));
|
|
5122
5271
|
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
const
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
return
|
|
5132
|
-
}
|
|
5133
|
-
|
|
5134
|
-
const
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5272
|
+
async read(max, signal) {
|
|
5273
|
+
while (this.readable.length === 0) {
|
|
5274
|
+
if (this.hungUp) return new Uint8Array(0);
|
|
5275
|
+
await this.changed(signal);
|
|
5276
|
+
}
|
|
5277
|
+
const head = this.readable[0];
|
|
5278
|
+
if (head === null) {
|
|
5279
|
+
this.readable.shift();
|
|
5280
|
+
return new Uint8Array(0);
|
|
5281
|
+
}
|
|
5282
|
+
if (head === void 0) return new Uint8Array(0);
|
|
5283
|
+
const chunks = [];
|
|
5284
|
+
let n = 0;
|
|
5285
|
+
while (this.readable.length > 0 && n < max) {
|
|
5286
|
+
const next = this.readable[0];
|
|
5287
|
+
if (next === null || next === void 0) break;
|
|
5288
|
+
const take = next.subarray(0, max - n);
|
|
5289
|
+
chunks.push(take);
|
|
5290
|
+
n += take.length;
|
|
5291
|
+
if (take.length < next.length) this.readable[0] = next.subarray(take.length);
|
|
5292
|
+
else this.readable.shift();
|
|
5293
|
+
if (this.canonical) break;
|
|
5140
5294
|
}
|
|
5295
|
+
const out = new Uint8Array(n);
|
|
5296
|
+
let at = 0;
|
|
5297
|
+
for (const chunk2 of chunks) {
|
|
5298
|
+
out.set(chunk2, at);
|
|
5299
|
+
at += chunk2.length;
|
|
5300
|
+
}
|
|
5301
|
+
return out;
|
|
5141
5302
|
}
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
// src/kernel/socket.ts
|
|
5145
|
-
var SOMAXCONN = 4096;
|
|
5146
|
-
var EPHEMERAL_FIRST = 32768;
|
|
5147
|
-
var EPHEMERAL_LAST = 60999;
|
|
5148
|
-
var SOL_SOCKET = 1;
|
|
5149
|
-
var SO_TYPE = 3;
|
|
5150
|
-
var SO_ERROR = 4;
|
|
5151
|
-
var SO_SNDBUF = 7;
|
|
5152
|
-
var SO_RCVBUF = 8;
|
|
5153
|
-
var SO_ACCEPTCONN = 30;
|
|
5154
|
-
var SO_DOMAIN = 39;
|
|
5155
|
-
var SOCK_STREAM = 1;
|
|
5156
|
-
var AF_UNIX = 1;
|
|
5157
|
-
var AF_INET = 2;
|
|
5158
|
-
var SHUT_RD = 0;
|
|
5159
|
-
var SHUT_WR = 1;
|
|
5160
|
-
var SHUT_RDWR = 2;
|
|
5161
|
-
function isLoopback(host) {
|
|
5162
|
-
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
5163
|
-
}
|
|
5164
|
-
async function pipeCall(op) {
|
|
5165
|
-
try {
|
|
5166
|
-
return await op();
|
|
5167
|
-
} catch (e) {
|
|
5168
|
-
if (e instanceof PipeError) throw new KernelError(e.code);
|
|
5169
|
-
throw e;
|
|
5170
|
-
}
|
|
5171
|
-
}
|
|
5172
|
-
var Changes = class {
|
|
5173
|
-
waiters = [];
|
|
5174
|
-
wait(signal) {
|
|
5303
|
+
changed(signal) {
|
|
5175
5304
|
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
5176
5305
|
return new Promise((resolve, reject) => {
|
|
5177
5306
|
const onAbort = () => {
|
|
@@ -5192,267 +5321,250 @@ var Changes = class {
|
|
|
5192
5321
|
for (const resolve of waiters) resolve();
|
|
5193
5322
|
}
|
|
5194
5323
|
};
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
}
|
|
5202
|
-
a.attach({ rx: ba, tx: ab });
|
|
5203
|
-
b.attach({ rx: ab, tx: ba });
|
|
5204
|
-
}
|
|
5205
|
-
var KernelSocket = class _KernelSocket {
|
|
5206
|
-
net;
|
|
5207
|
-
domain;
|
|
5208
|
-
state = "open";
|
|
5209
|
-
local;
|
|
5210
|
-
peer;
|
|
5211
|
-
link;
|
|
5212
|
-
readShut = false;
|
|
5213
|
-
writeShut = false;
|
|
5324
|
+
|
|
5325
|
+
// src/kernel/pty.ts
|
|
5326
|
+
var PtyPair = class {
|
|
5327
|
+
slave;
|
|
5328
|
+
locked = true;
|
|
5329
|
+
packet = false;
|
|
5214
5330
|
queue = [];
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
this.
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
return this.state === "listening";
|
|
5230
|
-
}
|
|
5231
|
-
connection() {
|
|
5232
|
-
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
5233
|
-
return this.link;
|
|
5234
|
-
}
|
|
5235
|
-
async read(max, signal) {
|
|
5236
|
-
const { rx } = this.connection();
|
|
5237
|
-
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
5238
|
-
return pipeCall(() => rx.read(max, signal));
|
|
5239
|
-
}
|
|
5240
|
-
async peek(max, signal) {
|
|
5241
|
-
const { rx } = this.connection();
|
|
5242
|
-
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
5243
|
-
return pipeCall(() => rx.peek(max, signal));
|
|
5244
|
-
}
|
|
5245
|
-
async write(bytes, signal) {
|
|
5246
|
-
const { tx } = this.connection();
|
|
5247
|
-
if (this.writeShut) throw new KernelError("EPIPE");
|
|
5248
|
-
return pipeCall(() => tx.write(bytes, signal));
|
|
5249
|
-
}
|
|
5250
|
-
poll() {
|
|
5251
|
-
if (this.state === "listening") {
|
|
5252
|
-
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
5253
|
-
}
|
|
5254
|
-
if (this.state !== "connected" || !this.link) {
|
|
5255
|
-
return { readable: false, writable: false, hangup: true };
|
|
5256
|
-
}
|
|
5257
|
-
const { rx, tx } = this.link;
|
|
5258
|
-
return {
|
|
5259
|
-
readable: this.readShut || rx.readReady,
|
|
5260
|
-
writable: this.writeShut || tx.writeReady,
|
|
5261
|
-
hangup: rx.writersGone && tx.readersGone
|
|
5262
|
-
};
|
|
5263
|
-
}
|
|
5264
|
-
changed(signal) {
|
|
5265
|
-
const own = this.changes.wait(signal);
|
|
5266
|
-
if (this.state !== "connected" || !this.link) return own;
|
|
5267
|
-
const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
|
|
5268
|
-
for (const wait of waits) wait.catch(() => void 0);
|
|
5269
|
-
return pipeCall(() => Promise.race(waits));
|
|
5270
|
-
}
|
|
5271
|
-
bind(addr) {
|
|
5272
|
-
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
5273
|
-
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
5274
|
-
this.local = this.net.bind(this, addr);
|
|
5275
|
-
this.bound = true;
|
|
5331
|
+
waiters = [];
|
|
5332
|
+
masterOpen = true;
|
|
5333
|
+
slaveRefs = 0;
|
|
5334
|
+
slaveSeen = false;
|
|
5335
|
+
index;
|
|
5336
|
+
onFree;
|
|
5337
|
+
constructor(index, signal, onFree) {
|
|
5338
|
+
this.index = index;
|
|
5339
|
+
this.onFree = onFree;
|
|
5340
|
+
this.slave = new KernelTty(
|
|
5341
|
+
{ write: (bytes) => this.fromSlave(bytes) },
|
|
5342
|
+
(sig) => signal(this.slave, sig)
|
|
5343
|
+
);
|
|
5344
|
+
this.slave.name = `/dev/pts/${index}`;
|
|
5276
5345
|
}
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5346
|
+
master() {
|
|
5347
|
+
return new OpenFile({
|
|
5348
|
+
read: (max, signal) => this.readMaster(max, signal),
|
|
5349
|
+
write: async (bytes) => {
|
|
5350
|
+
this.slave.receive(bytes);
|
|
5351
|
+
return bytes.length;
|
|
5352
|
+
},
|
|
5353
|
+
poll: () => ({
|
|
5354
|
+
readable: this.queue.length > 0 || this.slaveGone,
|
|
5355
|
+
writable: true,
|
|
5356
|
+
hangup: this.slaveGone
|
|
5357
|
+
}),
|
|
5358
|
+
changed: (signal) => this.changed(signal),
|
|
5359
|
+
close: () => this.closeMaster(),
|
|
5360
|
+
pty: this
|
|
5361
|
+
});
|
|
5286
5362
|
}
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
await this.changes.wait(signal);
|
|
5294
|
-
}
|
|
5363
|
+
openSlave() {
|
|
5364
|
+
if (!this.masterOpen || this.locked) throw new KernelError("EIO");
|
|
5365
|
+
const file = this.slave.file().file;
|
|
5366
|
+
this.slaveRefs += 1;
|
|
5367
|
+
this.slaveSeen = true;
|
|
5368
|
+
return new OpenFile({ ...file, close: () => this.closeSlave() });
|
|
5295
5369
|
}
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
this.queue.push(server);
|
|
5299
|
-
this.changes.wake();
|
|
5300
|
-
return true;
|
|
5370
|
+
get slaveGone() {
|
|
5371
|
+
return this.slaveSeen && this.slaveRefs === 0;
|
|
5301
5372
|
}
|
|
5302
|
-
|
|
5303
|
-
if (this.
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
const target = this.net.target(addr);
|
|
5307
|
-
if (!this.local) {
|
|
5308
|
-
if (addr.family === "inet") {
|
|
5309
|
-
this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
|
|
5310
|
-
this.bound = true;
|
|
5311
|
-
} else this.local = { family: "unix", path: "" };
|
|
5312
|
-
}
|
|
5313
|
-
const server = new _KernelSocket(this.net, this.domain);
|
|
5314
|
-
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost2(addr.host) } : addr;
|
|
5315
|
-
server.peer = this.local;
|
|
5316
|
-
this.peer = server.local;
|
|
5317
|
-
link(this, server);
|
|
5318
|
-
if (!target.offer(server)) {
|
|
5319
|
-
this.unlink();
|
|
5320
|
-
server.close();
|
|
5321
|
-
throw new KernelError("ECONNREFUSED");
|
|
5322
|
-
}
|
|
5373
|
+
fromSlave(bytes) {
|
|
5374
|
+
if (!this.masterOpen) return;
|
|
5375
|
+
this.queue.push(bytes.slice());
|
|
5376
|
+
this.wake();
|
|
5323
5377
|
}
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
link2.tx.closeWrite();
|
|
5378
|
+
async readMaster(max, signal) {
|
|
5379
|
+
while (this.queue.length === 0) {
|
|
5380
|
+
if (this.slaveGone) throw new KernelError("EIO");
|
|
5381
|
+
await this.changed(signal);
|
|
5329
5382
|
}
|
|
5330
|
-
this.
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
this.
|
|
5339
|
-
|
|
5383
|
+
const status = this.packet ? 1 : 0;
|
|
5384
|
+
const chunks = [];
|
|
5385
|
+
let n = 0;
|
|
5386
|
+
while (this.queue.length > 0 && n < max - status) {
|
|
5387
|
+
const next = this.queue[0];
|
|
5388
|
+
const take = next.subarray(0, max - status - n);
|
|
5389
|
+
chunks.push(take);
|
|
5390
|
+
n += take.length;
|
|
5391
|
+
if (take.length < next.length) this.queue[0] = next.subarray(take.length);
|
|
5392
|
+
else this.queue.shift();
|
|
5340
5393
|
}
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5394
|
+
const out = new Uint8Array(status + n);
|
|
5395
|
+
let at = status;
|
|
5396
|
+
for (const chunk2 of chunks) {
|
|
5397
|
+
out.set(chunk2, at);
|
|
5398
|
+
at += chunk2.length;
|
|
5344
5399
|
}
|
|
5345
|
-
|
|
5400
|
+
return out;
|
|
5346
5401
|
}
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
return 0;
|
|
5354
|
-
case SO_ACCEPTCONN:
|
|
5355
|
-
return this.state === "listening" ? 1 : 0;
|
|
5356
|
-
case SO_DOMAIN:
|
|
5357
|
-
return this.domain === "inet" ? AF_INET : AF_UNIX;
|
|
5358
|
-
case SO_SNDBUF:
|
|
5359
|
-
case SO_RCVBUF:
|
|
5360
|
-
return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
|
|
5361
|
-
}
|
|
5362
|
-
}
|
|
5363
|
-
return this.options.get(`${level}:${name2}`) ?? 0;
|
|
5402
|
+
closeMaster() {
|
|
5403
|
+
this.masterOpen = false;
|
|
5404
|
+
this.queue = [];
|
|
5405
|
+
this.slave.hangup();
|
|
5406
|
+
this.slave.signalHangup();
|
|
5407
|
+
this.freeIfUnused();
|
|
5364
5408
|
}
|
|
5365
|
-
|
|
5366
|
-
this.
|
|
5409
|
+
closeSlave() {
|
|
5410
|
+
this.slaveRefs -= 1;
|
|
5411
|
+
this.wake();
|
|
5412
|
+
this.freeIfUnused();
|
|
5367
5413
|
}
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
this.state = "closed";
|
|
5371
|
-
if (was === "listening") {
|
|
5372
|
-
for (const pending of this.queue.splice(0)) pending.close();
|
|
5373
|
-
}
|
|
5374
|
-
if (this.link) {
|
|
5375
|
-
if (!this.readShut) this.link.rx.closeRead();
|
|
5376
|
-
if (!this.writeShut) this.link.tx.closeWrite();
|
|
5377
|
-
this.readShut = true;
|
|
5378
|
-
this.writeShut = true;
|
|
5379
|
-
}
|
|
5380
|
-
if (this.bound && this.local) this.net.release(this.local, this);
|
|
5381
|
-
this.bound = false;
|
|
5382
|
-
this.changes.wake();
|
|
5414
|
+
freeIfUnused() {
|
|
5415
|
+
if (!this.masterOpen && this.slaveRefs === 0) this.onFree(this);
|
|
5383
5416
|
}
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5417
|
+
changed(signal) {
|
|
5418
|
+
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
5419
|
+
return new Promise((resolve, reject) => {
|
|
5420
|
+
const onAbort = () => {
|
|
5421
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
5422
|
+
reject(new KernelError("EINTR"));
|
|
5423
|
+
};
|
|
5424
|
+
const waiter = () => {
|
|
5425
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5426
|
+
resolve();
|
|
5427
|
+
};
|
|
5428
|
+
this.waiters.push(waiter);
|
|
5429
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5432
|
+
wake() {
|
|
5433
|
+
const waiters = this.waiters;
|
|
5434
|
+
this.waiters = [];
|
|
5435
|
+
for (const resolve of waiters) resolve();
|
|
5391
5436
|
}
|
|
5392
5437
|
};
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
}
|
|
5399
|
-
var LoopbackNet = class {
|
|
5400
|
-
bound = /* @__PURE__ */ new Map();
|
|
5401
|
-
activators = /* @__PURE__ */ new Map();
|
|
5402
|
-
nextEphemeral = EPHEMERAL_FIRST;
|
|
5403
|
-
activate(addr, start) {
|
|
5404
|
-
this.activators.set(keyOf(addr), start);
|
|
5438
|
+
var PtyTable = class {
|
|
5439
|
+
pairs = /* @__PURE__ */ new Map();
|
|
5440
|
+
signal;
|
|
5441
|
+
constructor(signal) {
|
|
5442
|
+
this.signal = signal;
|
|
5405
5443
|
}
|
|
5406
|
-
|
|
5407
|
-
|
|
5444
|
+
open() {
|
|
5445
|
+
let index = 0;
|
|
5446
|
+
while (this.pairs.has(index)) index += 1;
|
|
5447
|
+
const pair = new PtyPair(index, this.signal, (p) => this.pairs.delete(p.index));
|
|
5448
|
+
this.pairs.set(index, pair);
|
|
5449
|
+
return { pair, master: pair.master() };
|
|
5408
5450
|
}
|
|
5409
|
-
|
|
5410
|
-
const
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
return socket;
|
|
5451
|
+
get(index) {
|
|
5452
|
+
const pair = this.pairs.get(index);
|
|
5453
|
+
if (!pair) throw new KernelError("ENOENT");
|
|
5454
|
+
return pair;
|
|
5414
5455
|
}
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
socket.connect(addr);
|
|
5418
|
-
return socket;
|
|
5456
|
+
numbers() {
|
|
5457
|
+
return [...this.pairs.keys()].sort((a, b) => a - b);
|
|
5419
5458
|
}
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5459
|
+
};
|
|
5460
|
+
var PTY_OPS = [
|
|
5461
|
+
"pty-open",
|
|
5462
|
+
"pty-slave-open",
|
|
5463
|
+
"pty-number",
|
|
5464
|
+
"pty-lock",
|
|
5465
|
+
"pty-ctty",
|
|
5466
|
+
"pty-packet",
|
|
5467
|
+
"pty-list",
|
|
5468
|
+
"pty-winsz-set"
|
|
5469
|
+
];
|
|
5470
|
+
function masterOf(ctx, fd) {
|
|
5471
|
+
const pair = ctx.fds.get(fd).file.pty;
|
|
5472
|
+
if (!pair) throw new KernelError("ENOTTY");
|
|
5473
|
+
return pair;
|
|
5474
|
+
}
|
|
5475
|
+
function terminalOf(ctx, fd) {
|
|
5476
|
+
const file = ctx.fds.get(fd).file;
|
|
5477
|
+
const tty = file.tty ?? file.pty?.slave;
|
|
5478
|
+
if (!tty) throw new KernelError("ENOTTY");
|
|
5479
|
+
return tty;
|
|
5480
|
+
}
|
|
5481
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5482
|
+
var done3 = { ok: true, kind: "void" };
|
|
5483
|
+
function ptySyscall(req, ctx) {
|
|
5484
|
+
switch (req.op) {
|
|
5485
|
+
case "pty-open": {
|
|
5486
|
+
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5487
|
+
return json3(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5425
5488
|
}
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
release(addr, socket) {
|
|
5433
|
-
const key = keyOf(addr);
|
|
5434
|
-
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
5435
|
-
}
|
|
5436
|
-
target(addr) {
|
|
5437
|
-
if (addr.family === "inet" && !isLoopback(canonicalHost2(addr.host))) {
|
|
5438
|
-
throw new KernelError("ENETUNREACH");
|
|
5489
|
+
case "pty-slave-open": {
|
|
5490
|
+
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5491
|
+
const pair = ctx.ptys.get(req.n);
|
|
5492
|
+
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
5493
|
+
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
5494
|
+
return json3(fd);
|
|
5439
5495
|
}
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5496
|
+
case "pty-number":
|
|
5497
|
+
return json3(masterOf(ctx, req.fd).index);
|
|
5498
|
+
case "pty-lock":
|
|
5499
|
+
masterOf(ctx, req.fd).locked = req.lock;
|
|
5500
|
+
return done3;
|
|
5501
|
+
case "pty-ctty": {
|
|
5502
|
+
const tty = terminalOf(ctx, req.fd);
|
|
5503
|
+
const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
|
|
5504
|
+
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
|
|
5505
|
+
if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
|
|
5506
|
+
return done3;
|
|
5507
|
+
}
|
|
5508
|
+
case "pty-list":
|
|
5509
|
+
return json3(ctx.ptys?.numbers() ?? []);
|
|
5510
|
+
case "pty-packet":
|
|
5511
|
+
masterOf(ctx, req.fd).packet = req.on;
|
|
5512
|
+
return done3;
|
|
5513
|
+
case "pty-winsz-set":
|
|
5514
|
+
terminalOf(ctx, req.fd).resize(req.cols, req.rows);
|
|
5515
|
+
return done3;
|
|
5445
5516
|
}
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5517
|
+
}
|
|
5518
|
+
|
|
5519
|
+
// src/kernel/select.ts
|
|
5520
|
+
function ready(fds, read, write) {
|
|
5521
|
+
const hangup = /* @__PURE__ */ new Set();
|
|
5522
|
+
const pick = (list, dir) => list.filter((fd) => {
|
|
5523
|
+
const state = pollFile(fds.get(fd).file);
|
|
5524
|
+
if (state.hangup) hangup.add(fd);
|
|
5525
|
+
return state[dir] || state.hangup;
|
|
5526
|
+
});
|
|
5527
|
+
const out = { read: pick(read, "readable"), write: pick(write, "writable") };
|
|
5528
|
+
if (hangup.size > 0) out.hangup = [...hangup];
|
|
5529
|
+
return out;
|
|
5530
|
+
}
|
|
5531
|
+
function interruption(signal) {
|
|
5532
|
+
const { promise, reject } = Promise.withResolvers();
|
|
5533
|
+
const fail = () => reject(new KernelError("EINTR"));
|
|
5534
|
+
if (signal.aborted) fail();
|
|
5535
|
+
else signal.addEventListener("abort", fail, { once: true });
|
|
5536
|
+
return { promise, done: () => signal.removeEventListener("abort", fail) };
|
|
5537
|
+
}
|
|
5538
|
+
async function selectFds(fds, read, write, timeoutMs, interrupt) {
|
|
5539
|
+
const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
|
|
5540
|
+
const interrupted2 = interruption(interrupt);
|
|
5541
|
+
interrupted2.promise.catch(() => {
|
|
5542
|
+
});
|
|
5543
|
+
try {
|
|
5544
|
+
return await waitReady(fds, read, write, deadline, interrupted2.promise);
|
|
5545
|
+
} finally {
|
|
5546
|
+
interrupted2.done();
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
async function waitReady(fds, read, write, deadline, interrupted2) {
|
|
5550
|
+
for (; ; ) {
|
|
5551
|
+
const now = ready(fds, read, write);
|
|
5552
|
+
if (now.read.length > 0 || now.write.length > 0 || Date.now() >= deadline) return now;
|
|
5553
|
+
const round = new AbortController();
|
|
5554
|
+
const changes = [.../* @__PURE__ */ new Set([...read, ...write])].flatMap((fd) => {
|
|
5555
|
+
const file = fds.get(fd).file;
|
|
5556
|
+
return file.changed === void 0 ? [] : [file.changed(round.signal).catch(() => void 0)];
|
|
5557
|
+
});
|
|
5558
|
+
let timer;
|
|
5559
|
+
const expiry = deadline === Number.POSITIVE_INFINITY ? [] : [new Promise((resolve) => timer = setTimeout(resolve, deadline - Date.now()))];
|
|
5560
|
+
try {
|
|
5561
|
+
await Promise.race([...changes, ...expiry, interrupted2]);
|
|
5562
|
+
} finally {
|
|
5563
|
+
round.abort();
|
|
5564
|
+
clearTimeout(timer);
|
|
5452
5565
|
}
|
|
5453
|
-
throw new KernelError("EADDRINUSE");
|
|
5454
5566
|
}
|
|
5455
|
-
}
|
|
5567
|
+
}
|
|
5456
5568
|
|
|
5457
5569
|
// src/kernel/socket-syscalls.ts
|
|
5458
5570
|
var SOCKET_OPS = [
|
|
@@ -6984,8 +7096,8 @@ function mediumSlot(lost, onLost) {
|
|
|
6984
7096
|
|
|
6985
7097
|
// src/mount/fsa.ts
|
|
6986
7098
|
var FSA_CAPABILITIES = { listingStats: true };
|
|
6987
|
-
var
|
|
6988
|
-
var denied = (err) =>
|
|
7099
|
+
var codeOf2 = (err) => err?.code;
|
|
7100
|
+
var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
|
|
6989
7101
|
function attrOf(st) {
|
|
6990
7102
|
return {
|
|
6991
7103
|
kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
|
|
@@ -7007,7 +7119,7 @@ function kernelFsHandlers(fs) {
|
|
|
7007
7119
|
try {
|
|
7008
7120
|
return await fs.stat(path);
|
|
7009
7121
|
} catch (err) {
|
|
7010
|
-
if (
|
|
7122
|
+
if (codeOf2(err) === "ENOENT") return void 0;
|
|
7011
7123
|
throw err;
|
|
7012
7124
|
}
|
|
7013
7125
|
}
|
|
@@ -7091,8 +7203,8 @@ function removableMedium(onLost, open = (handle) => new OpfsFs(handle)) {
|
|
|
7091
7203
|
|
|
7092
7204
|
// src/mount/hostfs.ts
|
|
7093
7205
|
var HOSTFS_IO = 16 * 1024 * 1024;
|
|
7094
|
-
var
|
|
7095
|
-
var unreachable = (err) =>
|
|
7206
|
+
var codeOf3 = (err) => err?.code;
|
|
7207
|
+
var unreachable = (err) => codeOf3(err) === "ENOTCONN";
|
|
7096
7208
|
function rel(path) {
|
|
7097
7209
|
return path.replace(/^\/+/, "");
|
|
7098
7210
|
}
|
|
@@ -7227,7 +7339,7 @@ function hostfsHandlers(current) {
|
|
|
7227
7339
|
try {
|
|
7228
7340
|
return { opened: await opening(), restore: void 0 };
|
|
7229
7341
|
} catch (err) {
|
|
7230
|
-
if (!flags.write ||
|
|
7342
|
+
if (!flags.write || codeOf3(err) !== "EACCES") throw err;
|
|
7231
7343
|
const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
|
|
7232
7344
|
if (mode & 128) throw err;
|
|
7233
7345
|
const restore = { path: rel(path), mode };
|
|
@@ -8469,6 +8581,14 @@ function act2(session, req) {
|
|
|
8469
8581
|
function dirsChannel(name2) {
|
|
8470
8582
|
return name2 === false ? void 0 : `slicc-kernel-dirs:${name2}`;
|
|
8471
8583
|
}
|
|
8584
|
+
function caDb(req) {
|
|
8585
|
+
const name2 = req.ca ?? (req.metadata ? `${req.metadata}-ca` : CA_DB);
|
|
8586
|
+
const taken = [req.metadata ?? META_DB, req.media ?? `${req.metadata ?? META_DB}:media`];
|
|
8587
|
+
if (name2 !== false && taken.includes(name2)) {
|
|
8588
|
+
throw new Error(`the CA cannot share the IndexedDB database ${name2}`);
|
|
8589
|
+
}
|
|
8590
|
+
return name2;
|
|
8591
|
+
}
|
|
8472
8592
|
async function stores(deps, name2, media) {
|
|
8473
8593
|
const handles = media ?? (name2 === false ? false : `${name2}:media`);
|
|
8474
8594
|
if (handles !== false && handles === name2) {
|
|
@@ -8512,6 +8632,12 @@ function serveKernel(port, deps) {
|
|
|
8512
8632
|
void served.closed.then(() => clients.delete(served));
|
|
8513
8633
|
return port2;
|
|
8514
8634
|
}
|
|
8635
|
+
async function dial(req) {
|
|
8636
|
+
const socket = dialSocket((await ready2()).net, req.port, req.host);
|
|
8637
|
+
const { port1, port2 } = new MessageChannel();
|
|
8638
|
+
serveSocket(socket, port1);
|
|
8639
|
+
return port2;
|
|
8640
|
+
}
|
|
8515
8641
|
async function ready2() {
|
|
8516
8642
|
if (!launcher) throw new Error("the kernel is not initialized");
|
|
8517
8643
|
return launcher;
|
|
@@ -8545,7 +8671,7 @@ function serveKernel(port, deps) {
|
|
|
8545
8671
|
onMountPending: (medium) => port.postMessage({ medium }),
|
|
8546
8672
|
...req.hostfs ? { hostfs: askGrant } : {},
|
|
8547
8673
|
...mountPolicy(req.processMounts),
|
|
8548
|
-
caStore: caStore(deps.metadata ?
|
|
8674
|
+
caStore: caStore(deps.metadata ? caDb(req) : false)
|
|
8549
8675
|
});
|
|
8550
8676
|
await started.prepare();
|
|
8551
8677
|
return started;
|
|
@@ -8564,6 +8690,7 @@ function serveKernel(port, deps) {
|
|
|
8564
8690
|
if (req.op === "init") return init(req);
|
|
8565
8691
|
if (req.op === "open-terminal") return terminal(req);
|
|
8566
8692
|
if (req.op === "connect") return connect();
|
|
8693
|
+
if (req.op === "dial") return dial(req);
|
|
8567
8694
|
if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
|
|
8568
8695
|
return mountOp(req);
|
|
8569
8696
|
if (req.op === "terminal") {
|