@ai-ecoverse/slicc-kernel 1.2.0 → 1.3.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 +22 -1
- package/dist/index.js +150 -2
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +2400 -419
- package/dist/kernel-worker.js.map +4 -4
- package/dist/process-worker.js +249 -12
- package/dist/process-worker.js.map +4 -4
- package/package.json +6 -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;
|
|
@@ -1147,50 +894,1745 @@ var FdTable = class _FdTable {
|
|
|
1147
894
|
return fd;
|
|
1148
895
|
}
|
|
1149
896
|
}
|
|
1150
|
-
void Promise.resolve(file.release()).catch(() => void 0);
|
|
1151
|
-
throw new KernelError("EMFILE");
|
|
1152
|
-
}
|
|
1153
|
-
installAt(fd, file) {
|
|
1154
|
-
if (fd < 0 || fd >= _FdTable.MAX_FDS) {
|
|
1155
|
-
void Promise.resolve(file.release()).catch(() => void 0);
|
|
1156
|
-
throw new KernelError("EBADF");
|
|
897
|
+
void Promise.resolve(file.release()).catch(() => void 0);
|
|
898
|
+
throw new KernelError("EMFILE");
|
|
899
|
+
}
|
|
900
|
+
installAt(fd, file) {
|
|
901
|
+
if (fd < 0 || fd >= _FdTable.MAX_FDS) {
|
|
902
|
+
void Promise.resolve(file.release()).catch(() => void 0);
|
|
903
|
+
throw new KernelError("EBADF");
|
|
904
|
+
}
|
|
905
|
+
const previous = this.fds.get(fd);
|
|
906
|
+
this.fds.set(fd, file);
|
|
907
|
+
this.cloexec.delete(fd);
|
|
908
|
+
this.status.delete(fd);
|
|
909
|
+
void Promise.resolve(previous?.release()).catch(() => void 0);
|
|
910
|
+
}
|
|
911
|
+
dup(fd, min = 0) {
|
|
912
|
+
return this.install(this.get(fd).retain(), min);
|
|
913
|
+
}
|
|
914
|
+
dup2(oldFd, newFd) {
|
|
915
|
+
const file = this.get(oldFd);
|
|
916
|
+
if (oldFd !== newFd) this.installAt(newFd, file.retain());
|
|
917
|
+
return newFd;
|
|
918
|
+
}
|
|
919
|
+
close(fd) {
|
|
920
|
+
const file = this.get(fd);
|
|
921
|
+
this.fds.delete(fd);
|
|
922
|
+
this.cloexec.delete(fd);
|
|
923
|
+
this.status.delete(fd);
|
|
924
|
+
return file.release();
|
|
925
|
+
}
|
|
926
|
+
fork() {
|
|
927
|
+
const child = new _FdTable();
|
|
928
|
+
for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
|
|
929
|
+
for (const fd of this.cloexec) child.cloexec.add(fd);
|
|
930
|
+
for (const [fd, flags] of this.status) child.status.set(fd, flags);
|
|
931
|
+
return child;
|
|
932
|
+
}
|
|
933
|
+
async closeAll() {
|
|
934
|
+
const files = [...this.fds.values()];
|
|
935
|
+
this.fds.clear();
|
|
936
|
+
this.cloexec.clear();
|
|
937
|
+
this.status.clear();
|
|
938
|
+
await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
|
|
939
|
+
}
|
|
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 done3 = () => {
|
|
1210
|
+
signal.removeEventListener("abort", done3);
|
|
1211
|
+
resolve();
|
|
1212
|
+
};
|
|
1213
|
+
this.waiters.push(done3);
|
|
1214
|
+
signal.addEventListener("abort", done3, { 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 done3 = this.serve(conn).finally(() => {
|
|
1426
|
+
this.slots.release(1);
|
|
1427
|
+
served.delete(done3);
|
|
1428
|
+
});
|
|
1429
|
+
served.add(done3);
|
|
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) && typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
|
|
2454
|
+
}
|
|
2455
|
+
return env;
|
|
2456
|
+
}
|
|
2457
|
+
function argsOf(pkg, raw) {
|
|
2458
|
+
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
2459
|
+
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
2460
|
+
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
2461
|
+
}
|
|
2462
|
+
function commandsOf(pkg, manifest) {
|
|
2463
|
+
const slicc = manifest.slicc;
|
|
2464
|
+
if (!slicc || typeof slicc !== "object" || (slicc.abi ?? "emscripten") !== "emscripten")
|
|
2465
|
+
return [];
|
|
2466
|
+
const entries = slicc.commands;
|
|
2467
|
+
if (!entries || typeof entries !== "object") return [];
|
|
2468
|
+
const shared = envOf(pkg, slicc.env);
|
|
2469
|
+
const out = [];
|
|
2470
|
+
for (const [name2, raw] of Object.entries(entries)) {
|
|
2471
|
+
if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
|
|
2472
|
+
continue;
|
|
2473
|
+
}
|
|
2474
|
+
const glue = inside(pkg, raw.glue);
|
|
2475
|
+
const wasm = inside(pkg, raw.wasm);
|
|
2476
|
+
if ((raw.abi ?? "emscripten") !== "emscripten" || !glue || !wasm) continue;
|
|
2477
|
+
const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
|
|
2478
|
+
const args = argsOf(pkg, raw.args);
|
|
2479
|
+
const env = { ...shared, ...envOf(pkg, raw.env) };
|
|
2480
|
+
out.push({
|
|
2481
|
+
name: name2,
|
|
2482
|
+
glue,
|
|
2483
|
+
wasm,
|
|
2484
|
+
argv0,
|
|
2485
|
+
...args ? { args } : {},
|
|
2486
|
+
...Object.keys(env).length > 0 ? { env } : {}
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
return out;
|
|
2490
|
+
}
|
|
2491
|
+
async function packages(fs, modules) {
|
|
2492
|
+
const names = await fs.readdir(modules).catch(() => []);
|
|
2493
|
+
const dirs = [];
|
|
2494
|
+
for (const name2 of names) {
|
|
2495
|
+
if (name2.startsWith(".")) continue;
|
|
2496
|
+
if (!name2.startsWith("@")) {
|
|
2497
|
+
dirs.push(`${modules}/${name2}`);
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
|
|
2501
|
+
dirs.push(`${modules}/${name2}/${inner}`);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return dirs;
|
|
2505
|
+
}
|
|
2506
|
+
async function scanCommands(fs, modules) {
|
|
2507
|
+
const found = /* @__PURE__ */ new Map();
|
|
2508
|
+
for (const pkg of await packages(fs, modules)) {
|
|
2509
|
+
let manifest;
|
|
2510
|
+
try {
|
|
2511
|
+
manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
|
|
2512
|
+
} catch {
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
for (const command of commandsOf(pkg, manifest)) {
|
|
2516
|
+
if (!found.has(command.name)) found.set(command.name, command);
|
|
1157
2517
|
}
|
|
1158
|
-
const previous = this.fds.get(fd);
|
|
1159
|
-
this.fds.set(fd, file);
|
|
1160
|
-
this.cloexec.delete(fd);
|
|
1161
|
-
this.status.delete(fd);
|
|
1162
|
-
void Promise.resolve(previous?.release()).catch(() => void 0);
|
|
1163
2518
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
2519
|
+
if (!found.has("sh") && found.has("bash")) {
|
|
2520
|
+
found.set("sh", { ...found.get("bash"), name: "sh", argv0: "sh" });
|
|
1166
2521
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
2522
|
+
return found;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// src/fs/commands.ts
|
|
2526
|
+
var S_IFDIR2 = 16384;
|
|
2527
|
+
var S_IFREG2 = 32768;
|
|
2528
|
+
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
2529
|
+
var MAX_LINKS2 = 40;
|
|
2530
|
+
function missing2(err) {
|
|
2531
|
+
return err?.code === "ENOENT";
|
|
2532
|
+
}
|
|
2533
|
+
function synthetic(directory, path) {
|
|
2534
|
+
const epoch = /* @__PURE__ */ new Date(0);
|
|
2535
|
+
return {
|
|
2536
|
+
isFile: !directory,
|
|
2537
|
+
isDirectory: directory,
|
|
2538
|
+
isSymbolicLink: false,
|
|
2539
|
+
size: 0,
|
|
2540
|
+
mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
|
|
2541
|
+
mtime: epoch,
|
|
2542
|
+
atime: epoch,
|
|
2543
|
+
ctime: epoch,
|
|
2544
|
+
ino: inodeOf(path)
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
async function followLinks(fs, path) {
|
|
2548
|
+
let current = path;
|
|
2549
|
+
for (let hops = 0; hops < MAX_LINKS2; hops++) {
|
|
2550
|
+
const st = await fs.lstat(current).catch(() => void 0);
|
|
2551
|
+
if (!st?.isSymbolicLink) return current;
|
|
2552
|
+
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
2553
|
+
current = fs.resolvePath(parent, await fs.readlink(current));
|
|
1171
2554
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
2555
|
+
return current;
|
|
2556
|
+
}
|
|
2557
|
+
function withCommandDirs(fs, names) {
|
|
2558
|
+
async function children2(path) {
|
|
2559
|
+
if (path === "/") return ["bin", "usr"];
|
|
2560
|
+
if (path === "/usr") return ["bin"];
|
|
2561
|
+
if (path === "/bin" || path === "/usr/bin") return [...await names()];
|
|
2562
|
+
return void 0;
|
|
1178
2563
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
for (const [fd, flags] of this.status) child.status.set(fd, flags);
|
|
1184
|
-
return child;
|
|
2564
|
+
async function virtual(path) {
|
|
2565
|
+
if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
|
|
2566
|
+
const name2 = COMMAND.exec(path)?.[1];
|
|
2567
|
+
return name2 !== void 0 && (await names()).has(name2) ? synthetic(false, path) : void 0;
|
|
1185
2568
|
}
|
|
1186
|
-
async
|
|
1187
|
-
|
|
1188
|
-
this.fds.clear();
|
|
1189
|
-
this.cloexec.clear();
|
|
1190
|
-
this.status.clear();
|
|
1191
|
-
await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
|
|
2569
|
+
async function linked(path) {
|
|
2570
|
+
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
1192
2571
|
}
|
|
1193
|
-
|
|
2572
|
+
async function stat(path, real) {
|
|
2573
|
+
try {
|
|
2574
|
+
return await real(path);
|
|
2575
|
+
} catch (err) {
|
|
2576
|
+
const found = missing2(err) ? await linked(path) : void 0;
|
|
2577
|
+
if (found) return found;
|
|
2578
|
+
throw err;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
return {
|
|
2582
|
+
resolvePath: (base, path) => fs.resolvePath(base, path),
|
|
2583
|
+
readFile: (path) => fs.readFile(path),
|
|
2584
|
+
async readFileBuffer(path) {
|
|
2585
|
+
try {
|
|
2586
|
+
return await fs.readFileBuffer(path);
|
|
2587
|
+
} catch (err) {
|
|
2588
|
+
if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
|
|
2589
|
+
throw err;
|
|
2590
|
+
}
|
|
2591
|
+
},
|
|
2592
|
+
writeFile: (path, content) => fs.writeFile(path, content),
|
|
2593
|
+
async exists(path) {
|
|
2594
|
+
return await fs.exists(path) || await linked(path) !== void 0;
|
|
2595
|
+
},
|
|
2596
|
+
stat: (path) => stat(path, (p) => fs.stat(p)),
|
|
2597
|
+
lstat: (path) => stat(path, (p) => fs.lstat(p)),
|
|
2598
|
+
async readdir(path) {
|
|
2599
|
+
const extra = await children2(path);
|
|
2600
|
+
if (!extra) return fs.readdir(path);
|
|
2601
|
+
const real = await fs.readdir(path).catch((err) => {
|
|
2602
|
+
if (missing2(err)) return [];
|
|
2603
|
+
throw err;
|
|
2604
|
+
});
|
|
2605
|
+
return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
|
|
2606
|
+
},
|
|
2607
|
+
async readdirStat(path) {
|
|
2608
|
+
const extra = await children2(path);
|
|
2609
|
+
if (!extra) return fs.readdirStat(path);
|
|
2610
|
+
const real = await fs.readdirStat(path).catch((err) => {
|
|
2611
|
+
if (missing2(err)) return [];
|
|
2612
|
+
throw err;
|
|
2613
|
+
});
|
|
2614
|
+
const seen = new Set(real.map(([name2]) => name2));
|
|
2615
|
+
const base = path === "/" ? "" : path;
|
|
2616
|
+
const added = extra.filter((name2) => !seen.has(name2));
|
|
2617
|
+
const virtuals = await Promise.all(
|
|
2618
|
+
added.map(
|
|
2619
|
+
async (name2) => [
|
|
2620
|
+
name2,
|
|
2621
|
+
await virtual(`${base}/${name2}`)
|
|
2622
|
+
]
|
|
2623
|
+
)
|
|
2624
|
+
);
|
|
2625
|
+
return [...real, ...virtuals].sort(([a], [b]) => a < b ? -1 : 1);
|
|
2626
|
+
},
|
|
2627
|
+
mkdir: (path, options) => fs.mkdir(path, options),
|
|
2628
|
+
rm: (path, options) => fs.rm(path, options),
|
|
2629
|
+
rename: (from, to) => fs.rename(from, to),
|
|
2630
|
+
symlink: (target, path) => fs.symlink(target, path),
|
|
2631
|
+
readlink: (path) => fs.readlink(path),
|
|
2632
|
+
chmod: (path, mode) => fs.chmod(path, mode),
|
|
2633
|
+
utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
1194
2636
|
|
|
1195
2637
|
// src/kernel/children.ts
|
|
1196
2638
|
var SpawnError = class extends Error {
|
|
@@ -1214,12 +2656,12 @@ var CONTINUED_STATUS = 65535;
|
|
|
1214
2656
|
function waitStatus(code, termsig) {
|
|
1215
2657
|
return termsig ? termsig & 127 : (code & 255) << 8;
|
|
1216
2658
|
}
|
|
1217
|
-
function
|
|
2659
|
+
function concat3(chunks) {
|
|
1218
2660
|
const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
|
|
1219
2661
|
let offset = 0;
|
|
1220
|
-
for (const
|
|
1221
|
-
out.set(
|
|
1222
|
-
offset +=
|
|
2662
|
+
for (const chunk2 of chunks) {
|
|
2663
|
+
out.set(chunk2, offset);
|
|
2664
|
+
offset += chunk2.length;
|
|
1223
2665
|
}
|
|
1224
2666
|
return out;
|
|
1225
2667
|
}
|
|
@@ -1371,7 +2813,7 @@ var ChildTable = class {
|
|
|
1371
2813
|
if (!slots || !chunks) return new Uint8Array(0);
|
|
1372
2814
|
slots.delete(slot);
|
|
1373
2815
|
if (slots.size === 0) this.leftovers.delete(pid);
|
|
1374
|
-
return
|
|
2816
|
+
return concat3(chunks);
|
|
1375
2817
|
}
|
|
1376
2818
|
};
|
|
1377
2819
|
|
|
@@ -1397,12 +2839,12 @@ function syncError(code, label) {
|
|
|
1397
2839
|
|
|
1398
2840
|
// src/realm/sync-fs-dispatch.ts
|
|
1399
2841
|
function toErrno(err) {
|
|
1400
|
-
const
|
|
2842
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
1401
2843
|
const code = err?.code;
|
|
1402
2844
|
if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
|
|
1403
|
-
return { ok: false, errno: code, message };
|
|
2845
|
+
return { ok: false, errno: code, message: message2 };
|
|
1404
2846
|
}
|
|
1405
|
-
return { ok: false, errno: "EIO", message };
|
|
2847
|
+
return { ok: false, errno: "EIO", message: message2 };
|
|
1406
2848
|
}
|
|
1407
2849
|
function statJson(s) {
|
|
1408
2850
|
return {
|
|
@@ -1418,7 +2860,7 @@ function statJson(s) {
|
|
|
1418
2860
|
};
|
|
1419
2861
|
}
|
|
1420
2862
|
async function readdirStat(fs, dir) {
|
|
1421
|
-
return (await fs.readdirStat(dir)).map(([
|
|
2863
|
+
return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
|
|
1422
2864
|
}
|
|
1423
2865
|
var done = { ok: true, kind: "void" };
|
|
1424
2866
|
function json(value) {
|
|
@@ -1537,7 +2979,7 @@ function sabViews(sab) {
|
|
|
1537
2979
|
var PENDING_TTL_MS = SYNC_FS_REQUEST_TIMEOUT_MS + 5e3;
|
|
1538
2980
|
function attachSyncSabResponder(port, sab, token, opts) {
|
|
1539
2981
|
const views = sabViews(sab);
|
|
1540
|
-
const { header, window } = views;
|
|
2982
|
+
const { header: header2, window } = views;
|
|
1541
2983
|
const pending = /* @__PURE__ */ new Map();
|
|
1542
2984
|
let disposed = false;
|
|
1543
2985
|
const { dispatch } = opts;
|
|
@@ -1548,16 +2990,16 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1548
2990
|
pending.delete(id);
|
|
1549
2991
|
}
|
|
1550
2992
|
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 +
|
|
2993
|
+
const chunk2 = Math.min(window.byteLength, entry.payload.byteLength - offset);
|
|
2994
|
+
if (chunk2 > 0) window.set(entry.payload.subarray(offset, offset + chunk2));
|
|
2995
|
+
Atomics.store(header2, SAB_I_STATUS, entry.status);
|
|
2996
|
+
Atomics.store(header2, SAB_I_TOTAL, entry.payload.byteLength);
|
|
2997
|
+
Atomics.store(header2, SAB_I_CHUNK, chunk2);
|
|
2998
|
+
Atomics.store(header2, SAB_I_OFFSET, offset);
|
|
2999
|
+
Atomics.store(header2, SAB_I_SEQ, id);
|
|
3000
|
+
Atomics.store(header2, SAB_I_STATE, SAB_STATE_READY);
|
|
3001
|
+
Atomics.notify(header2, SAB_I_STATE);
|
|
3002
|
+
if (offset + chunk2 >= entry.payload.byteLength) drop(id);
|
|
1561
3003
|
}
|
|
1562
3004
|
function settle2(id, result) {
|
|
1563
3005
|
if (disposed) return;
|
|
@@ -1619,6 +3061,112 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1619
3061
|
};
|
|
1620
3062
|
}
|
|
1621
3063
|
|
|
3064
|
+
// src/kernel/net/http-syscalls.ts
|
|
3065
|
+
var HTTP_OPS = ["net-request", "net-read", "net-close"];
|
|
3066
|
+
var EMPTY = new Uint8Array(0);
|
|
3067
|
+
function message(e) {
|
|
3068
|
+
return e instanceof Error ? e.message : String(e);
|
|
3069
|
+
}
|
|
3070
|
+
async function* once(bytes) {
|
|
3071
|
+
yield bytes;
|
|
3072
|
+
}
|
|
3073
|
+
var HttpHandles = class {
|
|
3074
|
+
transport;
|
|
3075
|
+
open = /* @__PURE__ */ new Map();
|
|
3076
|
+
nextHandle = 0;
|
|
3077
|
+
constructor(transport) {
|
|
3078
|
+
this.transport = transport;
|
|
3079
|
+
}
|
|
3080
|
+
syscall(req) {
|
|
3081
|
+
if (req.op === "net-request") return this.request(req);
|
|
3082
|
+
if (req.op === "net-read") return this.read(req.handle, req.max);
|
|
3083
|
+
return this.close(req.handle);
|
|
3084
|
+
}
|
|
3085
|
+
add(abort, body, cancel) {
|
|
3086
|
+
const handle = ++this.nextHandle;
|
|
3087
|
+
this.open.set(handle, {
|
|
3088
|
+
abort,
|
|
3089
|
+
body: body[Symbol.asyncIterator](),
|
|
3090
|
+
left: EMPTY,
|
|
3091
|
+
done: false,
|
|
3092
|
+
reading: Promise.resolve(),
|
|
3093
|
+
cancel
|
|
3094
|
+
});
|
|
3095
|
+
return handle;
|
|
3096
|
+
}
|
|
3097
|
+
async request(req) {
|
|
3098
|
+
const abort = new AbortController();
|
|
3099
|
+
try {
|
|
3100
|
+
const response = await this.transport.fetch({
|
|
3101
|
+
url: req.url,
|
|
3102
|
+
method: req.method,
|
|
3103
|
+
headers: req.headers,
|
|
3104
|
+
...req.body ? { body: req.body } : {},
|
|
3105
|
+
signal: abort.signal
|
|
3106
|
+
});
|
|
3107
|
+
const handle = this.add(abort, response.body, () => response.cancel());
|
|
3108
|
+
const head = {
|
|
3109
|
+
handle,
|
|
3110
|
+
status: response.status,
|
|
3111
|
+
statusText: response.statusText,
|
|
3112
|
+
url: req.url,
|
|
3113
|
+
headers: response.headers
|
|
3114
|
+
};
|
|
3115
|
+
return { ok: true, kind: "json", json: head };
|
|
3116
|
+
} catch (e) {
|
|
3117
|
+
const text = message(e);
|
|
3118
|
+
if (text === NO_TRANSPORT) return { ok: false, errno: "ENETUNREACH", message: text };
|
|
3119
|
+
const status = e.status;
|
|
3120
|
+
if (typeof status !== "number" || status === 502) {
|
|
3121
|
+
return { ok: false, errno: "ECONNREFUSED", message: text };
|
|
3122
|
+
}
|
|
3123
|
+
const body = new TextEncoder().encode(text);
|
|
3124
|
+
const handle = this.add(abort, once(body), async () => void 0);
|
|
3125
|
+
const head = {
|
|
3126
|
+
handle,
|
|
3127
|
+
status,
|
|
3128
|
+
statusText: "",
|
|
3129
|
+
url: req.url,
|
|
3130
|
+
headers: [["content-type", "text/plain; charset=utf-8"]]
|
|
3131
|
+
};
|
|
3132
|
+
return { ok: true, kind: "json", json: head };
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
async read(handle, max) {
|
|
3136
|
+
const entry = this.open.get(handle);
|
|
3137
|
+
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
3138
|
+
const turn = entry.reading.then(() => this.take(entry, max));
|
|
3139
|
+
entry.reading = turn.then(() => void 0);
|
|
3140
|
+
return turn;
|
|
3141
|
+
}
|
|
3142
|
+
async take(entry, max) {
|
|
3143
|
+
if (entry.left.length === 0 && !entry.done) {
|
|
3144
|
+
try {
|
|
3145
|
+
const next = await entry.body.next();
|
|
3146
|
+
if (next.done) entry.done = true;
|
|
3147
|
+
else entry.left = next.value;
|
|
3148
|
+
} catch (e) {
|
|
3149
|
+
entry.done = true;
|
|
3150
|
+
return { ok: false, errno: "EIO", message: message(e) };
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
const bytes = entry.left.subarray(0, Math.max(0, max));
|
|
3154
|
+
entry.left = entry.left.subarray(bytes.length);
|
|
3155
|
+
return { ok: true, kind: "bytes", bytes };
|
|
3156
|
+
}
|
|
3157
|
+
async close(handle) {
|
|
3158
|
+
const entry = this.open.get(handle);
|
|
3159
|
+
if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
|
|
3160
|
+
this.open.delete(handle);
|
|
3161
|
+
entry.abort.abort();
|
|
3162
|
+
await entry.cancel().catch(() => void 0);
|
|
3163
|
+
return { ok: true, kind: "void" };
|
|
3164
|
+
}
|
|
3165
|
+
async closeAll() {
|
|
3166
|
+
await Promise.all([...this.open.keys()].map((handle) => this.close(handle)));
|
|
3167
|
+
}
|
|
3168
|
+
};
|
|
3169
|
+
|
|
1622
3170
|
// src/kernel/tty.ts
|
|
1623
3171
|
var ICRNL = 256;
|
|
1624
3172
|
var IUTF8 = 16384;
|
|
@@ -1787,9 +3335,9 @@ var KernelTty = class {
|
|
|
1787
3335
|
return n;
|
|
1788
3336
|
}
|
|
1789
3337
|
rubOut(n) {
|
|
1790
|
-
const
|
|
1791
|
-
const
|
|
1792
|
-
const cells =
|
|
3338
|
+
const dropped2 = this.line.splice(this.line.length - n, n);
|
|
3339
|
+
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
3340
|
+
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
1793
3341
|
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
1794
3342
|
this.output(encoder2.encode("\b \b".repeat(cells)), false);
|
|
1795
3343
|
}
|
|
@@ -1839,9 +3387,9 @@ var KernelTty = class {
|
|
|
1839
3387
|
}
|
|
1840
3388
|
const out = new Uint8Array(n);
|
|
1841
3389
|
let at = 0;
|
|
1842
|
-
for (const
|
|
1843
|
-
out.set(
|
|
1844
|
-
at +=
|
|
3390
|
+
for (const chunk2 of chunks) {
|
|
3391
|
+
out.set(chunk2, at);
|
|
3392
|
+
at += chunk2.length;
|
|
1845
3393
|
}
|
|
1846
3394
|
return out;
|
|
1847
3395
|
}
|
|
@@ -1938,9 +3486,9 @@ var PtyPair = class {
|
|
|
1938
3486
|
}
|
|
1939
3487
|
const out = new Uint8Array(status + n);
|
|
1940
3488
|
let at = status;
|
|
1941
|
-
for (const
|
|
1942
|
-
out.set(
|
|
1943
|
-
at +=
|
|
3489
|
+
for (const chunk2 of chunks) {
|
|
3490
|
+
out.set(chunk2, at);
|
|
3491
|
+
at += chunk2.length;
|
|
1944
3492
|
}
|
|
1945
3493
|
return out;
|
|
1946
3494
|
}
|
|
@@ -2111,6 +3659,391 @@ async function waitReady(fds, read, write, deadline, interrupted2) {
|
|
|
2111
3659
|
}
|
|
2112
3660
|
}
|
|
2113
3661
|
|
|
3662
|
+
// src/kernel/socket.ts
|
|
3663
|
+
var SOMAXCONN = 4096;
|
|
3664
|
+
var EPHEMERAL_FIRST = 32768;
|
|
3665
|
+
var EPHEMERAL_LAST = 60999;
|
|
3666
|
+
var SOL_SOCKET = 1;
|
|
3667
|
+
var SO_TYPE = 3;
|
|
3668
|
+
var SO_ERROR = 4;
|
|
3669
|
+
var SO_SNDBUF = 7;
|
|
3670
|
+
var SO_RCVBUF = 8;
|
|
3671
|
+
var SO_ACCEPTCONN = 30;
|
|
3672
|
+
var SO_DOMAIN = 39;
|
|
3673
|
+
var SOCK_STREAM = 1;
|
|
3674
|
+
var AF_UNIX = 1;
|
|
3675
|
+
var AF_INET = 2;
|
|
3676
|
+
var SHUT_RD = 0;
|
|
3677
|
+
var SHUT_WR = 1;
|
|
3678
|
+
var SHUT_RDWR = 2;
|
|
3679
|
+
function isLoopback(host) {
|
|
3680
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
3681
|
+
}
|
|
3682
|
+
async function pipeCall(op) {
|
|
3683
|
+
try {
|
|
3684
|
+
return await op();
|
|
3685
|
+
} catch (e) {
|
|
3686
|
+
if (e instanceof PipeError) throw new KernelError(e.code);
|
|
3687
|
+
throw e;
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
var Changes = class {
|
|
3691
|
+
waiters = [];
|
|
3692
|
+
wait(signal) {
|
|
3693
|
+
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
3694
|
+
return new Promise((resolve, reject) => {
|
|
3695
|
+
const onAbort = () => {
|
|
3696
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
3697
|
+
reject(new KernelError("EINTR"));
|
|
3698
|
+
};
|
|
3699
|
+
const waiter = () => {
|
|
3700
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3701
|
+
resolve();
|
|
3702
|
+
};
|
|
3703
|
+
this.waiters.push(waiter);
|
|
3704
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3705
|
+
});
|
|
3706
|
+
}
|
|
3707
|
+
wake() {
|
|
3708
|
+
const waiters = this.waiters;
|
|
3709
|
+
this.waiters = [];
|
|
3710
|
+
for (const resolve of waiters) resolve();
|
|
3711
|
+
}
|
|
3712
|
+
};
|
|
3713
|
+
function link(a, b) {
|
|
3714
|
+
const ab = new KernelPipe();
|
|
3715
|
+
const ba = new KernelPipe();
|
|
3716
|
+
for (const pipe of [ab, ba]) {
|
|
3717
|
+
pipe.openRead();
|
|
3718
|
+
pipe.openWrite();
|
|
3719
|
+
}
|
|
3720
|
+
a.attach({ rx: ba, tx: ab });
|
|
3721
|
+
b.attach({ rx: ab, tx: ba });
|
|
3722
|
+
}
|
|
3723
|
+
var KernelSocket = class _KernelSocket {
|
|
3724
|
+
net;
|
|
3725
|
+
domain;
|
|
3726
|
+
state = "open";
|
|
3727
|
+
local;
|
|
3728
|
+
peer;
|
|
3729
|
+
link;
|
|
3730
|
+
readShut = false;
|
|
3731
|
+
writeShut = false;
|
|
3732
|
+
queue = [];
|
|
3733
|
+
backlog = 0;
|
|
3734
|
+
bound = false;
|
|
3735
|
+
changes = new Changes();
|
|
3736
|
+
options = /* @__PURE__ */ new Map();
|
|
3737
|
+
constructor(net, domain) {
|
|
3738
|
+
this.net = net;
|
|
3739
|
+
this.domain = domain;
|
|
3740
|
+
}
|
|
3741
|
+
attach(link2) {
|
|
3742
|
+
this.link = link2;
|
|
3743
|
+
this.state = "connected";
|
|
3744
|
+
this.changes.wake();
|
|
3745
|
+
}
|
|
3746
|
+
get listening() {
|
|
3747
|
+
return this.state === "listening";
|
|
3748
|
+
}
|
|
3749
|
+
connection() {
|
|
3750
|
+
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
3751
|
+
return this.link;
|
|
3752
|
+
}
|
|
3753
|
+
async read(max, signal) {
|
|
3754
|
+
const { rx } = this.connection();
|
|
3755
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
3756
|
+
return pipeCall(() => rx.read(max, signal));
|
|
3757
|
+
}
|
|
3758
|
+
async peek(max, signal) {
|
|
3759
|
+
const { rx } = this.connection();
|
|
3760
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
3761
|
+
return pipeCall(() => rx.peek(max, signal));
|
|
3762
|
+
}
|
|
3763
|
+
async write(bytes, signal) {
|
|
3764
|
+
const { tx } = this.connection();
|
|
3765
|
+
if (this.writeShut) throw new KernelError("EPIPE");
|
|
3766
|
+
return pipeCall(() => tx.write(bytes, signal));
|
|
3767
|
+
}
|
|
3768
|
+
poll() {
|
|
3769
|
+
if (this.state === "listening") {
|
|
3770
|
+
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
3771
|
+
}
|
|
3772
|
+
if (this.state !== "connected" || !this.link) {
|
|
3773
|
+
return { readable: false, writable: false, hangup: true };
|
|
3774
|
+
}
|
|
3775
|
+
const { rx, tx } = this.link;
|
|
3776
|
+
return {
|
|
3777
|
+
readable: this.readShut || rx.readReady,
|
|
3778
|
+
writable: this.writeShut || tx.writeReady,
|
|
3779
|
+
hangup: rx.writersGone && tx.readersGone
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
changed(signal) {
|
|
3783
|
+
const own = this.changes.wait(signal);
|
|
3784
|
+
if (this.state !== "connected" || !this.link) return own;
|
|
3785
|
+
const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
|
|
3786
|
+
for (const wait of waits) wait.catch(() => void 0);
|
|
3787
|
+
return pipeCall(() => Promise.race(waits));
|
|
3788
|
+
}
|
|
3789
|
+
bind(addr) {
|
|
3790
|
+
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
3791
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
3792
|
+
this.local = this.net.bind(this, addr);
|
|
3793
|
+
this.bound = true;
|
|
3794
|
+
}
|
|
3795
|
+
listen(backlog) {
|
|
3796
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
3797
|
+
if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
|
|
3798
|
+
if (!this.local) {
|
|
3799
|
+
if (this.domain === "unix") throw new KernelError("EINVAL");
|
|
3800
|
+
this.bind({ family: "inet", host: "0.0.0.0", port: 0 });
|
|
3801
|
+
}
|
|
3802
|
+
this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
|
|
3803
|
+
this.state = "listening";
|
|
3804
|
+
}
|
|
3805
|
+
async accept(signal, nonblock = false) {
|
|
3806
|
+
for (; ; ) {
|
|
3807
|
+
if (this.state !== "listening") throw new KernelError("EINVAL");
|
|
3808
|
+
const next = this.queue.shift();
|
|
3809
|
+
if (next) return next;
|
|
3810
|
+
if (nonblock) throw new KernelError("EAGAIN");
|
|
3811
|
+
await this.changes.wait(signal);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
offer(server) {
|
|
3815
|
+
if (this.state !== "listening" || this.queue.length >= this.backlog) return false;
|
|
3816
|
+
this.queue.push(server);
|
|
3817
|
+
this.changes.wake();
|
|
3818
|
+
return true;
|
|
3819
|
+
}
|
|
3820
|
+
connect(addr) {
|
|
3821
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
3822
|
+
if (this.state !== "open") throw new KernelError("EINVAL");
|
|
3823
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
3824
|
+
const target = this.net.target(addr);
|
|
3825
|
+
if (!this.local) {
|
|
3826
|
+
if (addr.family === "inet") {
|
|
3827
|
+
this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
|
|
3828
|
+
this.bound = true;
|
|
3829
|
+
} else this.local = { family: "unix", path: "" };
|
|
3830
|
+
}
|
|
3831
|
+
const server = new _KernelSocket(this.net, this.domain);
|
|
3832
|
+
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost2(addr.host) } : addr;
|
|
3833
|
+
server.peer = this.local;
|
|
3834
|
+
this.peer = server.local;
|
|
3835
|
+
link(this, server);
|
|
3836
|
+
if (!target.offer(server)) {
|
|
3837
|
+
this.unlink();
|
|
3838
|
+
server.close();
|
|
3839
|
+
throw new KernelError("ECONNREFUSED");
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
unlink() {
|
|
3843
|
+
const link2 = this.link;
|
|
3844
|
+
if (link2) {
|
|
3845
|
+
link2.rx.closeRead();
|
|
3846
|
+
link2.tx.closeWrite();
|
|
3847
|
+
}
|
|
3848
|
+
this.link = void 0;
|
|
3849
|
+
this.peer = void 0;
|
|
3850
|
+
this.state = "open";
|
|
3851
|
+
}
|
|
3852
|
+
shutdown(how) {
|
|
3853
|
+
const { rx, tx } = this.connection();
|
|
3854
|
+
if (how !== SHUT_RD && how !== SHUT_WR && how !== SHUT_RDWR) throw new KernelError("EINVAL");
|
|
3855
|
+
if (how !== SHUT_WR && !this.readShut) {
|
|
3856
|
+
this.readShut = true;
|
|
3857
|
+
rx.closeRead();
|
|
3858
|
+
}
|
|
3859
|
+
if (how !== SHUT_RD && !this.writeShut) {
|
|
3860
|
+
this.writeShut = true;
|
|
3861
|
+
tx.closeWrite();
|
|
3862
|
+
}
|
|
3863
|
+
this.changes.wake();
|
|
3864
|
+
}
|
|
3865
|
+
getOption(level, name2) {
|
|
3866
|
+
if (level === SOL_SOCKET) {
|
|
3867
|
+
switch (name2) {
|
|
3868
|
+
case SO_TYPE:
|
|
3869
|
+
return SOCK_STREAM;
|
|
3870
|
+
case SO_ERROR:
|
|
3871
|
+
return 0;
|
|
3872
|
+
case SO_ACCEPTCONN:
|
|
3873
|
+
return this.state === "listening" ? 1 : 0;
|
|
3874
|
+
case SO_DOMAIN:
|
|
3875
|
+
return this.domain === "inet" ? AF_INET : AF_UNIX;
|
|
3876
|
+
case SO_SNDBUF:
|
|
3877
|
+
case SO_RCVBUF:
|
|
3878
|
+
return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
return this.options.get(`${level}:${name2}`) ?? 0;
|
|
3882
|
+
}
|
|
3883
|
+
setOption(level, name2, value) {
|
|
3884
|
+
this.options.set(`${level}:${name2}`, value);
|
|
3885
|
+
}
|
|
3886
|
+
close() {
|
|
3887
|
+
const was = this.state;
|
|
3888
|
+
this.state = "closed";
|
|
3889
|
+
if (was === "listening") {
|
|
3890
|
+
for (const pending of this.queue.splice(0)) pending.close();
|
|
3891
|
+
}
|
|
3892
|
+
if (this.link) {
|
|
3893
|
+
if (!this.readShut) this.link.rx.closeRead();
|
|
3894
|
+
if (!this.writeShut) this.link.tx.closeWrite();
|
|
3895
|
+
this.readShut = true;
|
|
3896
|
+
this.writeShut = true;
|
|
3897
|
+
}
|
|
3898
|
+
if (this.bound && this.local) this.net.release(this.local, this);
|
|
3899
|
+
this.bound = false;
|
|
3900
|
+
this.changes.wake();
|
|
3901
|
+
}
|
|
3902
|
+
static pair(net, domain) {
|
|
3903
|
+
const a = new _KernelSocket(net, domain);
|
|
3904
|
+
const b = new _KernelSocket(net, domain);
|
|
3905
|
+
const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
|
|
3906
|
+
a.local = b.local = a.peer = b.peer = name2;
|
|
3907
|
+
link(a, b);
|
|
3908
|
+
return [a, b];
|
|
3909
|
+
}
|
|
3910
|
+
};
|
|
3911
|
+
function canonicalHost2(host) {
|
|
3912
|
+
return host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
3913
|
+
}
|
|
3914
|
+
function keyOf(addr) {
|
|
3915
|
+
return addr.family === "inet" ? `inet:${addr.port}` : `unix:${addr.path}`;
|
|
3916
|
+
}
|
|
3917
|
+
var LoopbackNet = class {
|
|
3918
|
+
bound = /* @__PURE__ */ new Map();
|
|
3919
|
+
activators = /* @__PURE__ */ new Map();
|
|
3920
|
+
nextEphemeral = EPHEMERAL_FIRST;
|
|
3921
|
+
activate(addr, start) {
|
|
3922
|
+
this.activators.set(keyOf(addr), start);
|
|
3923
|
+
}
|
|
3924
|
+
socket(domain) {
|
|
3925
|
+
return new KernelSocket(this, domain);
|
|
3926
|
+
}
|
|
3927
|
+
listen(addr, backlog = 128) {
|
|
3928
|
+
const socket = this.socket(addr.family);
|
|
3929
|
+
socket.bind(addr);
|
|
3930
|
+
socket.listen(backlog);
|
|
3931
|
+
return socket;
|
|
3932
|
+
}
|
|
3933
|
+
connect(addr) {
|
|
3934
|
+
const socket = this.socket(addr.family);
|
|
3935
|
+
socket.connect(addr);
|
|
3936
|
+
return socket;
|
|
3937
|
+
}
|
|
3938
|
+
bind(socket, addr) {
|
|
3939
|
+
if (addr.family === "unix") {
|
|
3940
|
+
if (addr.path === "") throw new KernelError("EINVAL");
|
|
3941
|
+
} else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
|
|
3942
|
+
throw new KernelError("EADDRNOTAVAIL");
|
|
3943
|
+
}
|
|
3944
|
+
const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
|
|
3945
|
+
const key = keyOf(named);
|
|
3946
|
+
if (this.bound.has(key)) throw new KernelError("EADDRINUSE");
|
|
3947
|
+
this.bound.set(key, socket);
|
|
3948
|
+
return named;
|
|
3949
|
+
}
|
|
3950
|
+
release(addr, socket) {
|
|
3951
|
+
const key = keyOf(addr);
|
|
3952
|
+
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
3953
|
+
}
|
|
3954
|
+
target(addr) {
|
|
3955
|
+
if (addr.family === "inet" && !isLoopback(canonicalHost2(addr.host))) {
|
|
3956
|
+
throw new KernelError("ENETUNREACH");
|
|
3957
|
+
}
|
|
3958
|
+
const key = keyOf(addr);
|
|
3959
|
+
if (!this.bound.has(key)) this.activators.get(key)?.();
|
|
3960
|
+
const socket = this.bound.get(key);
|
|
3961
|
+
if (!socket?.listening) throw new KernelError("ECONNREFUSED");
|
|
3962
|
+
return socket;
|
|
3963
|
+
}
|
|
3964
|
+
ephemeral() {
|
|
3965
|
+
const span = EPHEMERAL_LAST - EPHEMERAL_FIRST + 1;
|
|
3966
|
+
for (let i = 0; i < span; i++) {
|
|
3967
|
+
const port = this.nextEphemeral;
|
|
3968
|
+
this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
|
|
3969
|
+
if (!this.bound.has(`inet:${port}`)) return port;
|
|
3970
|
+
}
|
|
3971
|
+
throw new KernelError("EADDRINUSE");
|
|
3972
|
+
}
|
|
3973
|
+
};
|
|
3974
|
+
|
|
3975
|
+
// src/kernel/socket-syscalls.ts
|
|
3976
|
+
var SOCKET_OPS = [
|
|
3977
|
+
"sock-open",
|
|
3978
|
+
"sock-pair",
|
|
3979
|
+
"sock-bind",
|
|
3980
|
+
"sock-listen",
|
|
3981
|
+
"sock-accept",
|
|
3982
|
+
"sock-connect",
|
|
3983
|
+
"sock-shutdown",
|
|
3984
|
+
"sock-name",
|
|
3985
|
+
"sock-getopt",
|
|
3986
|
+
"sock-setopt"
|
|
3987
|
+
];
|
|
3988
|
+
function socketAt(fds, fd) {
|
|
3989
|
+
const file = fds.get(fd).file;
|
|
3990
|
+
if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
|
|
3991
|
+
return file;
|
|
3992
|
+
}
|
|
3993
|
+
var ok = (json3) => json3 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json3 };
|
|
3994
|
+
async function socketSyscall(req, proc) {
|
|
3995
|
+
const { fds, net } = proc;
|
|
3996
|
+
switch (req.op) {
|
|
3997
|
+
case "sock-open":
|
|
3998
|
+
return ok(fds.install(new OpenFile(net.socket(req.domain)), 3));
|
|
3999
|
+
case "sock-pair": {
|
|
4000
|
+
const [a, b] = KernelSocket.pair(net, req.domain);
|
|
4001
|
+
const first = fds.install(new OpenFile(a), 3);
|
|
4002
|
+
try {
|
|
4003
|
+
return ok([first, fds.install(new OpenFile(b), 3)]);
|
|
4004
|
+
} catch (e) {
|
|
4005
|
+
await Promise.resolve(fds.close(first));
|
|
4006
|
+
throw e;
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
case "sock-bind":
|
|
4010
|
+
socketAt(fds, req.fd).bind(req.addr);
|
|
4011
|
+
return ok();
|
|
4012
|
+
case "sock-listen":
|
|
4013
|
+
socketAt(fds, req.fd).listen(req.backlog);
|
|
4014
|
+
return ok();
|
|
4015
|
+
case "sock-accept":
|
|
4016
|
+
return ok(await accept(proc, req.fd, req.nonblock));
|
|
4017
|
+
case "sock-connect":
|
|
4018
|
+
socketAt(fds, req.fd).connect(req.addr);
|
|
4019
|
+
if (req.nonblock) throw new KernelError("EINPROGRESS");
|
|
4020
|
+
return ok();
|
|
4021
|
+
case "sock-shutdown":
|
|
4022
|
+
socketAt(fds, req.fd).shutdown(req.how);
|
|
4023
|
+
return ok();
|
|
4024
|
+
case "sock-name": {
|
|
4025
|
+
const socket = socketAt(fds, req.fd);
|
|
4026
|
+
const addr = req.peer ? socket.peer : socket.local;
|
|
4027
|
+
if (req.peer && !addr) throw new KernelError("ENOTCONN");
|
|
4028
|
+
return ok(addr ?? unnamed(socket.domain));
|
|
4029
|
+
}
|
|
4030
|
+
case "sock-getopt":
|
|
4031
|
+
return ok(socketAt(fds, req.fd).getOption(req.level, req.name));
|
|
4032
|
+
case "sock-setopt":
|
|
4033
|
+
socketAt(fds, req.fd).setOption(req.level, req.name, req.value);
|
|
4034
|
+
return ok();
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
function unnamed(domain) {
|
|
4038
|
+
return domain === "inet" ? { family: "inet", host: "0.0.0.0", port: 0 } : { family: "unix", path: "" };
|
|
4039
|
+
}
|
|
4040
|
+
async function accept(proc, fd, nonblock) {
|
|
4041
|
+
const listener = socketAt(proc.fds, fd);
|
|
4042
|
+
const waits = !nonblock && !pollFile(listener).readable;
|
|
4043
|
+
const socket = await listener.accept(waits ? proc.blocking() : void 0, nonblock);
|
|
4044
|
+
return { fd: proc.fds.install(new OpenFile(socket), 3), peer: socket.peer };
|
|
4045
|
+
}
|
|
4046
|
+
|
|
2114
4047
|
// src/kernel/vfs-file.ts
|
|
2115
4048
|
var O_ACCMODE = 3;
|
|
2116
4049
|
var O_WRONLY = 1;
|
|
@@ -2140,14 +4073,14 @@ var VfsNode = class {
|
|
|
2140
4073
|
fs;
|
|
2141
4074
|
path;
|
|
2142
4075
|
orphaned;
|
|
2143
|
-
constructor(fs, path,
|
|
4076
|
+
constructor(fs, path, contents2, orphaned = false, dirty = false) {
|
|
2144
4077
|
this.fs = fs;
|
|
2145
4078
|
this.path = path;
|
|
2146
4079
|
this.orphaned = orphaned;
|
|
2147
|
-
if (
|
|
4080
|
+
if (contents2 !== void 0) this.data = new Uint8Array(contents2);
|
|
2148
4081
|
else if (orphaned) this.data = new Uint8Array(0);
|
|
2149
4082
|
this.length = this.data?.length ?? 0;
|
|
2150
|
-
if (dirty &&
|
|
4083
|
+
if (dirty && contents2 !== void 0) this.markDirty();
|
|
2151
4084
|
}
|
|
2152
4085
|
serial(op) {
|
|
2153
4086
|
const next = this.queue.then(op);
|
|
@@ -2167,8 +4100,8 @@ var VfsNode = class {
|
|
|
2167
4100
|
}
|
|
2168
4101
|
return this.data;
|
|
2169
4102
|
}
|
|
2170
|
-
replace(
|
|
2171
|
-
this.data = new Uint8Array(
|
|
4103
|
+
replace(contents2) {
|
|
4104
|
+
this.data = new Uint8Array(contents2);
|
|
2172
4105
|
this.length = this.data.length;
|
|
2173
4106
|
this.missing = false;
|
|
2174
4107
|
this.markDirty();
|
|
@@ -2249,13 +4182,13 @@ var VfsNodes = class {
|
|
|
2249
4182
|
node.opens++;
|
|
2250
4183
|
return node;
|
|
2251
4184
|
}
|
|
2252
|
-
adopt(path,
|
|
4185
|
+
adopt(path, contents2, dirty) {
|
|
2253
4186
|
let node = this.byPath.get(path);
|
|
2254
4187
|
if (!node) {
|
|
2255
|
-
node = new VfsNode(this.fs, path,
|
|
4188
|
+
node = new VfsNode(this.fs, path, contents2, false, dirty);
|
|
2256
4189
|
this.byPath.set(path, node);
|
|
2257
4190
|
} else if (dirty) {
|
|
2258
|
-
node.replace(
|
|
4191
|
+
node.replace(contents2);
|
|
2259
4192
|
}
|
|
2260
4193
|
node.opens++;
|
|
2261
4194
|
return node;
|
|
@@ -2377,6 +4310,12 @@ var JOB_OPS = /* @__PURE__ */ new Set([
|
|
|
2377
4310
|
function isJobSyscall(req) {
|
|
2378
4311
|
return JOB_OPS.has(req.op);
|
|
2379
4312
|
}
|
|
4313
|
+
function isHttpSyscall(req) {
|
|
4314
|
+
return req.op.startsWith("net-");
|
|
4315
|
+
}
|
|
4316
|
+
function isSocketSyscall(req) {
|
|
4317
|
+
return req.op.startsWith("sock-");
|
|
4318
|
+
}
|
|
2380
4319
|
function isPtySyscall(req) {
|
|
2381
4320
|
return req.op.startsWith("pty-");
|
|
2382
4321
|
}
|
|
@@ -2435,6 +4374,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
2435
4374
|
"tty-pgrp-set",
|
|
2436
4375
|
"sig-mask",
|
|
2437
4376
|
"sig-pause",
|
|
4377
|
+
...SOCKET_OPS,
|
|
4378
|
+
...HTTP_OPS,
|
|
2438
4379
|
...PTY_OPS
|
|
2439
4380
|
]);
|
|
2440
4381
|
function isWasmSyscall(req) {
|
|
@@ -2455,6 +4396,7 @@ var WasmProcess = class {
|
|
|
2455
4396
|
resumed = Promise.resolve();
|
|
2456
4397
|
wake;
|
|
2457
4398
|
stateListeners = [];
|
|
4399
|
+
net;
|
|
2458
4400
|
nodes;
|
|
2459
4401
|
pid;
|
|
2460
4402
|
fds;
|
|
@@ -2524,6 +4466,8 @@ var WasmProcess = class {
|
|
|
2524
4466
|
if (isFdSyscall(req)) return await this.fdSyscall(req);
|
|
2525
4467
|
if (isTtySyscall(req)) return this.ttySyscall(req);
|
|
2526
4468
|
if (isJobSyscall(req)) return this.jobSyscall(req);
|
|
4469
|
+
if (isSocketSyscall(req)) return await this.socketSyscall(req);
|
|
4470
|
+
if (isHttpSyscall(req)) return await this.httpSyscall(req);
|
|
2527
4471
|
if (isPtySyscall(req)) {
|
|
2528
4472
|
const { ptys, jobs } = this.options;
|
|
2529
4473
|
return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs });
|
|
@@ -2721,7 +4665,7 @@ var WasmProcess = class {
|
|
|
2721
4665
|
const flags = this.fds.statusFlags(fd);
|
|
2722
4666
|
return {
|
|
2723
4667
|
tty: file.tty !== void 0,
|
|
2724
|
-
kind: kernelFdKind(file),
|
|
4668
|
+
kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
|
|
2725
4669
|
...file.heldMeta ? { meta: file.heldMeta } : {},
|
|
2726
4670
|
...flags !== void 0 ? { flags } : {},
|
|
2727
4671
|
...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
@@ -2794,6 +4738,19 @@ var WasmProcess = class {
|
|
|
2794
4738
|
return { ok: true, kind: "json", json: jobs.setsid(this.pid) };
|
|
2795
4739
|
}
|
|
2796
4740
|
}
|
|
4741
|
+
httpSyscall(req) {
|
|
4742
|
+
const http = this.options.http;
|
|
4743
|
+
if (!http) return Promise.resolve({ ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT });
|
|
4744
|
+
return http.syscall(req);
|
|
4745
|
+
}
|
|
4746
|
+
socketSyscall(req) {
|
|
4747
|
+
this.net ??= this.options.net ?? new LoopbackNet();
|
|
4748
|
+
return socketSyscall(req, {
|
|
4749
|
+
fds: this.fds,
|
|
4750
|
+
net: this.net,
|
|
4751
|
+
blocking: () => this.blockingSignal()
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
2797
4754
|
tty(fd) {
|
|
2798
4755
|
const file = this.fds.get(fd).file;
|
|
2799
4756
|
const tty = file.tty ?? file.pty?.slave;
|
|
@@ -2892,6 +4849,7 @@ var WasmProcess = class {
|
|
|
2892
4849
|
this.clearAlarm();
|
|
2893
4850
|
for (const pid of this.children.pids()) this.options.onReap?.(pid);
|
|
2894
4851
|
await this.fds.closeAll();
|
|
4852
|
+
await this.options.http?.closeAll();
|
|
2895
4853
|
}
|
|
2896
4854
|
};
|
|
2897
4855
|
|
|
@@ -2913,7 +4871,7 @@ function inheritedFds(fds) {
|
|
|
2913
4871
|
return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
|
|
2914
4872
|
const open = fds.get(fd);
|
|
2915
4873
|
const flags = fds.statusFlags(fd);
|
|
2916
|
-
const kind = kernelFdKind(open.file);
|
|
4874
|
+
const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
|
|
2917
4875
|
const meta = open.file.heldMeta;
|
|
2918
4876
|
return {
|
|
2919
4877
|
fd,
|
|
@@ -2927,7 +4885,7 @@ function inheritedFds(fds) {
|
|
|
2927
4885
|
}
|
|
2928
4886
|
function spawnWasmProcess(opts) {
|
|
2929
4887
|
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
2930
|
-
const
|
|
4888
|
+
const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
|
|
2931
4889
|
const process = new WasmProcess(opts.pid, opts.fds, {
|
|
2932
4890
|
spawner: opts.spawner,
|
|
2933
4891
|
forker: opts.forker,
|
|
@@ -2935,10 +4893,12 @@ function spawnWasmProcess(opts) {
|
|
|
2935
4893
|
kill: opts.kill,
|
|
2936
4894
|
jobs: opts.jobs,
|
|
2937
4895
|
ptys: opts.ptys,
|
|
4896
|
+
net: opts.net,
|
|
4897
|
+
http: opts.http,
|
|
2938
4898
|
onReap: opts.onReap,
|
|
2939
|
-
onPending: (sig) => void Atomics.or(
|
|
2940
|
-
onTimer: (which) => void Atomics.or(
|
|
2941
|
-
hasPending: () => Atomics.load(
|
|
4899
|
+
onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
|
|
4900
|
+
onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
|
|
4901
|
+
hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
|
|
2942
4902
|
raise: (sig) => signal(sig)
|
|
2943
4903
|
});
|
|
2944
4904
|
const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
|
|
@@ -3073,9 +5033,9 @@ var JobTable = class {
|
|
|
3073
5033
|
this.terminals.delete(pid);
|
|
3074
5034
|
return pid;
|
|
3075
5035
|
}
|
|
3076
|
-
terminalNamed(
|
|
5036
|
+
terminalNamed(name2) {
|
|
3077
5037
|
const known = /* @__PURE__ */ new Set([...this.terminals.values(), ...this.foreground.keys()]);
|
|
3078
|
-
return [...known].find((tty) => tty.name ===
|
|
5038
|
+
return [...known].find((tty) => tty.name === name2);
|
|
3079
5039
|
}
|
|
3080
5040
|
controllingTerminal(pid) {
|
|
3081
5041
|
const member = this.members.get(pid);
|
|
@@ -3145,12 +5105,12 @@ function childHandle(handle) {
|
|
|
3145
5105
|
onState: (listener) => handle.onState(listener)
|
|
3146
5106
|
};
|
|
3147
5107
|
}
|
|
3148
|
-
function
|
|
3149
|
-
const out = new Uint8Array(chunks.reduce((n,
|
|
5108
|
+
function concat4(chunks) {
|
|
5109
|
+
const out = new Uint8Array(chunks.reduce((n, chunk2) => n + chunk2.length, 0));
|
|
3150
5110
|
let offset = 0;
|
|
3151
|
-
for (const
|
|
3152
|
-
out.set(
|
|
3153
|
-
offset +=
|
|
5111
|
+
for (const chunk2 of chunks) {
|
|
5112
|
+
out.set(chunk2, offset);
|
|
5113
|
+
offset += chunk2.length;
|
|
3154
5114
|
}
|
|
3155
5115
|
return out;
|
|
3156
5116
|
}
|
|
@@ -3167,13 +5127,23 @@ var Launcher = class {
|
|
|
3167
5127
|
orphans = /* @__PURE__ */ new Set();
|
|
3168
5128
|
jobs = new JobTable();
|
|
3169
5129
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
5130
|
+
net = new LoopbackNet();
|
|
5131
|
+
ca;
|
|
5132
|
+
transport;
|
|
3170
5133
|
nextPid = 1e3;
|
|
3171
5134
|
terminals = 0;
|
|
3172
5135
|
constructor(options) {
|
|
3173
5136
|
this.base = options.fs;
|
|
3174
5137
|
this.createWorker = options.createWorker;
|
|
3175
5138
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
3176
|
-
this.env = options.env
|
|
5139
|
+
this.env = { ...networkEnv(), ...options.env };
|
|
5140
|
+
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
5141
|
+
this.transport = options.transport ?? missingTransport();
|
|
5142
|
+
enableNetwork(this.net, {
|
|
5143
|
+
transport: this.transport,
|
|
5144
|
+
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
5145
|
+
ca: this.ca
|
|
5146
|
+
});
|
|
3177
5147
|
this.fs = withCommandDirs(options.fs, async () => new Set((await this.commands()).keys()));
|
|
3178
5148
|
}
|
|
3179
5149
|
commands() {
|
|
@@ -3181,9 +5151,9 @@ var Launcher = class {
|
|
|
3181
5151
|
return this.catalog;
|
|
3182
5152
|
}
|
|
3183
5153
|
async resolve(file, argv0, cwd) {
|
|
3184
|
-
const
|
|
3185
|
-
if (
|
|
3186
|
-
const command = (await this.commands()).get(
|
|
5154
|
+
const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
|
|
5155
|
+
if (name2 !== void 0) {
|
|
5156
|
+
const command = (await this.commands()).get(name2);
|
|
3187
5157
|
return command && targetOf(command);
|
|
3188
5158
|
}
|
|
3189
5159
|
const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
|
|
@@ -3276,6 +5246,8 @@ var Launcher = class {
|
|
|
3276
5246
|
kill: (target, sig) => this.kill(target, sig),
|
|
3277
5247
|
jobs: this.jobs,
|
|
3278
5248
|
ptys: this.ptys,
|
|
5249
|
+
net: this.net,
|
|
5250
|
+
http: new HttpHandles(this.transport),
|
|
3279
5251
|
onReap: (child) => this.reaped(child),
|
|
3280
5252
|
...req.fork ? { fork: req.fork } : {},
|
|
3281
5253
|
...req.ppid !== void 0 ? { ppid: req.ppid } : {}
|
|
@@ -3316,6 +5288,7 @@ var Launcher = class {
|
|
|
3316
5288
|
}
|
|
3317
5289
|
async prepare() {
|
|
3318
5290
|
for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
|
|
5291
|
+
await writeCaFile(this.base, this.ca).catch(() => void 0);
|
|
3319
5292
|
}
|
|
3320
5293
|
environment(cwd, extra) {
|
|
3321
5294
|
return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
|
|
@@ -3347,7 +5320,7 @@ var Launcher = class {
|
|
|
3347
5320
|
COLORTERM: "truecolor",
|
|
3348
5321
|
...options.env
|
|
3349
5322
|
});
|
|
3350
|
-
const report = (
|
|
5323
|
+
const report = (message2) => options.onData(encoder3.encode(`${message2}\r
|
|
3351
5324
|
`));
|
|
3352
5325
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
3353
5326
|
leader = handle.pid;
|
|
@@ -3375,11 +5348,11 @@ var Launcher = class {
|
|
|
3375
5348
|
};
|
|
3376
5349
|
const stdout = collect(out, options.onStdout);
|
|
3377
5350
|
const stderr = collect(err, options.onStderr);
|
|
3378
|
-
const report = (
|
|
5351
|
+
const report = (message2) => stderr(encoder3.encode(`${message2}
|
|
3379
5352
|
`));
|
|
3380
5353
|
if (!planned) {
|
|
3381
5354
|
report(`${file}: command not found`);
|
|
3382
|
-
return { status: NOT_FOUND, stdout:
|
|
5355
|
+
return { status: NOT_FOUND, stdout: concat4(out), stderr: concat4(err) };
|
|
3383
5356
|
}
|
|
3384
5357
|
const fds = new FdTable();
|
|
3385
5358
|
fds.installAt(0, options.stdin ? bytesSource(options.stdin) : nullFile());
|
|
@@ -3387,15 +5360,15 @@ var Launcher = class {
|
|
|
3387
5360
|
fds.installAt(2, sinkFile(stderr));
|
|
3388
5361
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
3389
5362
|
const status = await handle.exited;
|
|
3390
|
-
return { status, stdout:
|
|
5363
|
+
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
3391
5364
|
}
|
|
3392
5365
|
};
|
|
3393
5366
|
|
|
3394
5367
|
// src/serve.ts
|
|
3395
|
-
function signalNumber(
|
|
3396
|
-
const key =
|
|
3397
|
-
if (!
|
|
3398
|
-
throw new Error(`unknown signal ${
|
|
5368
|
+
function signalNumber(name2) {
|
|
5369
|
+
const key = name2.slice(3);
|
|
5370
|
+
if (!name2.startsWith("SIG") || !Object.hasOwn(SIG, key))
|
|
5371
|
+
throw new Error(`unknown signal ${name2}`);
|
|
3399
5372
|
return SIG[key];
|
|
3400
5373
|
}
|
|
3401
5374
|
function act(session, req) {
|
|
@@ -3406,6 +5379,7 @@ function act(session, req) {
|
|
|
3406
5379
|
}
|
|
3407
5380
|
function serveKernel(port, deps) {
|
|
3408
5381
|
let launcher;
|
|
5382
|
+
let remote;
|
|
3409
5383
|
const terminals = /* @__PURE__ */ new Map();
|
|
3410
5384
|
const reply = (id, body) => port.postMessage({ id, ...body });
|
|
3411
5385
|
async function ready2() {
|
|
@@ -3426,15 +5400,18 @@ function serveKernel(port, deps) {
|
|
|
3426
5400
|
async function handle(req) {
|
|
3427
5401
|
if (req.op === "init") {
|
|
3428
5402
|
launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
|
|
3429
|
-
const
|
|
3430
|
-
const meta =
|
|
5403
|
+
const name2 = req.metadata ?? META_DB;
|
|
5404
|
+
const meta = name2 === false ? void 0 : await deps.metadata?.(name2);
|
|
5405
|
+
remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
|
|
3431
5406
|
const fs = new OpfsFs(root, meta);
|
|
3432
5407
|
await fs.reconcile();
|
|
3433
5408
|
const started = new Launcher({
|
|
3434
5409
|
fs,
|
|
3435
5410
|
createWorker: deps.createWorker,
|
|
3436
5411
|
...req.modules ? { modules: req.modules } : {},
|
|
3437
|
-
...req.env ? { env: req.env } : {}
|
|
5412
|
+
...req.env ? { env: req.env } : {},
|
|
5413
|
+
...remote ? { transport: remote } : {},
|
|
5414
|
+
caStore: caStore(deps.metadata ? name2 : false)
|
|
3438
5415
|
});
|
|
3439
5416
|
await started.prepare();
|
|
3440
5417
|
return started;
|
|
@@ -3456,6 +5433,10 @@ function serveKernel(port, deps) {
|
|
|
3456
5433
|
});
|
|
3457
5434
|
}
|
|
3458
5435
|
port.addEventListener("message", (event) => {
|
|
5436
|
+
if (event.data.net !== void 0) {
|
|
5437
|
+
remote?.receive(event.data);
|
|
5438
|
+
return;
|
|
5439
|
+
}
|
|
3459
5440
|
const req = event.data;
|
|
3460
5441
|
handle(req).then(
|
|
3461
5442
|
(result) => reply(req.id, { result }),
|
|
@@ -3469,6 +5450,6 @@ var processWorker = new URL("./process-worker.js", import.meta.url);
|
|
|
3469
5450
|
serveKernel(globalThis, {
|
|
3470
5451
|
storage: () => navigator.storage.getDirectory(),
|
|
3471
5452
|
createWorker: () => new Worker(processWorker, { type: "module" }),
|
|
3472
|
-
metadata: (
|
|
5453
|
+
metadata: (name2) => openMeta(name2, indexedDB)
|
|
3473
5454
|
});
|
|
3474
5455
|
//# sourceMappingURL=kernel-worker.js.map
|