@ai-ecoverse/slicc-kernel 1.2.0 → 1.4.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 +50 -4
- package/dist/index.js +150 -2
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +2833 -431
- package/dist/kernel-worker.js.map +4 -4
- package/dist/process-worker.js +4598 -622
- package/dist/process-worker.js.map +4 -4
- package/package.json +12 -4
package/dist/kernel-worker.js
CHANGED
|
@@ -71,18 +71,18 @@ var IndexedDbMeta = class {
|
|
|
71
71
|
const [transaction, store] = this.transaction("readonly");
|
|
72
72
|
const found = paths.map(() => void 0);
|
|
73
73
|
paths.forEach((path, i) => {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
found[i] =
|
|
74
|
+
const request2 = store.get(path);
|
|
75
|
+
request2.onsuccess = () => {
|
|
76
|
+
found[i] = request2.result;
|
|
77
77
|
};
|
|
78
78
|
});
|
|
79
79
|
return settle(transaction, () => found);
|
|
80
80
|
}
|
|
81
81
|
async update(path, change) {
|
|
82
82
|
const [transaction, store] = this.transaction("readwrite");
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const next = change(
|
|
83
|
+
const request2 = store.get(path);
|
|
84
|
+
request2.onsuccess = () => {
|
|
85
|
+
const next = change(request2.result);
|
|
86
86
|
if (next) store.put(next);
|
|
87
87
|
else store.delete(path);
|
|
88
88
|
};
|
|
@@ -114,8 +114,8 @@ var IndexedDbMeta = class {
|
|
|
114
114
|
}
|
|
115
115
|
async links(dir) {
|
|
116
116
|
const [transaction, store] = this.transaction("readonly");
|
|
117
|
-
const
|
|
118
|
-
return settle(transaction, () =>
|
|
117
|
+
const request2 = store.index("dir").getAll(dir);
|
|
118
|
+
return settle(transaction, () => request2.result);
|
|
119
119
|
}
|
|
120
120
|
async under(path) {
|
|
121
121
|
const [transaction, store] = this.transaction("readonly");
|
|
@@ -130,20 +130,20 @@ var IndexedDbMeta = class {
|
|
|
130
130
|
}
|
|
131
131
|
async all() {
|
|
132
132
|
const [transaction, store] = this.transaction("readonly");
|
|
133
|
-
const
|
|
134
|
-
return settle(transaction, () =>
|
|
133
|
+
const request2 = store.getAll();
|
|
134
|
+
return settle(transaction, () => request2.result);
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
|
-
function openMeta(
|
|
137
|
+
function openMeta(name2, factory) {
|
|
138
138
|
return new Promise((resolve, reject) => {
|
|
139
|
-
const
|
|
140
|
-
|
|
139
|
+
const request2 = factory.open(name2, META_VERSION);
|
|
140
|
+
request2.onupgradeneeded = (event) => {
|
|
141
141
|
if (event.oldVersion < 1) {
|
|
142
|
-
|
|
142
|
+
request2.result.createObjectStore(META_STORE, { keyPath: "path" }).createIndex("dir", "dir");
|
|
143
143
|
}
|
|
144
144
|
};
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
request2.onsuccess = () => resolve(new IndexedDbMeta(request2.result));
|
|
146
|
+
request2.onerror = () => reject(request2.error);
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -193,8 +193,8 @@ var ERRNO_BY_DOM_ERROR = {
|
|
|
193
193
|
};
|
|
194
194
|
function translate(err, path) {
|
|
195
195
|
if (typeof err?.code === "string") return err;
|
|
196
|
-
const
|
|
197
|
-
return fsError(typeof
|
|
196
|
+
const name2 = err?.name;
|
|
197
|
+
return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
|
|
198
198
|
}
|
|
199
199
|
function missing(err) {
|
|
200
200
|
const code = err?.code;
|
|
@@ -240,19 +240,19 @@ var OpfsFs = class {
|
|
|
240
240
|
}
|
|
241
241
|
async parent(path) {
|
|
242
242
|
const parts = split(path);
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
245
|
-
return [await this.dir(`/${parts.join("/")}`),
|
|
243
|
+
const name2 = parts.pop();
|
|
244
|
+
if (name2 === void 0) throw fsError("EBUSY", path);
|
|
245
|
+
return [await this.dir(`/${parts.join("/")}`), name2];
|
|
246
246
|
}
|
|
247
247
|
async handle(path) {
|
|
248
248
|
if (split(path).length === 0) return this.root;
|
|
249
|
-
const [dir,
|
|
249
|
+
const [dir, name2] = await this.parent(path);
|
|
250
250
|
try {
|
|
251
|
-
return await dir.getFileHandle(
|
|
251
|
+
return await dir.getFileHandle(name2);
|
|
252
252
|
} catch (err) {
|
|
253
253
|
if (err?.name !== "TypeMismatchError") throw translate(err, path);
|
|
254
254
|
}
|
|
255
|
-
return dir.getDirectoryHandle(
|
|
255
|
+
return dir.getDirectoryHandle(name2);
|
|
256
256
|
}
|
|
257
257
|
real(path) {
|
|
258
258
|
return this.handle(path).then(
|
|
@@ -274,11 +274,11 @@ var OpfsFs = class {
|
|
|
274
274
|
return { path: current, handle: await this.handle(current) };
|
|
275
275
|
} catch (err) {
|
|
276
276
|
if (!missing(err)) throw err;
|
|
277
|
-
const
|
|
278
|
-
if (!
|
|
279
|
-
if (
|
|
280
|
-
const rest = current.slice(
|
|
281
|
-
current = resolvePath(parentOf2(
|
|
277
|
+
const link2 = await this.linkAlong(current);
|
|
278
|
+
if (!link2) throw err;
|
|
279
|
+
if (link2.path === current && !follow) return { path: current, link: link2 };
|
|
280
|
+
const rest = current.slice(link2.path.length);
|
|
281
|
+
current = resolvePath(parentOf2(link2.path), `${link2.link}${rest}`);
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
throw fsError("ELOOP", path);
|
|
@@ -305,15 +305,15 @@ var OpfsFs = class {
|
|
|
305
305
|
};
|
|
306
306
|
}
|
|
307
307
|
linkStat(entry) {
|
|
308
|
-
const
|
|
308
|
+
const time2 = new Date(entry.mtimeMs ?? 0);
|
|
309
309
|
return {
|
|
310
310
|
isFile: false,
|
|
311
311
|
isDirectory: false,
|
|
312
312
|
isSymbolicLink: true,
|
|
313
313
|
size: new TextEncoder().encode(entry.link).length,
|
|
314
314
|
mode: S_IFLNK | 511,
|
|
315
|
-
mtime:
|
|
316
|
-
atime:
|
|
315
|
+
mtime: time2,
|
|
316
|
+
atime: time2,
|
|
317
317
|
ctime: new Date(entry.ctimeMs ?? 0),
|
|
318
318
|
ino: entry.ino ?? inodeOf(entry.path)
|
|
319
319
|
};
|
|
@@ -364,10 +364,10 @@ var OpfsFs = class {
|
|
|
364
364
|
}
|
|
365
365
|
async writeFile(path, content) {
|
|
366
366
|
const target = await this.writeTarget(path);
|
|
367
|
-
const [dir,
|
|
367
|
+
const [dir, name2] = await this.parent(target);
|
|
368
368
|
let file;
|
|
369
369
|
try {
|
|
370
|
-
file = await dir.getFileHandle(
|
|
370
|
+
file = await dir.getFileHandle(name2, { create: true });
|
|
371
371
|
} catch (err) {
|
|
372
372
|
const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
|
|
373
373
|
throw code ? fsError(code, path) : translate(err, path);
|
|
@@ -380,26 +380,26 @@ var OpfsFs = class {
|
|
|
380
380
|
const found = await this.locate(path, true);
|
|
381
381
|
const handle = found.handle;
|
|
382
382
|
if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
383
|
-
const
|
|
384
|
-
for await (const child of handle.values())
|
|
385
|
-
for (const
|
|
386
|
-
const
|
|
387
|
-
if (!
|
|
383
|
+
const children2 = /* @__PURE__ */ new Map();
|
|
384
|
+
for await (const child of handle.values()) children2.set(child.name, child);
|
|
385
|
+
for (const link2 of await this.meta.links(found.path)) {
|
|
386
|
+
const name2 = baseOf(link2.path);
|
|
387
|
+
if (!children2.has(name2)) children2.set(name2, link2);
|
|
388
388
|
}
|
|
389
|
-
return [found.path, [...
|
|
389
|
+
return [found.path, [...children2].sort(([a], [b]) => a < b ? -1 : 1)];
|
|
390
390
|
}
|
|
391
391
|
async readdir(path) {
|
|
392
|
-
return (await this.listing(path))[1].map(([
|
|
392
|
+
return (await this.listing(path))[1].map(([name2]) => name2);
|
|
393
393
|
}
|
|
394
394
|
async readdirStat(path) {
|
|
395
|
-
const [dir,
|
|
395
|
+
const [dir, children2] = await this.listing(path);
|
|
396
396
|
const base = dir === "/" ? "" : dir;
|
|
397
|
-
const metas = await this.meta.get(
|
|
397
|
+
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
398
398
|
return Promise.all(
|
|
399
|
-
|
|
400
|
-
if (!isHandle(child)) return [
|
|
401
|
-
const stat = await this.statOf(`${base}/${
|
|
402
|
-
return [
|
|
399
|
+
children2.map(async ([name2, child], i) => {
|
|
400
|
+
if (!isHandle(child)) return [name2, this.linkStat(child)];
|
|
401
|
+
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
402
|
+
return [name2, stat];
|
|
403
403
|
})
|
|
404
404
|
);
|
|
405
405
|
}
|
|
@@ -432,9 +432,9 @@ var OpfsFs = class {
|
|
|
432
432
|
throw error;
|
|
433
433
|
}
|
|
434
434
|
if (found.handle) {
|
|
435
|
-
const [dir,
|
|
435
|
+
const [dir, name2] = await this.parent(found.path);
|
|
436
436
|
try {
|
|
437
|
-
await dir.removeEntry(
|
|
437
|
+
await dir.removeEntry(name2, { recursive: options.recursive === true });
|
|
438
438
|
} catch (err) {
|
|
439
439
|
throw translate(err, path);
|
|
440
440
|
}
|
|
@@ -473,9 +473,9 @@ var OpfsFs = class {
|
|
|
473
473
|
return;
|
|
474
474
|
}
|
|
475
475
|
const dir = parent.handle;
|
|
476
|
-
const
|
|
476
|
+
const name2 = baseOf(destination);
|
|
477
477
|
const swap = targetStat?.isDirectory === true;
|
|
478
|
-
const staging = swap ? `.${
|
|
478
|
+
const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
|
|
479
479
|
const timed = await this.explicitTimes(source.path);
|
|
480
480
|
const moved2 = await this.place(source.handle, dir, staging);
|
|
481
481
|
if (swap) await this.rm(destination, { recursive: true });
|
|
@@ -484,7 +484,7 @@ var OpfsFs = class {
|
|
|
484
484
|
let copied = !moved2;
|
|
485
485
|
if (swap) {
|
|
486
486
|
const staged = await dir.getDirectoryHandle(staging);
|
|
487
|
-
const placed = await this.place(staged, dir,
|
|
487
|
+
const placed = await this.place(staged, dir, name2);
|
|
488
488
|
if (!placed) await dir.removeEntry(staging, { recursive: true });
|
|
489
489
|
copied ||= !placed;
|
|
490
490
|
}
|
|
@@ -509,28 +509,28 @@ var OpfsFs = class {
|
|
|
509
509
|
await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
|
|
510
510
|
}
|
|
511
511
|
}
|
|
512
|
-
async place(handle, dir,
|
|
513
|
-
const moved2 = await handle.move?.(dir,
|
|
512
|
+
async place(handle, dir, name2) {
|
|
513
|
+
const moved2 = await handle.move?.(dir, name2).then(
|
|
514
514
|
() => true,
|
|
515
515
|
() => false
|
|
516
516
|
);
|
|
517
517
|
if (moved2) return true;
|
|
518
518
|
try {
|
|
519
|
-
await this.copy(handle, dir,
|
|
519
|
+
await this.copy(handle, dir, name2);
|
|
520
520
|
} catch (err) {
|
|
521
|
-
if (handle.kind === "directory") await dir.removeEntry(
|
|
522
|
-
throw translate(err,
|
|
521
|
+
if (handle.kind === "directory") await dir.removeEntry(name2, { recursive: true });
|
|
522
|
+
throw translate(err, name2);
|
|
523
523
|
}
|
|
524
524
|
return false;
|
|
525
525
|
}
|
|
526
|
-
async copy(handle, dir,
|
|
526
|
+
async copy(handle, dir, name2) {
|
|
527
527
|
if (handle.kind === "file") {
|
|
528
|
-
const writable = await (await dir.getFileHandle(
|
|
528
|
+
const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
|
|
529
529
|
await writable.write(await handle.getFile());
|
|
530
530
|
await writable.close();
|
|
531
531
|
return;
|
|
532
532
|
}
|
|
533
|
-
const target = await dir.getDirectoryHandle(
|
|
533
|
+
const target = await dir.getDirectoryHandle(name2, { create: true });
|
|
534
534
|
for await (const child of handle.values()) await this.copy(child, target, child.name);
|
|
535
535
|
}
|
|
536
536
|
async symlink(target, path) {
|
|
@@ -595,259 +595,6 @@ var OpfsFs = class {
|
|
|
595
595
|
}
|
|
596
596
|
};
|
|
597
597
|
|
|
598
|
-
// src/kernel/signals.ts
|
|
599
|
-
var SIG = {
|
|
600
|
-
HUP: 1,
|
|
601
|
-
INT: 2,
|
|
602
|
-
QUIT: 3,
|
|
603
|
-
ILL: 4,
|
|
604
|
-
TRAP: 5,
|
|
605
|
-
ABRT: 6,
|
|
606
|
-
BUS: 7,
|
|
607
|
-
FPE: 8,
|
|
608
|
-
KILL: 9,
|
|
609
|
-
USR1: 10,
|
|
610
|
-
SEGV: 11,
|
|
611
|
-
USR2: 12,
|
|
612
|
-
PIPE: 13,
|
|
613
|
-
ALRM: 14,
|
|
614
|
-
TERM: 15,
|
|
615
|
-
STKFLT: 16,
|
|
616
|
-
CHLD: 17,
|
|
617
|
-
CONT: 18,
|
|
618
|
-
STOP: 19,
|
|
619
|
-
TSTP: 20,
|
|
620
|
-
TTIN: 21,
|
|
621
|
-
TTOU: 22,
|
|
622
|
-
URG: 23,
|
|
623
|
-
XCPU: 24,
|
|
624
|
-
XFSZ: 25,
|
|
625
|
-
VTALRM: 26,
|
|
626
|
-
PROF: 27,
|
|
627
|
-
WINCH: 28,
|
|
628
|
-
POLL: 29,
|
|
629
|
-
PWR: 30,
|
|
630
|
-
SYS: 31
|
|
631
|
-
};
|
|
632
|
-
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
633
|
-
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
634
|
-
function defaultAction(sig) {
|
|
635
|
-
if (sig === SIG.CONT) return "continue";
|
|
636
|
-
if (STOPPING.has(sig)) return "stop";
|
|
637
|
-
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
638
|
-
}
|
|
639
|
-
function isSignal(sig) {
|
|
640
|
-
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
641
|
-
}
|
|
642
|
-
function sigbit(sig) {
|
|
643
|
-
return 1 << sig;
|
|
644
|
-
}
|
|
645
|
-
var SIGNAL_BY_NAME = {
|
|
646
|
-
SIGINT: SIG.INT,
|
|
647
|
-
SIGTERM: SIG.TERM,
|
|
648
|
-
SIGKILL: SIG.KILL,
|
|
649
|
-
SIGSTOP: SIG.STOP,
|
|
650
|
-
SIGCONT: SIG.CONT
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
// src/commands.ts
|
|
654
|
-
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
655
|
-
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
656
|
-
var PACKAGE = /\$\{package\}/g;
|
|
657
|
-
function inside(pkg, rel) {
|
|
658
|
-
if (typeof rel !== "string" || rel === "") return void 0;
|
|
659
|
-
const clean = rel.replace(/^\.\//, "");
|
|
660
|
-
if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
|
|
661
|
-
return `${pkg}/${clean}`;
|
|
662
|
-
}
|
|
663
|
-
function envOf(pkg, raw) {
|
|
664
|
-
const env = {};
|
|
665
|
-
if (!raw || typeof raw !== "object") return env;
|
|
666
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
667
|
-
if (ENV_KEY.test(key) && typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
|
|
668
|
-
}
|
|
669
|
-
return env;
|
|
670
|
-
}
|
|
671
|
-
function argsOf(pkg, raw) {
|
|
672
|
-
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
673
|
-
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
674
|
-
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
675
|
-
}
|
|
676
|
-
function commandsOf(pkg, manifest) {
|
|
677
|
-
const slicc = manifest.slicc;
|
|
678
|
-
if (!slicc || typeof slicc !== "object" || (slicc.abi ?? "emscripten") !== "emscripten")
|
|
679
|
-
return [];
|
|
680
|
-
const entries = slicc.commands;
|
|
681
|
-
if (!entries || typeof entries !== "object") return [];
|
|
682
|
-
const shared = envOf(pkg, slicc.env);
|
|
683
|
-
const out = [];
|
|
684
|
-
for (const [name, raw] of Object.entries(entries)) {
|
|
685
|
-
if (!NAME.test(name) || name === "." || name === ".." || !raw || typeof raw !== "object") {
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
const glue = inside(pkg, raw.glue);
|
|
689
|
-
const wasm = inside(pkg, raw.wasm);
|
|
690
|
-
if ((raw.abi ?? "emscripten") !== "emscripten" || !glue || !wasm) continue;
|
|
691
|
-
const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name;
|
|
692
|
-
const args = argsOf(pkg, raw.args);
|
|
693
|
-
const env = { ...shared, ...envOf(pkg, raw.env) };
|
|
694
|
-
out.push({
|
|
695
|
-
name,
|
|
696
|
-
glue,
|
|
697
|
-
wasm,
|
|
698
|
-
argv0,
|
|
699
|
-
...args ? { args } : {},
|
|
700
|
-
...Object.keys(env).length > 0 ? { env } : {}
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
return out;
|
|
704
|
-
}
|
|
705
|
-
async function packages(fs, modules) {
|
|
706
|
-
const names = await fs.readdir(modules).catch(() => []);
|
|
707
|
-
const dirs = [];
|
|
708
|
-
for (const name of names) {
|
|
709
|
-
if (name.startsWith(".")) continue;
|
|
710
|
-
if (!name.startsWith("@")) {
|
|
711
|
-
dirs.push(`${modules}/${name}`);
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
for (const inner of await fs.readdir(`${modules}/${name}`).catch(() => [])) {
|
|
715
|
-
dirs.push(`${modules}/${name}/${inner}`);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
return dirs;
|
|
719
|
-
}
|
|
720
|
-
async function scanCommands(fs, modules) {
|
|
721
|
-
const found = /* @__PURE__ */ new Map();
|
|
722
|
-
for (const pkg of await packages(fs, modules)) {
|
|
723
|
-
let manifest;
|
|
724
|
-
try {
|
|
725
|
-
manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
|
|
726
|
-
} catch {
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
for (const command of commandsOf(pkg, manifest)) {
|
|
730
|
-
if (!found.has(command.name)) found.set(command.name, command);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
if (!found.has("sh") && found.has("bash")) {
|
|
734
|
-
found.set("sh", { ...found.get("bash"), name: "sh", argv0: "sh" });
|
|
735
|
-
}
|
|
736
|
-
return found;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// src/fs/commands.ts
|
|
740
|
-
var S_IFDIR2 = 16384;
|
|
741
|
-
var S_IFREG2 = 32768;
|
|
742
|
-
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
743
|
-
var MAX_LINKS2 = 40;
|
|
744
|
-
function missing2(err) {
|
|
745
|
-
return err?.code === "ENOENT";
|
|
746
|
-
}
|
|
747
|
-
function synthetic(directory, path) {
|
|
748
|
-
const epoch = /* @__PURE__ */ new Date(0);
|
|
749
|
-
return {
|
|
750
|
-
isFile: !directory,
|
|
751
|
-
isDirectory: directory,
|
|
752
|
-
isSymbolicLink: false,
|
|
753
|
-
size: 0,
|
|
754
|
-
mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
|
|
755
|
-
mtime: epoch,
|
|
756
|
-
atime: epoch,
|
|
757
|
-
ctime: epoch,
|
|
758
|
-
ino: inodeOf(path)
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
async function followLinks(fs, path) {
|
|
762
|
-
let current = path;
|
|
763
|
-
for (let hops = 0; hops < MAX_LINKS2; hops++) {
|
|
764
|
-
const st = await fs.lstat(current).catch(() => void 0);
|
|
765
|
-
if (!st?.isSymbolicLink) return current;
|
|
766
|
-
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
767
|
-
current = fs.resolvePath(parent, await fs.readlink(current));
|
|
768
|
-
}
|
|
769
|
-
return current;
|
|
770
|
-
}
|
|
771
|
-
function withCommandDirs(fs, names) {
|
|
772
|
-
async function children(path) {
|
|
773
|
-
if (path === "/") return ["bin", "usr"];
|
|
774
|
-
if (path === "/usr") return ["bin"];
|
|
775
|
-
if (path === "/bin" || path === "/usr/bin") return [...await names()];
|
|
776
|
-
return void 0;
|
|
777
|
-
}
|
|
778
|
-
async function virtual(path) {
|
|
779
|
-
if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
|
|
780
|
-
const name = COMMAND.exec(path)?.[1];
|
|
781
|
-
return name !== void 0 && (await names()).has(name) ? synthetic(false, path) : void 0;
|
|
782
|
-
}
|
|
783
|
-
async function linked(path) {
|
|
784
|
-
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
785
|
-
}
|
|
786
|
-
async function stat(path, real) {
|
|
787
|
-
try {
|
|
788
|
-
return await real(path);
|
|
789
|
-
} catch (err) {
|
|
790
|
-
const found = missing2(err) ? await linked(path) : void 0;
|
|
791
|
-
if (found) return found;
|
|
792
|
-
throw err;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
return {
|
|
796
|
-
resolvePath: (base, path) => fs.resolvePath(base, path),
|
|
797
|
-
readFile: (path) => fs.readFile(path),
|
|
798
|
-
async readFileBuffer(path) {
|
|
799
|
-
try {
|
|
800
|
-
return await fs.readFileBuffer(path);
|
|
801
|
-
} catch (err) {
|
|
802
|
-
if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
|
|
803
|
-
throw err;
|
|
804
|
-
}
|
|
805
|
-
},
|
|
806
|
-
writeFile: (path, content) => fs.writeFile(path, content),
|
|
807
|
-
async exists(path) {
|
|
808
|
-
return await fs.exists(path) || await linked(path) !== void 0;
|
|
809
|
-
},
|
|
810
|
-
stat: (path) => stat(path, (p) => fs.stat(p)),
|
|
811
|
-
lstat: (path) => stat(path, (p) => fs.lstat(p)),
|
|
812
|
-
async readdir(path) {
|
|
813
|
-
const extra = await children(path);
|
|
814
|
-
if (!extra) return fs.readdir(path);
|
|
815
|
-
const real = await fs.readdir(path).catch((err) => {
|
|
816
|
-
if (missing2(err)) return [];
|
|
817
|
-
throw err;
|
|
818
|
-
});
|
|
819
|
-
return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
|
|
820
|
-
},
|
|
821
|
-
async readdirStat(path) {
|
|
822
|
-
const extra = await children(path);
|
|
823
|
-
if (!extra) return fs.readdirStat(path);
|
|
824
|
-
const real = await fs.readdirStat(path).catch((err) => {
|
|
825
|
-
if (missing2(err)) return [];
|
|
826
|
-
throw err;
|
|
827
|
-
});
|
|
828
|
-
const seen = new Set(real.map(([name]) => name));
|
|
829
|
-
const base = path === "/" ? "" : path;
|
|
830
|
-
const added = extra.filter((name) => !seen.has(name));
|
|
831
|
-
const virtuals = await Promise.all(
|
|
832
|
-
added.map(
|
|
833
|
-
async (name) => [
|
|
834
|
-
name,
|
|
835
|
-
await virtual(`${base}/${name}`)
|
|
836
|
-
]
|
|
837
|
-
)
|
|
838
|
-
);
|
|
839
|
-
return [...real, ...virtuals].sort(([a], [b]) => a < b ? -1 : 1);
|
|
840
|
-
},
|
|
841
|
-
mkdir: (path, options) => fs.mkdir(path, options),
|
|
842
|
-
rm: (path, options) => fs.rm(path, options),
|
|
843
|
-
rename: (from, to) => fs.rename(from, to),
|
|
844
|
-
symlink: (target, path) => fs.symlink(target, path),
|
|
845
|
-
readlink: (path) => fs.readlink(path),
|
|
846
|
-
chmod: (path, mode) => fs.chmod(path, mode),
|
|
847
|
-
utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
|
|
851
598
|
// src/kernel/pipe.ts
|
|
852
599
|
var PIPE_CAPACITY = 64 * 1024;
|
|
853
600
|
var PipeError = class extends Error {
|
|
@@ -916,10 +663,10 @@ var KernelPipe = class {
|
|
|
916
663
|
}
|
|
917
664
|
const out = new Uint8Array(Math.min(max, this.size));
|
|
918
665
|
let filled = 0;
|
|
919
|
-
for (const
|
|
666
|
+
for (const chunk2 of this.chunks) {
|
|
920
667
|
if (filled === out.length) break;
|
|
921
|
-
const count = Math.min(
|
|
922
|
-
out.set(
|
|
668
|
+
const count = Math.min(chunk2.length, out.length - filled);
|
|
669
|
+
out.set(chunk2.subarray(0, count), filled);
|
|
923
670
|
filled += count;
|
|
924
671
|
}
|
|
925
672
|
return out;
|
|
@@ -1190,7 +937,1742 @@ var FdTable = class _FdTable {
|
|
|
1190
937
|
this.status.clear();
|
|
1191
938
|
await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
|
|
1192
939
|
}
|
|
1193
|
-
};
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
// src/kernel/net/http1.ts
|
|
943
|
+
var HttpError = class extends Error {
|
|
944
|
+
status;
|
|
945
|
+
constructor(status, message2) {
|
|
946
|
+
super(message2);
|
|
947
|
+
this.status = status;
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
var CR = 13;
|
|
951
|
+
var LF = 10;
|
|
952
|
+
var READ_SIZE = 64 * 1024;
|
|
953
|
+
var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
954
|
+
var REASON = {
|
|
955
|
+
100: "Continue",
|
|
956
|
+
200: "OK",
|
|
957
|
+
400: "Bad Request",
|
|
958
|
+
403: "Forbidden",
|
|
959
|
+
408: "Request Timeout",
|
|
960
|
+
411: "Length Required",
|
|
961
|
+
413: "Content Too Large",
|
|
962
|
+
417: "Expectation Failed",
|
|
963
|
+
421: "Misdirected Request",
|
|
964
|
+
431: "Request Header Fields Too Large",
|
|
965
|
+
501: "Not Implemented",
|
|
966
|
+
502: "Bad Gateway",
|
|
967
|
+
503: "Service Unavailable",
|
|
968
|
+
505: "HTTP Version Not Supported"
|
|
969
|
+
};
|
|
970
|
+
var Incoming = class {
|
|
971
|
+
source;
|
|
972
|
+
buf = new Uint8Array(0);
|
|
973
|
+
eof = false;
|
|
974
|
+
constructor(source) {
|
|
975
|
+
this.source = source;
|
|
976
|
+
}
|
|
977
|
+
get buffered() {
|
|
978
|
+
return this.buf.length;
|
|
979
|
+
}
|
|
980
|
+
async fill(signal) {
|
|
981
|
+
if (this.eof) return false;
|
|
982
|
+
const chunk2 = await this.source.read(READ_SIZE, signal);
|
|
983
|
+
if (chunk2.length === 0) {
|
|
984
|
+
this.eof = true;
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
const next = new Uint8Array(this.buf.length + chunk2.length);
|
|
988
|
+
next.set(this.buf);
|
|
989
|
+
next.set(chunk2, this.buf.length);
|
|
990
|
+
this.buf = next;
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
take(n) {
|
|
994
|
+
const out = this.buf.slice(0, n);
|
|
995
|
+
this.buf = this.buf.subarray(n);
|
|
996
|
+
return out;
|
|
997
|
+
}
|
|
998
|
+
async some(max, signal) {
|
|
999
|
+
if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
|
|
1000
|
+
if (this.eof) return new Uint8Array(0);
|
|
1001
|
+
const chunk2 = await this.source.read(max, signal);
|
|
1002
|
+
if (chunk2.length === 0) this.eof = true;
|
|
1003
|
+
return chunk2;
|
|
1004
|
+
}
|
|
1005
|
+
async head(limit, signal) {
|
|
1006
|
+
let from = 0;
|
|
1007
|
+
for (; ; ) {
|
|
1008
|
+
const end = headEnd(this.buf, from);
|
|
1009
|
+
if (end > 0) {
|
|
1010
|
+
if (end > limit) throw new HttpError(431, "request head too large");
|
|
1011
|
+
return this.take(end);
|
|
1012
|
+
}
|
|
1013
|
+
if (this.buf.length > limit) throw new HttpError(431, "request head too large");
|
|
1014
|
+
from = Math.max(0, this.buf.length - 3);
|
|
1015
|
+
if (!await this.fill(signal)) {
|
|
1016
|
+
if (this.buf.length === 0) return null;
|
|
1017
|
+
throw new HttpError(400, "connection closed inside a request head");
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async line(limit, signal) {
|
|
1022
|
+
let from = 0;
|
|
1023
|
+
for (; ; ) {
|
|
1024
|
+
const at = this.buf.indexOf(LF, from);
|
|
1025
|
+
if (at >= 0) {
|
|
1026
|
+
const line = this.take(at + 1);
|
|
1027
|
+
const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
|
|
1028
|
+
return text;
|
|
1029
|
+
}
|
|
1030
|
+
if (this.buf.length > limit) throw new HttpError(400, "line too long");
|
|
1031
|
+
from = this.buf.length;
|
|
1032
|
+
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async exactly(n, signal) {
|
|
1036
|
+
while (this.buf.length < n) {
|
|
1037
|
+
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
1038
|
+
}
|
|
1039
|
+
return this.take(n);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
function headEnd(buf, from) {
|
|
1043
|
+
for (let i = from; i < buf.length; i++) {
|
|
1044
|
+
if (buf[i] !== LF) continue;
|
|
1045
|
+
if (buf[i + 1] === LF) return i + 2;
|
|
1046
|
+
if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
|
|
1047
|
+
}
|
|
1048
|
+
return 0;
|
|
1049
|
+
}
|
|
1050
|
+
function latin1(bytes) {
|
|
1051
|
+
let out = "";
|
|
1052
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
1053
|
+
out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
1054
|
+
}
|
|
1055
|
+
return out;
|
|
1056
|
+
}
|
|
1057
|
+
function latin1Bytes(text) {
|
|
1058
|
+
const out = new Uint8Array(text.length);
|
|
1059
|
+
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
|
|
1060
|
+
return out;
|
|
1061
|
+
}
|
|
1062
|
+
function parseRequestHead(bytes) {
|
|
1063
|
+
const lines = latin1(bytes).split(/\r?\n/);
|
|
1064
|
+
while (lines.length > 0 && lines[0] === "") lines.shift();
|
|
1065
|
+
const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
|
|
1066
|
+
if (!match) throw new HttpError(400, "malformed request line");
|
|
1067
|
+
if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
|
|
1068
|
+
return {
|
|
1069
|
+
method: match[1],
|
|
1070
|
+
target: match[2],
|
|
1071
|
+
minor: Number(match[4]),
|
|
1072
|
+
headers: parseFields(lines)
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
function parseFields(lines) {
|
|
1076
|
+
const headers = [];
|
|
1077
|
+
for (const line of lines) {
|
|
1078
|
+
if (line === "") continue;
|
|
1079
|
+
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
1080
|
+
throw new HttpError(400, "obsolete line folding");
|
|
1081
|
+
}
|
|
1082
|
+
const colon = line.indexOf(":");
|
|
1083
|
+
const name2 = colon > 0 ? line.slice(0, colon) : "";
|
|
1084
|
+
if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
|
|
1085
|
+
headers.push([name2, line.slice(colon + 1).trim()]);
|
|
1086
|
+
}
|
|
1087
|
+
return headers;
|
|
1088
|
+
}
|
|
1089
|
+
function fieldValues(headers, name2) {
|
|
1090
|
+
const lower = name2.toLowerCase();
|
|
1091
|
+
return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
|
|
1092
|
+
}
|
|
1093
|
+
function fieldTokens(headers, name2) {
|
|
1094
|
+
return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
|
|
1095
|
+
}
|
|
1096
|
+
function requestFraming(headers) {
|
|
1097
|
+
const codings = fieldTokens(headers, "transfer-encoding");
|
|
1098
|
+
const lengths = fieldValues(headers, "content-length");
|
|
1099
|
+
if (codings.length > 0) {
|
|
1100
|
+
if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
|
|
1101
|
+
if (codings.length !== 1 || codings[0] !== "chunked") {
|
|
1102
|
+
throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
|
|
1103
|
+
}
|
|
1104
|
+
return { kind: "chunked" };
|
|
1105
|
+
}
|
|
1106
|
+
if (lengths.length === 0) return { kind: "none" };
|
|
1107
|
+
const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
|
|
1108
|
+
const [only] = distinct;
|
|
1109
|
+
if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
|
|
1110
|
+
throw new HttpError(400, "invalid Content-Length");
|
|
1111
|
+
}
|
|
1112
|
+
const length2 = Number(only);
|
|
1113
|
+
return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
|
|
1114
|
+
}
|
|
1115
|
+
var MAX_CHUNK_LINE = 4096;
|
|
1116
|
+
var MAX_TRAILERS = 64 * 1024;
|
|
1117
|
+
async function readBody(incoming, framing, cap, signal) {
|
|
1118
|
+
if (framing.kind === "none") return void 0;
|
|
1119
|
+
if (framing.kind === "length") {
|
|
1120
|
+
if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
1121
|
+
return incoming.exactly(framing.length, signal);
|
|
1122
|
+
}
|
|
1123
|
+
const chunks = [];
|
|
1124
|
+
let total = 0;
|
|
1125
|
+
for (; ; ) {
|
|
1126
|
+
const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
|
|
1127
|
+
const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
|
|
1128
|
+
if (!size) throw new HttpError(400, "malformed chunk size");
|
|
1129
|
+
const n = Number.parseInt(size[1], 16);
|
|
1130
|
+
if (n === 0) break;
|
|
1131
|
+
total += n;
|
|
1132
|
+
if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
1133
|
+
chunks.push(await incoming.exactly(n, signal));
|
|
1134
|
+
if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
|
|
1135
|
+
}
|
|
1136
|
+
let trailers = 0;
|
|
1137
|
+
for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
|
|
1138
|
+
trailers += line.length;
|
|
1139
|
+
if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
|
|
1140
|
+
line = await incoming.line(MAX_TRAILERS, signal);
|
|
1141
|
+
}
|
|
1142
|
+
return concat(chunks, total);
|
|
1143
|
+
}
|
|
1144
|
+
function concat(chunks, total = chunks.reduce((n, c) => n + c.length, 0)) {
|
|
1145
|
+
const out = new Uint8Array(total);
|
|
1146
|
+
let at = 0;
|
|
1147
|
+
for (const chunk2 of chunks) {
|
|
1148
|
+
out.set(chunk2, at);
|
|
1149
|
+
at += chunk2.length;
|
|
1150
|
+
}
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
|
1153
|
+
function safeValue(value) {
|
|
1154
|
+
return !/[\r\n\0]/.test(value);
|
|
1155
|
+
}
|
|
1156
|
+
function responseHead(status, reason, headers) {
|
|
1157
|
+
const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
|
|
1158
|
+
let head = `HTTP/1.1 ${status} ${phrase}\r
|
|
1159
|
+
`;
|
|
1160
|
+
for (const [name2, value] of headers) {
|
|
1161
|
+
if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
|
|
1162
|
+
`;
|
|
1163
|
+
}
|
|
1164
|
+
return latin1Bytes(`${head}\r
|
|
1165
|
+
`);
|
|
1166
|
+
}
|
|
1167
|
+
function chunk(bytes) {
|
|
1168
|
+
const size = latin1Bytes(`${bytes.length.toString(16)}\r
|
|
1169
|
+
`);
|
|
1170
|
+
const out = new Uint8Array(size.length + bytes.length + 2);
|
|
1171
|
+
out.set(size);
|
|
1172
|
+
out.set(bytes, size.length);
|
|
1173
|
+
out[out.length - 2] = CR;
|
|
1174
|
+
out[out.length - 1] = LF;
|
|
1175
|
+
return out;
|
|
1176
|
+
}
|
|
1177
|
+
var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
|
|
1178
|
+
|
|
1179
|
+
// src/kernel/net/proxy-service.ts
|
|
1180
|
+
var REALM_PROXY_PORT = 3128;
|
|
1181
|
+
var DEFAULT_LIMITS = {
|
|
1182
|
+
maxConnections: 64,
|
|
1183
|
+
maxHead: 64 * 1024,
|
|
1184
|
+
idleMs: 12e4,
|
|
1185
|
+
bodyBudget: 128 * 1024 * 1024
|
|
1186
|
+
};
|
|
1187
|
+
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
1188
|
+
"connection",
|
|
1189
|
+
"keep-alive",
|
|
1190
|
+
"proxy-connection",
|
|
1191
|
+
"proxy-authenticate",
|
|
1192
|
+
"proxy-authorization",
|
|
1193
|
+
"te",
|
|
1194
|
+
"trailer",
|
|
1195
|
+
"transfer-encoding",
|
|
1196
|
+
"upgrade"
|
|
1197
|
+
]);
|
|
1198
|
+
var REQUEST_OWN = /* @__PURE__ */ new Set(["host", "content-length", "expect"]);
|
|
1199
|
+
var Budget = class {
|
|
1200
|
+
free;
|
|
1201
|
+
waiters = [];
|
|
1202
|
+
constructor(free) {
|
|
1203
|
+
this.free = free;
|
|
1204
|
+
}
|
|
1205
|
+
async acquire(n, signal) {
|
|
1206
|
+
while (this.free < n) {
|
|
1207
|
+
signal.throwIfAborted();
|
|
1208
|
+
await new Promise((resolve) => {
|
|
1209
|
+
const done4 = () => {
|
|
1210
|
+
signal.removeEventListener("abort", done4);
|
|
1211
|
+
resolve();
|
|
1212
|
+
};
|
|
1213
|
+
this.waiters.push(done4);
|
|
1214
|
+
signal.addEventListener("abort", done4, { once: true });
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
this.free -= n;
|
|
1218
|
+
}
|
|
1219
|
+
release(n) {
|
|
1220
|
+
this.free += n;
|
|
1221
|
+
const waiters = this.waiters;
|
|
1222
|
+
this.waiters = [];
|
|
1223
|
+
for (const wake of waiters) wake();
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
function canonicalHost(host) {
|
|
1227
|
+
const bare = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
1228
|
+
try {
|
|
1229
|
+
return new URL(`http://${bare}/`).hostname;
|
|
1230
|
+
} catch {
|
|
1231
|
+
return void 0;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function localV4(a, b) {
|
|
1235
|
+
return a === 127 || a === 0 || a === 169 && b === 254;
|
|
1236
|
+
}
|
|
1237
|
+
function hextets(address) {
|
|
1238
|
+
const [head, tail, extra] = address.split("::");
|
|
1239
|
+
if (extra !== void 0) return void 0;
|
|
1240
|
+
const part = (text) => text ? text.split(":").map((h) => Number.parseInt(h, 16)) : [];
|
|
1241
|
+
const front = part(head);
|
|
1242
|
+
const back = part(tail);
|
|
1243
|
+
const groups = tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
|
|
1244
|
+
return groups.length === 8 && groups.every((g) => g >= 0 && g <= 65535) ? groups : void 0;
|
|
1245
|
+
}
|
|
1246
|
+
function localV6(address) {
|
|
1247
|
+
const g = hextets(address);
|
|
1248
|
+
if (!g) return true;
|
|
1249
|
+
if ((g[0] & 65472) === 65152) return true;
|
|
1250
|
+
const zeroPrefix = g.slice(0, 5).every((x) => x === 0);
|
|
1251
|
+
if (!zeroPrefix) return false;
|
|
1252
|
+
if (g[5] === 65535 || g[5] === 0 && g[6] !== 0) return localV4(g[6] >> 8, g[6] & 255);
|
|
1253
|
+
return g[5] === 0 && g[6] === 0;
|
|
1254
|
+
}
|
|
1255
|
+
function isLoopbackHost(hostname) {
|
|
1256
|
+
const canonical = canonicalHost(hostname);
|
|
1257
|
+
if (canonical === void 0) return false;
|
|
1258
|
+
const host = canonical.replace(/\.+$/, "");
|
|
1259
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
1260
|
+
if (host.startsWith("[")) return localV6(host.slice(1, -1));
|
|
1261
|
+
const v4 = /^(\d+)\.(\d+)\.\d+\.\d+$/.exec(host);
|
|
1262
|
+
return v4 ? localV4(Number(v4[1]), Number(v4[2])) : false;
|
|
1263
|
+
}
|
|
1264
|
+
function dropped(headers) {
|
|
1265
|
+
return /* @__PURE__ */ new Set([...HOP_BY_HOP, ...fieldTokens(headers, "connection")]);
|
|
1266
|
+
}
|
|
1267
|
+
function forwardRequestHeaders(headers) {
|
|
1268
|
+
const drop = dropped(headers);
|
|
1269
|
+
return headers.filter(([name2]) => {
|
|
1270
|
+
const lower = name2.toLowerCase();
|
|
1271
|
+
return !drop.has(lower) && !REQUEST_OWN.has(lower);
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
function forwardResponseHeaders(headers, opts) {
|
|
1275
|
+
const drop = dropped(headers);
|
|
1276
|
+
if (!opts.encodedBodies) drop.add("content-encoding");
|
|
1277
|
+
if (!(opts.encodedBodies && opts.bodiless)) drop.add("content-length");
|
|
1278
|
+
return headers.filter(([name2]) => !drop.has(name2.toLowerCase()));
|
|
1279
|
+
}
|
|
1280
|
+
function keepsAlive(req) {
|
|
1281
|
+
const tokens = fieldTokens(req.headers, "connection");
|
|
1282
|
+
if (tokens.includes("close")) return false;
|
|
1283
|
+
return req.minor >= 1 || tokens.includes("keep-alive");
|
|
1284
|
+
}
|
|
1285
|
+
function tunnelRequestUrl(req, origin) {
|
|
1286
|
+
const base = new URL(origin);
|
|
1287
|
+
let url;
|
|
1288
|
+
try {
|
|
1289
|
+
url = new URL(req.target, req.target.startsWith("/") ? base : void 0);
|
|
1290
|
+
} catch {
|
|
1291
|
+
throw new HttpError(400, "malformed request target");
|
|
1292
|
+
}
|
|
1293
|
+
const hosts = fieldValues(req.headers, "host");
|
|
1294
|
+
const hostOk = hosts.every(
|
|
1295
|
+
(h) => URL.canParse(`https://${h}`) && new URL(`https://${h}`).host === base.host
|
|
1296
|
+
);
|
|
1297
|
+
if (url.origin !== base.origin || !hostOk) {
|
|
1298
|
+
throw new HttpError(421, `this tunnel is for ${base.host}`);
|
|
1299
|
+
}
|
|
1300
|
+
return url.href;
|
|
1301
|
+
}
|
|
1302
|
+
function requestUrl(req) {
|
|
1303
|
+
if (req.target.startsWith("/")) {
|
|
1304
|
+
throw new HttpError(400, "this is a proxy: send the absolute URL (GET http://host/path)");
|
|
1305
|
+
}
|
|
1306
|
+
let url;
|
|
1307
|
+
try {
|
|
1308
|
+
url = new URL(req.target);
|
|
1309
|
+
} catch {
|
|
1310
|
+
throw new HttpError(400, "malformed request target");
|
|
1311
|
+
}
|
|
1312
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
1313
|
+
throw new HttpError(400, `unsupported scheme ${url.protocol}`);
|
|
1314
|
+
}
|
|
1315
|
+
if (isLoopbackHost(url.hostname)) {
|
|
1316
|
+
throw new HttpError(
|
|
1317
|
+
403,
|
|
1318
|
+
`${url.hostname} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
return url.href;
|
|
1322
|
+
}
|
|
1323
|
+
function tunnelTarget(req) {
|
|
1324
|
+
const match = /^([A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\]):(\d{1,5})$/.exec(req.target);
|
|
1325
|
+
const port = Number(match?.[2]);
|
|
1326
|
+
if (!match || port < 1 || port > 65535) {
|
|
1327
|
+
throw new HttpError(400, "CONNECT needs host:port");
|
|
1328
|
+
}
|
|
1329
|
+
const host = canonicalHost(match[1]);
|
|
1330
|
+
if (host === void 0) throw new HttpError(400, "CONNECT needs host:port");
|
|
1331
|
+
if (isLoopbackHost(host)) {
|
|
1332
|
+
throw new HttpError(
|
|
1333
|
+
403,
|
|
1334
|
+
`${host} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
return { host, port };
|
|
1338
|
+
}
|
|
1339
|
+
async function watchHangup(conn, abort, until) {
|
|
1340
|
+
while (!until.aborted && !abort.signal.aborted) {
|
|
1341
|
+
if (conn.poll().hangup) {
|
|
1342
|
+
abort.abort(new KernelError("EPIPE"));
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
try {
|
|
1346
|
+
await conn.changed(until);
|
|
1347
|
+
} catch {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
function untilAborted(response, signal) {
|
|
1353
|
+
return new Promise((resolve, reject) => {
|
|
1354
|
+
const onAbort = () => {
|
|
1355
|
+
reject(signal.reason ?? new KernelError("EINTR"));
|
|
1356
|
+
response.then((late) => late.cancel()).catch(() => void 0);
|
|
1357
|
+
};
|
|
1358
|
+
if (signal.aborted) return onAbort();
|
|
1359
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1360
|
+
response.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
function plainResponse(status, message2) {
|
|
1364
|
+
const body = latin1Bytes(`slicc realm proxy: ${message2}
|
|
1365
|
+
`);
|
|
1366
|
+
return {
|
|
1367
|
+
status,
|
|
1368
|
+
statusText: REASON[status] ?? "",
|
|
1369
|
+
headers: [["Content-Type", "text/plain; charset=utf-8"]],
|
|
1370
|
+
body: (async function* () {
|
|
1371
|
+
yield body;
|
|
1372
|
+
})(),
|
|
1373
|
+
cancel: async () => void 0
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function timedSource(conn, idleMs, stop) {
|
|
1377
|
+
return {
|
|
1378
|
+
read: (max) => conn.read(max, AbortSignal.any([stop, AbortSignal.timeout(idleMs)]))
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
var RealmProxy = class {
|
|
1382
|
+
options;
|
|
1383
|
+
listener;
|
|
1384
|
+
stop = new AbortController();
|
|
1385
|
+
limits;
|
|
1386
|
+
slots;
|
|
1387
|
+
bodies;
|
|
1388
|
+
connections = /* @__PURE__ */ new Set();
|
|
1389
|
+
closed;
|
|
1390
|
+
port;
|
|
1391
|
+
constructor(options) {
|
|
1392
|
+
this.options = options;
|
|
1393
|
+
this.limits = { ...DEFAULT_LIMITS, ...options.limits };
|
|
1394
|
+
this.slots = new Budget(this.limits.maxConnections);
|
|
1395
|
+
this.bodies = new Budget(this.limits.bodyBudget);
|
|
1396
|
+
this.listener = options.net.listen({
|
|
1397
|
+
family: "inet",
|
|
1398
|
+
host: options.host ?? "127.0.0.1",
|
|
1399
|
+
port: options.port ?? REALM_PROXY_PORT
|
|
1400
|
+
});
|
|
1401
|
+
this.port = this.listener.local?.family === "inet" ? this.listener.local.port : 0;
|
|
1402
|
+
this.closed = this.acceptLoop();
|
|
1403
|
+
}
|
|
1404
|
+
get stopped() {
|
|
1405
|
+
return this.stop.signal.aborted;
|
|
1406
|
+
}
|
|
1407
|
+
close() {
|
|
1408
|
+
if (this.stop.signal.aborted) return;
|
|
1409
|
+
this.stop.abort();
|
|
1410
|
+
this.listener.close();
|
|
1411
|
+
for (const conn of this.connections) conn.close();
|
|
1412
|
+
}
|
|
1413
|
+
async acceptLoop() {
|
|
1414
|
+
const served = /* @__PURE__ */ new Set();
|
|
1415
|
+
try {
|
|
1416
|
+
for (; ; ) {
|
|
1417
|
+
await this.slots.acquire(1, this.stop.signal);
|
|
1418
|
+
let conn;
|
|
1419
|
+
try {
|
|
1420
|
+
conn = await this.listener.accept(this.stop.signal);
|
|
1421
|
+
} catch {
|
|
1422
|
+
this.slots.release(1);
|
|
1423
|
+
break;
|
|
1424
|
+
}
|
|
1425
|
+
const done4 = this.serve(conn).finally(() => {
|
|
1426
|
+
this.slots.release(1);
|
|
1427
|
+
served.delete(done4);
|
|
1428
|
+
});
|
|
1429
|
+
served.add(done4);
|
|
1430
|
+
}
|
|
1431
|
+
} catch {
|
|
1432
|
+
}
|
|
1433
|
+
await Promise.allSettled([...served]);
|
|
1434
|
+
}
|
|
1435
|
+
async serve(conn) {
|
|
1436
|
+
this.connections.add(conn);
|
|
1437
|
+
const incoming = new Incoming(timedSource(conn, this.limits.idleMs, this.stop.signal));
|
|
1438
|
+
try {
|
|
1439
|
+
await this.requests({ sink: conn, incoming, conn, socket: conn });
|
|
1440
|
+
} finally {
|
|
1441
|
+
this.connections.delete(conn);
|
|
1442
|
+
conn.close();
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
async requests(ctx) {
|
|
1446
|
+
try {
|
|
1447
|
+
for (; ; ) {
|
|
1448
|
+
const head = await ctx.incoming.head(this.limits.maxHead);
|
|
1449
|
+
if (!head || !await this.exchange(ctx, head)) break;
|
|
1450
|
+
}
|
|
1451
|
+
} catch (e) {
|
|
1452
|
+
if (e instanceof HttpError) await this.refuse(ctx.sink, e);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
async refuse(sink, error) {
|
|
1456
|
+
try {
|
|
1457
|
+
await this.relay(sink, "GET", 1, plainResponse(error.status, error.message), false);
|
|
1458
|
+
} catch {
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
async exchange(ctx, head) {
|
|
1462
|
+
const { sink: conn, incoming } = ctx;
|
|
1463
|
+
const req = parseRequestHead(head);
|
|
1464
|
+
if (req.method === "CONNECT") {
|
|
1465
|
+
if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
|
|
1466
|
+
await this.connect(ctx.conn, incoming, req);
|
|
1467
|
+
return false;
|
|
1468
|
+
}
|
|
1469
|
+
const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
|
|
1470
|
+
const keep = keepsAlive(req);
|
|
1471
|
+
const framing = requestFraming(req.headers);
|
|
1472
|
+
const reserve = framing.kind === "length" ? framing.length : framing.kind === "chunked" ? this.cap() : 0;
|
|
1473
|
+
if (reserve > this.cap()) throw new HttpError(413, `request body over ${this.cap()} bytes`);
|
|
1474
|
+
await this.expectContinue(conn, req, framing.kind !== "none");
|
|
1475
|
+
await this.bodies.acquire(reserve, this.stop.signal);
|
|
1476
|
+
let response;
|
|
1477
|
+
const abort = new AbortController();
|
|
1478
|
+
const stopExchange = () => abort.abort();
|
|
1479
|
+
this.stop.signal.addEventListener("abort", stopExchange, { once: true });
|
|
1480
|
+
try {
|
|
1481
|
+
try {
|
|
1482
|
+
const body = await readBody(incoming, framing, this.cap());
|
|
1483
|
+
const waiting = new AbortController();
|
|
1484
|
+
void watchHangup(ctx.socket, abort, waiting.signal);
|
|
1485
|
+
try {
|
|
1486
|
+
response = await this.upstream(req, url, body, abort.signal);
|
|
1487
|
+
} finally {
|
|
1488
|
+
waiting.abort();
|
|
1489
|
+
}
|
|
1490
|
+
} finally {
|
|
1491
|
+
this.bodies.release(reserve);
|
|
1492
|
+
}
|
|
1493
|
+
return await this.relay(conn, req.method, req.minor, response, keep);
|
|
1494
|
+
} finally {
|
|
1495
|
+
this.stop.signal.removeEventListener("abort", stopExchange);
|
|
1496
|
+
abort.abort();
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
cap() {
|
|
1500
|
+
return Math.min(this.options.transport.traits.maxRequestBody, this.limits.bodyBudget);
|
|
1501
|
+
}
|
|
1502
|
+
async expectContinue(conn, req, hasBody) {
|
|
1503
|
+
const expect = fieldTokens(req.headers, "expect");
|
|
1504
|
+
if (expect.length === 0) return;
|
|
1505
|
+
if (expect.length !== 1 || expect[0] !== "100-continue") {
|
|
1506
|
+
throw new HttpError(417, `unsupported expectation: ${expect.join(", ")}`);
|
|
1507
|
+
}
|
|
1508
|
+
if (hasBody && req.minor >= 1) {
|
|
1509
|
+
await conn.write(latin1Bytes("HTTP/1.1 100 Continue\r\n\r\n"), this.stop.signal);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
async upstream(req, url, body, signal) {
|
|
1513
|
+
try {
|
|
1514
|
+
const fetching = this.options.transport.fetch({
|
|
1515
|
+
url,
|
|
1516
|
+
method: req.method.toUpperCase(),
|
|
1517
|
+
headers: forwardRequestHeaders(req.headers),
|
|
1518
|
+
body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
|
|
1519
|
+
signal
|
|
1520
|
+
});
|
|
1521
|
+
return await untilAborted(fetching, signal);
|
|
1522
|
+
} catch (e) {
|
|
1523
|
+
if (signal.aborted) throw e;
|
|
1524
|
+
const message2 = e instanceof Error ? e.message : String(e);
|
|
1525
|
+
const status = e?.status;
|
|
1526
|
+
const own = typeof status === "number" && status >= 400 && status <= 599 ? status : 502;
|
|
1527
|
+
return plainResponse(own, message2 || "upstream request failed");
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
async relay(conn, method, minor, response, keepAlive) {
|
|
1531
|
+
const status = response.status;
|
|
1532
|
+
const bodiless = method === "HEAD" || status === 204 || status === 304 || status < 200;
|
|
1533
|
+
const headers = forwardResponseHeaders(response.headers, {
|
|
1534
|
+
encodedBodies: this.options.transport.traits.encodedBodies,
|
|
1535
|
+
bodiless
|
|
1536
|
+
});
|
|
1537
|
+
const chunked = !bodiless && minor >= 1;
|
|
1538
|
+
const keep = keepAlive && (bodiless || chunked);
|
|
1539
|
+
headers.push(["Connection", keep ? "keep-alive" : "close"]);
|
|
1540
|
+
if (chunked) headers.push(["Transfer-Encoding", "chunked"]);
|
|
1541
|
+
const signal = this.stop.signal;
|
|
1542
|
+
await conn.write(
|
|
1543
|
+
responseHead(status, response.statusText || REASON[status] || "", headers),
|
|
1544
|
+
signal
|
|
1545
|
+
);
|
|
1546
|
+
if (bodiless) {
|
|
1547
|
+
await response.cancel();
|
|
1548
|
+
return keep;
|
|
1549
|
+
}
|
|
1550
|
+
try {
|
|
1551
|
+
for await (const piece of response.body) {
|
|
1552
|
+
if (piece.length > 0) await conn.write(chunked ? chunk(piece) : piece, signal);
|
|
1553
|
+
}
|
|
1554
|
+
} catch {
|
|
1555
|
+
await response.cancel().catch(() => void 0);
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
if (chunked) await conn.write(LAST_CHUNK, signal);
|
|
1559
|
+
return keep;
|
|
1560
|
+
}
|
|
1561
|
+
async connect(conn, incoming, req) {
|
|
1562
|
+
const target = tunnelTarget(req);
|
|
1563
|
+
const tunnel = this.options.tunnel;
|
|
1564
|
+
if (!tunnel) {
|
|
1565
|
+
throw new HttpError(
|
|
1566
|
+
501,
|
|
1567
|
+
`CONNECT ${target.host}:${target.port}: no tunnels through this proxy`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
await this.options.tunnelReady?.().catch((e) => {
|
|
1571
|
+
throw new HttpError(
|
|
1572
|
+
501,
|
|
1573
|
+
`CONNECT ${target.host}:${target.port}: ${e instanceof Error ? e.message : String(e)}`
|
|
1574
|
+
);
|
|
1575
|
+
});
|
|
1576
|
+
await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
|
|
1577
|
+
try {
|
|
1578
|
+
await tunnel(
|
|
1579
|
+
conn,
|
|
1580
|
+
incoming,
|
|
1581
|
+
target,
|
|
1582
|
+
this.stop.signal,
|
|
1583
|
+
(source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn })
|
|
1584
|
+
);
|
|
1585
|
+
} catch {
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
|
|
1590
|
+
// src/kernel/net/x509.ts
|
|
1591
|
+
function concat2(parts) {
|
|
1592
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
1593
|
+
let at = 0;
|
|
1594
|
+
for (const p of parts) {
|
|
1595
|
+
out.set(p, at);
|
|
1596
|
+
at += p.length;
|
|
1597
|
+
}
|
|
1598
|
+
return out;
|
|
1599
|
+
}
|
|
1600
|
+
function length(n) {
|
|
1601
|
+
if (n < 128) return Uint8Array.of(n);
|
|
1602
|
+
const bytes = [];
|
|
1603
|
+
for (let v = n; v > 0; v = Math.floor(v / 256)) bytes.unshift(v & 255);
|
|
1604
|
+
return Uint8Array.of(128 | bytes.length, ...bytes);
|
|
1605
|
+
}
|
|
1606
|
+
function tlv(tag, ...contents2) {
|
|
1607
|
+
const body = concat2(contents2);
|
|
1608
|
+
return concat2([Uint8Array.of(tag), length(body.length), body]);
|
|
1609
|
+
}
|
|
1610
|
+
var seq = (...parts) => tlv(48, ...parts);
|
|
1611
|
+
var set = (...parts) => tlv(49, ...parts);
|
|
1612
|
+
var octets = (bytes) => tlv(4, bytes);
|
|
1613
|
+
var bits = (bytes, unused = 0) => tlv(3, Uint8Array.of(unused), bytes);
|
|
1614
|
+
var TRUE = tlv(1, Uint8Array.of(255));
|
|
1615
|
+
function integer(magnitude) {
|
|
1616
|
+
let start = 0;
|
|
1617
|
+
while (start < magnitude.length - 1 && magnitude[start] === 0) start++;
|
|
1618
|
+
const trimmed = magnitude.subarray(start);
|
|
1619
|
+
return tlv(2, trimmed[0] & 128 ? concat2([Uint8Array.of(0), trimmed]) : trimmed);
|
|
1620
|
+
}
|
|
1621
|
+
function oid(dotted) {
|
|
1622
|
+
const [a, b, ...rest] = dotted.split(".").map(Number);
|
|
1623
|
+
const out = [40 * a + b];
|
|
1624
|
+
for (const arc of rest) {
|
|
1625
|
+
const groups = [];
|
|
1626
|
+
for (let v = arc; ; v = Math.floor(v / 128)) {
|
|
1627
|
+
groups.unshift(v & 127);
|
|
1628
|
+
if (v < 128) break;
|
|
1629
|
+
}
|
|
1630
|
+
out.push(...groups.map((g, i) => i < groups.length - 1 ? g | 128 : g));
|
|
1631
|
+
}
|
|
1632
|
+
return tlv(6, Uint8Array.from(out));
|
|
1633
|
+
}
|
|
1634
|
+
var utf8 = (text) => tlv(12, new TextEncoder().encode(text));
|
|
1635
|
+
var ascii = (text) => Uint8Array.from(text, (c) => c.charCodeAt(0) & 127);
|
|
1636
|
+
function time(date) {
|
|
1637
|
+
const iso = date.toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
1638
|
+
const year = date.getUTCFullYear();
|
|
1639
|
+
return year < 2050 ? tlv(23, ascii(`${iso.slice(2)}Z`)) : tlv(24, ascii(`${iso}Z`));
|
|
1640
|
+
}
|
|
1641
|
+
var ECDSA_SHA256 = seq(oid("1.2.840.10045.4.3.2"));
|
|
1642
|
+
function name(dn) {
|
|
1643
|
+
const rdns = [];
|
|
1644
|
+
if (dn.organization) rdns.push(set(seq(oid("2.5.4.10"), utf8(dn.organization))));
|
|
1645
|
+
rdns.push(set(seq(oid("2.5.4.3"), utf8(dn.commonName))));
|
|
1646
|
+
return seq(...rdns);
|
|
1647
|
+
}
|
|
1648
|
+
function extension(id, critical, value) {
|
|
1649
|
+
return critical ? seq(oid(id), TRUE, octets(value)) : seq(oid(id), octets(value));
|
|
1650
|
+
}
|
|
1651
|
+
function children(der) {
|
|
1652
|
+
const out = [];
|
|
1653
|
+
const [, bodyStart, bodyEnd] = header(der, 0);
|
|
1654
|
+
for (let at = bodyStart; at < bodyEnd; ) {
|
|
1655
|
+
const [, , end] = header(der, at);
|
|
1656
|
+
out.push(der.subarray(at, end));
|
|
1657
|
+
at = end;
|
|
1658
|
+
}
|
|
1659
|
+
return out;
|
|
1660
|
+
}
|
|
1661
|
+
function header(der, at) {
|
|
1662
|
+
const tag = der[at];
|
|
1663
|
+
let len = der[at + 1];
|
|
1664
|
+
let start = at + 2;
|
|
1665
|
+
if (len & 128) {
|
|
1666
|
+
const count = len & 127;
|
|
1667
|
+
len = 0;
|
|
1668
|
+
for (let i = 0; i < count; i++) len = len * 256 + der[start + i];
|
|
1669
|
+
start += count;
|
|
1670
|
+
}
|
|
1671
|
+
if (start + len > der.length) throw new Error("truncated DER");
|
|
1672
|
+
return [tag, start, start + len];
|
|
1673
|
+
}
|
|
1674
|
+
function contents(element) {
|
|
1675
|
+
const [, start, end] = header(element, 0);
|
|
1676
|
+
return element.subarray(start, end);
|
|
1677
|
+
}
|
|
1678
|
+
function publicKeyBits(spki) {
|
|
1679
|
+
const key = children(spki)[1];
|
|
1680
|
+
return contents(key).subarray(1);
|
|
1681
|
+
}
|
|
1682
|
+
async function keyId(spki) {
|
|
1683
|
+
return new Uint8Array(
|
|
1684
|
+
await crypto.subtle.digest("SHA-1", publicKeyBits(spki))
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
function ecdsaDer(raw) {
|
|
1688
|
+
const half = raw.length / 2;
|
|
1689
|
+
return seq(integer(raw.subarray(0, half)), integer(raw.subarray(half)));
|
|
1690
|
+
}
|
|
1691
|
+
function randomSerial() {
|
|
1692
|
+
const serial = crypto.getRandomValues(new Uint8Array(16));
|
|
1693
|
+
serial[0] = serial[0] & 127 | 1;
|
|
1694
|
+
return serial;
|
|
1695
|
+
}
|
|
1696
|
+
function ipv4(host) {
|
|
1697
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
1698
|
+
if (!m) return void 0;
|
|
1699
|
+
const parts = m.slice(1).map(Number);
|
|
1700
|
+
return parts.every((p) => p <= 255) ? Uint8Array.from(parts) : void 0;
|
|
1701
|
+
}
|
|
1702
|
+
function ipv6(host) {
|
|
1703
|
+
if (!host.includes(":") || !/^[0-9A-Fa-f:.]+$/.test(host)) return void 0;
|
|
1704
|
+
const v4 = /(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
|
|
1705
|
+
let text = host;
|
|
1706
|
+
const tail = [];
|
|
1707
|
+
if (v4) {
|
|
1708
|
+
const bytes = ipv4(v4[1]);
|
|
1709
|
+
if (!bytes) return void 0;
|
|
1710
|
+
tail.push(bytes[0] << 8 | bytes[1], bytes[2] << 8 | bytes[3]);
|
|
1711
|
+
text = host.slice(0, -v4[1].length).replace(/:$/, host.endsWith(`::${v4[1]}`) ? ":" : "");
|
|
1712
|
+
}
|
|
1713
|
+
const halves = text.split("::");
|
|
1714
|
+
if (halves.length > 2) return void 0;
|
|
1715
|
+
const groups = (part) => part === "" ? [] : part.split(":");
|
|
1716
|
+
const head = groups(halves[0]);
|
|
1717
|
+
const back = halves.length === 2 ? groups(halves[1]) : [];
|
|
1718
|
+
if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
|
|
1719
|
+
const words = [...head, ...back].map((g) => Number.parseInt(g, 16));
|
|
1720
|
+
const missing3 = 8 - words.length - tail.length;
|
|
1721
|
+
if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
|
|
1722
|
+
const all = [
|
|
1723
|
+
...head.map((g) => Number.parseInt(g, 16)),
|
|
1724
|
+
...new Array(halves.length === 2 ? missing3 : 0).fill(0),
|
|
1725
|
+
...back.map((g) => Number.parseInt(g, 16)),
|
|
1726
|
+
...tail
|
|
1727
|
+
];
|
|
1728
|
+
return Uint8Array.from(all.flatMap((w) => [w >> 8, w & 255]));
|
|
1729
|
+
}
|
|
1730
|
+
async function extensions(req) {
|
|
1731
|
+
const list = [extension("2.5.29.14", false, octets(await keyId(req.spki)))];
|
|
1732
|
+
if (req.kind.ca) {
|
|
1733
|
+
list.push(extension("2.5.29.19", true, seq(TRUE, integer(Uint8Array.of(0)))));
|
|
1734
|
+
list.push(extension("2.5.29.15", true, bits(Uint8Array.of(134), 1)));
|
|
1735
|
+
} else {
|
|
1736
|
+
list.push(extension("2.5.29.35", false, seq(tlv(128, await keyId(req.issuerSpki)))));
|
|
1737
|
+
list.push(extension("2.5.29.19", true, seq()));
|
|
1738
|
+
list.push(extension("2.5.29.15", true, bits(Uint8Array.of(128), 7)));
|
|
1739
|
+
list.push(extension("2.5.29.37", false, seq(oid("1.3.6.1.5.5.7.3.1"))));
|
|
1740
|
+
const ip = ipv4(req.kind.host) ?? ipv6(req.kind.host);
|
|
1741
|
+
const altName = ip ? tlv(135, ip) : tlv(130, ascii(req.kind.host));
|
|
1742
|
+
list.push(extension("2.5.29.17", false, seq(altName)));
|
|
1743
|
+
}
|
|
1744
|
+
return tlv(163, seq(...list));
|
|
1745
|
+
}
|
|
1746
|
+
async function certificate(req) {
|
|
1747
|
+
const tbs = seq(
|
|
1748
|
+
tlv(160, integer(Uint8Array.of(2))),
|
|
1749
|
+
integer(req.serial),
|
|
1750
|
+
ECDSA_SHA256,
|
|
1751
|
+
name(req.issuer),
|
|
1752
|
+
seq(time(req.notBefore), time(req.notAfter)),
|
|
1753
|
+
name(req.subject),
|
|
1754
|
+
req.spki,
|
|
1755
|
+
await extensions(req)
|
|
1756
|
+
);
|
|
1757
|
+
const signature = ecdsaDer(await req.sign(tbs));
|
|
1758
|
+
return seq(tbs, ECDSA_SHA256, bits(signature));
|
|
1759
|
+
}
|
|
1760
|
+
function pem(der) {
|
|
1761
|
+
let b64 = "";
|
|
1762
|
+
for (let i = 0; i < der.length; i += 32768) {
|
|
1763
|
+
b64 += String.fromCharCode(...der.subarray(i, i + 32768));
|
|
1764
|
+
}
|
|
1765
|
+
const lines = btoa(b64).match(/.{1,64}/g) ?? [];
|
|
1766
|
+
return `-----BEGIN CERTIFICATE-----
|
|
1767
|
+
${lines.join("\n")}
|
|
1768
|
+
-----END CERTIFICATE-----
|
|
1769
|
+
`;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// src/kernel/net/realm-ca.ts
|
|
1773
|
+
function sameCert(a, b) {
|
|
1774
|
+
if (!a || !b) return a === b;
|
|
1775
|
+
return a.cert.length === b.cert.length && a.cert.every((byte, i) => byte === b.cert[i]);
|
|
1776
|
+
}
|
|
1777
|
+
function winner(current, expected, record) {
|
|
1778
|
+
return sameCert(current, expected) ? record : current;
|
|
1779
|
+
}
|
|
1780
|
+
var DAY = 24 * 60 * 60 * 1e3;
|
|
1781
|
+
var CA_LIFETIME = 3650 * DAY;
|
|
1782
|
+
var CA_RENEW_BEFORE = 30 * DAY;
|
|
1783
|
+
var LEAF_LIFETIME = 7 * DAY;
|
|
1784
|
+
var ECDSA = { name: "ECDSA", namedCurve: "P-256" };
|
|
1785
|
+
var SIGN = { name: "ECDSA", hash: "SHA-256" };
|
|
1786
|
+
function request(req) {
|
|
1787
|
+
return new Promise((resolve, reject) => {
|
|
1788
|
+
req.onsuccess = () => resolve(req.result);
|
|
1789
|
+
req.onerror = () => reject(req.error);
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
function indexedDbCaStore(dbName = "slicc-realm-ca") {
|
|
1793
|
+
let db;
|
|
1794
|
+
const open = () => {
|
|
1795
|
+
db ??= new Promise((resolve, reject) => {
|
|
1796
|
+
const req = indexedDB.open(dbName, 1);
|
|
1797
|
+
req.onupgradeneeded = () => req.result.createObjectStore("ca");
|
|
1798
|
+
req.onsuccess = () => resolve(req.result);
|
|
1799
|
+
req.onerror = () => reject(req.error);
|
|
1800
|
+
});
|
|
1801
|
+
db.catch(() => {
|
|
1802
|
+
db = void 0;
|
|
1803
|
+
});
|
|
1804
|
+
return db;
|
|
1805
|
+
};
|
|
1806
|
+
return {
|
|
1807
|
+
async get(owner) {
|
|
1808
|
+
const store = (await open()).transaction("ca").objectStore("ca");
|
|
1809
|
+
return await request(store.get(owner));
|
|
1810
|
+
},
|
|
1811
|
+
async swap(owner, expected, record) {
|
|
1812
|
+
const tx = (await open()).transaction("ca", "readwrite");
|
|
1813
|
+
const store = tx.objectStore("ca");
|
|
1814
|
+
let stored = record;
|
|
1815
|
+
const read = store.get(owner);
|
|
1816
|
+
read.onsuccess = () => {
|
|
1817
|
+
const current = read.result;
|
|
1818
|
+
const kept = winner(current, expected, record);
|
|
1819
|
+
if (kept === record) store.put(record, owner);
|
|
1820
|
+
else stored = kept;
|
|
1821
|
+
};
|
|
1822
|
+
await new Promise((resolve, reject) => {
|
|
1823
|
+
tx.oncomplete = () => resolve();
|
|
1824
|
+
tx.onerror = () => reject(tx.error);
|
|
1825
|
+
});
|
|
1826
|
+
return stored;
|
|
1827
|
+
}
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
var LeafNameError = class extends Error {
|
|
1831
|
+
};
|
|
1832
|
+
function validLeafName(host) {
|
|
1833
|
+
if (ipv6(host)) return true;
|
|
1834
|
+
if (host.length === 0 || host.length > 253) return false;
|
|
1835
|
+
return host.split(".").every((label) => /^(?!-)[a-z0-9-]{1,63}(?<!-)$/i.test(label));
|
|
1836
|
+
}
|
|
1837
|
+
var RealmCa = class _RealmCa {
|
|
1838
|
+
record;
|
|
1839
|
+
constructor(record) {
|
|
1840
|
+
this.record = record;
|
|
1841
|
+
}
|
|
1842
|
+
static async open(owner, store, now = Date.now()) {
|
|
1843
|
+
const stored = await store.get(owner).catch(() => void 0);
|
|
1844
|
+
if (stored && stored.notAfter - now > CA_RENEW_BEFORE) return new _RealmCa(stored);
|
|
1845
|
+
const record = await _RealmCa.create(owner, now);
|
|
1846
|
+
return new _RealmCa(await store.swap(owner, stored, record));
|
|
1847
|
+
}
|
|
1848
|
+
static async create(owner, now) {
|
|
1849
|
+
const pair = await crypto.subtle.generateKey(ECDSA, false, ["sign", "verify"]);
|
|
1850
|
+
const spki = new Uint8Array(await crypto.subtle.exportKey("spki", pair.publicKey));
|
|
1851
|
+
const id = Array.from(
|
|
1852
|
+
crypto.getRandomValues(new Uint8Array(4)),
|
|
1853
|
+
(b) => b.toString(16).padStart(2, "0")
|
|
1854
|
+
).join("");
|
|
1855
|
+
const name2 = { organization: "SLICC", commonName: `SLICC realm CA ${owner} ${id}` };
|
|
1856
|
+
const notAfter = now + CA_LIFETIME;
|
|
1857
|
+
const cert = await certificate({
|
|
1858
|
+
serial: randomSerial(),
|
|
1859
|
+
issuer: name2,
|
|
1860
|
+
subject: name2,
|
|
1861
|
+
notBefore: new Date(now - DAY),
|
|
1862
|
+
notAfter: new Date(notAfter),
|
|
1863
|
+
spki,
|
|
1864
|
+
issuerSpki: spki,
|
|
1865
|
+
kind: { ca: true },
|
|
1866
|
+
sign: signer(pair.privateKey)
|
|
1867
|
+
});
|
|
1868
|
+
return { key: pair.privateKey, cert, spki, notAfter, name: name2 };
|
|
1869
|
+
}
|
|
1870
|
+
get cert() {
|
|
1871
|
+
return this.record.cert;
|
|
1872
|
+
}
|
|
1873
|
+
get pem() {
|
|
1874
|
+
return pem(this.record.cert);
|
|
1875
|
+
}
|
|
1876
|
+
async issue(host, spki, now = Date.now()) {
|
|
1877
|
+
const name2 = host.toLowerCase();
|
|
1878
|
+
if (!validLeafName(name2)) throw new LeafNameError(`no certificate for ${host}`);
|
|
1879
|
+
return certificate({
|
|
1880
|
+
serial: randomSerial(),
|
|
1881
|
+
issuer: this.record.name,
|
|
1882
|
+
subject: { commonName: name2.length <= 64 ? name2 : "SLICC realm leaf" },
|
|
1883
|
+
notBefore: new Date(now - 60 * 60 * 1e3),
|
|
1884
|
+
notAfter: new Date(now + LEAF_LIFETIME),
|
|
1885
|
+
spki,
|
|
1886
|
+
issuerSpki: this.record.spki,
|
|
1887
|
+
kind: { ca: false, host: name2 },
|
|
1888
|
+
sign: signer(this.record.key)
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
function signer(key) {
|
|
1893
|
+
return async (tbs) => new Uint8Array(await crypto.subtle.sign(SIGN, key, tbs));
|
|
1894
|
+
}
|
|
1895
|
+
var opened = /* @__PURE__ */ new WeakMap();
|
|
1896
|
+
function realmCa(owner, store) {
|
|
1897
|
+
const cas = opened.get(store) ?? /* @__PURE__ */ new Map();
|
|
1898
|
+
opened.set(store, cas);
|
|
1899
|
+
let ca = cas.get(owner);
|
|
1900
|
+
if (!ca) {
|
|
1901
|
+
const opening = RealmCa.open(owner, store);
|
|
1902
|
+
ca = opening;
|
|
1903
|
+
cas.set(owner, opening);
|
|
1904
|
+
opening.catch(() => {
|
|
1905
|
+
if (cas.get(owner) === opening) cas.delete(owner);
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
return ca;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// src/kernel/net/tls-engine.ts
|
|
1912
|
+
var SCRATCH = 64 * 1024;
|
|
1913
|
+
var EOF = -1;
|
|
1914
|
+
var TlsError = class extends Error {
|
|
1915
|
+
code;
|
|
1916
|
+
constructor(code, message2) {
|
|
1917
|
+
super(message2);
|
|
1918
|
+
this.code = code;
|
|
1919
|
+
}
|
|
1920
|
+
};
|
|
1921
|
+
var TlsEngine = class {
|
|
1922
|
+
m;
|
|
1923
|
+
scratch;
|
|
1924
|
+
constructor(m) {
|
|
1925
|
+
this.m = m;
|
|
1926
|
+
this.scratch = m._malloc(SCRATCH);
|
|
1927
|
+
if (!this.scratch) throw new Error("tls engine: out of memory");
|
|
1928
|
+
}
|
|
1929
|
+
put(bytes) {
|
|
1930
|
+
const ptr = this.m._malloc(bytes.length + 1);
|
|
1931
|
+
if (!ptr) throw new Error("tls engine: out of memory");
|
|
1932
|
+
this.m.HEAPU8.set(bytes, ptr);
|
|
1933
|
+
this.m.HEAPU8[ptr + bytes.length] = 0;
|
|
1934
|
+
return ptr;
|
|
1935
|
+
}
|
|
1936
|
+
taken(n) {
|
|
1937
|
+
return this.m.HEAPU8.slice(this.scratch, this.scratch + n);
|
|
1938
|
+
}
|
|
1939
|
+
error(code, what) {
|
|
1940
|
+
this.m._tls_strerror(code, this.scratch, 256);
|
|
1941
|
+
const end = this.m.HEAPU8.indexOf(0, this.scratch);
|
|
1942
|
+
const text = new TextDecoder().decode(this.m.HEAPU8.subarray(this.scratch, end));
|
|
1943
|
+
return new TlsError(code, `${what}: ${text || code}`);
|
|
1944
|
+
}
|
|
1945
|
+
leaf() {
|
|
1946
|
+
const ptr = this.m._tls_leaf_new();
|
|
1947
|
+
if (!ptr) throw this.error(this.m._tls_last_error(), "leaf key");
|
|
1948
|
+
return new TlsLeaf(this, this.m, ptr);
|
|
1949
|
+
}
|
|
1950
|
+
session(leaf, host) {
|
|
1951
|
+
const name2 = this.put(new TextEncoder().encode(host));
|
|
1952
|
+
try {
|
|
1953
|
+
const ptr = this.m._tls_session_new(leaf.ptr, name2);
|
|
1954
|
+
if (!ptr) throw this.error(this.m._tls_last_error(), "tls session");
|
|
1955
|
+
return new TlsSession(this, this.m, ptr);
|
|
1956
|
+
} finally {
|
|
1957
|
+
this.m._free(name2);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
withBytes(bytes, fn) {
|
|
1961
|
+
const ptr = this.put(bytes);
|
|
1962
|
+
try {
|
|
1963
|
+
return fn(ptr);
|
|
1964
|
+
} finally {
|
|
1965
|
+
this.m._free(ptr);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
intoScratch(fn) {
|
|
1969
|
+
const n = fn(this.scratch, SCRATCH);
|
|
1970
|
+
return n > 0 ? this.taken(n) : n;
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
var TlsLeaf = class {
|
|
1974
|
+
engine;
|
|
1975
|
+
m;
|
|
1976
|
+
ptr;
|
|
1977
|
+
constructor(engine, m, ptr) {
|
|
1978
|
+
this.engine = engine;
|
|
1979
|
+
this.m = m;
|
|
1980
|
+
this.ptr = ptr;
|
|
1981
|
+
}
|
|
1982
|
+
spki() {
|
|
1983
|
+
const out = this.engine.intoScratch((ptr, max) => this.m._tls_leaf_spki(this.ptr, ptr, max));
|
|
1984
|
+
if (typeof out === "number") throw this.engine.error(out, "leaf public key");
|
|
1985
|
+
return out;
|
|
1986
|
+
}
|
|
1987
|
+
addCert(der) {
|
|
1988
|
+
const ret = this.engine.withBytes(
|
|
1989
|
+
der,
|
|
1990
|
+
(ptr) => this.m._tls_leaf_add_cert(this.ptr, ptr, der.length)
|
|
1991
|
+
);
|
|
1992
|
+
if (ret !== 0) throw this.engine.error(ret, "leaf certificate");
|
|
1993
|
+
}
|
|
1994
|
+
release() {
|
|
1995
|
+
this.m._tls_leaf_free(this.ptr);
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
var TlsSession = class {
|
|
1999
|
+
engine;
|
|
2000
|
+
m;
|
|
2001
|
+
ptr;
|
|
2002
|
+
constructor(engine, m, ptr) {
|
|
2003
|
+
this.engine = engine;
|
|
2004
|
+
this.m = m;
|
|
2005
|
+
this.ptr = ptr;
|
|
2006
|
+
}
|
|
2007
|
+
feed(bytes) {
|
|
2008
|
+
return this.engine.withBytes(bytes, (ptr) => this.m._tls_feed(this.ptr, ptr, bytes.length));
|
|
2009
|
+
}
|
|
2010
|
+
feedEnd() {
|
|
2011
|
+
this.m._tls_feed_eof(this.ptr);
|
|
2012
|
+
}
|
|
2013
|
+
read() {
|
|
2014
|
+
const out = this.engine.intoScratch((ptr, max) => this.m._tls_read(this.ptr, ptr, max));
|
|
2015
|
+
if (out instanceof Uint8Array) return out;
|
|
2016
|
+
if (out === 0) return "more";
|
|
2017
|
+
if (out === EOF) return "eof";
|
|
2018
|
+
throw this.engine.error(out, "tls");
|
|
2019
|
+
}
|
|
2020
|
+
write(bytes) {
|
|
2021
|
+
const n = this.engine.withBytes(bytes, (ptr) => this.m._tls_write(this.ptr, ptr, bytes.length));
|
|
2022
|
+
if (n < 0) throw this.engine.error(n, "tls");
|
|
2023
|
+
return n;
|
|
2024
|
+
}
|
|
2025
|
+
take() {
|
|
2026
|
+
if (this.m._tls_out_size(this.ptr) === 0) return new Uint8Array(0);
|
|
2027
|
+
const out = this.engine.intoScratch((ptr, max) => this.m._tls_out_take(this.ptr, ptr, max));
|
|
2028
|
+
return out instanceof Uint8Array ? out : new Uint8Array(0);
|
|
2029
|
+
}
|
|
2030
|
+
close() {
|
|
2031
|
+
this.m._tls_close(this.ptr);
|
|
2032
|
+
}
|
|
2033
|
+
free() {
|
|
2034
|
+
this.m._tls_session_free(this.ptr);
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
var loading = /* @__PURE__ */ new WeakMap();
|
|
2038
|
+
function loadTlsEngine(load) {
|
|
2039
|
+
let engine = loading.get(load);
|
|
2040
|
+
if (!engine) {
|
|
2041
|
+
const started = load().then((m) => new TlsEngine(m));
|
|
2042
|
+
engine = started;
|
|
2043
|
+
loading.set(load, started);
|
|
2044
|
+
started.catch(() => {
|
|
2045
|
+
if (loading.get(load) === started) loading.delete(load);
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
return engine;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// src/kernel/net/tls-tunnel.ts
|
|
2052
|
+
var LEAF_REUSE = LEAF_LIFETIME - 24 * 60 * 60 * 1e3;
|
|
2053
|
+
var LEAF_CACHE = 256;
|
|
2054
|
+
var CIPHER_READ = 64 * 1024;
|
|
2055
|
+
var TlsStream = class {
|
|
2056
|
+
session;
|
|
2057
|
+
cipherIn;
|
|
2058
|
+
cipherOut;
|
|
2059
|
+
pending = new Uint8Array(0);
|
|
2060
|
+
ended = false;
|
|
2061
|
+
constructor(session, cipherIn, cipherOut) {
|
|
2062
|
+
this.session = session;
|
|
2063
|
+
this.cipherIn = cipherIn;
|
|
2064
|
+
this.cipherOut = cipherOut;
|
|
2065
|
+
}
|
|
2066
|
+
async flush(signal) {
|
|
2067
|
+
for (let out = this.session.take(); out.length > 0; out = this.session.take()) {
|
|
2068
|
+
await this.cipherOut.write(out, signal);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
feedPending() {
|
|
2072
|
+
if (this.pending.length === 0) return;
|
|
2073
|
+
const n = this.session.feed(this.pending);
|
|
2074
|
+
this.pending = this.pending.subarray(n);
|
|
2075
|
+
}
|
|
2076
|
+
async read(_max, signal) {
|
|
2077
|
+
for (; ; ) {
|
|
2078
|
+
this.feedPending();
|
|
2079
|
+
const got = this.session.read();
|
|
2080
|
+
await this.flush(signal);
|
|
2081
|
+
if (got instanceof Uint8Array) return got;
|
|
2082
|
+
if (got === "eof") return new Uint8Array(0);
|
|
2083
|
+
if (this.pending.length > 0) continue;
|
|
2084
|
+
if (this.ended) {
|
|
2085
|
+
this.session.feedEnd();
|
|
2086
|
+
const last = this.session.read();
|
|
2087
|
+
await this.flush(signal);
|
|
2088
|
+
return last instanceof Uint8Array ? last : new Uint8Array(0);
|
|
2089
|
+
}
|
|
2090
|
+
const cipher = await this.cipherIn.read(CIPHER_READ, signal);
|
|
2091
|
+
if (cipher.length === 0) this.ended = true;
|
|
2092
|
+
else this.pending = cipher;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
async write(bytes, signal) {
|
|
2096
|
+
for (let at = 0; at < bytes.length; ) {
|
|
2097
|
+
const n = this.session.write(bytes.subarray(at, at + 16 * 1024));
|
|
2098
|
+
await this.flush(signal);
|
|
2099
|
+
at += n;
|
|
2100
|
+
}
|
|
2101
|
+
return bytes.length;
|
|
2102
|
+
}
|
|
2103
|
+
async close(signal) {
|
|
2104
|
+
this.session.close();
|
|
2105
|
+
await this.flush(signal);
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
var TlsTerminator = class {
|
|
2109
|
+
options;
|
|
2110
|
+
leaves = /* @__PURE__ */ new Map();
|
|
2111
|
+
now;
|
|
2112
|
+
constructor(options) {
|
|
2113
|
+
this.options = options;
|
|
2114
|
+
this.now = options.now ?? Date.now;
|
|
2115
|
+
}
|
|
2116
|
+
leafFor(engine, host) {
|
|
2117
|
+
const now = this.now();
|
|
2118
|
+
const cached = this.leaves.get(host);
|
|
2119
|
+
if (cached && cached.expires > now) {
|
|
2120
|
+
this.leaves.delete(host);
|
|
2121
|
+
this.leaves.set(host, cached);
|
|
2122
|
+
return cached.leaf;
|
|
2123
|
+
}
|
|
2124
|
+
if (cached) void this.drop(host, cached);
|
|
2125
|
+
const leaf = this.issue(engine, host, now);
|
|
2126
|
+
const entry = { leaf, expires: now + LEAF_REUSE };
|
|
2127
|
+
this.leaves.set(host, entry);
|
|
2128
|
+
leaf.catch(() => {
|
|
2129
|
+
if (this.leaves.get(host) === entry) this.leaves.delete(host);
|
|
2130
|
+
});
|
|
2131
|
+
for (const [oldest, old] of this.leaves) {
|
|
2132
|
+
if (this.leaves.size <= LEAF_CACHE) break;
|
|
2133
|
+
void this.drop(oldest, old);
|
|
2134
|
+
}
|
|
2135
|
+
return leaf;
|
|
2136
|
+
}
|
|
2137
|
+
async drop(host, entry) {
|
|
2138
|
+
if (this.leaves.get(host) === entry) this.leaves.delete(host);
|
|
2139
|
+
(await entry.leaf.catch(() => void 0))?.release();
|
|
2140
|
+
}
|
|
2141
|
+
async issue(engine, host, now) {
|
|
2142
|
+
const ca = await this.options.ca();
|
|
2143
|
+
const leaf = engine.leaf();
|
|
2144
|
+
try {
|
|
2145
|
+
leaf.addCert(await ca.issue(host, leaf.spki(), now));
|
|
2146
|
+
leaf.addCert(ca.cert);
|
|
2147
|
+
return leaf;
|
|
2148
|
+
} catch (e) {
|
|
2149
|
+
leaf.release();
|
|
2150
|
+
throw e;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
async close() {
|
|
2154
|
+
await Promise.all([...this.leaves].map(([host, entry]) => this.drop(host, entry)));
|
|
2155
|
+
}
|
|
2156
|
+
handler = async (conn, incoming, target, signal, serveHttp) => {
|
|
2157
|
+
const engine = await this.options.engine();
|
|
2158
|
+
const host = target.host.replace(/^\[|\]$/g, "");
|
|
2159
|
+
const leaf = await this.leafFor(engine, host);
|
|
2160
|
+
const session = engine.session(leaf, host);
|
|
2161
|
+
try {
|
|
2162
|
+
const stream = new TlsStream(session, cipherSource(incoming), conn);
|
|
2163
|
+
const origin = `https://${authority(target)}`;
|
|
2164
|
+
await serveHttp(stream, stream, origin);
|
|
2165
|
+
await stream.close(signal).catch(() => void 0);
|
|
2166
|
+
} finally {
|
|
2167
|
+
session.free();
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
};
|
|
2171
|
+
function authority(target) {
|
|
2172
|
+
return target.port === 443 ? target.host : `${target.host}:${target.port}`;
|
|
2173
|
+
}
|
|
2174
|
+
function cipherSource(incoming) {
|
|
2175
|
+
return { read: (max, signal) => incoming.some(max, signal) };
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// src/kernel/net/network.ts
|
|
2179
|
+
var NO_TRANSPORT = "slicc-kernel: no network transport (createKernel({ network: { transport } }))";
|
|
2180
|
+
var TLS_ENGINE_PACKAGE = "@ai-ecoverse/wasm-tls-engine";
|
|
2181
|
+
var CA_PATH = "/etc/ssl/certs/slicc-kernel-ca.pem";
|
|
2182
|
+
var PROXY_URL = `http://127.0.0.1:${REALM_PROXY_PORT}`;
|
|
2183
|
+
var NO_PROXY = "localhost,.localhost,127.0.0.1,127.0.0.0/8";
|
|
2184
|
+
var CA_OWNER = "slicc-kernel";
|
|
2185
|
+
function networkEnv() {
|
|
2186
|
+
return {
|
|
2187
|
+
http_proxy: PROXY_URL,
|
|
2188
|
+
https_proxy: PROXY_URL,
|
|
2189
|
+
HTTP_PROXY: PROXY_URL,
|
|
2190
|
+
HTTPS_PROXY: PROXY_URL,
|
|
2191
|
+
no_proxy: NO_PROXY,
|
|
2192
|
+
NO_PROXY,
|
|
2193
|
+
SSL_CERT_FILE: CA_PATH,
|
|
2194
|
+
CURL_CA_BUNDLE: CA_PATH,
|
|
2195
|
+
GIT_SSL_CAINFO: CA_PATH
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
function missingTransport() {
|
|
2199
|
+
return {
|
|
2200
|
+
traits: { manualRedirects: true, encodedBodies: true, maxRequestBody: 0 },
|
|
2201
|
+
fetch: () => Promise.reject(Object.assign(new Error(NO_TRANSPORT), { status: 502 }))
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
function memoryCaStore() {
|
|
2205
|
+
const records = /* @__PURE__ */ new Map();
|
|
2206
|
+
return {
|
|
2207
|
+
get: async (owner) => records.get(owner),
|
|
2208
|
+
swap: async (owner, expected, record) => {
|
|
2209
|
+
const kept = winner(records.get(owner), expected, record);
|
|
2210
|
+
records.set(owner, kept);
|
|
2211
|
+
return kept;
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function caStore(metadata) {
|
|
2216
|
+
return metadata === false ? memoryCaStore() : indexedDbCaStore(`${metadata}-ca`);
|
|
2217
|
+
}
|
|
2218
|
+
function base64(bytes) {
|
|
2219
|
+
let text = "";
|
|
2220
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
2221
|
+
text += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
2222
|
+
}
|
|
2223
|
+
return btoa(text);
|
|
2224
|
+
}
|
|
2225
|
+
function packageTlsEngine(fs, modules) {
|
|
2226
|
+
const dir = `${modules.replace(/\/+$/, "")}/${TLS_ENGINE_PACKAGE}/dist`;
|
|
2227
|
+
return async () => {
|
|
2228
|
+
let glue;
|
|
2229
|
+
let wasmBinary;
|
|
2230
|
+
try {
|
|
2231
|
+
[glue, wasmBinary] = await Promise.all([
|
|
2232
|
+
fs.readFileBuffer(`${dir}/slicc-tls-engine.mjs`),
|
|
2233
|
+
fs.readFileBuffer(`${dir}/slicc-tls-engine.wasm`)
|
|
2234
|
+
]);
|
|
2235
|
+
} catch {
|
|
2236
|
+
throw new Error(`TLS needs ${TLS_ENGINE_PACKAGE} installed in ${modules}`);
|
|
2237
|
+
}
|
|
2238
|
+
const url = `data:text/javascript;base64,${base64(glue)}`;
|
|
2239
|
+
const factory = await import(url);
|
|
2240
|
+
return factory.default({ wasmBinary, locateFile: (path) => path });
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
function kernelCa(store) {
|
|
2244
|
+
return () => realmCa(CA_OWNER, store);
|
|
2245
|
+
}
|
|
2246
|
+
function kernelTlsEngine(load) {
|
|
2247
|
+
return () => loadTlsEngine(load);
|
|
2248
|
+
}
|
|
2249
|
+
async function writeCaFile(fs, ca) {
|
|
2250
|
+
const { pem: pem2 } = await ca();
|
|
2251
|
+
const current = await fs.exists(CA_PATH) ? await fs.readFile(CA_PATH) : void 0;
|
|
2252
|
+
if (current === pem2) return;
|
|
2253
|
+
await fs.mkdir(CA_PATH.slice(0, CA_PATH.lastIndexOf("/")), { recursive: true });
|
|
2254
|
+
await fs.writeFile(CA_PATH, pem2);
|
|
2255
|
+
}
|
|
2256
|
+
function enableNetwork(net, options) {
|
|
2257
|
+
let proxy;
|
|
2258
|
+
const transport = options.transport ?? missingTransport();
|
|
2259
|
+
net.activate({ family: "inet", host: "127.0.0.1", port: REALM_PROXY_PORT }, () => {
|
|
2260
|
+
const tls = new TlsTerminator({
|
|
2261
|
+
ca: options.ca,
|
|
2262
|
+
engine: options.engine,
|
|
2263
|
+
...options.now ? { now: options.now } : {}
|
|
2264
|
+
});
|
|
2265
|
+
const started = new RealmProxy({
|
|
2266
|
+
net,
|
|
2267
|
+
transport,
|
|
2268
|
+
tunnel: tls.handler,
|
|
2269
|
+
tunnelReady: async () => {
|
|
2270
|
+
await options.engine();
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
proxy = started;
|
|
2274
|
+
void started.closed.then(async () => {
|
|
2275
|
+
if (proxy === started) proxy = void 0;
|
|
2276
|
+
await tls.close();
|
|
2277
|
+
});
|
|
2278
|
+
});
|
|
2279
|
+
return () => proxy;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/kernel/net/remote-transport.ts
|
|
2283
|
+
var TransportError = class extends Error {
|
|
2284
|
+
status;
|
|
2285
|
+
constructor(message2, status) {
|
|
2286
|
+
super(message2);
|
|
2287
|
+
this.status = status;
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
var RemoteTransport = class {
|
|
2291
|
+
traits;
|
|
2292
|
+
port;
|
|
2293
|
+
waiting = /* @__PURE__ */ new Map();
|
|
2294
|
+
nextId = 0;
|
|
2295
|
+
constructor(port, traits) {
|
|
2296
|
+
this.port = port;
|
|
2297
|
+
this.traits = traits;
|
|
2298
|
+
}
|
|
2299
|
+
receive(reply) {
|
|
2300
|
+
const waiter = this.waiting.get(reply.nid);
|
|
2301
|
+
if (!waiter) return;
|
|
2302
|
+
this.waiting.delete(reply.nid);
|
|
2303
|
+
if (reply.net === "error") waiter.reject(new TransportError(reply.message, reply.status));
|
|
2304
|
+
else waiter.resolve(reply);
|
|
2305
|
+
}
|
|
2306
|
+
ask(call) {
|
|
2307
|
+
return new Promise((resolve, reject) => {
|
|
2308
|
+
this.waiting.set(call.nid, { resolve, reject });
|
|
2309
|
+
this.port.postMessage(call);
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
cancel(nid) {
|
|
2313
|
+
const waiter = this.waiting.get(nid);
|
|
2314
|
+
this.waiting.delete(nid);
|
|
2315
|
+
waiter?.reject(new TransportError("the request was cancelled"));
|
|
2316
|
+
this.port.postMessage({ net: "cancel", nid });
|
|
2317
|
+
}
|
|
2318
|
+
async fetch(request2) {
|
|
2319
|
+
const nid = ++this.nextId;
|
|
2320
|
+
const { signal } = request2;
|
|
2321
|
+
signal.throwIfAborted();
|
|
2322
|
+
let finished = false;
|
|
2323
|
+
const abort = () => {
|
|
2324
|
+
finished = true;
|
|
2325
|
+
this.cancel(nid);
|
|
2326
|
+
};
|
|
2327
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
2328
|
+
const finish = () => {
|
|
2329
|
+
if (finished) return;
|
|
2330
|
+
finished = true;
|
|
2331
|
+
signal.removeEventListener("abort", abort);
|
|
2332
|
+
};
|
|
2333
|
+
let head;
|
|
2334
|
+
try {
|
|
2335
|
+
head = await this.ask({
|
|
2336
|
+
net: "fetch",
|
|
2337
|
+
nid,
|
|
2338
|
+
url: request2.url,
|
|
2339
|
+
method: request2.method,
|
|
2340
|
+
headers: request2.headers,
|
|
2341
|
+
...request2.body ? { body: request2.body } : {}
|
|
2342
|
+
});
|
|
2343
|
+
} catch (e) {
|
|
2344
|
+
finish();
|
|
2345
|
+
throw e;
|
|
2346
|
+
}
|
|
2347
|
+
const { status, statusText, headers } = head;
|
|
2348
|
+
const read = async () => {
|
|
2349
|
+
if (finished) return void 0;
|
|
2350
|
+
const reply = await this.ask({ net: "read", nid }).catch((e) => {
|
|
2351
|
+
finish();
|
|
2352
|
+
throw e;
|
|
2353
|
+
});
|
|
2354
|
+
if (reply.net === "chunk") return reply.bytes;
|
|
2355
|
+
finish();
|
|
2356
|
+
return void 0;
|
|
2357
|
+
};
|
|
2358
|
+
const cancel = async () => {
|
|
2359
|
+
if (finished) return;
|
|
2360
|
+
finish();
|
|
2361
|
+
this.cancel(nid);
|
|
2362
|
+
};
|
|
2363
|
+
return {
|
|
2364
|
+
status,
|
|
2365
|
+
statusText,
|
|
2366
|
+
headers,
|
|
2367
|
+
cancel,
|
|
2368
|
+
body: {
|
|
2369
|
+
[Symbol.asyncIterator]: () => ({
|
|
2370
|
+
next: async () => {
|
|
2371
|
+
const bytes = await read();
|
|
2372
|
+
return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
|
|
2373
|
+
},
|
|
2374
|
+
return: async () => {
|
|
2375
|
+
await cancel();
|
|
2376
|
+
return { value: void 0, done: true };
|
|
2377
|
+
}
|
|
2378
|
+
})
|
|
2379
|
+
}
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
|
|
2384
|
+
// src/kernel/signals.ts
|
|
2385
|
+
var SIG = {
|
|
2386
|
+
HUP: 1,
|
|
2387
|
+
INT: 2,
|
|
2388
|
+
QUIT: 3,
|
|
2389
|
+
ILL: 4,
|
|
2390
|
+
TRAP: 5,
|
|
2391
|
+
ABRT: 6,
|
|
2392
|
+
BUS: 7,
|
|
2393
|
+
FPE: 8,
|
|
2394
|
+
KILL: 9,
|
|
2395
|
+
USR1: 10,
|
|
2396
|
+
SEGV: 11,
|
|
2397
|
+
USR2: 12,
|
|
2398
|
+
PIPE: 13,
|
|
2399
|
+
ALRM: 14,
|
|
2400
|
+
TERM: 15,
|
|
2401
|
+
STKFLT: 16,
|
|
2402
|
+
CHLD: 17,
|
|
2403
|
+
CONT: 18,
|
|
2404
|
+
STOP: 19,
|
|
2405
|
+
TSTP: 20,
|
|
2406
|
+
TTIN: 21,
|
|
2407
|
+
TTOU: 22,
|
|
2408
|
+
URG: 23,
|
|
2409
|
+
XCPU: 24,
|
|
2410
|
+
XFSZ: 25,
|
|
2411
|
+
VTALRM: 26,
|
|
2412
|
+
PROF: 27,
|
|
2413
|
+
WINCH: 28,
|
|
2414
|
+
POLL: 29,
|
|
2415
|
+
PWR: 30,
|
|
2416
|
+
SYS: 31
|
|
2417
|
+
};
|
|
2418
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
2419
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
2420
|
+
function defaultAction(sig) {
|
|
2421
|
+
if (sig === SIG.CONT) return "continue";
|
|
2422
|
+
if (STOPPING.has(sig)) return "stop";
|
|
2423
|
+
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
2424
|
+
}
|
|
2425
|
+
function isSignal(sig) {
|
|
2426
|
+
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
2427
|
+
}
|
|
2428
|
+
function sigbit(sig) {
|
|
2429
|
+
return 1 << sig;
|
|
2430
|
+
}
|
|
2431
|
+
var SIGNAL_BY_NAME = {
|
|
2432
|
+
SIGINT: SIG.INT,
|
|
2433
|
+
SIGTERM: SIG.TERM,
|
|
2434
|
+
SIGKILL: SIG.KILL,
|
|
2435
|
+
SIGSTOP: SIG.STOP,
|
|
2436
|
+
SIGCONT: SIG.CONT
|
|
2437
|
+
};
|
|
2438
|
+
|
|
2439
|
+
// src/commands.ts
|
|
2440
|
+
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
2441
|
+
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2442
|
+
var PACKAGE = /\$\{package\}/g;
|
|
2443
|
+
function inside(pkg, rel) {
|
|
2444
|
+
if (typeof rel !== "string" || rel === "") return void 0;
|
|
2445
|
+
const clean = rel.replace(/^\.\//, "");
|
|
2446
|
+
if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
|
|
2447
|
+
return `${pkg}/${clean}`;
|
|
2448
|
+
}
|
|
2449
|
+
function envOf(pkg, raw) {
|
|
2450
|
+
const env = {};
|
|
2451
|
+
if (!raw || typeof raw !== "object") return env;
|
|
2452
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2453
|
+
if (!ENV_KEY.test(key)) continue;
|
|
2454
|
+
if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
|
|
2455
|
+
else if (value === null) env[key] = null;
|
|
2456
|
+
}
|
|
2457
|
+
return env;
|
|
2458
|
+
}
|
|
2459
|
+
function envFields(merged) {
|
|
2460
|
+
const env = {};
|
|
2461
|
+
const unset = [];
|
|
2462
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
2463
|
+
if (value === null) unset.push(key);
|
|
2464
|
+
else env[key] = value;
|
|
2465
|
+
}
|
|
2466
|
+
return {
|
|
2467
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
2468
|
+
...unset.length > 0 ? { unset } : {}
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
function argsOf(pkg, raw) {
|
|
2472
|
+
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
2473
|
+
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
2474
|
+
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
2475
|
+
}
|
|
2476
|
+
function abiOf(raw, fallback) {
|
|
2477
|
+
const abi = raw ?? fallback;
|
|
2478
|
+
return abi === "emscripten" || abi === "wasi" ? abi : void 0;
|
|
2479
|
+
}
|
|
2480
|
+
function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
2481
|
+
const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
|
|
2482
|
+
const script = inside(pkg, raw.script);
|
|
2483
|
+
if (script) {
|
|
2484
|
+
return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
|
|
2485
|
+
}
|
|
2486
|
+
const abi = abiOf(raw.abi, packageAbi);
|
|
2487
|
+
const wasm = inside(pkg, raw.wasm);
|
|
2488
|
+
const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
|
|
2489
|
+
if (!abi || !glue || !wasm) return void 0;
|
|
2490
|
+
const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
|
|
2491
|
+
const args = argsOf(pkg, raw.args);
|
|
2492
|
+
const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
|
|
2493
|
+
return {
|
|
2494
|
+
name: name2,
|
|
2495
|
+
abi,
|
|
2496
|
+
glue,
|
|
2497
|
+
wasm,
|
|
2498
|
+
argv0,
|
|
2499
|
+
...args ? { args } : {},
|
|
2500
|
+
...withEnv,
|
|
2501
|
+
...imports ? { imports } : {}
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
function commandsOf(pkg, manifest) {
|
|
2505
|
+
const slicc = manifest.slicc;
|
|
2506
|
+
if (!slicc || typeof slicc !== "object") return [];
|
|
2507
|
+
const packageAbi = abiOf(slicc.abi, "emscripten");
|
|
2508
|
+
const entries = slicc.commands;
|
|
2509
|
+
if (!packageAbi || !entries || typeof entries !== "object") return [];
|
|
2510
|
+
const shared = envOf(pkg, slicc.env);
|
|
2511
|
+
const out = [];
|
|
2512
|
+
for (const [name2, raw] of Object.entries(entries)) {
|
|
2513
|
+
if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
const command = commandOf(pkg, name2, raw, packageAbi, shared);
|
|
2517
|
+
if (command) out.push(command);
|
|
2518
|
+
}
|
|
2519
|
+
return out;
|
|
2520
|
+
}
|
|
2521
|
+
async function withPackagePaths(fs, pkg, command) {
|
|
2522
|
+
if (!command.env) return command;
|
|
2523
|
+
const env = {};
|
|
2524
|
+
for (const [key, value] of Object.entries(command.env)) {
|
|
2525
|
+
const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
|
|
2526
|
+
env[key] = path && await fs.exists(path) ? path : value;
|
|
2527
|
+
}
|
|
2528
|
+
return { ...command, env };
|
|
2529
|
+
}
|
|
2530
|
+
async function packages(fs, modules) {
|
|
2531
|
+
const names = await fs.readdir(modules).catch(() => []);
|
|
2532
|
+
const dirs = [];
|
|
2533
|
+
for (const name2 of names) {
|
|
2534
|
+
if (name2.startsWith(".")) continue;
|
|
2535
|
+
if (!name2.startsWith("@")) {
|
|
2536
|
+
dirs.push(`${modules}/${name2}`);
|
|
2537
|
+
continue;
|
|
2538
|
+
}
|
|
2539
|
+
for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
|
|
2540
|
+
dirs.push(`${modules}/${name2}/${inner}`);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
return dirs;
|
|
2544
|
+
}
|
|
2545
|
+
async function scanCommands(fs, modules) {
|
|
2546
|
+
const found = /* @__PURE__ */ new Map();
|
|
2547
|
+
for (const pkg of await packages(fs, modules)) {
|
|
2548
|
+
let manifest;
|
|
2549
|
+
try {
|
|
2550
|
+
manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
|
|
2551
|
+
} catch {
|
|
2552
|
+
continue;
|
|
2553
|
+
}
|
|
2554
|
+
for (const command of commandsOf(pkg, manifest)) {
|
|
2555
|
+
if (!found.has(command.name))
|
|
2556
|
+
found.set(command.name, await withPackagePaths(fs, pkg, command));
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
if (!found.has("sh") && found.has("bash")) {
|
|
2560
|
+
found.set("sh", { ...found.get("bash"), name: "sh", argv0: "sh" });
|
|
2561
|
+
}
|
|
2562
|
+
return found;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// src/fs/commands.ts
|
|
2566
|
+
var S_IFDIR2 = 16384;
|
|
2567
|
+
var S_IFREG2 = 32768;
|
|
2568
|
+
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
2569
|
+
var MAX_LINKS2 = 40;
|
|
2570
|
+
function missing2(err) {
|
|
2571
|
+
return err?.code === "ENOENT";
|
|
2572
|
+
}
|
|
2573
|
+
function synthetic(directory, path) {
|
|
2574
|
+
const epoch = /* @__PURE__ */ new Date(0);
|
|
2575
|
+
return {
|
|
2576
|
+
isFile: !directory,
|
|
2577
|
+
isDirectory: directory,
|
|
2578
|
+
isSymbolicLink: false,
|
|
2579
|
+
size: 0,
|
|
2580
|
+
mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
|
|
2581
|
+
mtime: epoch,
|
|
2582
|
+
atime: epoch,
|
|
2583
|
+
ctime: epoch,
|
|
2584
|
+
ino: inodeOf(path)
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
async function followLinks(fs, path) {
|
|
2588
|
+
let current = path;
|
|
2589
|
+
for (let hops = 0; hops < MAX_LINKS2; hops++) {
|
|
2590
|
+
const st = await fs.lstat(current).catch(() => void 0);
|
|
2591
|
+
if (!st?.isSymbolicLink) return current;
|
|
2592
|
+
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
2593
|
+
current = fs.resolvePath(parent, await fs.readlink(current));
|
|
2594
|
+
}
|
|
2595
|
+
return current;
|
|
2596
|
+
}
|
|
2597
|
+
function withCommandDirs(fs, names) {
|
|
2598
|
+
async function children2(path) {
|
|
2599
|
+
if (path === "/") return ["bin", "usr"];
|
|
2600
|
+
if (path === "/usr") return ["bin"];
|
|
2601
|
+
if (path === "/bin" || path === "/usr/bin") return [...await names()];
|
|
2602
|
+
return void 0;
|
|
2603
|
+
}
|
|
2604
|
+
async function virtual(path) {
|
|
2605
|
+
if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
|
|
2606
|
+
const name2 = COMMAND.exec(path)?.[1];
|
|
2607
|
+
return name2 !== void 0 && (await names()).has(name2) ? synthetic(false, path) : void 0;
|
|
2608
|
+
}
|
|
2609
|
+
async function linked(path) {
|
|
2610
|
+
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
2611
|
+
}
|
|
2612
|
+
async function stat(path, real) {
|
|
2613
|
+
try {
|
|
2614
|
+
return await real(path);
|
|
2615
|
+
} catch (err) {
|
|
2616
|
+
const found = missing2(err) ? await linked(path) : void 0;
|
|
2617
|
+
if (found) return found;
|
|
2618
|
+
throw err;
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
return {
|
|
2622
|
+
resolvePath: (base, path) => fs.resolvePath(base, path),
|
|
2623
|
+
readFile: (path) => fs.readFile(path),
|
|
2624
|
+
async readFileBuffer(path) {
|
|
2625
|
+
try {
|
|
2626
|
+
return await fs.readFileBuffer(path);
|
|
2627
|
+
} catch (err) {
|
|
2628
|
+
if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
|
|
2629
|
+
throw err;
|
|
2630
|
+
}
|
|
2631
|
+
},
|
|
2632
|
+
writeFile: (path, content) => fs.writeFile(path, content),
|
|
2633
|
+
async exists(path) {
|
|
2634
|
+
return await fs.exists(path) || await linked(path) !== void 0;
|
|
2635
|
+
},
|
|
2636
|
+
stat: (path) => stat(path, (p) => fs.stat(p)),
|
|
2637
|
+
lstat: (path) => stat(path, (p) => fs.lstat(p)),
|
|
2638
|
+
async readdir(path) {
|
|
2639
|
+
const extra = await children2(path);
|
|
2640
|
+
if (!extra) return fs.readdir(path);
|
|
2641
|
+
const real = await fs.readdir(path).catch((err) => {
|
|
2642
|
+
if (missing2(err)) return [];
|
|
2643
|
+
throw err;
|
|
2644
|
+
});
|
|
2645
|
+
return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
|
|
2646
|
+
},
|
|
2647
|
+
async readdirStat(path) {
|
|
2648
|
+
const extra = await children2(path);
|
|
2649
|
+
if (!extra) return fs.readdirStat(path);
|
|
2650
|
+
const real = await fs.readdirStat(path).catch((err) => {
|
|
2651
|
+
if (missing2(err)) return [];
|
|
2652
|
+
throw err;
|
|
2653
|
+
});
|
|
2654
|
+
const seen = new Set(real.map(([name2]) => name2));
|
|
2655
|
+
const base = path === "/" ? "" : path;
|
|
2656
|
+
const added = extra.filter((name2) => !seen.has(name2));
|
|
2657
|
+
const virtuals = await Promise.all(
|
|
2658
|
+
added.map(
|
|
2659
|
+
async (name2) => [
|
|
2660
|
+
name2,
|
|
2661
|
+
await virtual(`${base}/${name2}`)
|
|
2662
|
+
]
|
|
2663
|
+
)
|
|
2664
|
+
);
|
|
2665
|
+
return [...real, ...virtuals].sort(([a], [b]) => a < b ? -1 : 1);
|
|
2666
|
+
},
|
|
2667
|
+
mkdir: (path, options) => fs.mkdir(path, options),
|
|
2668
|
+
rm: (path, options) => fs.rm(path, options),
|
|
2669
|
+
rename: (from, to) => fs.rename(from, to),
|
|
2670
|
+
symlink: (target, path) => fs.symlink(target, path),
|
|
2671
|
+
readlink: (path) => fs.readlink(path),
|
|
2672
|
+
chmod: (path, mode) => fs.chmod(path, mode),
|
|
2673
|
+
utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
1194
2676
|
|
|
1195
2677
|
// src/kernel/children.ts
|
|
1196
2678
|
var SpawnError = class extends Error {
|
|
@@ -1214,12 +2696,12 @@ var CONTINUED_STATUS = 65535;
|
|
|
1214
2696
|
function waitStatus(code, termsig) {
|
|
1215
2697
|
return termsig ? termsig & 127 : (code & 255) << 8;
|
|
1216
2698
|
}
|
|
1217
|
-
function
|
|
2699
|
+
function concat3(chunks) {
|
|
1218
2700
|
const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
|
|
1219
2701
|
let offset = 0;
|
|
1220
|
-
for (const
|
|
1221
|
-
out.set(
|
|
1222
|
-
offset +=
|
|
2702
|
+
for (const chunk2 of chunks) {
|
|
2703
|
+
out.set(chunk2, offset);
|
|
2704
|
+
offset += chunk2.length;
|
|
1223
2705
|
}
|
|
1224
2706
|
return out;
|
|
1225
2707
|
}
|
|
@@ -1308,8 +2790,8 @@ var ChildTable = class {
|
|
|
1308
2790
|
for (; ; ) {
|
|
1309
2791
|
const candidates = this.candidates(pid, flags);
|
|
1310
2792
|
if (candidates.length === 0) throw new KernelError("ECHILD");
|
|
1311
|
-
const
|
|
1312
|
-
if (
|
|
2793
|
+
const done4 = candidates.find(([, child]) => child.code !== void 0);
|
|
2794
|
+
if (done4) return this.reap(done4[0], done4[1].code);
|
|
1313
2795
|
const changed = this.stateReport(candidates, flags);
|
|
1314
2796
|
if (changed) return changed;
|
|
1315
2797
|
if (nohang) return [0, 0];
|
|
@@ -1371,7 +2853,7 @@ var ChildTable = class {
|
|
|
1371
2853
|
if (!slots || !chunks) return new Uint8Array(0);
|
|
1372
2854
|
slots.delete(slot);
|
|
1373
2855
|
if (slots.size === 0) this.leftovers.delete(pid);
|
|
1374
|
-
return
|
|
2856
|
+
return concat3(chunks);
|
|
1375
2857
|
}
|
|
1376
2858
|
};
|
|
1377
2859
|
|
|
@@ -1397,12 +2879,12 @@ function syncError(code, label) {
|
|
|
1397
2879
|
|
|
1398
2880
|
// src/realm/sync-fs-dispatch.ts
|
|
1399
2881
|
function toErrno(err) {
|
|
1400
|
-
const
|
|
2882
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
1401
2883
|
const code = err?.code;
|
|
1402
2884
|
if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
|
|
1403
|
-
return { ok: false, errno: code, message };
|
|
2885
|
+
return { ok: false, errno: code, message: message2 };
|
|
1404
2886
|
}
|
|
1405
|
-
return { ok: false, errno: "EIO", message };
|
|
2887
|
+
return { ok: false, errno: "EIO", message: message2 };
|
|
1406
2888
|
}
|
|
1407
2889
|
function statJson(s) {
|
|
1408
2890
|
return {
|
|
@@ -1418,7 +2900,7 @@ function statJson(s) {
|
|
|
1418
2900
|
};
|
|
1419
2901
|
}
|
|
1420
2902
|
async function readdirStat(fs, dir) {
|
|
1421
|
-
return (await fs.readdirStat(dir)).map(([
|
|
2903
|
+
return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
|
|
1422
2904
|
}
|
|
1423
2905
|
var done = { ok: true, kind: "void" };
|
|
1424
2906
|
function json(value) {
|
|
@@ -1537,7 +3019,7 @@ function sabViews(sab) {
|
|
|
1537
3019
|
var PENDING_TTL_MS = SYNC_FS_REQUEST_TIMEOUT_MS + 5e3;
|
|
1538
3020
|
function attachSyncSabResponder(port, sab, token, opts) {
|
|
1539
3021
|
const views = sabViews(sab);
|
|
1540
|
-
const { header, window } = views;
|
|
3022
|
+
const { header: header2, window } = views;
|
|
1541
3023
|
const pending = /* @__PURE__ */ new Map();
|
|
1542
3024
|
let disposed = false;
|
|
1543
3025
|
const { dispatch } = opts;
|
|
@@ -1548,16 +3030,16 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1548
3030
|
pending.delete(id);
|
|
1549
3031
|
}
|
|
1550
3032
|
function publish(id, entry, offset) {
|
|
1551
|
-
const
|
|
1552
|
-
if (
|
|
1553
|
-
Atomics.store(
|
|
1554
|
-
Atomics.store(
|
|
1555
|
-
Atomics.store(
|
|
1556
|
-
Atomics.store(
|
|
1557
|
-
Atomics.store(
|
|
1558
|
-
Atomics.store(
|
|
1559
|
-
Atomics.notify(
|
|
1560
|
-
if (offset +
|
|
3033
|
+
const chunk2 = Math.min(window.byteLength, entry.payload.byteLength - offset);
|
|
3034
|
+
if (chunk2 > 0) window.set(entry.payload.subarray(offset, offset + chunk2));
|
|
3035
|
+
Atomics.store(header2, SAB_I_STATUS, entry.status);
|
|
3036
|
+
Atomics.store(header2, SAB_I_TOTAL, entry.payload.byteLength);
|
|
3037
|
+
Atomics.store(header2, SAB_I_CHUNK, chunk2);
|
|
3038
|
+
Atomics.store(header2, SAB_I_OFFSET, offset);
|
|
3039
|
+
Atomics.store(header2, SAB_I_SEQ, id);
|
|
3040
|
+
Atomics.store(header2, SAB_I_STATE, SAB_STATE_READY);
|
|
3041
|
+
Atomics.notify(header2, SAB_I_STATE);
|
|
3042
|
+
if (offset + chunk2 >= entry.payload.byteLength) drop(id);
|
|
1561
3043
|
}
|
|
1562
3044
|
function settle2(id, result) {
|
|
1563
3045
|
if (disposed) return;
|
|
@@ -1619,6 +3101,273 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1619
3101
|
};
|
|
1620
3102
|
}
|
|
1621
3103
|
|
|
3104
|
+
// src/kernel/host-ops.ts
|
|
3105
|
+
var HOST_OPS = [
|
|
3106
|
+
"lock",
|
|
3107
|
+
"unlock",
|
|
3108
|
+
"async-submit",
|
|
3109
|
+
"async-wait",
|
|
3110
|
+
"async-take",
|
|
3111
|
+
"async-resolve",
|
|
3112
|
+
"async-hold",
|
|
3113
|
+
"async-cancel",
|
|
3114
|
+
"async-close"
|
|
3115
|
+
];
|
|
3116
|
+
var json2 = (value) => ({ ok: true, kind: "json", json: value });
|
|
3117
|
+
var done2 = { ok: true, kind: "void" };
|
|
3118
|
+
var LockTable = class {
|
|
3119
|
+
held = /* @__PURE__ */ new Map();
|
|
3120
|
+
lock(pid, path, exclusive, fd) {
|
|
3121
|
+
const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
|
|
3122
|
+
for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
|
|
3123
|
+
const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
|
|
3124
|
+
mine.exclusive = exclusive;
|
|
3125
|
+
mine.fds.add(fd);
|
|
3126
|
+
holders.set(pid, mine);
|
|
3127
|
+
this.held.set(path, holders);
|
|
3128
|
+
return true;
|
|
3129
|
+
}
|
|
3130
|
+
unlock(pid, path) {
|
|
3131
|
+
const holders = this.held.get(path);
|
|
3132
|
+
holders?.delete(pid);
|
|
3133
|
+
if (holders?.size === 0) this.held.delete(path);
|
|
3134
|
+
}
|
|
3135
|
+
closed(pid, fd) {
|
|
3136
|
+
for (const [path, holders] of [...this.held]) {
|
|
3137
|
+
const mine = holders.get(pid);
|
|
3138
|
+
if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
release(pid) {
|
|
3142
|
+
for (const path of [...this.held.keys()]) this.unlock(pid, path);
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
var AsyncOps = class {
|
|
3146
|
+
next = 1;
|
|
3147
|
+
results = /* @__PURE__ */ new Map();
|
|
3148
|
+
ready = [];
|
|
3149
|
+
waiters = /* @__PURE__ */ new Set();
|
|
3150
|
+
closed = false;
|
|
3151
|
+
run;
|
|
3152
|
+
constructor(run2) {
|
|
3153
|
+
this.run = run2;
|
|
3154
|
+
}
|
|
3155
|
+
submit(req) {
|
|
3156
|
+
const id = this.hold();
|
|
3157
|
+
this.run(req).then(
|
|
3158
|
+
(result) => this.complete(id, result),
|
|
3159
|
+
(err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
|
|
3160
|
+
);
|
|
3161
|
+
return id;
|
|
3162
|
+
}
|
|
3163
|
+
resolve(value) {
|
|
3164
|
+
const id = this.hold();
|
|
3165
|
+
this.complete(id, json2(value));
|
|
3166
|
+
return id;
|
|
3167
|
+
}
|
|
3168
|
+
hold() {
|
|
3169
|
+
const id = this.next++;
|
|
3170
|
+
this.results.set(id, void 0);
|
|
3171
|
+
return id;
|
|
3172
|
+
}
|
|
3173
|
+
take(id) {
|
|
3174
|
+
if (!this.results.has(id)) throw new KernelError("EINVAL");
|
|
3175
|
+
const result = this.results.get(id);
|
|
3176
|
+
if (!result) throw new KernelError("EAGAIN");
|
|
3177
|
+
this.cancel(id);
|
|
3178
|
+
return result;
|
|
3179
|
+
}
|
|
3180
|
+
cancel(id) {
|
|
3181
|
+
this.results.delete(id);
|
|
3182
|
+
const at = this.ready.indexOf(id);
|
|
3183
|
+
if (at >= 0) this.ready.splice(at, 1);
|
|
3184
|
+
}
|
|
3185
|
+
async wait(timeoutMs, signal) {
|
|
3186
|
+
for (; ; ) {
|
|
3187
|
+
if (this.closed) return -1;
|
|
3188
|
+
const id = this.ready.shift();
|
|
3189
|
+
if (id !== void 0) return id;
|
|
3190
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
|
|
3191
|
+
const woke = await this.sleep(timeoutMs, signal);
|
|
3192
|
+
if (!woke) return 0;
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
close() {
|
|
3196
|
+
this.closed = true;
|
|
3197
|
+
this.wake();
|
|
3198
|
+
}
|
|
3199
|
+
sleep(timeoutMs, signal) {
|
|
3200
|
+
return new Promise((resolve, reject) => {
|
|
3201
|
+
let timer;
|
|
3202
|
+
const cleanup = () => {
|
|
3203
|
+
clearTimeout(timer);
|
|
3204
|
+
this.waiters.delete(wake);
|
|
3205
|
+
signal.removeEventListener("abort", abort);
|
|
3206
|
+
};
|
|
3207
|
+
const wake = () => {
|
|
3208
|
+
cleanup();
|
|
3209
|
+
resolve(true);
|
|
3210
|
+
};
|
|
3211
|
+
const abort = () => {
|
|
3212
|
+
cleanup();
|
|
3213
|
+
reject(new KernelError("EINTR"));
|
|
3214
|
+
};
|
|
3215
|
+
this.waiters.add(wake);
|
|
3216
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3217
|
+
if (timeoutMs !== void 0) {
|
|
3218
|
+
timer = setTimeout(() => {
|
|
3219
|
+
cleanup();
|
|
3220
|
+
resolve(false);
|
|
3221
|
+
}, timeoutMs);
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
complete(id, result) {
|
|
3226
|
+
if (!this.results.has(id)) return;
|
|
3227
|
+
this.results.set(id, result);
|
|
3228
|
+
this.ready.push(id);
|
|
3229
|
+
this.wake();
|
|
3230
|
+
}
|
|
3231
|
+
wake() {
|
|
3232
|
+
for (const wake of [...this.waiters]) wake();
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
async function hostSyscall(req, ctx) {
|
|
3236
|
+
switch (req.op) {
|
|
3237
|
+
case "lock":
|
|
3238
|
+
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
3239
|
+
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
3240
|
+
throw new KernelError("EAGAIN");
|
|
3241
|
+
return done2;
|
|
3242
|
+
case "unlock":
|
|
3243
|
+
ctx.locks?.unlock(ctx.pid, req.path);
|
|
3244
|
+
return done2;
|
|
3245
|
+
case "async-submit":
|
|
3246
|
+
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
3247
|
+
return json2(ctx.ops.submit(req.req));
|
|
3248
|
+
case "async-wait":
|
|
3249
|
+
return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
3250
|
+
case "async-take":
|
|
3251
|
+
return ctx.ops.take(req.id);
|
|
3252
|
+
case "async-resolve":
|
|
3253
|
+
return json2(ctx.ops.resolve(req.value));
|
|
3254
|
+
case "async-hold":
|
|
3255
|
+
return json2(ctx.ops.hold());
|
|
3256
|
+
case "async-cancel":
|
|
3257
|
+
ctx.ops.cancel(req.id);
|
|
3258
|
+
return done2;
|
|
3259
|
+
case "async-close":
|
|
3260
|
+
ctx.ops.close();
|
|
3261
|
+
return done2;
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
// src/kernel/net/http-syscalls.ts
|
|
3266
|
+
var HTTP_OPS = ["net-request", "net-read", "net-close"];
|
|
3267
|
+
var EMPTY = new Uint8Array(0);
|
|
3268
|
+
function message(e) {
|
|
3269
|
+
return e instanceof Error ? e.message : String(e);
|
|
3270
|
+
}
|
|
3271
|
+
async function* once(bytes) {
|
|
3272
|
+
yield bytes;
|
|
3273
|
+
}
|
|
3274
|
+
var HttpHandles = class {
|
|
3275
|
+
transport;
|
|
3276
|
+
open = /* @__PURE__ */ new Map();
|
|
3277
|
+
nextHandle = 0;
|
|
3278
|
+
constructor(transport) {
|
|
3279
|
+
this.transport = transport;
|
|
3280
|
+
}
|
|
3281
|
+
syscall(req) {
|
|
3282
|
+
if (req.op === "net-request") return this.request(req);
|
|
3283
|
+
if (req.op === "net-read") return this.read(req.handle, req.max);
|
|
3284
|
+
return this.close(req.handle);
|
|
3285
|
+
}
|
|
3286
|
+
add(abort, body, cancel) {
|
|
3287
|
+
const handle = ++this.nextHandle;
|
|
3288
|
+
this.open.set(handle, {
|
|
3289
|
+
abort,
|
|
3290
|
+
body: body[Symbol.asyncIterator](),
|
|
3291
|
+
left: EMPTY,
|
|
3292
|
+
done: false,
|
|
3293
|
+
reading: Promise.resolve(),
|
|
3294
|
+
cancel
|
|
3295
|
+
});
|
|
3296
|
+
return handle;
|
|
3297
|
+
}
|
|
3298
|
+
async request(req) {
|
|
3299
|
+
const abort = new AbortController();
|
|
3300
|
+
try {
|
|
3301
|
+
const response = await this.transport.fetch({
|
|
3302
|
+
url: req.url,
|
|
3303
|
+
method: req.method,
|
|
3304
|
+
headers: req.headers,
|
|
3305
|
+
...req.body ? { body: req.body } : {},
|
|
3306
|
+
signal: abort.signal
|
|
3307
|
+
});
|
|
3308
|
+
const handle = this.add(abort, response.body, () => response.cancel());
|
|
3309
|
+
const head = {
|
|
3310
|
+
handle,
|
|
3311
|
+
status: response.status,
|
|
3312
|
+
statusText: response.statusText,
|
|
3313
|
+
url: req.url,
|
|
3314
|
+
headers: response.headers
|
|
3315
|
+
};
|
|
3316
|
+
return { ok: true, kind: "json", json: head };
|
|
3317
|
+
} catch (e) {
|
|
3318
|
+
const text = message(e);
|
|
3319
|
+
if (text === NO_TRANSPORT) return { ok: false, errno: "ENETUNREACH", message: text };
|
|
3320
|
+
const status = e.status;
|
|
3321
|
+
if (typeof status !== "number" || status === 502) {
|
|
3322
|
+
return { ok: false, errno: "ECONNREFUSED", message: text };
|
|
3323
|
+
}
|
|
3324
|
+
const body = new TextEncoder().encode(text);
|
|
3325
|
+
const handle = this.add(abort, once(body), async () => void 0);
|
|
3326
|
+
const head = {
|
|
3327
|
+
handle,
|
|
3328
|
+
status,
|
|
3329
|
+
statusText: "",
|
|
3330
|
+
url: req.url,
|
|
3331
|
+
headers: [["content-type", "text/plain; charset=utf-8"]]
|
|
3332
|
+
};
|
|
3333
|
+
return { ok: true, kind: "json", json: head };
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
async read(handle, max) {
|
|
3337
|
+
const entry = this.open.get(handle);
|
|
3338
|
+
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
3339
|
+
const turn = entry.reading.then(() => this.take(entry, max));
|
|
3340
|
+
entry.reading = turn.then(() => void 0);
|
|
3341
|
+
return turn;
|
|
3342
|
+
}
|
|
3343
|
+
async take(entry, max) {
|
|
3344
|
+
if (entry.left.length === 0 && !entry.done) {
|
|
3345
|
+
try {
|
|
3346
|
+
const next = await entry.body.next();
|
|
3347
|
+
if (next.done) entry.done = true;
|
|
3348
|
+
else entry.left = next.value;
|
|
3349
|
+
} catch (e) {
|
|
3350
|
+
entry.done = true;
|
|
3351
|
+
return { ok: false, errno: "EIO", message: message(e) };
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
const bytes = entry.left.subarray(0, Math.max(0, max));
|
|
3355
|
+
entry.left = entry.left.subarray(bytes.length);
|
|
3356
|
+
return { ok: true, kind: "bytes", bytes };
|
|
3357
|
+
}
|
|
3358
|
+
async close(handle) {
|
|
3359
|
+
const entry = this.open.get(handle);
|
|
3360
|
+
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
3361
|
+
this.open.delete(handle);
|
|
3362
|
+
entry.abort.abort();
|
|
3363
|
+
await entry.cancel().catch(() => void 0);
|
|
3364
|
+
return { ok: true, kind: "void" };
|
|
3365
|
+
}
|
|
3366
|
+
async closeAll() {
|
|
3367
|
+
await Promise.all([...this.open.keys()].map((handle) => this.close(handle)));
|
|
3368
|
+
}
|
|
3369
|
+
};
|
|
3370
|
+
|
|
1622
3371
|
// src/kernel/tty.ts
|
|
1623
3372
|
var ICRNL = 256;
|
|
1624
3373
|
var IUTF8 = 16384;
|
|
@@ -1787,9 +3536,9 @@ var KernelTty = class {
|
|
|
1787
3536
|
return n;
|
|
1788
3537
|
}
|
|
1789
3538
|
rubOut(n) {
|
|
1790
|
-
const
|
|
1791
|
-
const
|
|
1792
|
-
const cells =
|
|
3539
|
+
const dropped2 = this.line.splice(this.line.length - n, n);
|
|
3540
|
+
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
3541
|
+
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
1793
3542
|
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
1794
3543
|
this.output(encoder2.encode("\b \b".repeat(cells)), false);
|
|
1795
3544
|
}
|
|
@@ -1839,9 +3588,9 @@ var KernelTty = class {
|
|
|
1839
3588
|
}
|
|
1840
3589
|
const out = new Uint8Array(n);
|
|
1841
3590
|
let at = 0;
|
|
1842
|
-
for (const
|
|
1843
|
-
out.set(
|
|
1844
|
-
at +=
|
|
3591
|
+
for (const chunk2 of chunks) {
|
|
3592
|
+
out.set(chunk2, at);
|
|
3593
|
+
at += chunk2.length;
|
|
1845
3594
|
}
|
|
1846
3595
|
return out;
|
|
1847
3596
|
}
|
|
@@ -1938,9 +3687,9 @@ var PtyPair = class {
|
|
|
1938
3687
|
}
|
|
1939
3688
|
const out = new Uint8Array(status + n);
|
|
1940
3689
|
let at = status;
|
|
1941
|
-
for (const
|
|
1942
|
-
out.set(
|
|
1943
|
-
at +=
|
|
3690
|
+
for (const chunk2 of chunks) {
|
|
3691
|
+
out.set(chunk2, at);
|
|
3692
|
+
at += chunk2.length;
|
|
1944
3693
|
}
|
|
1945
3694
|
return out;
|
|
1946
3695
|
}
|
|
@@ -2023,41 +3772,41 @@ function terminalOf(ctx, fd) {
|
|
|
2023
3772
|
if (!tty) throw new KernelError("ENOTTY");
|
|
2024
3773
|
return tty;
|
|
2025
3774
|
}
|
|
2026
|
-
var
|
|
2027
|
-
var
|
|
3775
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
3776
|
+
var done3 = { ok: true, kind: "void" };
|
|
2028
3777
|
function ptySyscall(req, ctx) {
|
|
2029
3778
|
switch (req.op) {
|
|
2030
3779
|
case "pty-open": {
|
|
2031
3780
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
2032
|
-
return
|
|
3781
|
+
return json3(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
2033
3782
|
}
|
|
2034
3783
|
case "pty-slave-open": {
|
|
2035
3784
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
2036
3785
|
const pair = ctx.ptys.get(req.n);
|
|
2037
3786
|
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
2038
3787
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
2039
|
-
return
|
|
3788
|
+
return json3(fd);
|
|
2040
3789
|
}
|
|
2041
3790
|
case "pty-number":
|
|
2042
|
-
return
|
|
3791
|
+
return json3(masterOf(ctx, req.fd).index);
|
|
2043
3792
|
case "pty-lock":
|
|
2044
3793
|
masterOf(ctx, req.fd).locked = req.lock;
|
|
2045
|
-
return
|
|
3794
|
+
return done3;
|
|
2046
3795
|
case "pty-ctty": {
|
|
2047
3796
|
const tty = terminalOf(ctx, req.fd);
|
|
2048
3797
|
const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
|
|
2049
|
-
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return
|
|
3798
|
+
if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
|
|
2050
3799
|
if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
|
|
2051
|
-
return
|
|
3800
|
+
return done3;
|
|
2052
3801
|
}
|
|
2053
3802
|
case "pty-list":
|
|
2054
|
-
return
|
|
3803
|
+
return json3(ctx.ptys?.numbers() ?? []);
|
|
2055
3804
|
case "pty-packet":
|
|
2056
3805
|
masterOf(ctx, req.fd).packet = req.on;
|
|
2057
|
-
return
|
|
3806
|
+
return done3;
|
|
2058
3807
|
case "pty-winsz-set":
|
|
2059
3808
|
terminalOf(ctx, req.fd).resize(req.cols, req.rows);
|
|
2060
|
-
return
|
|
3809
|
+
return done3;
|
|
2061
3810
|
}
|
|
2062
3811
|
}
|
|
2063
3812
|
|
|
@@ -2111,6 +3860,391 @@ async function waitReady(fds, read, write, deadline, interrupted2) {
|
|
|
2111
3860
|
}
|
|
2112
3861
|
}
|
|
2113
3862
|
|
|
3863
|
+
// src/kernel/socket.ts
|
|
3864
|
+
var SOMAXCONN = 4096;
|
|
3865
|
+
var EPHEMERAL_FIRST = 32768;
|
|
3866
|
+
var EPHEMERAL_LAST = 60999;
|
|
3867
|
+
var SOL_SOCKET = 1;
|
|
3868
|
+
var SO_TYPE = 3;
|
|
3869
|
+
var SO_ERROR = 4;
|
|
3870
|
+
var SO_SNDBUF = 7;
|
|
3871
|
+
var SO_RCVBUF = 8;
|
|
3872
|
+
var SO_ACCEPTCONN = 30;
|
|
3873
|
+
var SO_DOMAIN = 39;
|
|
3874
|
+
var SOCK_STREAM = 1;
|
|
3875
|
+
var AF_UNIX = 1;
|
|
3876
|
+
var AF_INET = 2;
|
|
3877
|
+
var SHUT_RD = 0;
|
|
3878
|
+
var SHUT_WR = 1;
|
|
3879
|
+
var SHUT_RDWR = 2;
|
|
3880
|
+
function isLoopback(host) {
|
|
3881
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
3882
|
+
}
|
|
3883
|
+
async function pipeCall(op) {
|
|
3884
|
+
try {
|
|
3885
|
+
return await op();
|
|
3886
|
+
} catch (e) {
|
|
3887
|
+
if (e instanceof PipeError) throw new KernelError(e.code);
|
|
3888
|
+
throw e;
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
var Changes = class {
|
|
3892
|
+
waiters = [];
|
|
3893
|
+
wait(signal) {
|
|
3894
|
+
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
3895
|
+
return new Promise((resolve, reject) => {
|
|
3896
|
+
const onAbort = () => {
|
|
3897
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
3898
|
+
reject(new KernelError("EINTR"));
|
|
3899
|
+
};
|
|
3900
|
+
const waiter = () => {
|
|
3901
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3902
|
+
resolve();
|
|
3903
|
+
};
|
|
3904
|
+
this.waiters.push(waiter);
|
|
3905
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
3908
|
+
wake() {
|
|
3909
|
+
const waiters = this.waiters;
|
|
3910
|
+
this.waiters = [];
|
|
3911
|
+
for (const resolve of waiters) resolve();
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
function link(a, b) {
|
|
3915
|
+
const ab = new KernelPipe();
|
|
3916
|
+
const ba = new KernelPipe();
|
|
3917
|
+
for (const pipe of [ab, ba]) {
|
|
3918
|
+
pipe.openRead();
|
|
3919
|
+
pipe.openWrite();
|
|
3920
|
+
}
|
|
3921
|
+
a.attach({ rx: ba, tx: ab });
|
|
3922
|
+
b.attach({ rx: ab, tx: ba });
|
|
3923
|
+
}
|
|
3924
|
+
var KernelSocket = class _KernelSocket {
|
|
3925
|
+
net;
|
|
3926
|
+
domain;
|
|
3927
|
+
state = "open";
|
|
3928
|
+
local;
|
|
3929
|
+
peer;
|
|
3930
|
+
link;
|
|
3931
|
+
readShut = false;
|
|
3932
|
+
writeShut = false;
|
|
3933
|
+
queue = [];
|
|
3934
|
+
backlog = 0;
|
|
3935
|
+
bound = false;
|
|
3936
|
+
changes = new Changes();
|
|
3937
|
+
options = /* @__PURE__ */ new Map();
|
|
3938
|
+
constructor(net, domain) {
|
|
3939
|
+
this.net = net;
|
|
3940
|
+
this.domain = domain;
|
|
3941
|
+
}
|
|
3942
|
+
attach(link2) {
|
|
3943
|
+
this.link = link2;
|
|
3944
|
+
this.state = "connected";
|
|
3945
|
+
this.changes.wake();
|
|
3946
|
+
}
|
|
3947
|
+
get listening() {
|
|
3948
|
+
return this.state === "listening";
|
|
3949
|
+
}
|
|
3950
|
+
connection() {
|
|
3951
|
+
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
3952
|
+
return this.link;
|
|
3953
|
+
}
|
|
3954
|
+
async read(max, signal) {
|
|
3955
|
+
const { rx } = this.connection();
|
|
3956
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
3957
|
+
return pipeCall(() => rx.read(max, signal));
|
|
3958
|
+
}
|
|
3959
|
+
async peek(max, signal) {
|
|
3960
|
+
const { rx } = this.connection();
|
|
3961
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
3962
|
+
return pipeCall(() => rx.peek(max, signal));
|
|
3963
|
+
}
|
|
3964
|
+
async write(bytes, signal) {
|
|
3965
|
+
const { tx } = this.connection();
|
|
3966
|
+
if (this.writeShut) throw new KernelError("EPIPE");
|
|
3967
|
+
return pipeCall(() => tx.write(bytes, signal));
|
|
3968
|
+
}
|
|
3969
|
+
poll() {
|
|
3970
|
+
if (this.state === "listening") {
|
|
3971
|
+
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
3972
|
+
}
|
|
3973
|
+
if (this.state !== "connected" || !this.link) {
|
|
3974
|
+
return { readable: false, writable: false, hangup: true };
|
|
3975
|
+
}
|
|
3976
|
+
const { rx, tx } = this.link;
|
|
3977
|
+
return {
|
|
3978
|
+
readable: this.readShut || rx.readReady,
|
|
3979
|
+
writable: this.writeShut || tx.writeReady,
|
|
3980
|
+
hangup: rx.writersGone && tx.readersGone
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
changed(signal) {
|
|
3984
|
+
const own = this.changes.wait(signal);
|
|
3985
|
+
if (this.state !== "connected" || !this.link) return own;
|
|
3986
|
+
const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
|
|
3987
|
+
for (const wait of waits) wait.catch(() => void 0);
|
|
3988
|
+
return pipeCall(() => Promise.race(waits));
|
|
3989
|
+
}
|
|
3990
|
+
bind(addr) {
|
|
3991
|
+
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
3992
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
3993
|
+
this.local = this.net.bind(this, addr);
|
|
3994
|
+
this.bound = true;
|
|
3995
|
+
}
|
|
3996
|
+
listen(backlog) {
|
|
3997
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
3998
|
+
if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
|
|
3999
|
+
if (!this.local) {
|
|
4000
|
+
if (this.domain === "unix") throw new KernelError("EINVAL");
|
|
4001
|
+
this.bind({ family: "inet", host: "0.0.0.0", port: 0 });
|
|
4002
|
+
}
|
|
4003
|
+
this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
|
|
4004
|
+
this.state = "listening";
|
|
4005
|
+
}
|
|
4006
|
+
async accept(signal, nonblock = false) {
|
|
4007
|
+
for (; ; ) {
|
|
4008
|
+
if (this.state !== "listening") throw new KernelError("EINVAL");
|
|
4009
|
+
const next = this.queue.shift();
|
|
4010
|
+
if (next) return next;
|
|
4011
|
+
if (nonblock) throw new KernelError("EAGAIN");
|
|
4012
|
+
await this.changes.wait(signal);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
offer(server) {
|
|
4016
|
+
if (this.state !== "listening" || this.queue.length >= this.backlog) return false;
|
|
4017
|
+
this.queue.push(server);
|
|
4018
|
+
this.changes.wake();
|
|
4019
|
+
return true;
|
|
4020
|
+
}
|
|
4021
|
+
connect(addr) {
|
|
4022
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
4023
|
+
if (this.state !== "open") throw new KernelError("EINVAL");
|
|
4024
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
4025
|
+
const target = this.net.target(addr);
|
|
4026
|
+
if (!this.local) {
|
|
4027
|
+
if (addr.family === "inet") {
|
|
4028
|
+
this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
|
|
4029
|
+
this.bound = true;
|
|
4030
|
+
} else this.local = { family: "unix", path: "" };
|
|
4031
|
+
}
|
|
4032
|
+
const server = new _KernelSocket(this.net, this.domain);
|
|
4033
|
+
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost2(addr.host) } : addr;
|
|
4034
|
+
server.peer = this.local;
|
|
4035
|
+
this.peer = server.local;
|
|
4036
|
+
link(this, server);
|
|
4037
|
+
if (!target.offer(server)) {
|
|
4038
|
+
this.unlink();
|
|
4039
|
+
server.close();
|
|
4040
|
+
throw new KernelError("ECONNREFUSED");
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
unlink() {
|
|
4044
|
+
const link2 = this.link;
|
|
4045
|
+
if (link2) {
|
|
4046
|
+
link2.rx.closeRead();
|
|
4047
|
+
link2.tx.closeWrite();
|
|
4048
|
+
}
|
|
4049
|
+
this.link = void 0;
|
|
4050
|
+
this.peer = void 0;
|
|
4051
|
+
this.state = "open";
|
|
4052
|
+
}
|
|
4053
|
+
shutdown(how) {
|
|
4054
|
+
const { rx, tx } = this.connection();
|
|
4055
|
+
if (how !== SHUT_RD && how !== SHUT_WR && how !== SHUT_RDWR) throw new KernelError("EINVAL");
|
|
4056
|
+
if (how !== SHUT_WR && !this.readShut) {
|
|
4057
|
+
this.readShut = true;
|
|
4058
|
+
rx.closeRead();
|
|
4059
|
+
}
|
|
4060
|
+
if (how !== SHUT_RD && !this.writeShut) {
|
|
4061
|
+
this.writeShut = true;
|
|
4062
|
+
tx.closeWrite();
|
|
4063
|
+
}
|
|
4064
|
+
this.changes.wake();
|
|
4065
|
+
}
|
|
4066
|
+
getOption(level, name2) {
|
|
4067
|
+
if (level === SOL_SOCKET) {
|
|
4068
|
+
switch (name2) {
|
|
4069
|
+
case SO_TYPE:
|
|
4070
|
+
return SOCK_STREAM;
|
|
4071
|
+
case SO_ERROR:
|
|
4072
|
+
return 0;
|
|
4073
|
+
case SO_ACCEPTCONN:
|
|
4074
|
+
return this.state === "listening" ? 1 : 0;
|
|
4075
|
+
case SO_DOMAIN:
|
|
4076
|
+
return this.domain === "inet" ? AF_INET : AF_UNIX;
|
|
4077
|
+
case SO_SNDBUF:
|
|
4078
|
+
case SO_RCVBUF:
|
|
4079
|
+
return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
return this.options.get(`${level}:${name2}`) ?? 0;
|
|
4083
|
+
}
|
|
4084
|
+
setOption(level, name2, value) {
|
|
4085
|
+
this.options.set(`${level}:${name2}`, value);
|
|
4086
|
+
}
|
|
4087
|
+
close() {
|
|
4088
|
+
const was = this.state;
|
|
4089
|
+
this.state = "closed";
|
|
4090
|
+
if (was === "listening") {
|
|
4091
|
+
for (const pending of this.queue.splice(0)) pending.close();
|
|
4092
|
+
}
|
|
4093
|
+
if (this.link) {
|
|
4094
|
+
if (!this.readShut) this.link.rx.closeRead();
|
|
4095
|
+
if (!this.writeShut) this.link.tx.closeWrite();
|
|
4096
|
+
this.readShut = true;
|
|
4097
|
+
this.writeShut = true;
|
|
4098
|
+
}
|
|
4099
|
+
if (this.bound && this.local) this.net.release(this.local, this);
|
|
4100
|
+
this.bound = false;
|
|
4101
|
+
this.changes.wake();
|
|
4102
|
+
}
|
|
4103
|
+
static pair(net, domain) {
|
|
4104
|
+
const a = new _KernelSocket(net, domain);
|
|
4105
|
+
const b = new _KernelSocket(net, domain);
|
|
4106
|
+
const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
|
|
4107
|
+
a.local = b.local = a.peer = b.peer = name2;
|
|
4108
|
+
link(a, b);
|
|
4109
|
+
return [a, b];
|
|
4110
|
+
}
|
|
4111
|
+
};
|
|
4112
|
+
function canonicalHost2(host) {
|
|
4113
|
+
return host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
4114
|
+
}
|
|
4115
|
+
function keyOf(addr) {
|
|
4116
|
+
return addr.family === "inet" ? `inet:${addr.port}` : `unix:${addr.path}`;
|
|
4117
|
+
}
|
|
4118
|
+
var LoopbackNet = class {
|
|
4119
|
+
bound = /* @__PURE__ */ new Map();
|
|
4120
|
+
activators = /* @__PURE__ */ new Map();
|
|
4121
|
+
nextEphemeral = EPHEMERAL_FIRST;
|
|
4122
|
+
activate(addr, start) {
|
|
4123
|
+
this.activators.set(keyOf(addr), start);
|
|
4124
|
+
}
|
|
4125
|
+
socket(domain) {
|
|
4126
|
+
return new KernelSocket(this, domain);
|
|
4127
|
+
}
|
|
4128
|
+
listen(addr, backlog = 128) {
|
|
4129
|
+
const socket = this.socket(addr.family);
|
|
4130
|
+
socket.bind(addr);
|
|
4131
|
+
socket.listen(backlog);
|
|
4132
|
+
return socket;
|
|
4133
|
+
}
|
|
4134
|
+
connect(addr) {
|
|
4135
|
+
const socket = this.socket(addr.family);
|
|
4136
|
+
socket.connect(addr);
|
|
4137
|
+
return socket;
|
|
4138
|
+
}
|
|
4139
|
+
bind(socket, addr) {
|
|
4140
|
+
if (addr.family === "unix") {
|
|
4141
|
+
if (addr.path === "") throw new KernelError("EINVAL");
|
|
4142
|
+
} else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
|
|
4143
|
+
throw new KernelError("EADDRNOTAVAIL");
|
|
4144
|
+
}
|
|
4145
|
+
const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
|
|
4146
|
+
const key = keyOf(named);
|
|
4147
|
+
if (this.bound.has(key)) throw new KernelError("EADDRINUSE");
|
|
4148
|
+
this.bound.set(key, socket);
|
|
4149
|
+
return named;
|
|
4150
|
+
}
|
|
4151
|
+
release(addr, socket) {
|
|
4152
|
+
const key = keyOf(addr);
|
|
4153
|
+
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
4154
|
+
}
|
|
4155
|
+
target(addr) {
|
|
4156
|
+
if (addr.family === "inet" && !isLoopback(canonicalHost2(addr.host))) {
|
|
4157
|
+
throw new KernelError("ENETUNREACH");
|
|
4158
|
+
}
|
|
4159
|
+
const key = keyOf(addr);
|
|
4160
|
+
if (!this.bound.has(key)) this.activators.get(key)?.();
|
|
4161
|
+
const socket = this.bound.get(key);
|
|
4162
|
+
if (!socket?.listening) throw new KernelError("ECONNREFUSED");
|
|
4163
|
+
return socket;
|
|
4164
|
+
}
|
|
4165
|
+
ephemeral() {
|
|
4166
|
+
const span = EPHEMERAL_LAST - EPHEMERAL_FIRST + 1;
|
|
4167
|
+
for (let i = 0; i < span; i++) {
|
|
4168
|
+
const port = this.nextEphemeral;
|
|
4169
|
+
this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
|
|
4170
|
+
if (!this.bound.has(`inet:${port}`)) return port;
|
|
4171
|
+
}
|
|
4172
|
+
throw new KernelError("EADDRINUSE");
|
|
4173
|
+
}
|
|
4174
|
+
};
|
|
4175
|
+
|
|
4176
|
+
// src/kernel/socket-syscalls.ts
|
|
4177
|
+
var SOCKET_OPS = [
|
|
4178
|
+
"sock-open",
|
|
4179
|
+
"sock-pair",
|
|
4180
|
+
"sock-bind",
|
|
4181
|
+
"sock-listen",
|
|
4182
|
+
"sock-accept",
|
|
4183
|
+
"sock-connect",
|
|
4184
|
+
"sock-shutdown",
|
|
4185
|
+
"sock-name",
|
|
4186
|
+
"sock-getopt",
|
|
4187
|
+
"sock-setopt"
|
|
4188
|
+
];
|
|
4189
|
+
function socketAt(fds, fd) {
|
|
4190
|
+
const file = fds.get(fd).file;
|
|
4191
|
+
if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
|
|
4192
|
+
return file;
|
|
4193
|
+
}
|
|
4194
|
+
var ok = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
|
|
4195
|
+
async function socketSyscall(req, proc) {
|
|
4196
|
+
const { fds, net } = proc;
|
|
4197
|
+
switch (req.op) {
|
|
4198
|
+
case "sock-open":
|
|
4199
|
+
return ok(fds.install(new OpenFile(net.socket(req.domain)), 3));
|
|
4200
|
+
case "sock-pair": {
|
|
4201
|
+
const [a, b] = KernelSocket.pair(net, req.domain);
|
|
4202
|
+
const first = fds.install(new OpenFile(a), 3);
|
|
4203
|
+
try {
|
|
4204
|
+
return ok([first, fds.install(new OpenFile(b), 3)]);
|
|
4205
|
+
} catch (e) {
|
|
4206
|
+
await Promise.resolve(fds.close(first));
|
|
4207
|
+
throw e;
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
case "sock-bind":
|
|
4211
|
+
socketAt(fds, req.fd).bind(req.addr);
|
|
4212
|
+
return ok();
|
|
4213
|
+
case "sock-listen":
|
|
4214
|
+
socketAt(fds, req.fd).listen(req.backlog);
|
|
4215
|
+
return ok();
|
|
4216
|
+
case "sock-accept":
|
|
4217
|
+
return ok(await accept(proc, req.fd, req.nonblock));
|
|
4218
|
+
case "sock-connect":
|
|
4219
|
+
socketAt(fds, req.fd).connect(req.addr);
|
|
4220
|
+
if (req.nonblock) throw new KernelError("EINPROGRESS");
|
|
4221
|
+
return ok();
|
|
4222
|
+
case "sock-shutdown":
|
|
4223
|
+
socketAt(fds, req.fd).shutdown(req.how);
|
|
4224
|
+
return ok();
|
|
4225
|
+
case "sock-name": {
|
|
4226
|
+
const socket = socketAt(fds, req.fd);
|
|
4227
|
+
const addr = req.peer ? socket.peer : socket.local;
|
|
4228
|
+
if (req.peer && !addr) throw new KernelError("ENOTCONN");
|
|
4229
|
+
return ok(addr ?? unnamed(socket.domain));
|
|
4230
|
+
}
|
|
4231
|
+
case "sock-getopt":
|
|
4232
|
+
return ok(socketAt(fds, req.fd).getOption(req.level, req.name));
|
|
4233
|
+
case "sock-setopt":
|
|
4234
|
+
socketAt(fds, req.fd).setOption(req.level, req.name, req.value);
|
|
4235
|
+
return ok();
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
function unnamed(domain) {
|
|
4239
|
+
return domain === "inet" ? { family: "inet", host: "0.0.0.0", port: 0 } : { family: "unix", path: "" };
|
|
4240
|
+
}
|
|
4241
|
+
async function accept(proc, fd, nonblock) {
|
|
4242
|
+
const listener = socketAt(proc.fds, fd);
|
|
4243
|
+
const waits = !nonblock && !pollFile(listener).readable;
|
|
4244
|
+
const socket = await listener.accept(waits ? proc.blocking() : void 0, nonblock);
|
|
4245
|
+
return { fd: proc.fds.install(new OpenFile(socket), 3), peer: socket.peer };
|
|
4246
|
+
}
|
|
4247
|
+
|
|
2114
4248
|
// src/kernel/vfs-file.ts
|
|
2115
4249
|
var O_ACCMODE = 3;
|
|
2116
4250
|
var O_WRONLY = 1;
|
|
@@ -2140,14 +4274,14 @@ var VfsNode = class {
|
|
|
2140
4274
|
fs;
|
|
2141
4275
|
path;
|
|
2142
4276
|
orphaned;
|
|
2143
|
-
constructor(fs, path,
|
|
4277
|
+
constructor(fs, path, contents2, orphaned = false, dirty = false) {
|
|
2144
4278
|
this.fs = fs;
|
|
2145
4279
|
this.path = path;
|
|
2146
4280
|
this.orphaned = orphaned;
|
|
2147
|
-
if (
|
|
4281
|
+
if (contents2 !== void 0) this.data = new Uint8Array(contents2);
|
|
2148
4282
|
else if (orphaned) this.data = new Uint8Array(0);
|
|
2149
4283
|
this.length = this.data?.length ?? 0;
|
|
2150
|
-
if (dirty &&
|
|
4284
|
+
if (dirty && contents2 !== void 0) this.markDirty();
|
|
2151
4285
|
}
|
|
2152
4286
|
serial(op) {
|
|
2153
4287
|
const next = this.queue.then(op);
|
|
@@ -2167,8 +4301,8 @@ var VfsNode = class {
|
|
|
2167
4301
|
}
|
|
2168
4302
|
return this.data;
|
|
2169
4303
|
}
|
|
2170
|
-
replace(
|
|
2171
|
-
this.data = new Uint8Array(
|
|
4304
|
+
replace(contents2) {
|
|
4305
|
+
this.data = new Uint8Array(contents2);
|
|
2172
4306
|
this.length = this.data.length;
|
|
2173
4307
|
this.missing = false;
|
|
2174
4308
|
this.markDirty();
|
|
@@ -2249,13 +4383,13 @@ var VfsNodes = class {
|
|
|
2249
4383
|
node.opens++;
|
|
2250
4384
|
return node;
|
|
2251
4385
|
}
|
|
2252
|
-
adopt(path,
|
|
4386
|
+
adopt(path, contents2, dirty) {
|
|
2253
4387
|
let node = this.byPath.get(path);
|
|
2254
4388
|
if (!node) {
|
|
2255
|
-
node = new VfsNode(this.fs, path,
|
|
4389
|
+
node = new VfsNode(this.fs, path, contents2, false, dirty);
|
|
2256
4390
|
this.byPath.set(path, node);
|
|
2257
4391
|
} else if (dirty) {
|
|
2258
|
-
node.replace(
|
|
4392
|
+
node.replace(contents2);
|
|
2259
4393
|
}
|
|
2260
4394
|
node.opens++;
|
|
2261
4395
|
return node;
|
|
@@ -2377,6 +4511,15 @@ var JOB_OPS = /* @__PURE__ */ new Set([
|
|
|
2377
4511
|
function isJobSyscall(req) {
|
|
2378
4512
|
return JOB_OPS.has(req.op);
|
|
2379
4513
|
}
|
|
4514
|
+
function isHttpSyscall(req) {
|
|
4515
|
+
return req.op.startsWith("net-");
|
|
4516
|
+
}
|
|
4517
|
+
function isSocketSyscall(req) {
|
|
4518
|
+
return req.op.startsWith("sock-");
|
|
4519
|
+
}
|
|
4520
|
+
function isHostSyscall(req) {
|
|
4521
|
+
return HOST_OPS.includes(req.op);
|
|
4522
|
+
}
|
|
2380
4523
|
function isPtySyscall(req) {
|
|
2381
4524
|
return req.op.startsWith("pty-");
|
|
2382
4525
|
}
|
|
@@ -2414,6 +4557,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
2414
4557
|
"fd-cloexec",
|
|
2415
4558
|
"fd-list",
|
|
2416
4559
|
"proc-alarm",
|
|
4560
|
+
"dl-log",
|
|
2417
4561
|
"fd-renumber",
|
|
2418
4562
|
"fd-promote",
|
|
2419
4563
|
"fd-open-tty",
|
|
@@ -2435,7 +4579,10 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
2435
4579
|
"tty-pgrp-set",
|
|
2436
4580
|
"sig-mask",
|
|
2437
4581
|
"sig-pause",
|
|
2438
|
-
...
|
|
4582
|
+
...SOCKET_OPS,
|
|
4583
|
+
...HTTP_OPS,
|
|
4584
|
+
...PTY_OPS,
|
|
4585
|
+
...HOST_OPS
|
|
2439
4586
|
]);
|
|
2440
4587
|
function isWasmSyscall(req) {
|
|
2441
4588
|
const op = req.op;
|
|
@@ -2455,6 +4602,7 @@ var WasmProcess = class {
|
|
|
2455
4602
|
resumed = Promise.resolve();
|
|
2456
4603
|
wake;
|
|
2457
4604
|
stateListeners = [];
|
|
4605
|
+
net;
|
|
2458
4606
|
nodes;
|
|
2459
4607
|
pid;
|
|
2460
4608
|
fds;
|
|
@@ -2482,6 +4630,9 @@ var WasmProcess = class {
|
|
|
2482
4630
|
const action = defaultAction(sig);
|
|
2483
4631
|
return action === "stop" ? this.stop(sig) : action;
|
|
2484
4632
|
}
|
|
4633
|
+
if (((this.options.pendingBits?.() ?? 0) & bit) !== 0 && defaultAction(sig) === "terminate") {
|
|
4634
|
+
return "terminate";
|
|
4635
|
+
}
|
|
2485
4636
|
this.options.onPending?.(sig);
|
|
2486
4637
|
const blocked = this.interrupt;
|
|
2487
4638
|
this.interrupt = new AbortController();
|
|
@@ -2524,6 +4675,17 @@ var WasmProcess = class {
|
|
|
2524
4675
|
if (isFdSyscall(req)) return await this.fdSyscall(req);
|
|
2525
4676
|
if (isTtySyscall(req)) return this.ttySyscall(req);
|
|
2526
4677
|
if (isJobSyscall(req)) return this.jobSyscall(req);
|
|
4678
|
+
if (isSocketSyscall(req)) return await this.socketSyscall(req);
|
|
4679
|
+
if (isHttpSyscall(req)) return await this.httpSyscall(req);
|
|
4680
|
+
if (isHostSyscall(req)) {
|
|
4681
|
+
return await hostSyscall(req, {
|
|
4682
|
+
pid: this.pid,
|
|
4683
|
+
locks: this.options.locks,
|
|
4684
|
+
ops: this.asyncOps,
|
|
4685
|
+
blocking: () => this.blockingSignal(),
|
|
4686
|
+
valid: (inner) => isWasmSyscall(inner) && !isHostSyscall(inner)
|
|
4687
|
+
});
|
|
4688
|
+
}
|
|
2527
4689
|
if (isPtySyscall(req)) {
|
|
2528
4690
|
const { ptys, jobs } = this.options;
|
|
2529
4691
|
return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs });
|
|
@@ -2587,6 +4749,7 @@ var WasmProcess = class {
|
|
|
2587
4749
|
case "fd-write":
|
|
2588
4750
|
return { ok: true, kind: "json", json: await this.write(req.fd, req.body, req.nonblock) };
|
|
2589
4751
|
case "fd-close":
|
|
4752
|
+
this.options.locks?.closed(this.pid, req.fd);
|
|
2590
4753
|
await Promise.resolve(this.fds.close(req.fd));
|
|
2591
4754
|
return { ok: true, kind: "void" };
|
|
2592
4755
|
case "fd-pipe": {
|
|
@@ -2721,7 +4884,7 @@ var WasmProcess = class {
|
|
|
2721
4884
|
const flags = this.fds.statusFlags(fd);
|
|
2722
4885
|
return {
|
|
2723
4886
|
tty: file.tty !== void 0,
|
|
2724
|
-
kind: kernelFdKind(file),
|
|
4887
|
+
kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
|
|
2725
4888
|
...file.heldMeta ? { meta: file.heldMeta } : {},
|
|
2726
4889
|
...flags !== void 0 ? { flags } : {},
|
|
2727
4890
|
...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
@@ -2794,6 +4957,19 @@ var WasmProcess = class {
|
|
|
2794
4957
|
return { ok: true, kind: "json", json: jobs.setsid(this.pid) };
|
|
2795
4958
|
}
|
|
2796
4959
|
}
|
|
4960
|
+
httpSyscall(req) {
|
|
4961
|
+
const http = this.options.http;
|
|
4962
|
+
if (!http) return Promise.resolve({ ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT });
|
|
4963
|
+
return http.syscall(req);
|
|
4964
|
+
}
|
|
4965
|
+
socketSyscall(req) {
|
|
4966
|
+
this.net ??= this.options.net ?? new LoopbackNet();
|
|
4967
|
+
return socketSyscall(req, {
|
|
4968
|
+
fds: this.fds,
|
|
4969
|
+
net: this.net,
|
|
4970
|
+
blocking: () => this.blockingSignal()
|
|
4971
|
+
});
|
|
4972
|
+
}
|
|
2797
4973
|
tty(fd) {
|
|
2798
4974
|
const file = this.fds.get(fd).file;
|
|
2799
4975
|
const tty = file.tty ?? file.pty?.slave;
|
|
@@ -2845,6 +5021,9 @@ var WasmProcess = class {
|
|
|
2845
5021
|
case "proc-alarm":
|
|
2846
5022
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
2847
5023
|
return { ok: true, kind: "void" };
|
|
5024
|
+
case "dl-log":
|
|
5025
|
+
if (req.append) this.dlLog.push(req.append);
|
|
5026
|
+
return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
|
|
2848
5027
|
case "sig-mask":
|
|
2849
5028
|
this.caught = req.caught;
|
|
2850
5029
|
this.ignored = req.ignored;
|
|
@@ -2862,6 +5041,8 @@ var WasmProcess = class {
|
|
|
2862
5041
|
});
|
|
2863
5042
|
}
|
|
2864
5043
|
alarm;
|
|
5044
|
+
dlLog = [];
|
|
5045
|
+
asyncOps = new AsyncOps((req) => this.syscall(req));
|
|
2865
5046
|
alarmEvery;
|
|
2866
5047
|
setAlarm(sig, first, every, timer) {
|
|
2867
5048
|
if (!isSignal(sig)) throw new KernelError("EINVAL");
|
|
@@ -2890,8 +5071,11 @@ var WasmProcess = class {
|
|
|
2890
5071
|
if (this.exited) return;
|
|
2891
5072
|
this.exited = true;
|
|
2892
5073
|
this.clearAlarm();
|
|
5074
|
+
this.options.locks?.release(this.pid);
|
|
5075
|
+
this.asyncOps.close();
|
|
2893
5076
|
for (const pid of this.children.pids()) this.options.onReap?.(pid);
|
|
2894
5077
|
await this.fds.closeAll();
|
|
5078
|
+
await this.options.http?.closeAll();
|
|
2895
5079
|
}
|
|
2896
5080
|
};
|
|
2897
5081
|
|
|
@@ -2899,6 +5083,10 @@ var WasmProcess = class {
|
|
|
2899
5083
|
var WASM_PROCESS_INIT = "wasm-process-init";
|
|
2900
5084
|
var WASM_PROCESS_EXIT = "wasm-process-exit";
|
|
2901
5085
|
var WASM_PROCESS_ERROR = "wasm-process-error";
|
|
5086
|
+
var WASM_THREAD_SPAWN = "wasm-thread-spawn";
|
|
5087
|
+
var WASM_THREAD_INIT = "wasm-thread-init";
|
|
5088
|
+
var WASM_THREAD_EXIT = "wasm-thread-exit";
|
|
5089
|
+
var WASM_MAX_THREADS = 64;
|
|
2902
5090
|
|
|
2903
5091
|
// src/kernel/host.ts
|
|
2904
5092
|
var CRASHED = 70;
|
|
@@ -2913,7 +5101,7 @@ function inheritedFds(fds) {
|
|
|
2913
5101
|
return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
|
|
2914
5102
|
const open = fds.get(fd);
|
|
2915
5103
|
const flags = fds.statusFlags(fd);
|
|
2916
|
-
const kind = kernelFdKind(open.file);
|
|
5104
|
+
const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
|
|
2917
5105
|
const meta = open.file.heldMeta;
|
|
2918
5106
|
return {
|
|
2919
5107
|
fd,
|
|
@@ -2927,7 +5115,7 @@ function inheritedFds(fds) {
|
|
|
2927
5115
|
}
|
|
2928
5116
|
function spawnWasmProcess(opts) {
|
|
2929
5117
|
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
2930
|
-
const
|
|
5118
|
+
const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
|
|
2931
5119
|
const process = new WasmProcess(opts.pid, opts.fds, {
|
|
2932
5120
|
spawner: opts.spawner,
|
|
2933
5121
|
forker: opts.forker,
|
|
@@ -2935,11 +5123,15 @@ function spawnWasmProcess(opts) {
|
|
|
2935
5123
|
kill: opts.kill,
|
|
2936
5124
|
jobs: opts.jobs,
|
|
2937
5125
|
ptys: opts.ptys,
|
|
5126
|
+
net: opts.net,
|
|
5127
|
+
http: opts.http,
|
|
5128
|
+
locks: opts.locks,
|
|
2938
5129
|
onReap: opts.onReap,
|
|
2939
|
-
onPending: (sig) => void Atomics.or(
|
|
2940
|
-
onTimer: (which) => void Atomics.or(
|
|
2941
|
-
hasPending: () => Atomics.load(
|
|
2942
|
-
raise: (sig) => signal(sig)
|
|
5130
|
+
onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
|
|
5131
|
+
onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
|
|
5132
|
+
hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
|
|
5133
|
+
raise: (sig) => signal(sig),
|
|
5134
|
+
...opts.program.abi === "wasi" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
|
|
2943
5135
|
});
|
|
2944
5136
|
const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
|
|
2945
5137
|
const worker = opts.createWorker();
|
|
@@ -2950,14 +5142,15 @@ function spawnWasmProcess(opts) {
|
|
|
2950
5142
|
const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
|
|
2951
5143
|
let settle2;
|
|
2952
5144
|
const exited = new Promise((resolve) => settle2 = resolve);
|
|
2953
|
-
let
|
|
5145
|
+
let done4 = false;
|
|
2954
5146
|
let endedBy;
|
|
2955
5147
|
const finish = (code, sig) => {
|
|
2956
|
-
if (
|
|
2957
|
-
|
|
5148
|
+
if (done4) return;
|
|
5149
|
+
done4 = true;
|
|
2958
5150
|
endedBy = sig ?? process.execTermsig;
|
|
2959
5151
|
worker.removeEventListener("message", onMessage);
|
|
2960
5152
|
worker.removeEventListener("error", onError);
|
|
5153
|
+
for (const tid of [...threads.keys()]) endThread(tid);
|
|
2961
5154
|
responder.dispose();
|
|
2962
5155
|
revokeSyncFsToken(token);
|
|
2963
5156
|
worker.terminate();
|
|
@@ -2966,15 +5159,17 @@ function spawnWasmProcess(opts) {
|
|
|
2966
5159
|
() => settle2(code)
|
|
2967
5160
|
);
|
|
2968
5161
|
};
|
|
2969
|
-
const
|
|
2970
|
-
const data = event.data;
|
|
5162
|
+
const handle = (data) => {
|
|
2971
5163
|
if (data?.type === WASM_PROCESS_EXIT) {
|
|
2972
5164
|
finish(typeof data.code === "number" ? data.code : CRASHED);
|
|
2973
5165
|
} else if (data?.type === WASM_PROCESS_ERROR) {
|
|
2974
5166
|
opts.onError?.(String(data.message));
|
|
2975
5167
|
finish(CRASHED);
|
|
5168
|
+
} else if (data?.type === WASM_THREAD_SPAWN && data.thread) {
|
|
5169
|
+
spawnThread(data.thread);
|
|
2976
5170
|
}
|
|
2977
5171
|
};
|
|
5172
|
+
const onMessage = (event) => handle(event.data);
|
|
2978
5173
|
const onError = (event) => {
|
|
2979
5174
|
event.preventDefault();
|
|
2980
5175
|
opts.onError?.(String(event.message ?? "worker error"));
|
|
@@ -2982,6 +5177,49 @@ function spawnWasmProcess(opts) {
|
|
|
2982
5177
|
};
|
|
2983
5178
|
worker.addEventListener("message", onMessage);
|
|
2984
5179
|
worker.addEventListener("error", onError);
|
|
5180
|
+
const threads = /* @__PURE__ */ new Map();
|
|
5181
|
+
const endThread = (tid) => {
|
|
5182
|
+
const end = threads.get(tid);
|
|
5183
|
+
threads.delete(tid);
|
|
5184
|
+
end?.();
|
|
5185
|
+
};
|
|
5186
|
+
const spawnThread = (thread) => {
|
|
5187
|
+
if (done4) return;
|
|
5188
|
+
if (threads.size >= WASM_MAX_THREADS - 1) {
|
|
5189
|
+
opts.onError?.(`more than ${WASM_MAX_THREADS} threads`);
|
|
5190
|
+
finish(CRASHED);
|
|
5191
|
+
return;
|
|
5192
|
+
}
|
|
5193
|
+
const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
5194
|
+
const tw = opts.createWorker();
|
|
5195
|
+
const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch });
|
|
5196
|
+
const onThreadMessage = (event) => {
|
|
5197
|
+
const data = event.data;
|
|
5198
|
+
if (data?.type === WASM_THREAD_EXIT) endThread(thread.tid);
|
|
5199
|
+
else handle(data);
|
|
5200
|
+
};
|
|
5201
|
+
tw.addEventListener("message", onThreadMessage);
|
|
5202
|
+
tw.addEventListener("error", onError);
|
|
5203
|
+
threads.set(thread.tid, () => {
|
|
5204
|
+
tw.removeEventListener("message", onThreadMessage);
|
|
5205
|
+
tw.removeEventListener("error", onError);
|
|
5206
|
+
tresponder.dispose();
|
|
5207
|
+
tw.terminate();
|
|
5208
|
+
});
|
|
5209
|
+
const tinit = {
|
|
5210
|
+
type: WASM_THREAD_INIT,
|
|
5211
|
+
pid: opts.pid,
|
|
5212
|
+
program: opts.program,
|
|
5213
|
+
argv0: opts.argv0,
|
|
5214
|
+
args: opts.args,
|
|
5215
|
+
env: opts.env,
|
|
5216
|
+
cwd: opts.cwd,
|
|
5217
|
+
sab: tsab,
|
|
5218
|
+
...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
|
|
5219
|
+
thread
|
|
5220
|
+
};
|
|
5221
|
+
tw.postMessage(tinit);
|
|
5222
|
+
};
|
|
2985
5223
|
const init = {
|
|
2986
5224
|
type: WASM_PROCESS_INIT,
|
|
2987
5225
|
pid: opts.pid,
|
|
@@ -3073,9 +5311,9 @@ var JobTable = class {
|
|
|
3073
5311
|
this.terminals.delete(pid);
|
|
3074
5312
|
return pid;
|
|
3075
5313
|
}
|
|
3076
|
-
terminalNamed(
|
|
5314
|
+
terminalNamed(name2) {
|
|
3077
5315
|
const known = /* @__PURE__ */ new Set([...this.terminals.values(), ...this.foreground.keys()]);
|
|
3078
|
-
return [...known].find((tty) => tty.name ===
|
|
5316
|
+
return [...known].find((tty) => tty.name === name2);
|
|
3079
5317
|
}
|
|
3080
5318
|
controllingTerminal(pid) {
|
|
3081
5319
|
const member = this.members.get(pid);
|
|
@@ -3116,8 +5354,67 @@ var JobTable = class {
|
|
|
3116
5354
|
}
|
|
3117
5355
|
};
|
|
3118
5356
|
|
|
5357
|
+
// src/process/wasi/wasi-module.ts
|
|
5358
|
+
function importedMemory(bytes) {
|
|
5359
|
+
let at = 8;
|
|
5360
|
+
const u32 = () => {
|
|
5361
|
+
let result = 0;
|
|
5362
|
+
let shift = 0;
|
|
5363
|
+
for (; ; ) {
|
|
5364
|
+
const b = bytes[at++];
|
|
5365
|
+
result |= (b & 127) << shift;
|
|
5366
|
+
if (!(b & 128)) return result >>> 0;
|
|
5367
|
+
shift += 7;
|
|
5368
|
+
}
|
|
5369
|
+
};
|
|
5370
|
+
const name2 = () => {
|
|
5371
|
+
const len = u32();
|
|
5372
|
+
const s = new TextDecoder().decode(bytes.subarray(at, at + len));
|
|
5373
|
+
at += len;
|
|
5374
|
+
return s;
|
|
5375
|
+
};
|
|
5376
|
+
const limits = () => {
|
|
5377
|
+
const flags = bytes[at++];
|
|
5378
|
+
const initial = u32();
|
|
5379
|
+
const maximum = flags & 1 ? u32() : void 0;
|
|
5380
|
+
return { initial, maximum, shared: (flags & 2) !== 0 };
|
|
5381
|
+
};
|
|
5382
|
+
while (at < bytes.length) {
|
|
5383
|
+
const id = bytes[at++];
|
|
5384
|
+
const size = u32();
|
|
5385
|
+
const end = at + size;
|
|
5386
|
+
if (id !== 2) {
|
|
5387
|
+
at = end;
|
|
5388
|
+
continue;
|
|
5389
|
+
}
|
|
5390
|
+
const count = u32();
|
|
5391
|
+
for (let i = 0; i < count; i++) {
|
|
5392
|
+
const module = name2();
|
|
5393
|
+
const field = name2();
|
|
5394
|
+
const kind = bytes[at++];
|
|
5395
|
+
if (kind === 0) u32();
|
|
5396
|
+
else if (kind === 1) {
|
|
5397
|
+
at++;
|
|
5398
|
+
limits();
|
|
5399
|
+
} else if (kind === 2) {
|
|
5400
|
+
const l = limits();
|
|
5401
|
+
return { module, name: field, ...l };
|
|
5402
|
+
} else if (kind === 3) at += 2;
|
|
5403
|
+
else if (kind === 4) {
|
|
5404
|
+
at++;
|
|
5405
|
+
u32();
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
return void 0;
|
|
5409
|
+
}
|
|
5410
|
+
return void 0;
|
|
5411
|
+
}
|
|
5412
|
+
|
|
3119
5413
|
// src/launcher.ts
|
|
3120
5414
|
var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
5415
|
+
var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
|
|
5416
|
+
var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
5417
|
+
var WASM_MAGIC = [0, 97, 115, 109];
|
|
3121
5418
|
var SHEBANG_MAX = 256;
|
|
3122
5419
|
var NOT_FOUND = 127;
|
|
3123
5420
|
var SHARED_DIRS = ["/tmp", "/home"];
|
|
@@ -3128,13 +5425,43 @@ function baseName(path) {
|
|
|
3128
5425
|
function modulePath(glue) {
|
|
3129
5426
|
return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
|
|
3130
5427
|
}
|
|
5428
|
+
function isWasm(bytes) {
|
|
5429
|
+
return WASM_MAGIC.every((b, i) => bytes[i] === b);
|
|
5430
|
+
}
|
|
5431
|
+
function shebang(head) {
|
|
5432
|
+
if (head[0] !== 35 || head[1] !== 33) return void 0;
|
|
5433
|
+
return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
|
|
5434
|
+
}
|
|
5435
|
+
function interpreter(head) {
|
|
5436
|
+
const words = shebang(head);
|
|
5437
|
+
if (!words) return "node";
|
|
5438
|
+
if (baseName(words[0]) === "env") words.shift();
|
|
5439
|
+
if (words[0] === "-S") words.shift();
|
|
5440
|
+
return baseName(words[0] ?? "");
|
|
5441
|
+
}
|
|
5442
|
+
function expandDefaults(defaults, env) {
|
|
5443
|
+
const out = {};
|
|
5444
|
+
for (const [key, value] of Object.entries(defaults ?? {})) {
|
|
5445
|
+
let missing3 = false;
|
|
5446
|
+
const expanded = value.replace(ENV_REFERENCE, (_, name2) => {
|
|
5447
|
+
const set2 = Object.hasOwn(env, name2) ? env[name2] : void 0;
|
|
5448
|
+
if (set2 === void 0) missing3 = true;
|
|
5449
|
+
return set2 ?? "";
|
|
5450
|
+
});
|
|
5451
|
+
if (!missing3) out[key] = expanded;
|
|
5452
|
+
}
|
|
5453
|
+
return out;
|
|
5454
|
+
}
|
|
3131
5455
|
function targetOf(command) {
|
|
3132
5456
|
return {
|
|
5457
|
+
abi: command.abi,
|
|
3133
5458
|
glue: command.glue,
|
|
3134
5459
|
wasm: command.wasm,
|
|
3135
5460
|
argv0: command.argv0,
|
|
3136
5461
|
...command.args ? { prefix: command.args } : {},
|
|
3137
|
-
...command.env ? { env: command.env } : {}
|
|
5462
|
+
...command.env ? { env: command.env } : {},
|
|
5463
|
+
...command.unset ? { unset: command.unset } : {},
|
|
5464
|
+
...command.imports ? { imports: command.imports } : {}
|
|
3138
5465
|
};
|
|
3139
5466
|
}
|
|
3140
5467
|
function childHandle(handle) {
|
|
@@ -3145,12 +5472,12 @@ function childHandle(handle) {
|
|
|
3145
5472
|
onState: (listener) => handle.onState(listener)
|
|
3146
5473
|
};
|
|
3147
5474
|
}
|
|
3148
|
-
function
|
|
3149
|
-
const out = new Uint8Array(chunks.reduce((n,
|
|
5475
|
+
function concat4(chunks) {
|
|
5476
|
+
const out = new Uint8Array(chunks.reduce((n, chunk2) => n + chunk2.length, 0));
|
|
3150
5477
|
let offset = 0;
|
|
3151
|
-
for (const
|
|
3152
|
-
out.set(
|
|
3153
|
-
offset +=
|
|
5478
|
+
for (const chunk2 of chunks) {
|
|
5479
|
+
out.set(chunk2, offset);
|
|
5480
|
+
offset += chunk2.length;
|
|
3154
5481
|
}
|
|
3155
5482
|
return out;
|
|
3156
5483
|
}
|
|
@@ -3166,14 +5493,25 @@ var Launcher = class {
|
|
|
3166
5493
|
zombies = /* @__PURE__ */ new Set();
|
|
3167
5494
|
orphans = /* @__PURE__ */ new Set();
|
|
3168
5495
|
jobs = new JobTable();
|
|
5496
|
+
locks = new LockTable();
|
|
3169
5497
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
5498
|
+
net = new LoopbackNet();
|
|
5499
|
+
ca;
|
|
5500
|
+
transport;
|
|
3170
5501
|
nextPid = 1e3;
|
|
3171
5502
|
terminals = 0;
|
|
3172
5503
|
constructor(options) {
|
|
3173
5504
|
this.base = options.fs;
|
|
3174
5505
|
this.createWorker = options.createWorker;
|
|
3175
5506
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
3176
|
-
this.env = options.env
|
|
5507
|
+
this.env = { ...networkEnv(), ...options.env };
|
|
5508
|
+
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
5509
|
+
this.transport = options.transport ?? missingTransport();
|
|
5510
|
+
enableNetwork(this.net, {
|
|
5511
|
+
transport: this.transport,
|
|
5512
|
+
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
5513
|
+
ca: this.ca
|
|
5514
|
+
});
|
|
3177
5515
|
this.fs = withCommandDirs(options.fs, async () => new Set((await this.commands()).keys()));
|
|
3178
5516
|
}
|
|
3179
5517
|
commands() {
|
|
@@ -3181,38 +5519,65 @@ var Launcher = class {
|
|
|
3181
5519
|
return this.catalog;
|
|
3182
5520
|
}
|
|
3183
5521
|
async resolve(file, argv0, cwd) {
|
|
3184
|
-
const
|
|
3185
|
-
if (
|
|
3186
|
-
const command = (await this.commands()).get(
|
|
3187
|
-
return command && targetOf(command);
|
|
5522
|
+
const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
|
|
5523
|
+
if (name2 !== void 0) {
|
|
5524
|
+
const command = (await this.commands()).get(name2);
|
|
5525
|
+
return command && !command.script ? targetOf(command) : void 0;
|
|
3188
5526
|
}
|
|
3189
5527
|
const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
|
|
3190
5528
|
const linked = COMMAND2.exec(glue)?.[1];
|
|
3191
5529
|
if (linked !== void 0) return this.resolve(`/bin/${linked}`, argv0, cwd);
|
|
5530
|
+
const head = await this.head(glue);
|
|
5531
|
+
if (!head) return void 0;
|
|
5532
|
+
if (isWasm(head)) {
|
|
5533
|
+
return {
|
|
5534
|
+
abi: "wasi",
|
|
5535
|
+
glue,
|
|
5536
|
+
wasm: glue,
|
|
5537
|
+
argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
|
|
5538
|
+
};
|
|
5539
|
+
}
|
|
5540
|
+
if (interpreter(head) !== "node") return void 0;
|
|
3192
5541
|
const wasm = modulePath(glue);
|
|
3193
|
-
if (!await this.base.exists(
|
|
3194
|
-
return { glue, wasm, argv0: baseName(argv0 || file) };
|
|
5542
|
+
if (!await this.base.exists(wasm)) return void 0;
|
|
5543
|
+
return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
|
|
3195
5544
|
}
|
|
3196
|
-
async
|
|
3197
|
-
let head;
|
|
5545
|
+
async head(path) {
|
|
3198
5546
|
try {
|
|
3199
|
-
|
|
3200
|
-
0,
|
|
3201
|
-
SHEBANG_MAX
|
|
3202
|
-
);
|
|
5547
|
+
return (await this.base.readFileBuffer(path)).subarray(0, SHEBANG_MAX);
|
|
3203
5548
|
} catch {
|
|
3204
5549
|
return void 0;
|
|
3205
5550
|
}
|
|
3206
|
-
|
|
3207
|
-
|
|
5551
|
+
}
|
|
5552
|
+
async scriptCommand(file) {
|
|
5553
|
+
const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
|
|
5554
|
+
if (name2 === void 0) return void 0;
|
|
5555
|
+
const command = (await this.commands()).get(name2);
|
|
5556
|
+
return command?.script ? command : void 0;
|
|
5557
|
+
}
|
|
5558
|
+
async interpreted(file, argv, cwd) {
|
|
5559
|
+
const command = await this.scriptCommand(file);
|
|
5560
|
+
const script = command?.script ?? this.fs.resolvePath(cwd, file);
|
|
5561
|
+
const words = shebang(await this.head(script) ?? new Uint8Array());
|
|
5562
|
+
if (!words) return void 0;
|
|
3208
5563
|
if (baseName(words[0]) === "env") words.shift();
|
|
3209
5564
|
const [interp, ...rest] = words;
|
|
3210
|
-
const
|
|
3211
|
-
if (!
|
|
5565
|
+
const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
|
|
5566
|
+
if (!found) return void 0;
|
|
5567
|
+
const target = command ? {
|
|
5568
|
+
...found,
|
|
5569
|
+
env: { ...found.env, ...command.env },
|
|
5570
|
+
unset: [...found.unset ?? [], ...command.unset ?? []]
|
|
5571
|
+
} : found;
|
|
3212
5572
|
const arg = rest.join(" ");
|
|
3213
5573
|
return {
|
|
3214
5574
|
target,
|
|
3215
|
-
args: [
|
|
5575
|
+
args: [
|
|
5576
|
+
...target.prefix ?? [],
|
|
5577
|
+
...arg ? [arg] : [],
|
|
5578
|
+
command ? script : file,
|
|
5579
|
+
...argv.slice(1)
|
|
5580
|
+
]
|
|
3216
5581
|
};
|
|
3217
5582
|
}
|
|
3218
5583
|
async plan(file, argv, cwd) {
|
|
@@ -3229,33 +5594,58 @@ var Launcher = class {
|
|
|
3229
5594
|
const key = `${path}:${st.size}:${st.mtime.getTime()}`;
|
|
3230
5595
|
let module = this.compiled.get(key);
|
|
3231
5596
|
if (!module) {
|
|
3232
|
-
module = this.base.readFileBuffer(path).then((bytes) =>
|
|
5597
|
+
module = this.base.readFileBuffer(path).then(async (bytes) => {
|
|
5598
|
+
const memory = importedMemory(bytes);
|
|
5599
|
+
const compiled = await WebAssembly.compile(bytes);
|
|
5600
|
+
return { module: compiled, ...memory ? { memory } : {} };
|
|
5601
|
+
});
|
|
3233
5602
|
this.compiled.set(key, module);
|
|
3234
5603
|
module.catch(() => this.compiled.delete(key));
|
|
3235
5604
|
}
|
|
3236
5605
|
return module;
|
|
3237
5606
|
});
|
|
3238
5607
|
}
|
|
5608
|
+
async names(path, module) {
|
|
5609
|
+
if (WebAssembly.Module.customSections(module, "name").length > 0) return void 0;
|
|
5610
|
+
const beside = `${path}.names`;
|
|
5611
|
+
if (await this.base.exists(beside)) return beside;
|
|
5612
|
+
const root = PACKAGE_ROOT.exec(path)?.[1];
|
|
5613
|
+
if (root === void 0) return void 0;
|
|
5614
|
+
const optional = `${root}-names${beside.slice(root.length)}`;
|
|
5615
|
+
return await this.base.exists(optional) ? optional : void 0;
|
|
5616
|
+
}
|
|
5617
|
+
async program(target, env) {
|
|
5618
|
+
if (target.abi !== "wasi") {
|
|
5619
|
+
const [glue, { module: module2 }] = await Promise.all([
|
|
5620
|
+
this.base.readFile(target.glue),
|
|
5621
|
+
this.module(target.wasm)
|
|
5622
|
+
]);
|
|
5623
|
+
return { glue, module: module2 };
|
|
5624
|
+
}
|
|
5625
|
+
const { module, memory } = await this.module(target.wasm);
|
|
5626
|
+
const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
|
|
5627
|
+
const imports = target.imports ? await this.base.readFile(target.imports) : void 0;
|
|
5628
|
+
return {
|
|
5629
|
+
abi: "wasi",
|
|
5630
|
+
glue: "",
|
|
5631
|
+
module,
|
|
5632
|
+
...memory ? { memory } : {},
|
|
5633
|
+
...names ? { names } : {},
|
|
5634
|
+
...imports !== void 0 ? { imports } : {}
|
|
5635
|
+
};
|
|
5636
|
+
}
|
|
3239
5637
|
async launch(planned, req) {
|
|
3240
5638
|
const { target, args } = planned;
|
|
5639
|
+
const env = { ...expandDefaults(target.env, { ...req.env, cwd: req.cwd }), ...req.env };
|
|
5640
|
+
for (const key of target.unset ?? []) delete env[key];
|
|
3241
5641
|
let program;
|
|
3242
5642
|
try {
|
|
3243
|
-
|
|
3244
|
-
this.base.readFile(target.glue),
|
|
3245
|
-
this.module(target.wasm)
|
|
3246
|
-
]);
|
|
3247
|
-
program = { glue, module };
|
|
5643
|
+
program = await this.program(target, env);
|
|
3248
5644
|
} catch (err) {
|
|
3249
5645
|
await req.fds.closeAll();
|
|
3250
5646
|
throw err;
|
|
3251
5647
|
}
|
|
3252
|
-
return this.start({
|
|
3253
|
-
...req,
|
|
3254
|
-
program,
|
|
3255
|
-
argv0: target.argv0,
|
|
3256
|
-
args,
|
|
3257
|
-
env: { ...target.env, ...req.env }
|
|
3258
|
-
});
|
|
5648
|
+
return this.start({ ...req, program, argv0: target.argv0, args, env });
|
|
3259
5649
|
}
|
|
3260
5650
|
start(req) {
|
|
3261
5651
|
const pid = this.nextPid++;
|
|
@@ -3276,6 +5666,9 @@ var Launcher = class {
|
|
|
3276
5666
|
kill: (target, sig) => this.kill(target, sig),
|
|
3277
5667
|
jobs: this.jobs,
|
|
3278
5668
|
ptys: this.ptys,
|
|
5669
|
+
net: this.net,
|
|
5670
|
+
http: new HttpHandles(this.transport),
|
|
5671
|
+
locks: this.locks,
|
|
3279
5672
|
onReap: (child) => this.reaped(child),
|
|
3280
5673
|
...req.fork ? { fork: req.fork } : {},
|
|
3281
5674
|
...req.ppid !== void 0 ? { ppid: req.ppid } : {}
|
|
@@ -3316,6 +5709,7 @@ var Launcher = class {
|
|
|
3316
5709
|
}
|
|
3317
5710
|
async prepare() {
|
|
3318
5711
|
for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
|
|
5712
|
+
await writeCaFile(this.base, this.ca).catch(() => void 0);
|
|
3319
5713
|
}
|
|
3320
5714
|
environment(cwd, extra) {
|
|
3321
5715
|
return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
|
|
@@ -3347,7 +5741,7 @@ var Launcher = class {
|
|
|
3347
5741
|
COLORTERM: "truecolor",
|
|
3348
5742
|
...options.env
|
|
3349
5743
|
});
|
|
3350
|
-
const report = (
|
|
5744
|
+
const report = (message2) => options.onData(encoder3.encode(`${message2}\r
|
|
3351
5745
|
`));
|
|
3352
5746
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
3353
5747
|
leader = handle.pid;
|
|
@@ -3375,11 +5769,11 @@ var Launcher = class {
|
|
|
3375
5769
|
};
|
|
3376
5770
|
const stdout = collect(out, options.onStdout);
|
|
3377
5771
|
const stderr = collect(err, options.onStderr);
|
|
3378
|
-
const report = (
|
|
5772
|
+
const report = (message2) => stderr(encoder3.encode(`${message2}
|
|
3379
5773
|
`));
|
|
3380
5774
|
if (!planned) {
|
|
3381
5775
|
report(`${file}: command not found`);
|
|
3382
|
-
return { status: NOT_FOUND, stdout:
|
|
5776
|
+
return { status: NOT_FOUND, stdout: concat4(out), stderr: concat4(err) };
|
|
3383
5777
|
}
|
|
3384
5778
|
const fds = new FdTable();
|
|
3385
5779
|
fds.installAt(0, options.stdin ? bytesSource(options.stdin) : nullFile());
|
|
@@ -3387,15 +5781,15 @@ var Launcher = class {
|
|
|
3387
5781
|
fds.installAt(2, sinkFile(stderr));
|
|
3388
5782
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
3389
5783
|
const status = await handle.exited;
|
|
3390
|
-
return { status, stdout:
|
|
5784
|
+
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
3391
5785
|
}
|
|
3392
5786
|
};
|
|
3393
5787
|
|
|
3394
5788
|
// src/serve.ts
|
|
3395
|
-
function signalNumber(
|
|
3396
|
-
const key =
|
|
3397
|
-
if (!
|
|
3398
|
-
throw new Error(`unknown signal ${
|
|
5789
|
+
function signalNumber(name2) {
|
|
5790
|
+
const key = name2.slice(3);
|
|
5791
|
+
if (!name2.startsWith("SIG") || !Object.hasOwn(SIG, key))
|
|
5792
|
+
throw new Error(`unknown signal ${name2}`);
|
|
3399
5793
|
return SIG[key];
|
|
3400
5794
|
}
|
|
3401
5795
|
function act(session, req) {
|
|
@@ -3406,6 +5800,7 @@ function act(session, req) {
|
|
|
3406
5800
|
}
|
|
3407
5801
|
function serveKernel(port, deps) {
|
|
3408
5802
|
let launcher;
|
|
5803
|
+
let remote;
|
|
3409
5804
|
const terminals = /* @__PURE__ */ new Map();
|
|
3410
5805
|
const reply = (id, body) => port.postMessage({ id, ...body });
|
|
3411
5806
|
async function ready2() {
|
|
@@ -3426,15 +5821,18 @@ function serveKernel(port, deps) {
|
|
|
3426
5821
|
async function handle(req) {
|
|
3427
5822
|
if (req.op === "init") {
|
|
3428
5823
|
launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
|
|
3429
|
-
const
|
|
3430
|
-
const meta =
|
|
5824
|
+
const name2 = req.metadata ?? META_DB;
|
|
5825
|
+
const meta = name2 === false ? void 0 : await deps.metadata?.(name2);
|
|
5826
|
+
remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
|
|
3431
5827
|
const fs = new OpfsFs(root, meta);
|
|
3432
5828
|
await fs.reconcile();
|
|
3433
5829
|
const started = new Launcher({
|
|
3434
5830
|
fs,
|
|
3435
5831
|
createWorker: deps.createWorker,
|
|
3436
5832
|
...req.modules ? { modules: req.modules } : {},
|
|
3437
|
-
...req.env ? { env: req.env } : {}
|
|
5833
|
+
...req.env ? { env: req.env } : {},
|
|
5834
|
+
...remote ? { transport: remote } : {},
|
|
5835
|
+
caStore: caStore(deps.metadata ? name2 : false)
|
|
3438
5836
|
});
|
|
3439
5837
|
await started.prepare();
|
|
3440
5838
|
return started;
|
|
@@ -3456,6 +5854,10 @@ function serveKernel(port, deps) {
|
|
|
3456
5854
|
});
|
|
3457
5855
|
}
|
|
3458
5856
|
port.addEventListener("message", (event) => {
|
|
5857
|
+
if (event.data.net !== void 0) {
|
|
5858
|
+
remote?.receive(event.data);
|
|
5859
|
+
return;
|
|
5860
|
+
}
|
|
3459
5861
|
const req = event.data;
|
|
3460
5862
|
handle(req).then(
|
|
3461
5863
|
(result) => reply(req.id, { result }),
|
|
@@ -3469,6 +5871,6 @@ var processWorker = new URL("./process-worker.js", import.meta.url);
|
|
|
3469
5871
|
serveKernel(globalThis, {
|
|
3470
5872
|
storage: () => navigator.storage.getDirectory(),
|
|
3471
5873
|
createWorker: () => new Worker(processWorker, { type: "module" }),
|
|
3472
|
-
metadata: (
|
|
5874
|
+
metadata: (name2) => openMeta(name2, indexedDB)
|
|
3473
5875
|
});
|
|
3474
5876
|
//# sourceMappingURL=kernel-worker.js.map
|