@ai-ecoverse/slicc-kernel 1.18.1 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/driver-worker.js +254 -5
- package/dist/driver-worker.js.map +4 -4
- package/dist/index.js +280 -6
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +1900 -1768
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +254 -5
- 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 +5066 -4692
- 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 +1 -1
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,1222 +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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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);
|
|
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;
|
|
301
|
+
}
|
|
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;
|
|
382
328
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const bytes = next.value.slice();
|
|
394
|
-
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
395
|
-
}
|
|
396
|
-
} catch (e) {
|
|
397
|
-
fail(nid, e);
|
|
329
|
+
this.wake();
|
|
330
|
+
}
|
|
331
|
+
closeWrite() {
|
|
332
|
+
if (this.writers > 0) this.writers -= 1;
|
|
333
|
+
this.wake();
|
|
334
|
+
}
|
|
335
|
+
async read(max, signal) {
|
|
336
|
+
while (this.size === 0) {
|
|
337
|
+
if (this.writers === 0) return new Uint8Array(0);
|
|
338
|
+
await this.changed(signal);
|
|
398
339
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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);
|
|
340
|
+
const out = this.take(Math.min(max, this.size));
|
|
341
|
+
this.wake();
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
async peek(max, signal) {
|
|
345
|
+
while (this.size === 0) {
|
|
346
|
+
if (this.writers === 0) return new Uint8Array(0);
|
|
347
|
+
await this.changed(signal);
|
|
414
348
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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;
|
|
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;
|
|
436
356
|
}
|
|
437
|
-
|
|
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 = "";
|
|
357
|
+
return out;
|
|
442
358
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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;
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
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();
|
|
451
378
|
}
|
|
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)) };
|
|
379
|
+
return bytes.length;
|
|
479
380
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const watches = /* @__PURE__ */ new Map();
|
|
491
|
-
let net;
|
|
492
|
-
const ended = Promise.withResolvers();
|
|
493
|
-
const send = (message2, transfer = []) => {
|
|
494
|
-
if (state !== "closed") port.postMessage(message2, transfer);
|
|
495
|
-
};
|
|
496
|
-
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
497
|
-
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
498
|
-
if (state === "closed") return;
|
|
499
|
-
send({ bye });
|
|
500
|
-
state = "closed";
|
|
501
|
-
net?.close();
|
|
502
|
-
for (const unwatch of watches.values()) unwatch();
|
|
503
|
-
watches.clear();
|
|
504
|
-
for (const session of terminals.values()) session.close();
|
|
505
|
-
terminals.clear();
|
|
506
|
-
const pids = [...groups];
|
|
507
|
-
groups.clear();
|
|
508
|
-
if (kill && pids.length > 0) {
|
|
509
|
-
void host.launcher().then((launcher) => {
|
|
510
|
-
for (const pid of pids) launcher.kill(-pid, SIG.KILL);
|
|
511
|
-
});
|
|
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);
|
|
512
391
|
}
|
|
513
|
-
|
|
514
|
-
|
|
392
|
+
this.size -= n;
|
|
393
|
+
return out;
|
|
515
394
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
hello: {
|
|
530
|
-
protocol: PROTOCOL,
|
|
531
|
-
traits: transport.traits,
|
|
532
|
-
...host.lock ? { lock: host.lock } : {}
|
|
533
|
-
}
|
|
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 });
|
|
534
408
|
});
|
|
535
409
|
}
|
|
536
|
-
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
});
|
|
541
|
-
if (state === "closed") {
|
|
542
|
-
session.close();
|
|
543
|
-
} else {
|
|
544
|
-
terminals.set(req.id, session);
|
|
545
|
-
reply(req.id, { started: session.pid });
|
|
546
|
-
}
|
|
547
|
-
try {
|
|
548
|
-
return { result: await session.exited };
|
|
549
|
-
} finally {
|
|
550
|
-
terminals.delete(req.id);
|
|
551
|
-
}
|
|
410
|
+
wake() {
|
|
411
|
+
const waiters = this.waiters;
|
|
412
|
+
this.waiters = [];
|
|
413
|
+
for (const resolve of waiters) resolve();
|
|
552
414
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
onStarted: (pid) => {
|
|
562
|
-
leader = pid;
|
|
563
|
-
if (state === "closed") return;
|
|
564
|
-
groups.add(pid);
|
|
565
|
-
reply(req.id, { started: pid });
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
569
|
-
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
570
|
-
return { result: result.status };
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/kernel/fd-table.ts
|
|
418
|
+
var KernelError = class extends Error {
|
|
419
|
+
code;
|
|
420
|
+
constructor(code) {
|
|
421
|
+
super(code);
|
|
422
|
+
this.code = code;
|
|
571
423
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
590
|
-
return { result: true };
|
|
591
|
-
case "ps":
|
|
592
|
-
return {
|
|
593
|
-
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
594
|
-
};
|
|
595
|
-
case "fs":
|
|
596
|
-
if (!FS_METHODS.includes(req.method)) {
|
|
597
|
-
throw new Error(`unknown file system call ${req.method}`);
|
|
598
|
-
}
|
|
599
|
-
return fsCall(l.fs, req.method, req.args);
|
|
600
|
-
case "watch": {
|
|
601
|
-
const unwatch = l.watchers.watch(
|
|
602
|
-
req.paths,
|
|
603
|
-
{ recursive: req.recursive },
|
|
604
|
-
(change) => send({ watch: req.id, change })
|
|
605
|
-
);
|
|
606
|
-
watches.set(req.id, unwatch);
|
|
607
|
-
return { result: req.id };
|
|
608
|
-
}
|
|
609
|
-
case "unwatch":
|
|
610
|
-
watches.get(req.watch)?.();
|
|
611
|
-
watches.delete(req.watch);
|
|
612
|
-
return { result: true };
|
|
613
|
-
case "mount":
|
|
614
|
-
return { result: await l.mount(req.spec) };
|
|
615
|
-
case "umount":
|
|
616
|
-
l.umount(req.target);
|
|
617
|
-
return { result: true };
|
|
618
|
-
case "mounts":
|
|
619
|
-
return { result: l.mounts.list() };
|
|
620
|
-
case "detach":
|
|
621
|
-
return { result: true };
|
|
622
|
-
default:
|
|
623
|
-
throw new Error(`unknown op ${req.op}`);
|
|
624
|
-
}
|
|
424
|
+
};
|
|
425
|
+
function pollFile(file) {
|
|
426
|
+
return file.poll?.() ?? { readable: !!file.read, writable: !!file.write, hangup: false };
|
|
427
|
+
}
|
|
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();
|
|
625
441
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (data.net !== void 0) {
|
|
640
|
-
net?.answer(data);
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
const req = data;
|
|
644
|
-
handle(req).then(
|
|
645
|
-
({ result, transfer }) => {
|
|
646
|
-
reply(req.id, { result }, transfer);
|
|
647
|
-
if (req.op === "detach") detach(req.kill === true);
|
|
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
|
+
}
|
|
648
455
|
},
|
|
649
|
-
(
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
+
};
|
|
661
473
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
NotFoundError: "ENOENT",
|
|
674
|
-
TypeMismatchError: "ENOTDIR",
|
|
675
|
-
InvalidModificationError: "ENOTEMPTY",
|
|
676
|
-
NoModificationAllowedError: "EBUSY",
|
|
677
|
-
TypeError: "EINVAL",
|
|
678
|
-
NotAllowedError: "EACCES"
|
|
679
|
-
};
|
|
680
|
-
function translate(err, path) {
|
|
681
|
-
if (typeof err?.code === "string") return err;
|
|
682
|
-
const name2 = err?.name;
|
|
683
|
-
return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
|
|
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
|
+
});
|
|
684
485
|
}
|
|
685
|
-
function
|
|
686
|
-
|
|
687
|
-
|
|
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
|
+
});
|
|
688
495
|
}
|
|
689
|
-
function
|
|
690
|
-
return
|
|
496
|
+
function nullFile() {
|
|
497
|
+
return new OpenFile({
|
|
498
|
+
read: async () => new Uint8Array(0),
|
|
499
|
+
write: async (bytes) => bytes.length,
|
|
500
|
+
close: () => {
|
|
501
|
+
}
|
|
502
|
+
});
|
|
691
503
|
}
|
|
692
|
-
function
|
|
693
|
-
|
|
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
|
+
});
|
|
694
523
|
}
|
|
695
|
-
function
|
|
696
|
-
return
|
|
524
|
+
function heldFile(meta) {
|
|
525
|
+
return new OpenFile({ held: true, ...meta ? { heldMeta: meta } : {}, close: () => {
|
|
526
|
+
} });
|
|
697
527
|
}
|
|
698
|
-
function
|
|
699
|
-
|
|
700
|
-
|
|
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";
|
|
701
533
|
}
|
|
702
|
-
|
|
703
|
-
|
|
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");
|
|
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;
|
|
603
|
+
}
|
|
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();
|
|
610
|
+
}
|
|
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;
|
|
617
|
+
}
|
|
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())));
|
|
624
|
+
}
|
|
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);
|
|
704
646
|
}
|
|
705
|
-
function
|
|
706
|
-
|
|
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;
|
|
653
|
+
}
|
|
707
654
|
}
|
|
708
|
-
var
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
this.meta = meta;
|
|
717
|
-
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
718
|
-
if (this.channel) {
|
|
719
|
-
this.channel.onmessage = ({ data }) => {
|
|
720
|
-
for (const path of data) this.forget(path);
|
|
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"));
|
|
721
663
|
};
|
|
722
|
-
|
|
723
|
-
|
|
664
|
+
const waiter = () => {
|
|
665
|
+
signal?.removeEventListener("abort", onAbort);
|
|
666
|
+
resolve();
|
|
667
|
+
};
|
|
668
|
+
this.waiters.push(waiter);
|
|
669
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
670
|
+
});
|
|
724
671
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
this.
|
|
672
|
+
wake() {
|
|
673
|
+
const waiters = this.waiters;
|
|
674
|
+
this.waiters = [];
|
|
675
|
+
for (const resolve of waiters) resolve();
|
|
728
676
|
}
|
|
729
|
-
|
|
730
|
-
|
|
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();
|
|
731
684
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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;
|
|
740
705
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
706
|
+
attach(link2) {
|
|
707
|
+
this.link = link2;
|
|
708
|
+
this.state = "connected";
|
|
709
|
+
this.changes.wake();
|
|
710
|
+
}
|
|
711
|
+
get listening() {
|
|
712
|
+
return this.state === "listening";
|
|
713
|
+
}
|
|
714
|
+
connection() {
|
|
715
|
+
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
716
|
+
return this.link;
|
|
717
|
+
}
|
|
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));
|
|
722
|
+
}
|
|
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));
|
|
727
|
+
}
|
|
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));
|
|
732
|
+
}
|
|
733
|
+
poll() {
|
|
734
|
+
if (this.state === "listening") {
|
|
735
|
+
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
753
736
|
}
|
|
754
|
-
|
|
755
|
-
return
|
|
756
|
-
} catch (err) {
|
|
757
|
-
throw translate(err, path);
|
|
737
|
+
if (this.state !== "connected" || !this.link) {
|
|
738
|
+
return { readable: false, writable: false, hangup: true };
|
|
758
739
|
}
|
|
740
|
+
const { rx, tx } = this.link;
|
|
741
|
+
return {
|
|
742
|
+
readable: this.readShut || rx.readReady,
|
|
743
|
+
writable: this.writeShut || tx.writeReady,
|
|
744
|
+
hangup: rx.writersGone && tx.readersGone
|
|
745
|
+
};
|
|
759
746
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
return
|
|
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));
|
|
766
753
|
}
|
|
767
|
-
|
|
768
|
-
|
|
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;
|
|
769
759
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
throw err;
|
|
777
|
-
this.forget(path);
|
|
778
|
-
return op(await this.dir(path));
|
|
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 });
|
|
779
766
|
}
|
|
767
|
+
this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
|
|
768
|
+
this.state = "listening";
|
|
780
769
|
}
|
|
781
|
-
async
|
|
782
|
-
|
|
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
|
+
}
|
|
783
778
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
return
|
|
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;
|
|
789
784
|
}
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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");
|
|
805
|
+
}
|
|
806
|
+
}
|
|
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";
|
|
816
|
+
}
|
|
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();
|
|
823
|
+
}
|
|
824
|
+
if (how !== SHUT_RD && !this.writeShut) {
|
|
825
|
+
this.writeShut = true;
|
|
826
|
+
tx.closeWrite();
|
|
827
|
+
}
|
|
828
|
+
this.changes.wake();
|
|
829
|
+
}
|
|
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;
|
|
800
844
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
throw translate(err, path);
|
|
804
|
-
});
|
|
845
|
+
}
|
|
846
|
+
return this.options.get(`${level}:${name2}`) ?? 0;
|
|
805
847
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
() => true,
|
|
809
|
-
() => false
|
|
810
|
-
);
|
|
848
|
+
setOption(level, name2, value) {
|
|
849
|
+
this.options.set(`${level}:${name2}`, value);
|
|
811
850
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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();
|
|
816
856
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
try {
|
|
823
|
-
return { path: current, handle: await this.handle(current) };
|
|
824
|
-
} catch (err) {
|
|
825
|
-
if (!missing(err)) throw err;
|
|
826
|
-
const link2 = await this.linkAlong(current);
|
|
827
|
-
if (!link2) throw err;
|
|
828
|
-
if (link2.path === current && !follow) return { path: current, link: link2 };
|
|
829
|
-
const rest = current.slice(link2.path.length);
|
|
830
|
-
current = resolvePath(parentOf2(link2.path), `${link2.link}${rest}`);
|
|
831
|
-
}
|
|
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;
|
|
832
862
|
}
|
|
833
|
-
|
|
863
|
+
if (this.bound && this.local) this.net.release(this.local, this);
|
|
864
|
+
this.bound = false;
|
|
865
|
+
this.changes.wake();
|
|
834
866
|
}
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
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];
|
|
838
874
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
|
|
853
|
-
ino: meta?.ino ?? inodeOf(path)
|
|
854
|
-
};
|
|
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);
|
|
855
888
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
return {
|
|
859
|
-
isFile: false,
|
|
860
|
-
isDirectory: false,
|
|
861
|
-
isSymbolicLink: true,
|
|
862
|
-
size: new TextEncoder().encode(entry.link).length,
|
|
863
|
-
mode: S_IFLNK | 511,
|
|
864
|
-
mtime: time2,
|
|
865
|
-
atime: time2,
|
|
866
|
-
ctime: new Date(entry.ctimeMs ?? 0),
|
|
867
|
-
ino: entry.ino ?? inodeOf(entry.path)
|
|
868
|
-
};
|
|
889
|
+
socket(domain) {
|
|
890
|
+
return new KernelSocket(this, domain);
|
|
869
891
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
892
|
+
listen(addr, backlog = 128) {
|
|
893
|
+
const socket = this.socket(addr.family);
|
|
894
|
+
socket.bind(addr);
|
|
895
|
+
socket.listen(backlog);
|
|
896
|
+
return socket;
|
|
874
897
|
}
|
|
875
|
-
|
|
876
|
-
|
|
898
|
+
connect(addr) {
|
|
899
|
+
const socket = this.socket(addr.family);
|
|
900
|
+
socket.connect(addr);
|
|
901
|
+
return socket;
|
|
877
902
|
}
|
|
878
|
-
|
|
879
|
-
|
|
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");
|
|
908
|
+
}
|
|
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;
|
|
880
914
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
() => false
|
|
885
|
-
);
|
|
915
|
+
release(addr, socket) {
|
|
916
|
+
const key = keyOf(addr);
|
|
917
|
+
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
886
918
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
919
|
+
target(addr) {
|
|
920
|
+
if (addr.family === "inet" && !isLoopback(canonicalHost(addr.host))) {
|
|
921
|
+
throw new KernelError("ENETUNREACH");
|
|
922
|
+
}
|
|
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;
|
|
928
|
+
}
|
|
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;
|
|
935
|
+
}
|
|
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;
|
|
891
972
|
try {
|
|
892
|
-
|
|
973
|
+
await socket.write(data);
|
|
974
|
+
send({ ack: true });
|
|
893
975
|
} catch (err) {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
976
|
+
send({ error: codeOf(err) });
|
|
977
|
+
}
|
|
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) });
|
|
1006
|
+
}
|
|
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 } : {}
|
|
1082
|
+
});
|
|
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;
|
|
898
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);
|
|
899
1110
|
}
|
|
900
|
-
}
|
|
901
|
-
async
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
async writeTarget(path) {
|
|
1111
|
+
};
|
|
1112
|
+
const read = async (nid) => {
|
|
1113
|
+
const body = open.get(nid)?.body;
|
|
1114
|
+
if (!body) return;
|
|
905
1115
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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);
|
|
909
1126
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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);
|
|
923
1142
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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;
|
|
938
1164
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
async readdirStat(path) {
|
|
945
|
-
const [dir, children2] = await this.listing(path);
|
|
946
|
-
const base = dir === "/" ? "" : dir;
|
|
947
|
-
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
948
|
-
return Promise.all(
|
|
949
|
-
children2.map(async ([name2, child], i) => {
|
|
950
|
-
if (!isHandle(child)) return [name2, this.linkStat(child)];
|
|
951
|
-
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
952
|
-
return [name2, stat];
|
|
953
|
-
})
|
|
954
|
-
);
|
|
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 = "";
|
|
955
1170
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const directory = found.handle?.kind === "directory";
|
|
965
|
-
if (options.recursive && directory) return;
|
|
966
|
-
throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
|
|
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] };
|
|
967
1179
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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)) };
|
|
974
1207
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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();
|
|
1259
|
+
}
|
|
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;
|
|
995
1266
|
}
|
|
996
|
-
await
|
|
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
|
+
});
|
|
997
1279
|
}
|
|
998
|
-
async
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const parent = await this.locate(parentOf2(to), true);
|
|
1003
|
-
const destination = resolvePath(parent.path, baseOf(to));
|
|
1004
|
-
const sourceStat = await this.describe(source);
|
|
1005
|
-
if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
|
|
1006
|
-
const target = await this.locate(destination, false).catch((err) => {
|
|
1007
|
-
if (missing(err)) return null;
|
|
1008
|
-
throw err;
|
|
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() })
|
|
1009
1284
|
});
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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 });
|
|
1014
1291
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1292
|
+
try {
|
|
1293
|
+
return { result: await session.exited };
|
|
1294
|
+
} finally {
|
|
1295
|
+
terminals.delete(req.id);
|
|
1017
1296
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1297
|
+
}
|
|
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
|
+
}
|
|
1024
1314
|
});
|
|
1025
|
-
if (
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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 };
|
|
1318
|
+
}
|
|
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`);
|
|
1029
1322
|
}
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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}`);
|
|
1046
1373
|
}
|
|
1047
|
-
if (copied) await this.restamp(destination, timed);
|
|
1048
|
-
this.changed(source.path, destination);
|
|
1049
|
-
}
|
|
1050
|
-
async modified(path) {
|
|
1051
|
-
const handle = await this.handle(path);
|
|
1052
|
-
return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
|
|
1053
1374
|
}
|
|
1054
|
-
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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;
|
|
1060
1386
|
}
|
|
1061
|
-
return
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
+
}
|
|
1411
|
+
|
|
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"
|
|
1428
|
+
};
|
|
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?.();
|
|
1067
1472
|
}
|
|
1068
1473
|
}
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
() => false
|
|
1073
|
-
);
|
|
1074
|
-
if (moved2) return true;
|
|
1075
|
-
try {
|
|
1076
|
-
await this.copy(handle, dir, name2);
|
|
1077
|
-
} catch (err) {
|
|
1078
|
-
if (handle.kind === "directory") await dir.removeEntry(name2, { recursive: true });
|
|
1079
|
-
throw translate(err, name2);
|
|
1080
|
-
}
|
|
1081
|
-
return false;
|
|
1474
|
+
changed(...paths) {
|
|
1475
|
+
for (const path of paths) this.forget(path);
|
|
1476
|
+
this.channel?.postMessage(paths);
|
|
1082
1477
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
|
|
1086
|
-
await writable.write(await handle.getFile());
|
|
1087
|
-
await writable.close();
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
const target = await dir.getDirectoryHandle(name2, { create: true });
|
|
1091
|
-
for await (const child of handle.values()) await this.copy(child, target, child.name);
|
|
1478
|
+
resolvePath(base, path) {
|
|
1479
|
+
return resolvePath(base, path);
|
|
1092
1480
|
}
|
|
1093
|
-
async
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
()
|
|
1099
|
-
(err) => !missing(err)
|
|
1100
|
-
)) {
|
|
1101
|
-
throw fsError("EEXIST", path);
|
|
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());
|
|
1102
1487
|
}
|
|
1103
|
-
|
|
1104
|
-
await this.meta.update(at, () => ({
|
|
1105
|
-
path: at,
|
|
1106
|
-
link: target,
|
|
1107
|
-
dir: parent.path,
|
|
1108
|
-
ino: inodeOf(at),
|
|
1109
|
-
mtimeMs: now,
|
|
1110
|
-
ctimeMs: now
|
|
1111
|
-
}));
|
|
1112
|
-
}
|
|
1113
|
-
async readlink(path) {
|
|
1114
|
-
const found = await this.locate(path, false);
|
|
1115
|
-
if (found.link) return found.link.link;
|
|
1116
|
-
throw fsError("EINVAL", path);
|
|
1117
|
-
}
|
|
1118
|
-
async chmod(path, mode) {
|
|
1119
|
-
const found = await this.locate(path, true);
|
|
1120
|
-
const ctimeMs = Date.now();
|
|
1121
|
-
await this.meta.update(found.path, (entry) => ({
|
|
1122
|
-
...entry,
|
|
1123
|
-
path: found.path,
|
|
1124
|
-
mode: mode & PERM_MASK,
|
|
1125
|
-
ctimeMs
|
|
1126
|
-
}));
|
|
1127
|
-
}
|
|
1128
|
-
async utimes(path, atime, mtime) {
|
|
1129
|
-
const found = await this.locate(path, true);
|
|
1130
|
-
const handle = found.handle;
|
|
1131
|
-
const file = handle.kind === "file" ? await handle.getFile() : void 0;
|
|
1132
|
-
const ctimeMs = Date.now();
|
|
1133
|
-
await this.meta.update(found.path, (entry) => ({
|
|
1134
|
-
...entry,
|
|
1135
|
-
path: found.path,
|
|
1136
|
-
atimeMs: atime.getTime(),
|
|
1137
|
-
mtimeMs: mtime.getTime(),
|
|
1138
|
-
mtimeFor: file?.lastModified ?? 0,
|
|
1139
|
-
ctimeMs
|
|
1140
|
-
}));
|
|
1488
|
+
return dir;
|
|
1141
1489
|
}
|
|
1142
|
-
async
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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);
|
|
1150
1507
|
}
|
|
1151
|
-
return removed;
|
|
1152
|
-
}
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
// src/kernel/pipe.ts
|
|
1156
|
-
var PIPE_CAPACITY = 64 * 1024;
|
|
1157
|
-
var PipeError = class extends Error {
|
|
1158
|
-
code;
|
|
1159
|
-
constructor(code) {
|
|
1160
|
-
super(code);
|
|
1161
|
-
this.code = code;
|
|
1162
|
-
}
|
|
1163
|
-
};
|
|
1164
|
-
var KernelPipe = class {
|
|
1165
|
-
chunks = [];
|
|
1166
|
-
size = 0;
|
|
1167
|
-
readers = 0;
|
|
1168
|
-
writers = 0;
|
|
1169
|
-
waiters = [];
|
|
1170
|
-
capacity;
|
|
1171
|
-
constructor(capacity = PIPE_CAPACITY) {
|
|
1172
|
-
this.capacity = capacity;
|
|
1173
|
-
}
|
|
1174
|
-
get buffered() {
|
|
1175
|
-
return this.size;
|
|
1176
|
-
}
|
|
1177
|
-
get readReady() {
|
|
1178
|
-
return this.size > 0 || this.writers === 0;
|
|
1179
|
-
}
|
|
1180
|
-
get writeReady() {
|
|
1181
|
-
return this.size < this.capacity || this.readers === 0;
|
|
1182
1508
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
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;
|
|
1185
1515
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1516
|
+
forget(path) {
|
|
1517
|
+
for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
|
|
1188
1518
|
}
|
|
1189
|
-
|
|
1190
|
-
this.
|
|
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));
|
|
1528
|
+
}
|
|
1191
1529
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1530
|
+
async child(parent, name2, kind) {
|
|
1531
|
+
return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
|
|
1194
1532
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
}
|
|
1201
|
-
this.wake();
|
|
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];
|
|
1202
1538
|
}
|
|
1203
|
-
|
|
1204
|
-
if (
|
|
1205
|
-
this.
|
|
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;
|
|
1549
|
+
}
|
|
1550
|
+
return this.child(parent, name2, second);
|
|
1551
|
+
}).catch((err) => {
|
|
1552
|
+
throw translate(err, path);
|
|
1553
|
+
});
|
|
1206
1554
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
const out = this.take(Math.min(max, this.size));
|
|
1213
|
-
this.wake();
|
|
1214
|
-
return out;
|
|
1555
|
+
real(path) {
|
|
1556
|
+
return this.handle(path).then(
|
|
1557
|
+
() => true,
|
|
1558
|
+
() => false
|
|
1559
|
+
);
|
|
1215
1560
|
}
|
|
1216
|
-
async
|
|
1217
|
-
|
|
1218
|
-
if (
|
|
1219
|
-
await this.
|
|
1220
|
-
}
|
|
1221
|
-
const out = new Uint8Array(Math.min(max, this.size));
|
|
1222
|
-
let filled = 0;
|
|
1223
|
-
for (const chunk2 of this.chunks) {
|
|
1224
|
-
if (filled === out.length) break;
|
|
1225
|
-
const count = Math.min(chunk2.length, out.length - filled);
|
|
1226
|
-
out.set(chunk2.subarray(0, count), filled);
|
|
1227
|
-
filled += count;
|
|
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;
|
|
1228
1565
|
}
|
|
1229
|
-
return
|
|
1566
|
+
return void 0;
|
|
1230
1567
|
}
|
|
1231
|
-
async
|
|
1232
|
-
let
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1245
|
-
const n = Math.min(room, bytes.length - offset);
|
|
1246
|
-
this.chunks.push(bytes.slice(offset, offset + n));
|
|
1247
|
-
this.size += n;
|
|
1248
|
-
offset += n;
|
|
1249
|
-
this.wake();
|
|
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
|
+
}
|
|
1250
1581
|
}
|
|
1251
|
-
|
|
1582
|
+
throw fsError("ELOOP", path);
|
|
1252
1583
|
}
|
|
1253
|
-
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
while (filled < n) {
|
|
1257
|
-
const head = this.chunks[0];
|
|
1258
|
-
const count = Math.min(head.length, n - filled);
|
|
1259
|
-
out.set(head.subarray(0, count), filled);
|
|
1260
|
-
filled += count;
|
|
1261
|
-
if (count === head.length) this.chunks.shift();
|
|
1262
|
-
else this.chunks[0] = head.subarray(count);
|
|
1263
|
-
}
|
|
1264
|
-
this.size -= n;
|
|
1265
|
-
return out;
|
|
1584
|
+
mode(path, directory, meta) {
|
|
1585
|
+
const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
|
|
1586
|
+
return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
|
|
1266
1587
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
+
};
|
|
1281
1604
|
}
|
|
1282
|
-
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
+
};
|
|
1286
1618
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
code;
|
|
1292
|
-
constructor(code) {
|
|
1293
|
-
super(code);
|
|
1294
|
-
this.code = code;
|
|
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);
|
|
1295
1623
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
return file.poll?.() ?? { readable: !!file.read, writable: !!file.write, hangup: false };
|
|
1299
|
-
}
|
|
1300
|
-
var OpenFile = class {
|
|
1301
|
-
refs = 1;
|
|
1302
|
-
file;
|
|
1303
|
-
constructor(file) {
|
|
1304
|
-
this.file = file;
|
|
1624
|
+
async stat(path) {
|
|
1625
|
+
return this.describe(await this.locate(path, true));
|
|
1305
1626
|
}
|
|
1306
|
-
|
|
1307
|
-
this.
|
|
1308
|
-
return this;
|
|
1627
|
+
async lstat(path) {
|
|
1628
|
+
return this.describe(await this.locate(path, false));
|
|
1309
1629
|
}
|
|
1310
|
-
|
|
1311
|
-
this.
|
|
1312
|
-
|
|
1630
|
+
async exists(path) {
|
|
1631
|
+
return this.locate(path, true).then(
|
|
1632
|
+
() => true,
|
|
1633
|
+
() => false
|
|
1634
|
+
);
|
|
1313
1635
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
return await pipe.read(max, signal);
|
|
1324
|
-
} catch (e) {
|
|
1325
|
-
throw new KernelError(e.code);
|
|
1326
|
-
}
|
|
1327
|
-
},
|
|
1328
|
-
close: () => pipe.closeRead(),
|
|
1329
|
-
poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
|
|
1330
|
-
changed: (signal) => pipe.changed(signal)
|
|
1331
|
-
}),
|
|
1332
|
-
write: new OpenFile({
|
|
1333
|
-
write: async (bytes, signal) => {
|
|
1334
|
-
try {
|
|
1335
|
-
return await pipe.write(bytes, signal);
|
|
1336
|
-
} catch (e) {
|
|
1337
|
-
throw new KernelError(e.code);
|
|
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);
|
|
1338
1645
|
}
|
|
1339
|
-
|
|
1340
|
-
close: () => pipe.closeWrite(),
|
|
1341
|
-
poll: () => ({ readable: false, writable: pipe.writeReady, hangup: pipe.readersGone }),
|
|
1342
|
-
changed: (signal) => pipe.changed(signal)
|
|
1343
|
-
})
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
function bytesSource(data) {
|
|
1347
|
-
let offset = 0;
|
|
1348
|
-
return new OpenFile({
|
|
1349
|
-
read: async (max) => {
|
|
1350
|
-
const out = data.subarray(offset, offset + max);
|
|
1351
|
-
offset += out.length;
|
|
1352
|
-
return out;
|
|
1353
|
-
},
|
|
1354
|
-
close: () => {
|
|
1355
|
-
}
|
|
1356
|
-
});
|
|
1357
|
-
}
|
|
1358
|
-
function sinkFile(onData) {
|
|
1359
|
-
return new OpenFile({
|
|
1360
|
-
write: async (bytes) => {
|
|
1361
|
-
onData(bytes.slice());
|
|
1362
|
-
return bytes.length;
|
|
1363
|
-
},
|
|
1364
|
-
close: () => {
|
|
1365
|
-
}
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
function nullFile() {
|
|
1369
|
-
return new OpenFile({
|
|
1370
|
-
read: async () => new Uint8Array(0),
|
|
1371
|
-
write: async (bytes) => bytes.length,
|
|
1372
|
-
close: () => {
|
|
1373
|
-
}
|
|
1374
|
-
});
|
|
1375
|
-
}
|
|
1376
|
-
function deviceFile(device, access) {
|
|
1377
|
-
const read = async (max) => {
|
|
1378
|
-
if (device === "null") return new Uint8Array(0);
|
|
1379
|
-
const out = new Uint8Array(max);
|
|
1380
|
-
if (device === "urandom") {
|
|
1381
|
-
for (let at = 0; at < max; at += 65536) {
|
|
1382
|
-
crypto.getRandomValues(out.subarray(at, Math.min(max, at + 65536)));
|
|
1646
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 10));
|
|
1383
1647
|
}
|
|
1384
1648
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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;
|
|
1393
1658
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
return new OpenFile({ held: true, ...meta ? { heldMeta: meta } : {}, close: () => {
|
|
1398
|
-
} });
|
|
1399
|
-
}
|
|
1400
|
-
function kernelFdKind(file) {
|
|
1401
|
-
if (file.held) return "held";
|
|
1402
|
-
if (file.heldMeta && "device" in file.heldMeta) return "device";
|
|
1403
|
-
if (file.tty) return "tty";
|
|
1404
|
-
return file.seek ? "file" : "stream";
|
|
1405
|
-
}
|
|
1406
|
-
var FdTable = class _FdTable {
|
|
1407
|
-
static MAX_FDS = 1024;
|
|
1408
|
-
fds = /* @__PURE__ */ new Map();
|
|
1409
|
-
cloexec = /* @__PURE__ */ new Set();
|
|
1410
|
-
status = /* @__PURE__ */ new Map();
|
|
1411
|
-
get(fd) {
|
|
1412
|
-
const file = this.fds.get(fd);
|
|
1413
|
-
if (!file) throw new KernelError("EBADF");
|
|
1414
|
-
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];
|
|
1415
1662
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
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);
|
|
1420
1672
|
}
|
|
1421
|
-
|
|
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);
|
|
1687
|
+
}
|
|
1688
|
+
return [found.path, [...children2].sort(([a], [b]) => a < b ? -1 : 1)];
|
|
1689
|
+
}
|
|
1690
|
+
async readdir(path) {
|
|
1691
|
+
return (await this.listing(path))[1].map(([name2]) => name2);
|
|
1422
1692
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
+
);
|
|
1425
1704
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
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 });
|
|
1428
1723
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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]);
|
|
1432
1746
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
this.
|
|
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);
|
|
1436
1798
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1799
|
+
async modified(path) {
|
|
1800
|
+
const handle = await this.handle(path);
|
|
1801
|
+
return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
|
|
1439
1802
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
this.
|
|
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;
|
|
1443
1811
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
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 });
|
|
1816
|
+
}
|
|
1446
1817
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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);
|
|
1453
1829
|
}
|
|
1454
|
-
|
|
1455
|
-
throw new KernelError("EMFILE");
|
|
1830
|
+
return false;
|
|
1456
1831
|
}
|
|
1457
|
-
|
|
1458
|
-
if (
|
|
1459
|
-
|
|
1460
|
-
|
|
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;
|
|
1461
1838
|
}
|
|
1462
|
-
const
|
|
1463
|
-
this.
|
|
1464
|
-
this.cloexec.delete(fd);
|
|
1465
|
-
this.status.delete(fd);
|
|
1466
|
-
void Promise.resolve(previous?.release()).catch(() => void 0);
|
|
1839
|
+
const target = await dir.getDirectoryHandle(name2, { create: true });
|
|
1840
|
+
for await (const child of handle.values()) await this.copy(child, target, child.name);
|
|
1467
1841
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
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
|
+
}));
|
|
1470
1861
|
}
|
|
1471
|
-
|
|
1472
|
-
const
|
|
1473
|
-
if (
|
|
1474
|
-
|
|
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);
|
|
1475
1866
|
}
|
|
1476
|
-
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1479
|
-
this.
|
|
1480
|
-
|
|
1481
|
-
|
|
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
|
+
}));
|
|
1482
1876
|
}
|
|
1483
|
-
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
+
}));
|
|
1489
1890
|
}
|
|
1490
|
-
async
|
|
1491
|
-
|
|
1492
|
-
this.
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
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;
|
|
1496
1901
|
}
|
|
1497
1902
|
};
|
|
1498
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
|
+
|
|
1499
1915
|
// src/kernel/net/http1.ts
|
|
1500
1916
|
var HttpError = class extends Error {
|
|
1501
1917
|
status;
|
|
@@ -1780,7 +2196,7 @@ var Budget = class {
|
|
|
1780
2196
|
for (const wake of waiters) wake();
|
|
1781
2197
|
}
|
|
1782
2198
|
};
|
|
1783
|
-
function
|
|
2199
|
+
function canonicalHost2(host) {
|
|
1784
2200
|
const bare = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
1785
2201
|
try {
|
|
1786
2202
|
return new URL(`http://${bare}/`).hostname;
|
|
@@ -1810,7 +2226,7 @@ function localV6(address) {
|
|
|
1810
2226
|
return g[5] === 0 && g[6] === 0;
|
|
1811
2227
|
}
|
|
1812
2228
|
function isLoopbackHost(hostname) {
|
|
1813
|
-
const canonical =
|
|
2229
|
+
const canonical = canonicalHost2(hostname);
|
|
1814
2230
|
if (canonical === void 0) return false;
|
|
1815
2231
|
const host = canonical.replace(/\.+$/, "");
|
|
1816
2232
|
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
@@ -1883,7 +2299,7 @@ function tunnelTarget(req) {
|
|
|
1883
2299
|
if (!match || port < 1 || port > 65535) {
|
|
1884
2300
|
throw new HttpError(400, "CONNECT needs host:port");
|
|
1885
2301
|
}
|
|
1886
|
-
const host =
|
|
2302
|
+
const host = canonicalHost2(match[1]);
|
|
1887
2303
|
if (host === void 0) throw new HttpError(400, "CONNECT needs host:port");
|
|
1888
2304
|
if (isLoopbackHost(host)) {
|
|
1889
2305
|
throw new HttpError(
|
|
@@ -2069,7 +2485,7 @@ var RealmProxy = class {
|
|
|
2069
2485
|
async upstream(req, url, body, signal) {
|
|
2070
2486
|
try {
|
|
2071
2487
|
const fetching = this.options.transport.fetch({
|
|
2072
|
-
url,
|
|
2488
|
+
url: toHostLoopback(url),
|
|
2073
2489
|
method: req.method.toUpperCase(),
|
|
2074
2490
|
headers: forwardRequestHeaders(req.headers),
|
|
2075
2491
|
body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
|
|
@@ -2749,7 +3165,10 @@ function networkEnv() {
|
|
|
2749
3165
|
NO_PROXY,
|
|
2750
3166
|
SSL_CERT_FILE: CA_PATH,
|
|
2751
3167
|
CURL_CA_BUNDLE: CA_PATH,
|
|
2752
|
-
GIT_SSL_CAINFO: CA_PATH
|
|
3168
|
+
GIT_SSL_CAINFO: CA_PATH,
|
|
3169
|
+
SLICC_HOST_LOOPBACK: HOST_LOOPBACK,
|
|
3170
|
+
SLICC_PAGE_LOOPBACK: PAGE_LOOPBACK,
|
|
3171
|
+
SLICC_PAGE_LOOPBACK_URL: PAGE_LOOPBACK_URL
|
|
2753
3172
|
};
|
|
2754
3173
|
}
|
|
2755
3174
|
function missingTransport() {
|
|
@@ -4724,444 +5143,148 @@ var KernelTty = class {
|
|
|
4724
5143
|
winsize() {
|
|
4725
5144
|
return [this.rows, this.cols];
|
|
4726
5145
|
}
|
|
4727
|
-
file() {
|
|
4728
|
-
return new OpenFile({
|
|
4729
|
-
read: (max, signal) => this.read(max, signal),
|
|
4730
|
-
write: async (bytes) => {
|
|
4731
|
-
if (this.hungUp) throw new KernelError("EIO");
|
|
4732
|
-
this.output(bytes);
|
|
4733
|
-
return bytes.length;
|
|
4734
|
-
},
|
|
4735
|
-
poll: () => ({
|
|
4736
|
-
readable: this.readable.length > 0 || this.hungUp,
|
|
4737
|
-
writable: true,
|
|
4738
|
-
hangup: this.hungUp
|
|
4739
|
-
}),
|
|
4740
|
-
changed: (signal) => this.changed(signal),
|
|
4741
|
-
close: () => {
|
|
4742
|
-
},
|
|
4743
|
-
tty: this
|
|
4744
|
-
});
|
|
4745
|
-
}
|
|
4746
|
-
get canonical() {
|
|
4747
|
-
return (this.termios.c_lflag & ICANON) !== 0;
|
|
4748
|
-
}
|
|
4749
|
-
cc(slot) {
|
|
4750
|
-
return this.termios.c_cc[slot] ?? 0;
|
|
4751
|
-
}
|
|
4752
|
-
key(input) {
|
|
4753
|
-
const { c_iflag, c_lflag } = this.termios;
|
|
4754
|
-
const byte = input === 13 && c_iflag & ICRNL ? 10 : input;
|
|
4755
|
-
if (c_lflag & ISIG && this.signalKey(byte)) return;
|
|
4756
|
-
if (!this.canonical) {
|
|
4757
|
-
this.readable.push(Uint8Array.of(byte));
|
|
4758
|
-
if (c_lflag & ECHO) this.echo(byte);
|
|
4759
|
-
return;
|
|
4760
|
-
}
|
|
4761
|
-
this.edit(byte);
|
|
4762
|
-
}
|
|
4763
|
-
signalKey(byte) {
|
|
4764
|
-
const sig = byte === this.cc(VINTR) ? SIG.INT : byte === this.cc(VQUIT) ? SIG.QUIT : byte === this.cc(VSUSP) ? SIG.TSTP : 0;
|
|
4765
|
-
if (!sig || byte === 0) return false;
|
|
4766
|
-
if (this.termios.c_lflag & ECHO) this.echo(byte);
|
|
4767
|
-
if (!(this.termios.c_lflag & NOFLSH)) {
|
|
4768
|
-
this.line = [];
|
|
4769
|
-
this.readable = [];
|
|
4770
|
-
}
|
|
4771
|
-
this.signal(sig);
|
|
4772
|
-
return true;
|
|
4773
|
-
}
|
|
4774
|
-
edit(byte) {
|
|
4775
|
-
const echo = (this.termios.c_lflag & ECHO) !== 0;
|
|
4776
|
-
if (byte === this.cc(VERASE) || byte === 8) {
|
|
4777
|
-
this.rubOut(this.lastCharLength());
|
|
4778
|
-
} else if (byte === this.cc(VKILL)) {
|
|
4779
|
-
this.rubOut(this.line.length);
|
|
4780
|
-
} else if (byte === this.cc(VWERASE)) {
|
|
4781
|
-
this.rubOut(this.lastWordLength());
|
|
4782
|
-
} else if (byte === this.cc(VEOF)) {
|
|
4783
|
-
this.readable.push(this.line.length > 0 ? Uint8Array.from(this.line) : null);
|
|
4784
|
-
this.line = [];
|
|
4785
|
-
} else if (byte === 10) {
|
|
4786
|
-
this.line.push(byte);
|
|
4787
|
-
if (echo) this.output(encoder2.encode("\n"));
|
|
4788
|
-
this.readable.push(Uint8Array.from(this.line));
|
|
4789
|
-
this.line = [];
|
|
4790
|
-
} else {
|
|
4791
|
-
this.line.push(byte);
|
|
4792
|
-
if (echo) this.echo(byte);
|
|
4793
|
-
}
|
|
4794
|
-
}
|
|
4795
|
-
lastWordLength() {
|
|
4796
|
-
let end = this.line.length;
|
|
4797
|
-
while (end > 0 && this.line[end - 1] === 32) end--;
|
|
4798
|
-
while (end > 0 && this.line[end - 1] !== 32) end--;
|
|
4799
|
-
return this.line.length - end;
|
|
4800
|
-
}
|
|
4801
|
-
lastCharLength() {
|
|
4802
|
-
const { line } = this;
|
|
4803
|
-
let n = Math.min(1, line.length);
|
|
4804
|
-
if (this.termios.c_iflag & IUTF8) {
|
|
4805
|
-
while (n < line.length && isContinuation(line[line.length - n] ?? 0)) n++;
|
|
4806
|
-
}
|
|
4807
|
-
return n;
|
|
4808
|
-
}
|
|
4809
|
-
rubOut(n) {
|
|
4810
|
-
const dropped2 = this.line.splice(this.line.length - n, n);
|
|
4811
|
-
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
4812
|
-
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
4813
|
-
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
4814
|
-
this.output(encoder2.encode("\b \b".repeat(cells)), false);
|
|
4815
|
-
}
|
|
4816
|
-
}
|
|
4817
|
-
echo(byte) {
|
|
4818
|
-
if (byte < 32 && byte !== 10 && byte !== 9 && this.termios.c_lflag & ECHOCTL) {
|
|
4819
|
-
this.output(Uint8Array.of(94, byte + 64), false);
|
|
4820
|
-
return;
|
|
4821
|
-
}
|
|
4822
|
-
this.output(Uint8Array.of(byte));
|
|
4823
|
-
}
|
|
4824
|
-
output(bytes, translate2 = true) {
|
|
4825
|
-
const { c_oflag } = this.termios;
|
|
4826
|
-
if (!translate2 || !(c_oflag & OPOST && c_oflag & ONLCR) || !bytes.includes(10)) {
|
|
4827
|
-
this.screen.write(bytes);
|
|
4828
|
-
return;
|
|
4829
|
-
}
|
|
4830
|
-
const out = [];
|
|
4831
|
-
for (const byte of bytes) {
|
|
4832
|
-
if (byte === 10) out.push(13);
|
|
4833
|
-
out.push(byte);
|
|
4834
|
-
}
|
|
4835
|
-
this.screen.write(Uint8Array.from(out));
|
|
4836
|
-
}
|
|
4837
|
-
async read(max, signal) {
|
|
4838
|
-
while (this.readable.length === 0) {
|
|
4839
|
-
if (this.hungUp) return new Uint8Array(0);
|
|
4840
|
-
await this.changed(signal);
|
|
4841
|
-
}
|
|
4842
|
-
const head = this.readable[0];
|
|
4843
|
-
if (head === null) {
|
|
4844
|
-
this.readable.shift();
|
|
4845
|
-
return new Uint8Array(0);
|
|
4846
|
-
}
|
|
4847
|
-
if (head === void 0) return new Uint8Array(0);
|
|
4848
|
-
const chunks = [];
|
|
4849
|
-
let n = 0;
|
|
4850
|
-
while (this.readable.length > 0 && n < max) {
|
|
4851
|
-
const next = this.readable[0];
|
|
4852
|
-
if (next === null || next === void 0) break;
|
|
4853
|
-
const take = next.subarray(0, max - n);
|
|
4854
|
-
chunks.push(take);
|
|
4855
|
-
n += take.length;
|
|
4856
|
-
if (take.length < next.length) this.readable[0] = next.subarray(take.length);
|
|
4857
|
-
else this.readable.shift();
|
|
4858
|
-
if (this.canonical) break;
|
|
4859
|
-
}
|
|
4860
|
-
const out = new Uint8Array(n);
|
|
4861
|
-
let at = 0;
|
|
4862
|
-
for (const chunk2 of chunks) {
|
|
4863
|
-
out.set(chunk2, at);
|
|
4864
|
-
at += chunk2.length;
|
|
4865
|
-
}
|
|
4866
|
-
return out;
|
|
4867
|
-
}
|
|
4868
|
-
changed(signal) {
|
|
4869
|
-
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
4870
|
-
return new Promise((resolve, reject) => {
|
|
4871
|
-
const onAbort = () => {
|
|
4872
|
-
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
4873
|
-
reject(new KernelError("EINTR"));
|
|
4874
|
-
};
|
|
4875
|
-
const waiter = () => {
|
|
4876
|
-
signal?.removeEventListener("abort", onAbort);
|
|
4877
|
-
resolve();
|
|
4878
|
-
};
|
|
4879
|
-
this.waiters.push(waiter);
|
|
4880
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4881
|
-
});
|
|
4882
|
-
}
|
|
4883
|
-
wake() {
|
|
4884
|
-
const waiters = this.waiters;
|
|
4885
|
-
this.waiters = [];
|
|
4886
|
-
for (const resolve of waiters) resolve();
|
|
4887
|
-
}
|
|
4888
|
-
};
|
|
4889
|
-
|
|
4890
|
-
// src/kernel/pty.ts
|
|
4891
|
-
var PtyPair = class {
|
|
4892
|
-
slave;
|
|
4893
|
-
locked = true;
|
|
4894
|
-
packet = false;
|
|
4895
|
-
queue = [];
|
|
4896
|
-
waiters = [];
|
|
4897
|
-
masterOpen = true;
|
|
4898
|
-
slaveRefs = 0;
|
|
4899
|
-
slaveSeen = false;
|
|
4900
|
-
index;
|
|
4901
|
-
onFree;
|
|
4902
|
-
constructor(index, signal, onFree) {
|
|
4903
|
-
this.index = index;
|
|
4904
|
-
this.onFree = onFree;
|
|
4905
|
-
this.slave = new KernelTty(
|
|
4906
|
-
{ write: (bytes) => this.fromSlave(bytes) },
|
|
4907
|
-
(sig) => signal(this.slave, sig)
|
|
4908
|
-
);
|
|
4909
|
-
this.slave.name = `/dev/pts/${index}`;
|
|
4910
|
-
}
|
|
4911
|
-
master() {
|
|
4912
|
-
return new OpenFile({
|
|
4913
|
-
read: (max, signal) => this.readMaster(max, signal),
|
|
4914
|
-
write: async (bytes) => {
|
|
4915
|
-
this.slave.receive(bytes);
|
|
4916
|
-
return bytes.length;
|
|
4917
|
-
},
|
|
4918
|
-
poll: () => ({
|
|
4919
|
-
readable: this.queue.length > 0 || this.slaveGone,
|
|
4920
|
-
writable: true,
|
|
4921
|
-
hangup: this.slaveGone
|
|
4922
|
-
}),
|
|
4923
|
-
changed: (signal) => this.changed(signal),
|
|
4924
|
-
close: () => this.closeMaster(),
|
|
4925
|
-
pty: this
|
|
4926
|
-
});
|
|
4927
|
-
}
|
|
4928
|
-
openSlave() {
|
|
4929
|
-
if (!this.masterOpen || this.locked) throw new KernelError("EIO");
|
|
4930
|
-
const file = this.slave.file().file;
|
|
4931
|
-
this.slaveRefs += 1;
|
|
4932
|
-
this.slaveSeen = true;
|
|
4933
|
-
return new OpenFile({ ...file, close: () => this.closeSlave() });
|
|
4934
|
-
}
|
|
4935
|
-
get slaveGone() {
|
|
4936
|
-
return this.slaveSeen && this.slaveRefs === 0;
|
|
4937
|
-
}
|
|
4938
|
-
fromSlave(bytes) {
|
|
4939
|
-
if (!this.masterOpen) return;
|
|
4940
|
-
this.queue.push(bytes.slice());
|
|
4941
|
-
this.wake();
|
|
4942
|
-
}
|
|
4943
|
-
async readMaster(max, signal) {
|
|
4944
|
-
while (this.queue.length === 0) {
|
|
4945
|
-
if (this.slaveGone) throw new KernelError("EIO");
|
|
4946
|
-
await this.changed(signal);
|
|
4947
|
-
}
|
|
4948
|
-
const status = this.packet ? 1 : 0;
|
|
4949
|
-
const chunks = [];
|
|
4950
|
-
let n = 0;
|
|
4951
|
-
while (this.queue.length > 0 && n < max - status) {
|
|
4952
|
-
const next = this.queue[0];
|
|
4953
|
-
const take = next.subarray(0, max - status - n);
|
|
4954
|
-
chunks.push(take);
|
|
4955
|
-
n += take.length;
|
|
4956
|
-
if (take.length < next.length) this.queue[0] = next.subarray(take.length);
|
|
4957
|
-
else this.queue.shift();
|
|
4958
|
-
}
|
|
4959
|
-
const out = new Uint8Array(status + n);
|
|
4960
|
-
let at = status;
|
|
4961
|
-
for (const chunk2 of chunks) {
|
|
4962
|
-
out.set(chunk2, at);
|
|
4963
|
-
at += chunk2.length;
|
|
4964
|
-
}
|
|
4965
|
-
return out;
|
|
4966
|
-
}
|
|
4967
|
-
closeMaster() {
|
|
4968
|
-
this.masterOpen = false;
|
|
4969
|
-
this.queue = [];
|
|
4970
|
-
this.slave.hangup();
|
|
4971
|
-
this.slave.signalHangup();
|
|
4972
|
-
this.freeIfUnused();
|
|
4973
|
-
}
|
|
4974
|
-
closeSlave() {
|
|
4975
|
-
this.slaveRefs -= 1;
|
|
4976
|
-
this.wake();
|
|
4977
|
-
this.freeIfUnused();
|
|
4978
|
-
}
|
|
4979
|
-
freeIfUnused() {
|
|
4980
|
-
if (!this.masterOpen && this.slaveRefs === 0) this.onFree(this);
|
|
4981
|
-
}
|
|
4982
|
-
changed(signal) {
|
|
4983
|
-
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
4984
|
-
return new Promise((resolve, reject) => {
|
|
4985
|
-
const onAbort = () => {
|
|
4986
|
-
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
4987
|
-
reject(new KernelError("EINTR"));
|
|
4988
|
-
};
|
|
4989
|
-
const waiter = () => {
|
|
4990
|
-
signal?.removeEventListener("abort", onAbort);
|
|
4991
|
-
resolve();
|
|
4992
|
-
};
|
|
4993
|
-
this.waiters.push(waiter);
|
|
4994
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5146
|
+
file() {
|
|
5147
|
+
return new OpenFile({
|
|
5148
|
+
read: (max, signal) => this.read(max, signal),
|
|
5149
|
+
write: async (bytes) => {
|
|
5150
|
+
if (this.hungUp) throw new KernelError("EIO");
|
|
5151
|
+
this.output(bytes);
|
|
5152
|
+
return bytes.length;
|
|
5153
|
+
},
|
|
5154
|
+
poll: () => ({
|
|
5155
|
+
readable: this.readable.length > 0 || this.hungUp,
|
|
5156
|
+
writable: true,
|
|
5157
|
+
hangup: this.hungUp
|
|
5158
|
+
}),
|
|
5159
|
+
changed: (signal) => this.changed(signal),
|
|
5160
|
+
close: () => {
|
|
5161
|
+
},
|
|
5162
|
+
tty: this
|
|
4995
5163
|
});
|
|
4996
5164
|
}
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
this.waiters = [];
|
|
5000
|
-
for (const resolve of waiters) resolve();
|
|
5165
|
+
get canonical() {
|
|
5166
|
+
return (this.termios.c_lflag & ICANON) !== 0;
|
|
5001
5167
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
pairs = /* @__PURE__ */ new Map();
|
|
5005
|
-
signal;
|
|
5006
|
-
constructor(signal) {
|
|
5007
|
-
this.signal = signal;
|
|
5168
|
+
cc(slot) {
|
|
5169
|
+
return this.termios.c_cc[slot] ?? 0;
|
|
5008
5170
|
}
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
this.
|
|
5014
|
-
|
|
5171
|
+
key(input) {
|
|
5172
|
+
const { c_iflag, c_lflag } = this.termios;
|
|
5173
|
+
const byte = input === 13 && c_iflag & ICRNL ? 10 : input;
|
|
5174
|
+
if (c_lflag & ISIG && this.signalKey(byte)) return;
|
|
5175
|
+
if (!this.canonical) {
|
|
5176
|
+
this.readable.push(Uint8Array.of(byte));
|
|
5177
|
+
if (c_lflag & ECHO) this.echo(byte);
|
|
5178
|
+
return;
|
|
5179
|
+
}
|
|
5180
|
+
this.edit(byte);
|
|
5015
5181
|
}
|
|
5016
|
-
|
|
5017
|
-
const
|
|
5018
|
-
if (!
|
|
5019
|
-
|
|
5182
|
+
signalKey(byte) {
|
|
5183
|
+
const sig = byte === this.cc(VINTR) ? SIG.INT : byte === this.cc(VQUIT) ? SIG.QUIT : byte === this.cc(VSUSP) ? SIG.TSTP : 0;
|
|
5184
|
+
if (!sig || byte === 0) return false;
|
|
5185
|
+
if (this.termios.c_lflag & ECHO) this.echo(byte);
|
|
5186
|
+
if (!(this.termios.c_lflag & NOFLSH)) {
|
|
5187
|
+
this.line = [];
|
|
5188
|
+
this.readable = [];
|
|
5189
|
+
}
|
|
5190
|
+
this.signal(sig);
|
|
5191
|
+
return true;
|
|
5020
5192
|
}
|
|
5021
|
-
|
|
5022
|
-
|
|
5193
|
+
edit(byte) {
|
|
5194
|
+
const echo = (this.termios.c_lflag & ECHO) !== 0;
|
|
5195
|
+
if (byte === this.cc(VERASE) || byte === 8) {
|
|
5196
|
+
this.rubOut(this.lastCharLength());
|
|
5197
|
+
} else if (byte === this.cc(VKILL)) {
|
|
5198
|
+
this.rubOut(this.line.length);
|
|
5199
|
+
} else if (byte === this.cc(VWERASE)) {
|
|
5200
|
+
this.rubOut(this.lastWordLength());
|
|
5201
|
+
} else if (byte === this.cc(VEOF)) {
|
|
5202
|
+
this.readable.push(this.line.length > 0 ? Uint8Array.from(this.line) : null);
|
|
5203
|
+
this.line = [];
|
|
5204
|
+
} else if (byte === 10) {
|
|
5205
|
+
this.line.push(byte);
|
|
5206
|
+
if (echo) this.output(encoder2.encode("\n"));
|
|
5207
|
+
this.readable.push(Uint8Array.from(this.line));
|
|
5208
|
+
this.line = [];
|
|
5209
|
+
} else {
|
|
5210
|
+
this.line.push(byte);
|
|
5211
|
+
if (echo) this.echo(byte);
|
|
5212
|
+
}
|
|
5023
5213
|
}
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
]
|
|
5035
|
-
function masterOf(ctx, fd) {
|
|
5036
|
-
const pair = ctx.fds.get(fd).file.pty;
|
|
5037
|
-
if (!pair) throw new KernelError("ENOTTY");
|
|
5038
|
-
return pair;
|
|
5039
|
-
}
|
|
5040
|
-
function terminalOf(ctx, fd) {
|
|
5041
|
-
const file = ctx.fds.get(fd).file;
|
|
5042
|
-
const tty = file.tty ?? file.pty?.slave;
|
|
5043
|
-
if (!tty) throw new KernelError("ENOTTY");
|
|
5044
|
-
return tty;
|
|
5045
|
-
}
|
|
5046
|
-
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5047
|
-
var done3 = { ok: true, kind: "void" };
|
|
5048
|
-
function ptySyscall(req, ctx) {
|
|
5049
|
-
switch (req.op) {
|
|
5050
|
-
case "pty-open": {
|
|
5051
|
-
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5052
|
-
return json3(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5214
|
+
lastWordLength() {
|
|
5215
|
+
let end = this.line.length;
|
|
5216
|
+
while (end > 0 && this.line[end - 1] === 32) end--;
|
|
5217
|
+
while (end > 0 && this.line[end - 1] !== 32) end--;
|
|
5218
|
+
return this.line.length - end;
|
|
5219
|
+
}
|
|
5220
|
+
lastCharLength() {
|
|
5221
|
+
const { line } = this;
|
|
5222
|
+
let n = Math.min(1, line.length);
|
|
5223
|
+
if (this.termios.c_iflag & IUTF8) {
|
|
5224
|
+
while (n < line.length && isContinuation(line[line.length - n] ?? 0)) n++;
|
|
5053
5225
|
}
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5226
|
+
return n;
|
|
5227
|
+
}
|
|
5228
|
+
rubOut(n) {
|
|
5229
|
+
const dropped2 = this.line.splice(this.line.length - n, n);
|
|
5230
|
+
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
5231
|
+
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
5232
|
+
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
5233
|
+
this.output(encoder2.encode("\b \b".repeat(cells)), false);
|
|
5060
5234
|
}
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
return
|
|
5066
|
-
case "pty-ctty": {
|
|
5067
|
-
const tty = terminalOf(ctx, req.fd);
|
|
5068
|
-
const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
|
|
5069
|
-
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
|
|
5070
|
-
if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
|
|
5071
|
-
return done3;
|
|
5235
|
+
}
|
|
5236
|
+
echo(byte) {
|
|
5237
|
+
if (byte < 32 && byte !== 10 && byte !== 9 && this.termios.c_lflag & ECHOCTL) {
|
|
5238
|
+
this.output(Uint8Array.of(94, byte + 64), false);
|
|
5239
|
+
return;
|
|
5072
5240
|
}
|
|
5073
|
-
|
|
5074
|
-
return json3(ctx.ptys?.numbers() ?? []);
|
|
5075
|
-
case "pty-packet":
|
|
5076
|
-
masterOf(ctx, req.fd).packet = req.on;
|
|
5077
|
-
return done3;
|
|
5078
|
-
case "pty-winsz-set":
|
|
5079
|
-
terminalOf(ctx, req.fd).resize(req.cols, req.rows);
|
|
5080
|
-
return done3;
|
|
5241
|
+
this.output(Uint8Array.of(byte));
|
|
5081
5242
|
}
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
const
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
return out;
|
|
5095
|
-
}
|
|
5096
|
-
function interruption(signal) {
|
|
5097
|
-
const { promise, reject } = Promise.withResolvers();
|
|
5098
|
-
const fail = () => reject(new KernelError("EINTR"));
|
|
5099
|
-
if (signal.aborted) fail();
|
|
5100
|
-
else signal.addEventListener("abort", fail, { once: true });
|
|
5101
|
-
return { promise, done: () => signal.removeEventListener("abort", fail) };
|
|
5102
|
-
}
|
|
5103
|
-
async function selectFds(fds, read, write, timeoutMs, interrupt) {
|
|
5104
|
-
const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
|
|
5105
|
-
const interrupted2 = interruption(interrupt);
|
|
5106
|
-
interrupted2.promise.catch(() => {
|
|
5107
|
-
});
|
|
5108
|
-
try {
|
|
5109
|
-
return await waitReady(fds, read, write, deadline, interrupted2.promise);
|
|
5110
|
-
} finally {
|
|
5111
|
-
interrupted2.done();
|
|
5243
|
+
output(bytes, translate2 = true) {
|
|
5244
|
+
const { c_oflag } = this.termios;
|
|
5245
|
+
if (!translate2 || !(c_oflag & OPOST && c_oflag & ONLCR) || !bytes.includes(10)) {
|
|
5246
|
+
this.screen.write(bytes);
|
|
5247
|
+
return;
|
|
5248
|
+
}
|
|
5249
|
+
const out = [];
|
|
5250
|
+
for (const byte of bytes) {
|
|
5251
|
+
if (byte === 10) out.push(13);
|
|
5252
|
+
out.push(byte);
|
|
5253
|
+
}
|
|
5254
|
+
this.screen.write(Uint8Array.from(out));
|
|
5112
5255
|
}
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
const
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
return
|
|
5122
|
-
}
|
|
5123
|
-
|
|
5124
|
-
const
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5256
|
+
async read(max, signal) {
|
|
5257
|
+
while (this.readable.length === 0) {
|
|
5258
|
+
if (this.hungUp) return new Uint8Array(0);
|
|
5259
|
+
await this.changed(signal);
|
|
5260
|
+
}
|
|
5261
|
+
const head = this.readable[0];
|
|
5262
|
+
if (head === null) {
|
|
5263
|
+
this.readable.shift();
|
|
5264
|
+
return new Uint8Array(0);
|
|
5265
|
+
}
|
|
5266
|
+
if (head === void 0) return new Uint8Array(0);
|
|
5267
|
+
const chunks = [];
|
|
5268
|
+
let n = 0;
|
|
5269
|
+
while (this.readable.length > 0 && n < max) {
|
|
5270
|
+
const next = this.readable[0];
|
|
5271
|
+
if (next === null || next === void 0) break;
|
|
5272
|
+
const take = next.subarray(0, max - n);
|
|
5273
|
+
chunks.push(take);
|
|
5274
|
+
n += take.length;
|
|
5275
|
+
if (take.length < next.length) this.readable[0] = next.subarray(take.length);
|
|
5276
|
+
else this.readable.shift();
|
|
5277
|
+
if (this.canonical) break;
|
|
5130
5278
|
}
|
|
5279
|
+
const out = new Uint8Array(n);
|
|
5280
|
+
let at = 0;
|
|
5281
|
+
for (const chunk2 of chunks) {
|
|
5282
|
+
out.set(chunk2, at);
|
|
5283
|
+
at += chunk2.length;
|
|
5284
|
+
}
|
|
5285
|
+
return out;
|
|
5131
5286
|
}
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
// src/kernel/socket.ts
|
|
5135
|
-
var SOMAXCONN = 4096;
|
|
5136
|
-
var EPHEMERAL_FIRST = 32768;
|
|
5137
|
-
var EPHEMERAL_LAST = 60999;
|
|
5138
|
-
var SOL_SOCKET = 1;
|
|
5139
|
-
var SO_TYPE = 3;
|
|
5140
|
-
var SO_ERROR = 4;
|
|
5141
|
-
var SO_SNDBUF = 7;
|
|
5142
|
-
var SO_RCVBUF = 8;
|
|
5143
|
-
var SO_ACCEPTCONN = 30;
|
|
5144
|
-
var SO_DOMAIN = 39;
|
|
5145
|
-
var SOCK_STREAM = 1;
|
|
5146
|
-
var AF_UNIX = 1;
|
|
5147
|
-
var AF_INET = 2;
|
|
5148
|
-
var SHUT_RD = 0;
|
|
5149
|
-
var SHUT_WR = 1;
|
|
5150
|
-
var SHUT_RDWR = 2;
|
|
5151
|
-
function isLoopback(host) {
|
|
5152
|
-
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
5153
|
-
}
|
|
5154
|
-
async function pipeCall(op) {
|
|
5155
|
-
try {
|
|
5156
|
-
return await op();
|
|
5157
|
-
} catch (e) {
|
|
5158
|
-
if (e instanceof PipeError) throw new KernelError(e.code);
|
|
5159
|
-
throw e;
|
|
5160
|
-
}
|
|
5161
|
-
}
|
|
5162
|
-
var Changes = class {
|
|
5163
|
-
waiters = [];
|
|
5164
|
-
wait(signal) {
|
|
5287
|
+
changed(signal) {
|
|
5165
5288
|
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
5166
5289
|
return new Promise((resolve, reject) => {
|
|
5167
5290
|
const onAbort = () => {
|
|
@@ -5182,267 +5305,250 @@ var Changes = class {
|
|
|
5182
5305
|
for (const resolve of waiters) resolve();
|
|
5183
5306
|
}
|
|
5184
5307
|
};
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
}
|
|
5192
|
-
a.attach({ rx: ba, tx: ab });
|
|
5193
|
-
b.attach({ rx: ab, tx: ba });
|
|
5194
|
-
}
|
|
5195
|
-
var KernelSocket = class _KernelSocket {
|
|
5196
|
-
net;
|
|
5197
|
-
domain;
|
|
5198
|
-
state = "open";
|
|
5199
|
-
local;
|
|
5200
|
-
peer;
|
|
5201
|
-
link;
|
|
5202
|
-
readShut = false;
|
|
5203
|
-
writeShut = false;
|
|
5308
|
+
|
|
5309
|
+
// src/kernel/pty.ts
|
|
5310
|
+
var PtyPair = class {
|
|
5311
|
+
slave;
|
|
5312
|
+
locked = true;
|
|
5313
|
+
packet = false;
|
|
5204
5314
|
queue = [];
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
this.
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
return this.state === "listening";
|
|
5220
|
-
}
|
|
5221
|
-
connection() {
|
|
5222
|
-
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
5223
|
-
return this.link;
|
|
5224
|
-
}
|
|
5225
|
-
async read(max, signal) {
|
|
5226
|
-
const { rx } = this.connection();
|
|
5227
|
-
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
5228
|
-
return pipeCall(() => rx.read(max, signal));
|
|
5229
|
-
}
|
|
5230
|
-
async peek(max, signal) {
|
|
5231
|
-
const { rx } = this.connection();
|
|
5232
|
-
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
5233
|
-
return pipeCall(() => rx.peek(max, signal));
|
|
5234
|
-
}
|
|
5235
|
-
async write(bytes, signal) {
|
|
5236
|
-
const { tx } = this.connection();
|
|
5237
|
-
if (this.writeShut) throw new KernelError("EPIPE");
|
|
5238
|
-
return pipeCall(() => tx.write(bytes, signal));
|
|
5239
|
-
}
|
|
5240
|
-
poll() {
|
|
5241
|
-
if (this.state === "listening") {
|
|
5242
|
-
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
5243
|
-
}
|
|
5244
|
-
if (this.state !== "connected" || !this.link) {
|
|
5245
|
-
return { readable: false, writable: false, hangup: true };
|
|
5246
|
-
}
|
|
5247
|
-
const { rx, tx } = this.link;
|
|
5248
|
-
return {
|
|
5249
|
-
readable: this.readShut || rx.readReady,
|
|
5250
|
-
writable: this.writeShut || tx.writeReady,
|
|
5251
|
-
hangup: rx.writersGone && tx.readersGone
|
|
5252
|
-
};
|
|
5253
|
-
}
|
|
5254
|
-
changed(signal) {
|
|
5255
|
-
const own = this.changes.wait(signal);
|
|
5256
|
-
if (this.state !== "connected" || !this.link) return own;
|
|
5257
|
-
const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
|
|
5258
|
-
for (const wait of waits) wait.catch(() => void 0);
|
|
5259
|
-
return pipeCall(() => Promise.race(waits));
|
|
5260
|
-
}
|
|
5261
|
-
bind(addr) {
|
|
5262
|
-
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
5263
|
-
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
5264
|
-
this.local = this.net.bind(this, addr);
|
|
5265
|
-
this.bound = true;
|
|
5266
|
-
}
|
|
5267
|
-
listen(backlog) {
|
|
5268
|
-
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
5269
|
-
if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
|
|
5270
|
-
if (!this.local) {
|
|
5271
|
-
if (this.domain === "unix") throw new KernelError("EINVAL");
|
|
5272
|
-
this.bind({ family: "inet", host: "0.0.0.0", port: 0 });
|
|
5273
|
-
}
|
|
5274
|
-
this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
|
|
5275
|
-
this.state = "listening";
|
|
5315
|
+
waiters = [];
|
|
5316
|
+
masterOpen = true;
|
|
5317
|
+
slaveRefs = 0;
|
|
5318
|
+
slaveSeen = false;
|
|
5319
|
+
index;
|
|
5320
|
+
onFree;
|
|
5321
|
+
constructor(index, signal, onFree) {
|
|
5322
|
+
this.index = index;
|
|
5323
|
+
this.onFree = onFree;
|
|
5324
|
+
this.slave = new KernelTty(
|
|
5325
|
+
{ write: (bytes) => this.fromSlave(bytes) },
|
|
5326
|
+
(sig) => signal(this.slave, sig)
|
|
5327
|
+
);
|
|
5328
|
+
this.slave.name = `/dev/pts/${index}`;
|
|
5276
5329
|
}
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5330
|
+
master() {
|
|
5331
|
+
return new OpenFile({
|
|
5332
|
+
read: (max, signal) => this.readMaster(max, signal),
|
|
5333
|
+
write: async (bytes) => {
|
|
5334
|
+
this.slave.receive(bytes);
|
|
5335
|
+
return bytes.length;
|
|
5336
|
+
},
|
|
5337
|
+
poll: () => ({
|
|
5338
|
+
readable: this.queue.length > 0 || this.slaveGone,
|
|
5339
|
+
writable: true,
|
|
5340
|
+
hangup: this.slaveGone
|
|
5341
|
+
}),
|
|
5342
|
+
changed: (signal) => this.changed(signal),
|
|
5343
|
+
close: () => this.closeMaster(),
|
|
5344
|
+
pty: this
|
|
5345
|
+
});
|
|
5285
5346
|
}
|
|
5286
|
-
|
|
5287
|
-
if (this.
|
|
5288
|
-
this.
|
|
5289
|
-
this.
|
|
5290
|
-
|
|
5347
|
+
openSlave() {
|
|
5348
|
+
if (!this.masterOpen || this.locked) throw new KernelError("EIO");
|
|
5349
|
+
const file = this.slave.file().file;
|
|
5350
|
+
this.slaveRefs += 1;
|
|
5351
|
+
this.slaveSeen = true;
|
|
5352
|
+
return new OpenFile({ ...file, close: () => this.closeSlave() });
|
|
5291
5353
|
}
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
if (this.state !== "open") throw new KernelError("EINVAL");
|
|
5295
|
-
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
5296
|
-
const target = this.net.target(addr);
|
|
5297
|
-
if (!this.local) {
|
|
5298
|
-
if (addr.family === "inet") {
|
|
5299
|
-
this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
|
|
5300
|
-
this.bound = true;
|
|
5301
|
-
} else this.local = { family: "unix", path: "" };
|
|
5302
|
-
}
|
|
5303
|
-
const server = new _KernelSocket(this.net, this.domain);
|
|
5304
|
-
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost2(addr.host) } : addr;
|
|
5305
|
-
server.peer = this.local;
|
|
5306
|
-
this.peer = server.local;
|
|
5307
|
-
link(this, server);
|
|
5308
|
-
if (!target.offer(server)) {
|
|
5309
|
-
this.unlink();
|
|
5310
|
-
server.close();
|
|
5311
|
-
throw new KernelError("ECONNREFUSED");
|
|
5312
|
-
}
|
|
5354
|
+
get slaveGone() {
|
|
5355
|
+
return this.slaveSeen && this.slaveRefs === 0;
|
|
5313
5356
|
}
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
link2.tx.closeWrite();
|
|
5319
|
-
}
|
|
5320
|
-
this.link = void 0;
|
|
5321
|
-
this.peer = void 0;
|
|
5322
|
-
this.state = "open";
|
|
5357
|
+
fromSlave(bytes) {
|
|
5358
|
+
if (!this.masterOpen) return;
|
|
5359
|
+
this.queue.push(bytes.slice());
|
|
5360
|
+
this.wake();
|
|
5323
5361
|
}
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
this.readShut = true;
|
|
5329
|
-
rx.closeRead();
|
|
5330
|
-
}
|
|
5331
|
-
if (how !== SHUT_RD && !this.writeShut) {
|
|
5332
|
-
this.writeShut = true;
|
|
5333
|
-
tx.closeWrite();
|
|
5362
|
+
async readMaster(max, signal) {
|
|
5363
|
+
while (this.queue.length === 0) {
|
|
5364
|
+
if (this.slaveGone) throw new KernelError("EIO");
|
|
5365
|
+
await this.changed(signal);
|
|
5334
5366
|
}
|
|
5335
|
-
this.
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
return this.state === "listening" ? 1 : 0;
|
|
5346
|
-
case SO_DOMAIN:
|
|
5347
|
-
return this.domain === "inet" ? AF_INET : AF_UNIX;
|
|
5348
|
-
case SO_SNDBUF:
|
|
5349
|
-
case SO_RCVBUF:
|
|
5350
|
-
return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
|
|
5351
|
-
}
|
|
5367
|
+
const status = this.packet ? 1 : 0;
|
|
5368
|
+
const chunks = [];
|
|
5369
|
+
let n = 0;
|
|
5370
|
+
while (this.queue.length > 0 && n < max - status) {
|
|
5371
|
+
const next = this.queue[0];
|
|
5372
|
+
const take = next.subarray(0, max - status - n);
|
|
5373
|
+
chunks.push(take);
|
|
5374
|
+
n += take.length;
|
|
5375
|
+
if (take.length < next.length) this.queue[0] = next.subarray(take.length);
|
|
5376
|
+
else this.queue.shift();
|
|
5352
5377
|
}
|
|
5353
|
-
|
|
5378
|
+
const out = new Uint8Array(status + n);
|
|
5379
|
+
let at = status;
|
|
5380
|
+
for (const chunk2 of chunks) {
|
|
5381
|
+
out.set(chunk2, at);
|
|
5382
|
+
at += chunk2.length;
|
|
5383
|
+
}
|
|
5384
|
+
return out;
|
|
5354
5385
|
}
|
|
5355
|
-
|
|
5356
|
-
this.
|
|
5386
|
+
closeMaster() {
|
|
5387
|
+
this.masterOpen = false;
|
|
5388
|
+
this.queue = [];
|
|
5389
|
+
this.slave.hangup();
|
|
5390
|
+
this.slave.signalHangup();
|
|
5391
|
+
this.freeIfUnused();
|
|
5357
5392
|
}
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
this.
|
|
5361
|
-
|
|
5362
|
-
for (const pending of this.queue.splice(0)) pending.close();
|
|
5363
|
-
}
|
|
5364
|
-
if (this.link) {
|
|
5365
|
-
if (!this.readShut) this.link.rx.closeRead();
|
|
5366
|
-
if (!this.writeShut) this.link.tx.closeWrite();
|
|
5367
|
-
this.readShut = true;
|
|
5368
|
-
this.writeShut = true;
|
|
5369
|
-
}
|
|
5370
|
-
if (this.bound && this.local) this.net.release(this.local, this);
|
|
5371
|
-
this.bound = false;
|
|
5372
|
-
this.changes.wake();
|
|
5393
|
+
closeSlave() {
|
|
5394
|
+
this.slaveRefs -= 1;
|
|
5395
|
+
this.wake();
|
|
5396
|
+
this.freeIfUnused();
|
|
5373
5397
|
}
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
const b = new _KernelSocket(net, domain);
|
|
5377
|
-
const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
|
|
5378
|
-
a.local = b.local = a.peer = b.peer = name2;
|
|
5379
|
-
link(a, b);
|
|
5380
|
-
return [a, b];
|
|
5398
|
+
freeIfUnused() {
|
|
5399
|
+
if (!this.masterOpen && this.slaveRefs === 0) this.onFree(this);
|
|
5381
5400
|
}
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
}
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5401
|
+
changed(signal) {
|
|
5402
|
+
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
5403
|
+
return new Promise((resolve, reject) => {
|
|
5404
|
+
const onAbort = () => {
|
|
5405
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
5406
|
+
reject(new KernelError("EINTR"));
|
|
5407
|
+
};
|
|
5408
|
+
const waiter = () => {
|
|
5409
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5410
|
+
resolve();
|
|
5411
|
+
};
|
|
5412
|
+
this.waiters.push(waiter);
|
|
5413
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5414
|
+
});
|
|
5395
5415
|
}
|
|
5396
|
-
|
|
5397
|
-
|
|
5416
|
+
wake() {
|
|
5417
|
+
const waiters = this.waiters;
|
|
5418
|
+
this.waiters = [];
|
|
5419
|
+
for (const resolve of waiters) resolve();
|
|
5398
5420
|
}
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5421
|
+
};
|
|
5422
|
+
var PtyTable = class {
|
|
5423
|
+
pairs = /* @__PURE__ */ new Map();
|
|
5424
|
+
signal;
|
|
5425
|
+
constructor(signal) {
|
|
5426
|
+
this.signal = signal;
|
|
5404
5427
|
}
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5428
|
+
open() {
|
|
5429
|
+
let index = 0;
|
|
5430
|
+
while (this.pairs.has(index)) index += 1;
|
|
5431
|
+
const pair = new PtyPair(index, this.signal, (p) => this.pairs.delete(p.index));
|
|
5432
|
+
this.pairs.set(index, pair);
|
|
5433
|
+
return { pair, master: pair.master() };
|
|
5409
5434
|
}
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
throw new KernelError("EADDRNOTAVAIL");
|
|
5415
|
-
}
|
|
5416
|
-
const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
|
|
5417
|
-
const key = keyOf(named);
|
|
5418
|
-
if (this.bound.has(key)) throw new KernelError("EADDRINUSE");
|
|
5419
|
-
this.bound.set(key, socket);
|
|
5420
|
-
return named;
|
|
5435
|
+
get(index) {
|
|
5436
|
+
const pair = this.pairs.get(index);
|
|
5437
|
+
if (!pair) throw new KernelError("ENOENT");
|
|
5438
|
+
return pair;
|
|
5421
5439
|
}
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
5440
|
+
numbers() {
|
|
5441
|
+
return [...this.pairs.keys()].sort((a, b) => a - b);
|
|
5425
5442
|
}
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5443
|
+
};
|
|
5444
|
+
var PTY_OPS = [
|
|
5445
|
+
"pty-open",
|
|
5446
|
+
"pty-slave-open",
|
|
5447
|
+
"pty-number",
|
|
5448
|
+
"pty-lock",
|
|
5449
|
+
"pty-ctty",
|
|
5450
|
+
"pty-packet",
|
|
5451
|
+
"pty-list",
|
|
5452
|
+
"pty-winsz-set"
|
|
5453
|
+
];
|
|
5454
|
+
function masterOf(ctx, fd) {
|
|
5455
|
+
const pair = ctx.fds.get(fd).file.pty;
|
|
5456
|
+
if (!pair) throw new KernelError("ENOTTY");
|
|
5457
|
+
return pair;
|
|
5458
|
+
}
|
|
5459
|
+
function terminalOf(ctx, fd) {
|
|
5460
|
+
const file = ctx.fds.get(fd).file;
|
|
5461
|
+
const tty = file.tty ?? file.pty?.slave;
|
|
5462
|
+
if (!tty) throw new KernelError("ENOTTY");
|
|
5463
|
+
return tty;
|
|
5464
|
+
}
|
|
5465
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5466
|
+
var done3 = { ok: true, kind: "void" };
|
|
5467
|
+
function ptySyscall(req, ctx) {
|
|
5468
|
+
switch (req.op) {
|
|
5469
|
+
case "pty-open": {
|
|
5470
|
+
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5471
|
+
return json3(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5429
5472
|
}
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5473
|
+
case "pty-slave-open": {
|
|
5474
|
+
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5475
|
+
const pair = ctx.ptys.get(req.n);
|
|
5476
|
+
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
5477
|
+
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
5478
|
+
return json3(fd);
|
|
5479
|
+
}
|
|
5480
|
+
case "pty-number":
|
|
5481
|
+
return json3(masterOf(ctx, req.fd).index);
|
|
5482
|
+
case "pty-lock":
|
|
5483
|
+
masterOf(ctx, req.fd).locked = req.lock;
|
|
5484
|
+
return done3;
|
|
5485
|
+
case "pty-ctty": {
|
|
5486
|
+
const tty = terminalOf(ctx, req.fd);
|
|
5487
|
+
const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
|
|
5488
|
+
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
|
|
5489
|
+
if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
|
|
5490
|
+
return done3;
|
|
5491
|
+
}
|
|
5492
|
+
case "pty-list":
|
|
5493
|
+
return json3(ctx.ptys?.numbers() ?? []);
|
|
5494
|
+
case "pty-packet":
|
|
5495
|
+
masterOf(ctx, req.fd).packet = req.on;
|
|
5496
|
+
return done3;
|
|
5497
|
+
case "pty-winsz-set":
|
|
5498
|
+
terminalOf(ctx, req.fd).resize(req.cols, req.rows);
|
|
5499
|
+
return done3;
|
|
5435
5500
|
}
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
// src/kernel/select.ts
|
|
5504
|
+
function ready(fds, read, write) {
|
|
5505
|
+
const hangup = /* @__PURE__ */ new Set();
|
|
5506
|
+
const pick = (list, dir) => list.filter((fd) => {
|
|
5507
|
+
const state = pollFile(fds.get(fd).file);
|
|
5508
|
+
if (state.hangup) hangup.add(fd);
|
|
5509
|
+
return state[dir] || state.hangup;
|
|
5510
|
+
});
|
|
5511
|
+
const out = { read: pick(read, "readable"), write: pick(write, "writable") };
|
|
5512
|
+
if (hangup.size > 0) out.hangup = [...hangup];
|
|
5513
|
+
return out;
|
|
5514
|
+
}
|
|
5515
|
+
function interruption(signal) {
|
|
5516
|
+
const { promise, reject } = Promise.withResolvers();
|
|
5517
|
+
const fail = () => reject(new KernelError("EINTR"));
|
|
5518
|
+
if (signal.aborted) fail();
|
|
5519
|
+
else signal.addEventListener("abort", fail, { once: true });
|
|
5520
|
+
return { promise, done: () => signal.removeEventListener("abort", fail) };
|
|
5521
|
+
}
|
|
5522
|
+
async function selectFds(fds, read, write, timeoutMs, interrupt) {
|
|
5523
|
+
const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
|
|
5524
|
+
const interrupted2 = interruption(interrupt);
|
|
5525
|
+
interrupted2.promise.catch(() => {
|
|
5526
|
+
});
|
|
5527
|
+
try {
|
|
5528
|
+
return await waitReady(fds, read, write, deadline, interrupted2.promise);
|
|
5529
|
+
} finally {
|
|
5530
|
+
interrupted2.done();
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
async function waitReady(fds, read, write, deadline, interrupted2) {
|
|
5534
|
+
for (; ; ) {
|
|
5535
|
+
const now = ready(fds, read, write);
|
|
5536
|
+
if (now.read.length > 0 || now.write.length > 0 || Date.now() >= deadline) return now;
|
|
5537
|
+
const round = new AbortController();
|
|
5538
|
+
const changes = [.../* @__PURE__ */ new Set([...read, ...write])].flatMap((fd) => {
|
|
5539
|
+
const file = fds.get(fd).file;
|
|
5540
|
+
return file.changed === void 0 ? [] : [file.changed(round.signal).catch(() => void 0)];
|
|
5541
|
+
});
|
|
5542
|
+
let timer;
|
|
5543
|
+
const expiry = deadline === Number.POSITIVE_INFINITY ? [] : [new Promise((resolve) => timer = setTimeout(resolve, deadline - Date.now()))];
|
|
5544
|
+
try {
|
|
5545
|
+
await Promise.race([...changes, ...expiry, interrupted2]);
|
|
5546
|
+
} finally {
|
|
5547
|
+
round.abort();
|
|
5548
|
+
clearTimeout(timer);
|
|
5442
5549
|
}
|
|
5443
|
-
throw new KernelError("EADDRINUSE");
|
|
5444
5550
|
}
|
|
5445
|
-
}
|
|
5551
|
+
}
|
|
5446
5552
|
|
|
5447
5553
|
// src/kernel/socket-syscalls.ts
|
|
5448
5554
|
var SOCKET_OPS = [
|
|
@@ -6715,6 +6821,17 @@ var JobTable = class {
|
|
|
6715
6821
|
const parent = this.members.get(this.shown(pid))?.ppid;
|
|
6716
6822
|
return parent === void 0 ? void 0 : this.shown(parent);
|
|
6717
6823
|
}
|
|
6824
|
+
sessionOf(pgid) {
|
|
6825
|
+
for (const member of this.members.values()) if (member.pgid === pgid) return member.sid;
|
|
6826
|
+
return void 0;
|
|
6827
|
+
}
|
|
6828
|
+
join(pid, pgid) {
|
|
6829
|
+
const sid = this.sessionOf(pgid);
|
|
6830
|
+
if (sid === void 0) throw new KernelError("ESRCH");
|
|
6831
|
+
const member = this.member(pid);
|
|
6832
|
+
member.pgid = pgid;
|
|
6833
|
+
member.sid = sid;
|
|
6834
|
+
}
|
|
6718
6835
|
pgidOf(pid) {
|
|
6719
6836
|
return this.members.get(pid)?.pgid;
|
|
6720
6837
|
}
|
|
@@ -6963,8 +7080,8 @@ function mediumSlot(lost, onLost) {
|
|
|
6963
7080
|
|
|
6964
7081
|
// src/mount/fsa.ts
|
|
6965
7082
|
var FSA_CAPABILITIES = { listingStats: true };
|
|
6966
|
-
var
|
|
6967
|
-
var denied = (err) =>
|
|
7083
|
+
var codeOf2 = (err) => err?.code;
|
|
7084
|
+
var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
|
|
6968
7085
|
function attrOf(st) {
|
|
6969
7086
|
return {
|
|
6970
7087
|
kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
|
|
@@ -6986,7 +7103,7 @@ function kernelFsHandlers(fs) {
|
|
|
6986
7103
|
try {
|
|
6987
7104
|
return await fs.stat(path);
|
|
6988
7105
|
} catch (err) {
|
|
6989
|
-
if (
|
|
7106
|
+
if (codeOf2(err) === "ENOENT") return void 0;
|
|
6990
7107
|
throw err;
|
|
6991
7108
|
}
|
|
6992
7109
|
}
|
|
@@ -7070,8 +7187,8 @@ function removableMedium(onLost, open = (handle) => new OpfsFs(handle)) {
|
|
|
7070
7187
|
|
|
7071
7188
|
// src/mount/hostfs.ts
|
|
7072
7189
|
var HOSTFS_IO = 16 * 1024 * 1024;
|
|
7073
|
-
var
|
|
7074
|
-
var unreachable = (err) =>
|
|
7190
|
+
var codeOf3 = (err) => err?.code;
|
|
7191
|
+
var unreachable = (err) => codeOf3(err) === "ENOTCONN";
|
|
7075
7192
|
function rel(path) {
|
|
7076
7193
|
return path.replace(/^\/+/, "");
|
|
7077
7194
|
}
|
|
@@ -7206,7 +7323,7 @@ function hostfsHandlers(current) {
|
|
|
7206
7323
|
try {
|
|
7207
7324
|
return { opened: await opening(), restore: void 0 };
|
|
7208
7325
|
} catch (err) {
|
|
7209
|
-
if (!flags.write ||
|
|
7326
|
+
if (!flags.write || codeOf3(err) !== "EACCES") throw err;
|
|
7210
7327
|
const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
|
|
7211
7328
|
if (mode & 128) throw err;
|
|
7212
7329
|
const restore = { path: rel(path), mode };
|
|
@@ -8082,7 +8199,13 @@ var Launcher = class {
|
|
|
8082
8199
|
}
|
|
8083
8200
|
return this.start({ ...req, program, argv0: target.argv0, args, env });
|
|
8084
8201
|
}
|
|
8202
|
+
groupSession(pgid) {
|
|
8203
|
+
return this.jobs.sessionOf(pgid);
|
|
8204
|
+
}
|
|
8085
8205
|
start(req) {
|
|
8206
|
+
if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
|
|
8207
|
+
throw fsError("ESRCH", `no process group ${req.pgid}`);
|
|
8208
|
+
}
|
|
8086
8209
|
const pid = this.nextPid++;
|
|
8087
8210
|
const terminal = req.fds.stdioTerminal();
|
|
8088
8211
|
const handle = spawnWasmProcess({
|
|
@@ -8118,6 +8241,7 @@ var Launcher = class {
|
|
|
8118
8241
|
this.processes.set(pid, handle);
|
|
8119
8242
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
8120
8243
|
if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
|
|
8244
|
+
if (req.pgid !== void 0) this.jobs.join(pid, req.pgid);
|
|
8121
8245
|
this.described.set(pid, {
|
|
8122
8246
|
argv: [req.argv0, ...req.args],
|
|
8123
8247
|
tty: terminal?.name ?? null,
|
|
@@ -8417,7 +8541,8 @@ var Launcher = class {
|
|
|
8417
8541
|
fds.installAt(0, options.stdin ? bytesSource(options.stdin) : nullFile());
|
|
8418
8542
|
fds.installAt(1, sinkFile(stdout));
|
|
8419
8543
|
fds.installAt(2, sinkFile(stderr));
|
|
8420
|
-
const
|
|
8544
|
+
const group = options.pgid !== void 0 ? { pgid: options.pgid } : {};
|
|
8545
|
+
const handle = await this.launch(planned, { env, cwd, fds, report, ...group });
|
|
8421
8546
|
options.onStarted?.(handle.pid);
|
|
8422
8547
|
const status = await handle.exited;
|
|
8423
8548
|
return { status, stdout: concat5(out), stderr: concat5(err) };
|
|
@@ -8483,6 +8608,12 @@ function serveKernel(port, deps) {
|
|
|
8483
8608
|
void served.closed.then(() => clients.delete(served));
|
|
8484
8609
|
return port2;
|
|
8485
8610
|
}
|
|
8611
|
+
async function dial(req) {
|
|
8612
|
+
const socket = dialSocket((await ready2()).net, req.port, req.host);
|
|
8613
|
+
const { port1, port2 } = new MessageChannel();
|
|
8614
|
+
serveSocket(socket, port1);
|
|
8615
|
+
return port2;
|
|
8616
|
+
}
|
|
8486
8617
|
async function ready2() {
|
|
8487
8618
|
if (!launcher) throw new Error("the kernel is not initialized");
|
|
8488
8619
|
return launcher;
|
|
@@ -8535,6 +8666,7 @@ function serveKernel(port, deps) {
|
|
|
8535
8666
|
if (req.op === "init") return init(req);
|
|
8536
8667
|
if (req.op === "open-terminal") return terminal(req);
|
|
8537
8668
|
if (req.op === "connect") return connect();
|
|
8669
|
+
if (req.op === "dial") return dial(req);
|
|
8538
8670
|
if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
|
|
8539
8671
|
return mountOp(req);
|
|
8540
8672
|
if (req.op === "terminal") {
|