@ai-ecoverse/slicc-kernel 1.89.0 → 1.91.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/dist/node.js CHANGED
@@ -92,6 +92,100 @@ function portCdpHook(ask, gone) {
92
92
  });
93
93
  }
94
94
 
95
+ // src/facade/port.ts
96
+ var FACADE_GONE = "the facade host is gone";
97
+ function serveFacadePort(port, hook, { name: name2, stamp }, onDone) {
98
+ let conn;
99
+ let done4 = false;
100
+ const end = () => {
101
+ done4 = true;
102
+ port.close();
103
+ onDone?.();
104
+ };
105
+ const finish = () => {
106
+ if (done4) return;
107
+ port.postMessage({ close: true });
108
+ end();
109
+ };
110
+ port.addEventListener(
111
+ "message",
112
+ ({ data }) => {
113
+ if (data.b !== void 0) conn?.send(data.b);
114
+ else if (data.close && !done4) {
115
+ end();
116
+ conn?.close(typeof data.close === "string" ? data.close : void 0);
117
+ }
118
+ }
119
+ );
120
+ port.start?.();
121
+ new Promise((resolve2) => resolve2(hook(stamp, { name: name2 }))).then(
122
+ (opened2) => {
123
+ conn = opened2;
124
+ if (done4) return opened2.close();
125
+ opened2.onmessage = (bytes) => {
126
+ if (done4) return;
127
+ const copy = bytes.slice();
128
+ port.postMessage({ b: copy }, [copy.buffer]);
129
+ };
130
+ opened2.onclose = () => finish();
131
+ port.postMessage({ open: true });
132
+ },
133
+ (e) => {
134
+ if (done4) return;
135
+ port.postMessage({ error: e instanceof Error ? e.message : String(e) });
136
+ end();
137
+ }
138
+ );
139
+ return () => {
140
+ if (done4) return;
141
+ conn?.close();
142
+ finish();
143
+ };
144
+ }
145
+ function portFacadeHook(ask, gone) {
146
+ return (stamp, { name: name2 }) => new Promise((resolve2, reject) => {
147
+ const { port1, port2 } = new MessageChannel();
148
+ let closed = false;
149
+ const shut = () => {
150
+ closed = true;
151
+ port1.close();
152
+ };
153
+ const conn = {
154
+ send: (bytes) => {
155
+ if (closed) return;
156
+ const copy = bytes.slice();
157
+ port1.postMessage({ b: copy }, [copy.buffer]);
158
+ },
159
+ onmessage: null,
160
+ onclose: null,
161
+ close: (reason) => {
162
+ if (closed) return;
163
+ port1.postMessage({ close: reason ?? true });
164
+ shut();
165
+ }
166
+ };
167
+ port1.addEventListener("message", ({ data }) => {
168
+ if ("b" in data) conn.onmessage?.(data.b);
169
+ else if ("open" in data) resolve2(conn);
170
+ else if ("error" in data) {
171
+ shut();
172
+ reject(new Error(data.error));
173
+ } else if (!closed) {
174
+ shut();
175
+ conn.onclose?.();
176
+ }
177
+ });
178
+ port1.start();
179
+ void gone?.then(() => {
180
+ if (closed) return;
181
+ shut();
182
+ reject(new Error(FACADE_GONE));
183
+ conn.onclose?.(FACADE_GONE);
184
+ });
185
+ ask({ name: name2, stamp }, port2);
186
+ });
187
+ }
188
+
95
189
  // src/fs/types.ts
96
190
  var DEFAULT_USERS = "enforce";
97
191
  function rangedOps(fs) {
@@ -376,8 +470,8 @@ async function access(fs, cred, path, want, options = {}) {
376
470
  if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
377
471
  return { ...walked, missing: true };
378
472
  }
379
- function refused(path, errno = "EACCES") {
380
- return fsError(errno, path);
473
+ function refused(path, errno2 = "EACCES") {
474
+ return fsError(errno2, path);
381
475
  }
382
476
 
383
477
  // src/fs/dac.ts
@@ -437,9 +531,9 @@ var Created = class {
437
531
  function linked(g, options) {
438
532
  return g.links === void 0 ? options : { ...options, links: g.links };
439
533
  }
440
- function deny(g, op, path, errno = "EACCES") {
534
+ function deny(g, op, path, errno2 = "EACCES") {
441
535
  const tag = g.tag === void 0 ? {} : { tag: g.tag };
442
- g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
536
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno: errno2, ...tag });
443
537
  }
444
538
  var TTY_MODE = 400;
445
539
  function ttyOpenable(g, tty) {
@@ -1106,7 +1200,7 @@ var WASI_SIGNAL_TO_POSIX = Object.fromEntries(
1106
1200
 
1107
1201
  // src/process/wasi-errno.ts
1108
1202
  var WASI_ERRNO = {
1109
- ...Object.fromEntries(Object.entries(E).map(([name2, errno]) => [`E${name2}`, errno])),
1203
+ ...Object.fromEntries(Object.entries(E).map(([name2, errno2]) => [`E${name2}`, errno2])),
1110
1204
  EWOULDBLOCK: E.AGAIN,
1111
1205
  ENOTSUP: 138,
1112
1206
  EOPNOTSUPP: 138,
@@ -1203,7 +1297,7 @@ function dialError(e, timedOut, closed) {
1203
1297
  if (timedOut) return new KernelError("ETIMEDOUT");
1204
1298
  if (closed) return new KernelError("ECONNABORTED");
1205
1299
  const code = e?.code;
1206
- const known = DIAL_ERRORS.find((errno) => errno === code);
1300
+ const known = DIAL_ERRORS.find((errno2) => errno2 === code);
1207
1301
  return new KernelError(known ?? "EHOSTUNREACH");
1208
1302
  }
1209
1303
  function isLoopback(host) {
@@ -1260,6 +1354,7 @@ var KernelSocket = class _KernelSocket {
1260
1354
  local;
1261
1355
  peer;
1262
1356
  peerCred;
1357
+ peerStamp;
1263
1358
  listenerCred;
1264
1359
  link;
1265
1360
  readShut = false;
@@ -1330,10 +1425,10 @@ var KernelSocket = class _KernelSocket {
1330
1425
  for (const wait2 of waits) wait2.catch(() => void 0);
1331
1426
  return pipeCall(() => Promise.race(waits));
1332
1427
  }
1333
- bind(addr) {
1428
+ bind(addr, kernel = false) {
1334
1429
  if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
1335
1430
  if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
1336
- this.local = this.net.bind(this, addr);
1431
+ this.local = this.net.bind(this, addr, kernel);
1337
1432
  this.boundAt = this.local;
1338
1433
  }
1339
1434
  listen(backlog, cred) {
@@ -1362,7 +1457,7 @@ var KernelSocket = class _KernelSocket {
1362
1457
  this.changes.wake();
1363
1458
  return true;
1364
1459
  }
1365
- connect(addr, viaUplink = true, cred) {
1460
+ connect(addr, viaUplink = true, cred, stamp) {
1366
1461
  if (this.state === "connected") throw new KernelError("EISCONN");
1367
1462
  if (this.state === "connecting") throw new KernelError("EALREADY");
1368
1463
  if (this.state !== "open") throw new KernelError("EINVAL");
@@ -1381,6 +1476,7 @@ var KernelSocket = class _KernelSocket {
1381
1476
  server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
1382
1477
  server.peer = this.local;
1383
1478
  server.peerCred = cred;
1479
+ server.peerStamp = stamp;
1384
1480
  this.peer = server.local;
1385
1481
  this.peerCred = target.listenerCred;
1386
1482
  link(this, server);
@@ -1521,6 +1617,8 @@ var LoopbackNet = class {
1521
1617
  bound = /* @__PURE__ */ new Map();
1522
1618
  activators = /* @__PURE__ */ new Map();
1523
1619
  internal = /* @__PURE__ */ new Set();
1620
+ reserved = /* @__PURE__ */ new Set();
1621
+ facades = /* @__PURE__ */ new Map();
1524
1622
  nextEphemeral = EPHEMERAL_FIRST;
1525
1623
  route;
1526
1624
  useUplink(route) {
@@ -1535,6 +1633,18 @@ var LoopbackNet = class {
1535
1633
  if (reserve) this.internal.add(port);
1536
1634
  return this.internal.has(port);
1537
1635
  }
1636
+ reserve(port) {
1637
+ this.reserved.add(port);
1638
+ }
1639
+ facade(port, name2) {
1640
+ this.reserve(port);
1641
+ this.kernelOnly(port, true);
1642
+ this.facades.set(port, name2);
1643
+ }
1644
+ facadeAt(addr) {
1645
+ if (addr.family !== "inet" || !isLoopback(canonicalHost(addr.host))) return void 0;
1646
+ return this.facades.get(addr.port);
1647
+ }
1538
1648
  activate(addr, start2) {
1539
1649
  this.activators.set(keyOf(addr), start2);
1540
1650
  }
@@ -1543,7 +1653,7 @@ var LoopbackNet = class {
1543
1653
  }
1544
1654
  listen(addr, backlog = 128) {
1545
1655
  const socket = this.socket(addr.family);
1546
- socket.bind(addr);
1656
+ socket.bind(addr, true);
1547
1657
  socket.listen(backlog);
1548
1658
  return socket;
1549
1659
  }
@@ -1552,11 +1662,13 @@ var LoopbackNet = class {
1552
1662
  void socket.connect(addr, false);
1553
1663
  return socket;
1554
1664
  }
1555
- bind(socket, addr) {
1665
+ bind(socket, addr, kernel = false) {
1556
1666
  if (addr.family === "unix") {
1557
1667
  if (addr.path === "") throw new KernelError("EINVAL");
1558
1668
  } else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
1559
1669
  throw new KernelError("EADDRNOTAVAIL");
1670
+ } else if (!kernel && this.reserved.has(addr.port)) {
1671
+ throw new KernelError("EADDRINUSE");
1560
1672
  }
1561
1673
  const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
1562
1674
  const key = keyOf(named);
@@ -1583,7 +1695,7 @@ var LoopbackNet = class {
1583
1695
  for (let i = 0; i < span; i++) {
1584
1696
  const port = this.nextEphemeral;
1585
1697
  this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
1586
- if (!this.bound.has(`inet:${port}`)) return port;
1698
+ if (!this.bound.has(`inet:${port}`) && !this.reserved.has(port)) return port;
1587
1699
  }
1588
1700
  throw new KernelError("EADDRINUSE");
1589
1701
  }
@@ -2041,11 +2153,11 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
2041
2153
  };
2042
2154
  const sockets = /* @__PURE__ */ new Map();
2043
2155
  const credit = /* @__PURE__ */ new Map();
2044
- const refill = (nid, n, closing = false) => {
2156
+ const refill = (nid, n, closing2 = false) => {
2045
2157
  const flow = credit.get(nid);
2046
2158
  if (!flow) return;
2047
2159
  flow.inflight -= n;
2048
- if (closing && !flow.closing) {
2160
+ if (closing2 && !flow.closing) {
2049
2161
  flow.closing = true;
2050
2162
  flow.deadline();
2051
2163
  }
@@ -2068,12 +2180,12 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
2068
2180
  }
2069
2181
  sockets.set(call.nid, socket);
2070
2182
  peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
2071
- void relay(call.nid, socket);
2183
+ void relay2(call.nid, socket);
2072
2184
  } catch (e) {
2073
2185
  fail3(call.nid, e);
2074
2186
  }
2075
2187
  };
2076
- const relay = async (nid, socket) => {
2188
+ const relay2 = async (nid, socket) => {
2077
2189
  let ending;
2078
2190
  let late;
2079
2191
  const unanswered = new Promise((resolve2) => {
@@ -2509,7 +2621,7 @@ var UserDb = class {
2509
2621
  };
2510
2622
 
2511
2623
  // src/client/protocol.ts
2512
- var PROTOCOL = [1, 10];
2624
+ var PROTOCOL = [1, 11];
2513
2625
  var FS_METHODS = [
2514
2626
  "readFile",
2515
2627
  "writeFile",
@@ -2734,6 +2846,45 @@ function cdpRegistry(send, gone) {
2734
2846
  }
2735
2847
  };
2736
2848
  }
2849
+ function facadeRegistry(send, gone) {
2850
+ const registered = /* @__PURE__ */ new Map();
2851
+ const drop = (id2) => {
2852
+ registered.get(id2)?.();
2853
+ return registered.delete(id2);
2854
+ };
2855
+ return {
2856
+ serve(l, req) {
2857
+ const hook = portFacadeHook(
2858
+ (request, facade) => send({ facadeOpen: { ...request, registration: req.id }, port: facade }, [
2859
+ facade
2860
+ ]),
2861
+ gone
2862
+ );
2863
+ registered.set(req.id, l.facades.register(req.name, hook));
2864
+ return req.id;
2865
+ },
2866
+ drop,
2867
+ close: () => {
2868
+ for (const id2 of [...registered.keys()]) drop(id2);
2869
+ }
2870
+ };
2871
+ }
2872
+ function hostRegistries(send, gone) {
2873
+ const cdps = cdpRegistry(send, gone);
2874
+ const facades = facadeRegistry(send, gone);
2875
+ return {
2876
+ answer(l, req) {
2877
+ if (req.op === "serve-cdp") return cdps.serve(l, req);
2878
+ if (req.op === "unserve-cdp") return cdps.drop(req.registration);
2879
+ if (req.op === "serve-facade") return facades.serve(l, req);
2880
+ return facades.drop(req.registration);
2881
+ },
2882
+ close() {
2883
+ cdps.close();
2884
+ facades.close();
2885
+ }
2886
+ };
2887
+ }
2737
2888
  function visibleTo(g, notify) {
2738
2889
  if (!g?.dac.enforcing) return notify;
2739
2890
  let queue = Promise.resolve();
@@ -2777,7 +2928,7 @@ function serveClient(port, host) {
2777
2928
  if (state !== "closed") port.postMessage(message3, transfer);
2778
2929
  };
2779
2930
  const reply = (id2, body, transfer) => send({ id: id2, ...body }, transfer);
2780
- const cdps = cdpRegistry(send, ended.promise);
2931
+ const hosts = hostRegistries(send, ended.promise);
2781
2932
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
2782
2933
  if (state === "closed") return;
2783
2934
  send({ bye });
@@ -2785,7 +2936,7 @@ function serveClient(port, host) {
2785
2936
  net?.close();
2786
2937
  for (const unwatch of watches.values()) unwatch();
2787
2938
  watches.clear();
2788
- cdps.close();
2939
+ hosts.close();
2789
2940
  for (const session of terminals.values()) session.close();
2790
2941
  terminals.clear();
2791
2942
  if (kill) killGroups(host, [...groups]);
@@ -2882,16 +3033,16 @@ function serveClient(port, host) {
2882
3033
  case "mount":
2883
3034
  return { result: await l.mountAs(clientCred(self), req.spec) };
2884
3035
  case "umount":
2885
- await l.umountAs(clientCred(self), req.target);
2886
- return { result: true };
3036
+ return { result: await l.umountAs(clientCred(self), req.target).then(() => true) };
2887
3037
  case "mounts":
2888
3038
  return { result: l.mounts.list() };
2889
3039
  case "dial":
2890
3040
  return dialFor(l, req, watches);
2891
3041
  case "serve-cdp":
2892
- return { result: cdps.serve(l, req) };
2893
3042
  case "unserve-cdp":
2894
- return { result: cdps.drop(req.registration) };
3043
+ case "serve-facade":
3044
+ case "unserve-facade":
3045
+ return { result: hosts.answer(l, req) };
2895
3046
  case "detach":
2896
3047
  return { result: true };
2897
3048
  default:
@@ -4646,15 +4797,15 @@ var UnlinkedKeeper = class {
4646
4797
  wrap(fs = this.fs) {
4647
4798
  const keeper = this;
4648
4799
  const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
4649
- const counted2 = (st) => {
4800
+ const counted3 = (st) => {
4650
4801
  const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
4651
4802
  return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
4652
4803
  };
4653
4804
  return {
4654
4805
  ...fs,
4655
4806
  links: (path) => mayLink(fs, path),
4656
- stat: async (path) => counted2(await fs.stat(path)),
4657
- lstat: async (path) => counted2(await fs.lstat(path)),
4807
+ stat: async (path) => counted3(await fs.stat(path)),
4808
+ lstat: async (path) => counted3(await fs.lstat(path)),
4658
4809
  async rm(path, options) {
4659
4810
  const target = fs.resolvePath("/", path);
4660
4811
  const pinned = await keeper.pin(target);
@@ -4668,7 +4819,7 @@ var UnlinkedKeeper = class {
4668
4819
  await keeper.settle(pinned, () => fs.rename(from, to));
4669
4820
  },
4670
4821
  readdir: async (path) => visible(await fs.readdir(path)),
4671
- readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
4822
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted3(st)])
4672
4823
  };
4673
4824
  }
4674
4825
  async settle(pinned, op) {
@@ -5095,6 +5246,9 @@ async function acceptKey(headers) {
5095
5246
  }
5096
5247
  const key = field(headers, "sec-websocket-key") ?? "";
5097
5248
  if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
5249
+ return acceptFor(key);
5250
+ }
5251
+ async function acceptFor(key) {
5098
5252
  const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
5099
5253
  return btoa(String.fromCharCode(...new Uint8Array(digest)));
5100
5254
  }
@@ -5118,6 +5272,19 @@ function frame(opcode, payload) {
5118
5272
  out.set(payload, size);
5119
5273
  return out;
5120
5274
  }
5275
+ function clientFrame(opcode, payload) {
5276
+ const plain = frame(opcode, payload);
5277
+ const head = plain.length - payload.length;
5278
+ const mask = crypto.getRandomValues(new Uint8Array(4));
5279
+ const out = new Uint8Array(plain.length + 4);
5280
+ out.set(plain.subarray(0, head));
5281
+ out[1] = out[1] | 128;
5282
+ out.set(mask, head);
5283
+ for (let i = 0; i < payload.length; i++) {
5284
+ out[head + 4 + i] = payload[i] ^ mask[i & 3];
5285
+ }
5286
+ return out;
5287
+ }
5121
5288
  function closeFrame(code, reason = "") {
5122
5289
  let bytes = new TextEncoder().encode(reason);
5123
5290
  if (bytes.length > 123) {
@@ -5157,10 +5324,12 @@ var WsReader = class {
5157
5324
  continuing = false;
5158
5325
  binaryMessage = false;
5159
5326
  binary;
5160
- constructor(incoming, max = MAX_MESSAGE, binary) {
5327
+ fromServer;
5328
+ constructor(incoming, max = MAX_MESSAGE, binary, fromServer = false) {
5161
5329
  this.incoming = incoming;
5162
5330
  this.max = max;
5163
5331
  this.binary = binary;
5332
+ this.fromServer = fromServer;
5164
5333
  }
5165
5334
  async next(signal) {
5166
5335
  for (; ; ) {
@@ -5172,7 +5341,7 @@ var WsReader = class {
5172
5341
  const fin = (head[0] & 128) !== 0;
5173
5342
  const opcode = head[0] & 15;
5174
5343
  if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
5175
- if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
5344
+ const masked = this.masking(head[1]);
5176
5345
  const length2 = await this.length(head[1] & 127, signal);
5177
5346
  const control = opcode >= 8;
5178
5347
  if (control && (length2 > 125 || !fin)) {
@@ -5181,15 +5350,19 @@ var WsReader = class {
5181
5350
  if (!control && this.size + length2 > this.max) {
5182
5351
  throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
5183
5352
  }
5184
- const mask = new Uint8Array(4);
5185
- await fill(this.incoming, mask, signal);
5186
- const payload = new Uint8Array(length2);
5187
- await fill(this.incoming, payload, signal);
5188
- for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5353
+ const payload = await this.payload(length2, masked, signal);
5189
5354
  const event = this.take(opcode, fin, payload);
5190
5355
  if (event) return event;
5191
5356
  }
5192
5357
  }
5358
+ masking(second) {
5359
+ const masked = (second & 128) !== 0;
5360
+ if (masked !== this.fromServer) return masked;
5361
+ throw new WsError(
5362
+ CLOSE.protocol,
5363
+ this.fromServer ? "server frames must not be masked" : "client frames must be masked"
5364
+ );
5365
+ }
5193
5366
  data(opcode, fin, payload) {
5194
5367
  if (opcode === OP.continuation !== this.continuing) {
5195
5368
  throw new WsError(CLOSE.protocol, "unexpected continuation frame");
@@ -5204,6 +5377,14 @@ var WsReader = class {
5204
5377
  this.size = 0;
5205
5378
  return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
5206
5379
  }
5380
+ async payload(length2, masked, signal) {
5381
+ const mask = new Uint8Array(4);
5382
+ if (masked) await fill(this.incoming, mask, signal);
5383
+ const payload = new Uint8Array(length2);
5384
+ await fill(this.incoming, payload, signal);
5385
+ if (masked) for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5386
+ return payload;
5387
+ }
5207
5388
  async length(short, signal) {
5208
5389
  if (short < 126) return short;
5209
5390
  const ext = new Uint8Array(short === 126 ? 2 : 8);
@@ -5478,6 +5659,7 @@ var Relay = class {
5478
5659
  function enableCdp(net, hosts) {
5479
5660
  let facade;
5480
5661
  net.kernelOnly(CDP_PORT, true);
5662
+ net.reserve(CDP_PORT);
5481
5663
  net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
5482
5664
  const started = new CdpFacade({ net, hosts });
5483
5665
  facade = started;
@@ -5792,9 +5974,9 @@ async function scanFilesystems(fs, modules) {
5792
5974
  } catch {
5793
5975
  continue;
5794
5976
  }
5795
- const declared = manifest.slicc?.filesystems;
5796
- if (!declared || typeof declared !== "object") continue;
5797
- for (const [type, entry] of Object.entries(declared)) {
5977
+ const declared2 = manifest.slicc?.filesystems;
5978
+ if (!declared2 || typeof declared2 !== "object") continue;
5979
+ for (const [type, entry] of Object.entries(declared2)) {
5798
5980
  const module = inside(pkg, entry?.module);
5799
5981
  if (NAME2.test(type) && module && !found.has(type)) found.set(type, module);
5800
5982
  }
@@ -5812,9 +5994,9 @@ async function scanBinfmts(fs, modules, warn = () => {
5812
5994
  } catch {
5813
5995
  continue;
5814
5996
  }
5815
- const declared = manifest.slicc?.binfmt;
5816
- if (!declared || typeof declared !== "object") continue;
5817
- for (const [suffix, command] of Object.entries(declared)) {
5997
+ const declared2 = manifest.slicc?.binfmt;
5998
+ if (!declared2 || typeof declared2 !== "object") continue;
5999
+ for (const [suffix, command] of Object.entries(declared2)) {
5818
6000
  if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME2.test(command)) continue;
5819
6001
  const owner = owners.get(suffix);
5820
6002
  if (owner === void 0) {
@@ -5844,94 +6026,831 @@ function binfmtOf(binfmts, path) {
5844
6026
  return best === void 0 ? void 0 : binfmts.get(best);
5845
6027
  }
5846
6028
 
5847
- // src/fs/attest.ts
5848
- var ATTEST_MAX = 1024 * 1024;
5849
- var UNOWNED = 1e3;
5850
- function refuse(code, path, why) {
5851
- throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
5852
- }
5853
- function chainOf(path) {
5854
- const parts2 = path.split("/").filter(Boolean);
5855
- return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
6029
+ // src/kernel/net/loopback-names.ts
6030
+ var HOST_LOOPBACK = "host.slicc.internal";
6031
+ var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
6032
+ var PAGE_LOOPBACK = "kernel.localhost";
6033
+ var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
6034
+ function toHostLoopback(href) {
6035
+ const url = new URL(href);
6036
+ if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
6037
+ url.hostname = "127.0.0.1";
6038
+ return url.href;
5856
6039
  }
5857
- function under2(path, target) {
5858
- return path === target || path.startsWith(`${target}/`);
6040
+ var LOOPBACK_V4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
6041
+ var LOOPBACK_V6 = /* @__PURE__ */ new Set(["::1", "::", "0:0:0:0:0:0:0:1"]);
6042
+ var MAPPED_V4 = /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/;
6043
+ var KERNEL_PORT = /^(\d{1,5})\.kernel\.localhost$/;
6044
+ var SCHEMES = {
6045
+ "http:": 80,
6046
+ "ws:": 80,
6047
+ "https:": 443,
6048
+ "wss:": 443
6049
+ };
6050
+ function isKernelLoopbackHost(hostname) {
6051
+ const host = hostname.replace(/^\[(.*)\]$/, "$1").replace(/\.$/, "").toLowerCase();
6052
+ return host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0" || LOOPBACK_V4.test(host) || LOOPBACK_V6.has(host) || MAPPED_V4.test(host);
5859
6053
  }
5860
- function owned(path, st) {
5861
- return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
6054
+ function kernelLoopbackPort(href) {
6055
+ let url;
6056
+ try {
6057
+ url = new URL(href);
6058
+ } catch {
6059
+ return void 0;
6060
+ }
6061
+ const fallback = SCHEMES[url.protocol];
6062
+ if (fallback === void 0 || !isKernelLoopbackHost(url.hostname)) return void 0;
6063
+ const named = KERNEL_PORT.exec(url.hostname.replace(/\.$/, "").toLowerCase())?.[1];
6064
+ const port = Number(named ?? (url.port || fallback));
6065
+ return port >= 1 && port <= 65535 ? port : void 0;
5862
6066
  }
5863
- function unchanged(a, b, bytes) {
5864
- return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
6067
+ var DEFAULT_HOSTNAME = "slicc";
6068
+ var HOSTNAME_ADDRESS = "127.0.1.1";
6069
+ var LIBC_HOSTNAMES = ["emscripten", "wasmer.sh"];
6070
+ var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
6071
+ function reserved(name2) {
6072
+ const lower = name2.toLowerCase();
6073
+ return lower === "localhost" || lower.endsWith(".localhost") || lower === HOST_LOOPBACK || lower === "wasmer.sh" || /^\d+(\.\d+){3}$/.test(lower);
5865
6074
  }
5866
- async function sha2562(bytes) {
5867
- const digest = new Uint8Array(
5868
- await crypto.subtle.digest("SHA-256", bytes)
5869
- );
5870
- return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
6075
+ function isHostname(name2) {
6076
+ if (typeof name2 !== "string" || name2.length > 253 || reserved(name2)) return false;
6077
+ return name2.split(".").every((l) => LABEL.test(l));
5871
6078
  }
5872
- function attested(path, st, bytes, digest, o, parents) {
5873
- const { uid, gid, mode } = owned(path, st);
5874
- return {
5875
- path,
5876
- bytes,
5877
- uid,
5878
- gid,
5879
- mode,
5880
- ino: st.ino,
5881
- mtimeMs: st.mtime.getTime(),
5882
- sha256: digest,
5883
- users: o.users,
5884
- onRootFs: true,
5885
- parents
5886
- };
6079
+ function ownNames(hostname) {
6080
+ return [.../* @__PURE__ */ new Set([hostname.toLowerCase(), ...LIBC_HOSTNAMES])];
5887
6081
  }
5888
- async function trustedParents(fs, path, o) {
5889
- const parents = [];
5890
- for (const dir of chainOf(path).slice(0, -1)) {
5891
- const st = await fs.lstat(dir);
5892
- if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
5893
- if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
5894
- const perm = owned(dir, st);
5895
- if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
5896
- refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
6082
+
6083
+ // src/kernel/net/proxy-service.ts
6084
+ var SEND_BUFFER2 = 1024 * 1024;
6085
+ var TLS_HANDSHAKE = 22;
6086
+ var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
6087
+ var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
6088
+ var REALM_PROXY_PORT = 3128;
6089
+ var DEFAULT_LIMITS = {
6090
+ maxConnections: 64,
6091
+ maxHead: 64 * 1024,
6092
+ idleMs: 12e4,
6093
+ bodyBudget: 128 * 1024 * 1024,
6094
+ closeWaitMs: 5e3
6095
+ };
6096
+ var HOP_BY_HOP = /* @__PURE__ */ new Set([
6097
+ "connection",
6098
+ "keep-alive",
6099
+ "proxy-connection",
6100
+ "proxy-authenticate",
6101
+ "proxy-authorization",
6102
+ "te",
6103
+ "trailer",
6104
+ "transfer-encoding",
6105
+ "upgrade"
6106
+ ]);
6107
+ var REQUEST_OWN = /* @__PURE__ */ new Set(["host", "content-length", "expect"]);
6108
+ var Budget = class {
6109
+ free;
6110
+ waiters = [];
6111
+ constructor(free) {
6112
+ this.free = free;
6113
+ }
6114
+ async acquire(n, signal) {
6115
+ while (this.free < n) {
6116
+ signal.throwIfAborted();
6117
+ await new Promise((resolve2) => {
6118
+ const done4 = () => {
6119
+ signal.removeEventListener("abort", done4);
6120
+ resolve2();
6121
+ };
6122
+ this.waiters.push(done4);
6123
+ signal.addEventListener("abort", done4, { once: true });
6124
+ });
5897
6125
  }
5898
- parents.push(perm);
6126
+ this.free -= n;
5899
6127
  }
5900
- return parents;
5901
- }
5902
- async function attest(fs, asked, o) {
5903
- if (typeof asked !== "string" || !asked.startsWith("/")) {
5904
- refuse("EINVAL", String(asked), "not an absolute path");
6128
+ release(n) {
6129
+ this.free += n;
6130
+ const waiters = this.waiters;
6131
+ this.waiters = [];
6132
+ for (const wake of waiters) wake();
5905
6133
  }
5906
- const path = normalizePath(asked);
5907
- const mount = o.mounts.find((target) => {
5908
- const at = normalizePath(target);
5909
- return at === "/" || under2(path, at);
5910
- });
5911
- if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
5912
- const parents = await trustedParents(fs, path, o);
5913
- for (let tries = 0; ; tries++) {
5914
- const before = await fs.lstat(path);
5915
- if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
5916
- if (!before.isFile)
5917
- refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
5918
- if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
5919
- const bytes = (await fs.readFileBuffer(path)).slice();
5920
- const after = await fs.lstat(path);
5921
- if (unchanged(before, after, bytes)) {
5922
- return attested(path, after, bytes, await sha2562(bytes), o, parents);
5923
- }
5924
- if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
6134
+ };
6135
+ function canonicalHost2(host) {
6136
+ const bare = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
6137
+ try {
6138
+ return new URL(`http://${bare}/`).hostname;
6139
+ } catch {
6140
+ return void 0;
5925
6141
  }
5926
6142
  }
5927
- var WATCH_DEBOUNCE_MS = 50;
5928
- var FileWatches = class {
5929
- live = /* @__PURE__ */ new Set();
5930
- local;
5931
- remote;
5932
- constructor(local, remote) {
5933
- this.local = local;
5934
- this.remote = remote;
6143
+ function localV4(a, b) {
6144
+ return a === 127 || a === 0 || a === 169 && b === 254;
6145
+ }
6146
+ function hextets(address) {
6147
+ const [head, tail, extra] = address.split("::");
6148
+ if (extra !== void 0) return void 0;
6149
+ const part = (text) => text ? text.split(":").map((h) => Number.parseInt(h, 16)) : [];
6150
+ const front = part(head);
6151
+ const back = part(tail);
6152
+ const groups = tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
6153
+ return groups.length === 8 && groups.every((g) => g >= 0 && g <= 65535) ? groups : void 0;
6154
+ }
6155
+ function localV6(address) {
6156
+ const g = hextets(address);
6157
+ if (!g) return true;
6158
+ if ((g[0] & 65472) === 65152) return true;
6159
+ const zeroPrefix = g.slice(0, 5).every((x) => x === 0);
6160
+ if (!zeroPrefix) return false;
6161
+ if (g[5] === 65535 || g[5] === 0 && g[6] !== 0) return localV4(g[6] >> 8, g[6] & 255);
6162
+ return g[5] === 0 && g[6] === 0;
6163
+ }
6164
+ function isLoopbackHost(hostname) {
6165
+ const canonical = canonicalHost2(hostname);
6166
+ if (canonical === void 0) return false;
6167
+ const host = canonical.replace(/\.+$/, "");
6168
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
6169
+ if (host.startsWith("[")) return localV6(host.slice(1, -1));
6170
+ const v4 = /^(\d+)\.(\d+)\.\d+\.\d+$/.exec(host);
6171
+ return v4 ? localV4(Number(v4[1]), Number(v4[2])) : false;
6172
+ }
6173
+ function dropped(headers) {
6174
+ return /* @__PURE__ */ new Set([...HOP_BY_HOP, ...fieldTokens(headers, "connection")]);
6175
+ }
6176
+ function forwardRequestHeaders(headers) {
6177
+ const drop = dropped(headers);
6178
+ return headers.filter(([name2]) => {
6179
+ const lower = name2.toLowerCase();
6180
+ return !drop.has(lower) && !REQUEST_OWN.has(lower);
6181
+ });
6182
+ }
6183
+ function forwardResponseHeaders(headers, opts) {
6184
+ const drop = dropped(headers);
6185
+ if (!opts.encodedBodies) drop.add("content-encoding");
6186
+ if (!(opts.encodedBodies && opts.bodiless)) drop.add("content-length");
6187
+ return headers.filter(([name2]) => !drop.has(name2.toLowerCase()));
6188
+ }
6189
+ function keepsAlive(req) {
6190
+ const tokens = fieldTokens(req.headers, "connection");
6191
+ if (tokens.includes("close")) return false;
6192
+ return req.minor >= 1 || tokens.includes("keep-alive");
6193
+ }
6194
+ function tunnelRequestUrl(req, origin) {
6195
+ const base = new URL(origin);
6196
+ let url;
6197
+ try {
6198
+ url = new URL(req.target, req.target.startsWith("/") ? base : void 0);
6199
+ } catch {
6200
+ throw new HttpError(400, "malformed request target");
6201
+ }
6202
+ const hosts = fieldValues(req.headers, "host");
6203
+ const hostOk = hosts.every(
6204
+ (h) => URL.canParse(`https://${h}`) && new URL(`https://${h}`).host === base.host
6205
+ );
6206
+ if (url.origin !== base.origin || !hostOk) {
6207
+ throw new HttpError(421, `this tunnel is for ${base.host}`);
6208
+ }
6209
+ return url.href;
6210
+ }
6211
+ function requestUrl(req) {
6212
+ if (req.target.startsWith("/")) {
6213
+ throw new HttpError(400, "this is a proxy: send the absolute URL (GET http://host/path)");
6214
+ }
6215
+ let url;
6216
+ try {
6217
+ url = new URL(req.target);
6218
+ } catch {
6219
+ throw new HttpError(400, "malformed request target");
6220
+ }
6221
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
6222
+ throw new HttpError(400, `unsupported scheme ${url.protocol}`);
6223
+ }
6224
+ if (isLoopbackHost(url.hostname)) {
6225
+ throw new HttpError(
6226
+ 403,
6227
+ `${url.hostname} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
6228
+ );
6229
+ }
6230
+ return url.href;
6231
+ }
6232
+ function tunnelTarget(req) {
6233
+ const match = /^([A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\]):(\d{1,5})$/.exec(req.target);
6234
+ const port = Number(match?.[2]);
6235
+ if (!match || port < 1 || port > 65535) {
6236
+ throw new HttpError(400, "CONNECT needs host:port");
6237
+ }
6238
+ const host = canonicalHost2(match[1]);
6239
+ if (host === void 0) throw new HttpError(400, "CONNECT needs host:port");
6240
+ if (isLoopbackHost(host)) {
6241
+ throw new HttpError(
6242
+ 403,
6243
+ `${host} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
6244
+ );
6245
+ }
6246
+ return { host, port };
6247
+ }
6248
+ async function watchHangup(conn, abort, until) {
6249
+ while (!until.aborted && !abort.signal.aborted) {
6250
+ if (conn.poll().hangup) {
6251
+ abort.abort(new KernelError("EPIPE"));
6252
+ return;
6253
+ }
6254
+ try {
6255
+ await conn.changed(until);
6256
+ } catch {
6257
+ return;
6258
+ }
6259
+ }
6260
+ }
6261
+ function untilAborted(response, signal) {
6262
+ return new Promise((resolve2, reject) => {
6263
+ const onAbort = () => {
6264
+ reject(signal.reason ?? new KernelError("EINTR"));
6265
+ response.then((late) => late.cancel()).catch(() => void 0);
6266
+ };
6267
+ if (signal.aborted) return onAbort();
6268
+ signal.addEventListener("abort", onAbort, { once: true });
6269
+ response.then(resolve2, reject).finally(() => signal.removeEventListener("abort", onAbort));
6270
+ });
6271
+ }
6272
+ function plainResponse(status, message3) {
6273
+ const body = latin1Bytes(`slicc realm proxy: ${message3}
6274
+ `);
6275
+ return {
6276
+ status,
6277
+ statusText: REASON[status] ?? "",
6278
+ headers: [["Content-Type", "text/plain; charset=utf-8"]],
6279
+ body: (async function* () {
6280
+ yield body;
6281
+ })(),
6282
+ cancel: async () => void 0
6283
+ };
6284
+ }
6285
+ function timedSource(conn, idle, stop) {
6286
+ return {
6287
+ read: (max, signal) => {
6288
+ const own = signal ? [stop, signal] : [stop];
6289
+ if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
6290
+ return conn.read(max, AbortSignal.any(own));
6291
+ }
6292
+ };
6293
+ }
6294
+ function webSocketTarget(req) {
6295
+ return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
6296
+ }
6297
+ function wireCode(code) {
6298
+ if (code === 1005) return 1e3;
6299
+ return sendableCode(code) ? code : 1011;
6300
+ }
6301
+ function quietly(ms) {
6302
+ return new Promise((resolve2) => {
6303
+ AbortSignal.timeout(ms).addEventListener("abort", () => resolve2(), { once: true });
6304
+ });
6305
+ }
6306
+ var RealmProxy = class {
6307
+ options;
6308
+ listener;
6309
+ stop = new AbortController();
6310
+ limits;
6311
+ slots;
6312
+ bodies;
6313
+ connections = /* @__PURE__ */ new Set();
6314
+ warnedWebSocket = false;
6315
+ closed;
6316
+ port;
6317
+ constructor(options) {
6318
+ this.options = options;
6319
+ this.limits = { ...DEFAULT_LIMITS, ...options.limits };
6320
+ this.slots = new Budget(this.limits.maxConnections);
6321
+ this.bodies = new Budget(this.limits.bodyBudget);
6322
+ this.listener = options.net.listen({
6323
+ family: "inet",
6324
+ host: options.host ?? "127.0.0.1",
6325
+ port: options.port ?? REALM_PROXY_PORT
6326
+ });
6327
+ this.port = this.listener.local?.family === "inet" ? this.listener.local.port : 0;
6328
+ this.closed = this.acceptLoop();
6329
+ }
6330
+ get stopped() {
6331
+ return this.stop.signal.aborted;
6332
+ }
6333
+ close() {
6334
+ if (this.stop.signal.aborted) return;
6335
+ this.stop.abort();
6336
+ this.listener.close();
6337
+ for (const conn of this.connections) conn.close();
6338
+ }
6339
+ async acceptLoop() {
6340
+ const served = /* @__PURE__ */ new Set();
6341
+ try {
6342
+ for (; ; ) {
6343
+ await this.slots.acquire(1, this.stop.signal);
6344
+ let conn;
6345
+ try {
6346
+ conn = await this.listener.accept(this.stop.signal);
6347
+ } catch {
6348
+ this.slots.release(1);
6349
+ break;
6350
+ }
6351
+ const done4 = this.serve(conn).finally(() => {
6352
+ this.slots.release(1);
6353
+ served.delete(done4);
6354
+ });
6355
+ served.add(done4);
6356
+ }
6357
+ } catch {
6358
+ }
6359
+ await Promise.allSettled([...served]);
6360
+ }
6361
+ async serve(conn) {
6362
+ this.connections.add(conn);
6363
+ const idle = { ms: this.limits.idleMs };
6364
+ const incoming = new Incoming(timedSource(conn, idle, this.stop.signal));
6365
+ try {
6366
+ await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
6367
+ } finally {
6368
+ this.connections.delete(conn);
6369
+ conn.close();
6370
+ }
6371
+ }
6372
+ async requests(ctx) {
6373
+ try {
6374
+ for (; ; ) {
6375
+ const head = await ctx.incoming.head(this.limits.maxHead);
6376
+ if (!head || !await this.exchange(ctx, head)) break;
6377
+ }
6378
+ } catch (e) {
6379
+ if (e instanceof HttpError) await this.refuse(ctx.sink, e);
6380
+ }
6381
+ }
6382
+ async refuse(sink, error) {
6383
+ try {
6384
+ await this.relay(sink, "GET", 1, plainResponse(error.status, error.message), false);
6385
+ } catch {
6386
+ }
6387
+ }
6388
+ async exchange(ctx, head) {
6389
+ const { sink: conn, incoming } = ctx;
6390
+ const req = parseRequestHead(head);
6391
+ if (req.method === "CONNECT") {
6392
+ if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
6393
+ await this.connect(ctx.conn, incoming, req, ctx.idle);
6394
+ return false;
6395
+ }
6396
+ if (req.method === "GET" && isUpgrade(req.headers)) {
6397
+ await this.websocket(ctx, req);
6398
+ return false;
6399
+ }
6400
+ const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
6401
+ const keep2 = keepsAlive(req);
6402
+ const framing = requestFraming(req.headers);
6403
+ const reserve = framing.kind === "length" ? framing.length : framing.kind === "chunked" ? this.cap() : 0;
6404
+ if (reserve > this.cap()) throw new HttpError(413, `request body over ${this.cap()} bytes`);
6405
+ await this.expectContinue(conn, req, framing.kind !== "none");
6406
+ await this.bodies.acquire(reserve, this.stop.signal);
6407
+ let response;
6408
+ const abort = new AbortController();
6409
+ const stopExchange = () => abort.abort();
6410
+ this.stop.signal.addEventListener("abort", stopExchange, { once: true });
6411
+ try {
6412
+ try {
6413
+ const body = await readBody(incoming, framing, this.cap());
6414
+ const waiting = new AbortController();
6415
+ void watchHangup(ctx.socket, abort, waiting.signal);
6416
+ try {
6417
+ response = await this.upstream(req, url, body, abort.signal, ctx.socket.peerCred?.pid);
6418
+ } finally {
6419
+ waiting.abort();
6420
+ }
6421
+ } finally {
6422
+ this.bodies.release(reserve);
6423
+ }
6424
+ return await this.relay(conn, req.method, req.minor, response, keep2);
6425
+ } finally {
6426
+ this.stop.signal.removeEventListener("abort", stopExchange);
6427
+ abort.abort();
6428
+ }
6429
+ }
6430
+ cap() {
6431
+ return Math.min(this.options.transport.traits.maxRequestBody, this.limits.bodyBudget);
6432
+ }
6433
+ async expectContinue(conn, req, hasBody) {
6434
+ const expect = fieldTokens(req.headers, "expect");
6435
+ if (expect.length === 0) return;
6436
+ if (expect.length !== 1 || expect[0] !== "100-continue") {
6437
+ throw new HttpError(417, `unsupported expectation: ${expect.join(", ")}`);
6438
+ }
6439
+ if (hasBody && req.minor >= 1) {
6440
+ await conn.write(latin1Bytes("HTTP/1.1 100 Continue\r\n\r\n"), this.stop.signal);
6441
+ }
6442
+ }
6443
+ async upstream(req, url, body, signal, owner) {
6444
+ try {
6445
+ const fetching = this.options.transport.fetch(
6446
+ {
6447
+ url: toHostLoopback(url),
6448
+ method: req.method.toUpperCase(),
6449
+ headers: forwardRequestHeaders(req.headers),
6450
+ body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
6451
+ signal
6452
+ },
6453
+ owner
6454
+ );
6455
+ return await untilAborted(fetching, signal);
6456
+ } catch (e) {
6457
+ if (signal.aborted) throw e;
6458
+ const message3 = e instanceof Error ? e.message : String(e);
6459
+ const status = e?.status;
6460
+ const own = typeof status === "number" && status >= 400 && status <= 599 ? status : 502;
6461
+ return plainResponse(own, message3 || "upstream request failed");
6462
+ }
6463
+ }
6464
+ async relay(conn, method, minor, response, keepAlive) {
6465
+ const status = response.status;
6466
+ const bodiless2 = method === "HEAD" || status === 204 || status === 304 || status < 200;
6467
+ const headers = forwardResponseHeaders(response.headers, {
6468
+ encodedBodies: this.options.transport.traits.encodedBodies,
6469
+ bodiless: bodiless2
6470
+ });
6471
+ const chunked3 = !bodiless2 && minor >= 1;
6472
+ const keep2 = keepAlive && (bodiless2 || chunked3);
6473
+ headers.push(["Connection", keep2 ? "keep-alive" : "close"]);
6474
+ if (chunked3) headers.push(["Transfer-Encoding", "chunked"]);
6475
+ const signal = this.stop.signal;
6476
+ await conn.write(
6477
+ responseHead(status, response.statusText || REASON[status] || "", headers),
6478
+ signal
6479
+ );
6480
+ if (bodiless2) {
6481
+ await response.cancel();
6482
+ return keep2;
6483
+ }
6484
+ try {
6485
+ for await (const piece of response.body) {
6486
+ if (piece.length > 0) await conn.write(chunked3 ? chunk(piece) : piece, signal);
6487
+ }
6488
+ } catch {
6489
+ await response.cancel().catch(() => void 0);
6490
+ return false;
6491
+ }
6492
+ if (chunked3) await conn.write(LAST_CHUNK, signal);
6493
+ return keep2;
6494
+ }
6495
+ async connect(conn, incoming, req, idle) {
6496
+ const target = tunnelTarget(req);
6497
+ const refused2 = target.port === 443 ? await this.tunnelReady(target) : void 0;
6498
+ if (refused2) throw refused2;
6499
+ await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
6500
+ try {
6501
+ const first = await incoming.peek(this.stop.signal);
6502
+ if (first === void 0) return;
6503
+ if (first !== TLS_HANDSHAKE) {
6504
+ const origin = `http://${target.port === 80 ? target.host : `${target.host}:${target.port}`}`;
6505
+ await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
6506
+ return;
6507
+ }
6508
+ const tunnel = this.options.tunnel;
6509
+ if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
6510
+ await tunnel(
6511
+ conn,
6512
+ incoming,
6513
+ target,
6514
+ this.stop.signal,
6515
+ (source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
6516
+ );
6517
+ } catch {
6518
+ }
6519
+ }
6520
+ async tunnelReady(target) {
6521
+ const name2 = `CONNECT ${target.host}:${target.port}`;
6522
+ if (!this.options.tunnel) return new HttpError(501, `${name2}: no tunnels through this proxy`);
6523
+ try {
6524
+ await this.options.tunnelReady?.();
6525
+ return void 0;
6526
+ } catch (e) {
6527
+ return new HttpError(501, `${name2}: ${e instanceof Error ? e.message : String(e)}`);
6528
+ }
6529
+ }
6530
+ async websocket(ctx, req) {
6531
+ const href = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(webSocketTarget(req));
6532
+ const { transport } = this.options;
6533
+ if (!transport.traits.websocket || !transport.websocket) {
6534
+ if (!this.warnedWebSocket) {
6535
+ this.warnedWebSocket = true;
6536
+ console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
6537
+ }
6538
+ throw new HttpError(501, NO_WEBSOCKET2);
6539
+ }
6540
+ const accept2 = await acceptKey(req.headers);
6541
+ const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
6542
+ const hangup = new AbortController();
6543
+ const waiting = new AbortController();
6544
+ void watchHangup(ctx.socket, hangup, waiting.signal);
6545
+ let socket;
6546
+ try {
6547
+ socket = await transport.websocket({
6548
+ url: toHostLoopback(href).replace(/^http/, "ws"),
6549
+ protocols,
6550
+ headers: forwardRequestHeaders(req.headers),
6551
+ signal: AbortSignal.any([this.stop.signal, hangup.signal])
6552
+ });
6553
+ } catch (e) {
6554
+ waiting.abort();
6555
+ throw new HttpError(502, e instanceof Error ? e.message : String(e));
6556
+ }
6557
+ ctx.idle.ms = Number.POSITIVE_INFINITY;
6558
+ try {
6559
+ if (hangup.signal.aborted) throw hangup.signal.reason;
6560
+ if (socket.protocol && !(protocols.includes(socket.protocol) && TOKEN2.test(socket.protocol))) {
6561
+ throw new HttpError(502, "the far end chose a subprotocol the client did not offer");
6562
+ }
6563
+ await ctx.sink.write(switchingHead(accept2, socket.protocol), this.stop.signal);
6564
+ await this.bridge(ctx, socket, hangup.signal);
6565
+ } catch (e) {
6566
+ socket.close(1001, "the client went away");
6567
+ throw e;
6568
+ } finally {
6569
+ waiting.abort();
6570
+ }
6571
+ }
6572
+ async bridge(ctx, socket, hangup) {
6573
+ const stop = this.stop.signal;
6574
+ let chain2 = Promise.resolve();
6575
+ const toGuest = (bytes) => {
6576
+ chain2 = chain2.then(() => ctx.sink.write(bytes, stop));
6577
+ return chain2;
6578
+ };
6579
+ const done4 = new AbortController();
6580
+ const reading = AbortSignal.any([stop, done4.signal, hangup]);
6581
+ const stopping = () => socket.close(1001, "the proxy is stopping");
6582
+ stop.addEventListener("abort", stopping, { once: true });
6583
+ let guestDone = false;
6584
+ let closeSent = false;
6585
+ const outbound2 = (async () => {
6586
+ for await (const data of socket.messages) {
6587
+ if (guestDone) continue;
6588
+ const text = typeof data === "string";
6589
+ await toGuest(
6590
+ frame(text ? OP_TEXT : OP_BINARY, text ? new TextEncoder().encode(data) : data)
6591
+ );
6592
+ }
6593
+ const closed = await socket.closed;
6594
+ if (guestDone) return;
6595
+ closeSent = true;
6596
+ await toGuest(closeFrame(wireCode(closed.code), closed.reason));
6597
+ AbortSignal.timeout(this.limits.closeWaitMs).addEventListener("abort", () => done4.abort(), {
6598
+ once: true
6599
+ });
6600
+ })().catch(() => done4.abort());
6601
+ const reader = new WsReader(ctx.incoming, MESSAGE_LIMIT, true);
6602
+ try {
6603
+ for (; ; ) {
6604
+ const event = await reader.next(reading);
6605
+ if (!event) break;
6606
+ if (event.kind === "ping") {
6607
+ await toGuest(pongFrame(event.payload));
6608
+ continue;
6609
+ }
6610
+ if (event.kind === "close") {
6611
+ guestDone = true;
6612
+ if (closeSent) break;
6613
+ const code = wireCode(event.code);
6614
+ socket.close(code, event.reason);
6615
+ const reply = await Promise.race([
6616
+ socket.closed,
6617
+ quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
6618
+ ]);
6619
+ await toGuest(closeFrame(wireCode(reply.code), reply.reason));
6620
+ break;
6621
+ }
6622
+ while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
6623
+ socket.send(event.kind === "text" ? event.text : event.bytes);
6624
+ }
6625
+ } catch (e) {
6626
+ if (e instanceof WsError) {
6627
+ guestDone = true;
6628
+ socket.close(e.code, e.message);
6629
+ await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
6630
+ }
6631
+ } finally {
6632
+ guestDone = true;
6633
+ socket.close(1e3, "the client went away");
6634
+ stop.removeEventListener("abort", stopping);
6635
+ await Promise.race([outbound2, quietly(this.limits.closeWaitMs)]);
6636
+ }
6637
+ }
6638
+ };
6639
+
6640
+ // src/facade/types.ts
6641
+ var FACADE_NAME = /^[a-z][a-z0-9-]*$/;
6642
+ function facadeEnvName(name2) {
6643
+ return `SLICC_${name2.toUpperCase().replaceAll("-", "_")}_URL`;
6644
+ }
6645
+ function facadeUrl(port) {
6646
+ return `tcp://127.0.0.1:${port}`;
6647
+ }
6648
+
6649
+ // src/facade/hosts.ts
6650
+ var TAKEN_PORTS = /* @__PURE__ */ new Set([CDP_PORT, REALM_PROXY_PORT]);
6651
+ var READ_CHUNK = 64 * 1024;
6652
+ var FACADE_MAX_PENDING = 16 * 1024 * 1024;
6653
+ var FACADE_FLOODED = `slicc-kernel: more than ${FACADE_MAX_PENDING} bytes are waiting for the program to read`;
6654
+ function declared(specs) {
6655
+ const out = /* @__PURE__ */ new Map();
6656
+ const ports = /* @__PURE__ */ new Set();
6657
+ for (const [name2, spec] of Object.entries(specs ?? {})) {
6658
+ if (!FACADE_NAME.test(name2)) throw new TypeError(`facades: not a facade name: ${name2}`);
6659
+ const port = spec?.port;
6660
+ if (!Number.isInteger(port) || port < 1 || port > 65535 || TAKEN_PORTS.has(port)) {
6661
+ throw new TypeError(`facades.${name2}: not a free port: ${String(port)}`);
6662
+ }
6663
+ if (ports.has(port)) throw new TypeError(`facades.${name2}: port ${port} is taken twice`);
6664
+ if (spec.hook !== void 0 && typeof spec.hook !== "function") {
6665
+ throw new TypeError(`facades.${name2}: hook must be a function`);
6666
+ }
6667
+ ports.add(port);
6668
+ out.set(name2, { port, embedder: spec.hook, clients: [] });
6669
+ }
6670
+ return out;
6671
+ }
6672
+ var FacadeHosts = class {
6673
+ facades;
6674
+ constructor(specs) {
6675
+ this.facades = declared(specs);
6676
+ }
6677
+ env() {
6678
+ const env = {};
6679
+ for (const [name2, { port }] of this.facades) env[facadeEnvName(name2)] = facadeUrl(port);
6680
+ return env;
6681
+ }
6682
+ enable(net) {
6683
+ for (const [name2, { port }] of this.facades) {
6684
+ net.facade(port, name2);
6685
+ net.activate({ family: "inet", host: "127.0.0.1", port }, () => {
6686
+ const listener = net.listen({ family: "inet", host: "127.0.0.1", port });
6687
+ void acceptAll(listener, (socket) => this.serve(name2, socket));
6688
+ });
6689
+ }
6690
+ }
6691
+ register(name2, hook) {
6692
+ const entry = this.facades.get(name2);
6693
+ if (!entry || typeof hook !== "function") throw new KernelError("EINVAL");
6694
+ entry.clients.push(hook);
6695
+ return () => {
6696
+ const at = entry.clients.indexOf(hook);
6697
+ if (at >= 0) entry.clients.splice(at, 1);
6698
+ };
6699
+ }
6700
+ async serve(name2, socket) {
6701
+ const entry = this.facades.get(name2);
6702
+ const hook = entry.clients.at(-1) ?? entry.embedder;
6703
+ const stamp = socket.peerStamp;
6704
+ if (!hook || !stamp) return socket.close();
6705
+ let conn;
6706
+ try {
6707
+ conn = await hook(structuredClone(stamp), { name: name2 });
6708
+ } catch {
6709
+ return socket.close();
6710
+ }
6711
+ await relay(socket, conn);
6712
+ }
6713
+ };
6714
+ async function acceptAll(listener, serve) {
6715
+ for (; ; ) {
6716
+ const socket = await listener.accept();
6717
+ void serve(socket);
6718
+ }
6719
+ }
6720
+ async function relay(socket, conn) {
6721
+ let ended = false;
6722
+ let pending = 0;
6723
+ let writes = Promise.resolve();
6724
+ const end = () => {
6725
+ if (ended) return;
6726
+ ended = true;
6727
+ socket.close();
6728
+ };
6729
+ conn.onmessage = (bytes) => {
6730
+ if (ended) return;
6731
+ const chunk2 = bytes.slice();
6732
+ pending += chunk2.length;
6733
+ if (pending > FACADE_MAX_PENDING) {
6734
+ end();
6735
+ conn.close(FACADE_FLOODED);
6736
+ return;
6737
+ }
6738
+ writes = writes.then(async () => {
6739
+ if (ended) return;
6740
+ let at = 0;
6741
+ while (at < chunk2.length) at += await socket.write(chunk2.subarray(at));
6742
+ pending -= chunk2.length;
6743
+ });
6744
+ writes.catch(() => {
6745
+ end();
6746
+ conn.close();
6747
+ });
6748
+ };
6749
+ conn.onclose = () => {
6750
+ writes.then(end, end);
6751
+ };
6752
+ try {
6753
+ for (; ; ) {
6754
+ const bytes = await socket.read(READ_CHUNK);
6755
+ if (bytes.length === 0 || ended) break;
6756
+ conn.send(bytes);
6757
+ }
6758
+ } catch {
6759
+ }
6760
+ if (!ended) {
6761
+ end();
6762
+ conn.close();
6763
+ }
6764
+ }
6765
+
6766
+ // src/fs/attest.ts
6767
+ var ATTEST_MAX = 1024 * 1024;
6768
+ var UNOWNED = 1e3;
6769
+ function refuse(code, path, why) {
6770
+ throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
6771
+ }
6772
+ function chainOf(path) {
6773
+ const parts2 = path.split("/").filter(Boolean);
6774
+ return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
6775
+ }
6776
+ function under2(path, target) {
6777
+ return path === target || path.startsWith(`${target}/`);
6778
+ }
6779
+ function owned(path, st) {
6780
+ return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
6781
+ }
6782
+ function unchanged(a, b, bytes) {
6783
+ return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
6784
+ }
6785
+ async function sha2562(bytes) {
6786
+ const digest = new Uint8Array(
6787
+ await crypto.subtle.digest("SHA-256", bytes)
6788
+ );
6789
+ return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
6790
+ }
6791
+ function attested(path, st, bytes, digest, o, parents) {
6792
+ const { uid, gid, mode } = owned(path, st);
6793
+ return {
6794
+ path,
6795
+ bytes,
6796
+ uid,
6797
+ gid,
6798
+ mode,
6799
+ ino: st.ino,
6800
+ mtimeMs: st.mtime.getTime(),
6801
+ sha256: digest,
6802
+ users: o.users,
6803
+ onRootFs: true,
6804
+ parents
6805
+ };
6806
+ }
6807
+ async function trustedParents(fs, path, o) {
6808
+ const parents = [];
6809
+ for (const dir of chainOf(path).slice(0, -1)) {
6810
+ const st = await fs.lstat(dir);
6811
+ if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
6812
+ if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
6813
+ const perm = owned(dir, st);
6814
+ if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
6815
+ refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
6816
+ }
6817
+ parents.push(perm);
6818
+ }
6819
+ return parents;
6820
+ }
6821
+ async function attest(fs, asked, o) {
6822
+ if (typeof asked !== "string" || !asked.startsWith("/")) {
6823
+ refuse("EINVAL", String(asked), "not an absolute path");
6824
+ }
6825
+ const path = normalizePath(asked);
6826
+ const mount = o.mounts.find((target) => {
6827
+ const at = normalizePath(target);
6828
+ return at === "/" || under2(path, at);
6829
+ });
6830
+ if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
6831
+ const parents = await trustedParents(fs, path, o);
6832
+ for (let tries = 0; ; tries++) {
6833
+ const before = await fs.lstat(path);
6834
+ if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
6835
+ if (!before.isFile)
6836
+ refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
6837
+ if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
6838
+ const bytes = (await fs.readFileBuffer(path)).slice();
6839
+ const after = await fs.lstat(path);
6840
+ if (unchanged(before, after, bytes)) {
6841
+ return attested(path, after, bytes, await sha2562(bytes), o, parents);
6842
+ }
6843
+ if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
6844
+ }
6845
+ }
6846
+ var WATCH_DEBOUNCE_MS = 50;
6847
+ var FileWatches = class {
6848
+ live = /* @__PURE__ */ new Set();
6849
+ local;
6850
+ remote;
6851
+ constructor(local, remote) {
6852
+ this.local = local;
6853
+ this.remote = remote;
5935
6854
  }
5936
6855
  announce() {
5937
6856
  return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
@@ -8868,58 +9787,31 @@ var ProcessClocks = class {
8868
9787
  }
8869
9788
  freeze() {
8870
9789
  for (const c of this.all()) c.freeze();
8871
- }
8872
- times() {
8873
- if (!this.enabled) return void 0;
8874
- const sum = { ...this.ended };
8875
- for (const c of this.all()) {
8876
- const t = c.times();
8877
- sum.utime += t.utime;
8878
- sum.stime += t.stime;
8879
- }
8880
- return sum;
8881
- }
8882
- running() {
8883
- return this.enabled ? this.all().filter((c) => c.running()).length : 0;
8884
- }
8885
- timed(clock, dispatch2) {
8886
- const c = clock ?? this.main;
8887
- return (req) => {
8888
- c.enter(req.op);
8889
- return dispatch2(req).finally(() => c.leave());
8890
- };
8891
- }
8892
- all() {
8893
- return [this.main, ...this.threads.values()];
8894
- }
8895
- };
8896
-
8897
- // src/kernel/net/loopback-names.ts
8898
- var HOST_LOOPBACK = "host.slicc.internal";
8899
- var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
8900
- var PAGE_LOOPBACK = "kernel.localhost";
8901
- var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
8902
- function toHostLoopback(href) {
8903
- const url = new URL(href);
8904
- if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
8905
- url.hostname = "127.0.0.1";
8906
- return url.href;
8907
- }
8908
- var DEFAULT_HOSTNAME = "slicc";
8909
- var HOSTNAME_ADDRESS = "127.0.1.1";
8910
- var LIBC_HOSTNAMES = ["emscripten", "wasmer.sh"];
8911
- var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
8912
- function reserved(name2) {
8913
- const lower = name2.toLowerCase();
8914
- return lower === "localhost" || lower.endsWith(".localhost") || lower === HOST_LOOPBACK || lower === "wasmer.sh" || /^\d+(\.\d+){3}$/.test(lower);
8915
- }
8916
- function isHostname(name2) {
8917
- if (typeof name2 !== "string" || name2.length > 253 || reserved(name2)) return false;
8918
- return name2.split(".").every((l) => LABEL.test(l));
8919
- }
8920
- function ownNames(hostname) {
8921
- return [.../* @__PURE__ */ new Set([hostname.toLowerCase(), ...LIBC_HOSTNAMES])];
8922
- }
9790
+ }
9791
+ times() {
9792
+ if (!this.enabled) return void 0;
9793
+ const sum = { ...this.ended };
9794
+ for (const c of this.all()) {
9795
+ const t = c.times();
9796
+ sum.utime += t.utime;
9797
+ sum.stime += t.stime;
9798
+ }
9799
+ return sum;
9800
+ }
9801
+ running() {
9802
+ return this.enabled ? this.all().filter((c) => c.running()).length : 0;
9803
+ }
9804
+ timed(clock, dispatch2) {
9805
+ const c = clock ?? this.main;
9806
+ return (req) => {
9807
+ c.enter(req.op);
9808
+ return dispatch2(req).finally(() => c.leave());
9809
+ };
9810
+ }
9811
+ all() {
9812
+ return [this.main, ...this.threads.values()];
9813
+ }
9814
+ };
8923
9815
 
8924
9816
  // src/fs/pages.ts
8925
9817
  var PAGE = 64 * 1024;
@@ -9172,7 +10064,7 @@ function mountTable(mounts = []) {
9172
10064
  }
9173
10065
 
9174
10066
  // src/version.ts
9175
- var KERNEL_VERSION = true ? "1.89.0" : "0.0.0-dev";
10067
+ var KERNEL_VERSION = true ? "1.91.0" : "0.0.0-dev";
9176
10068
  var OS_RELEASE = "6.0.0-slicc";
9177
10069
  function procVersion() {
9178
10070
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9427,893 +10319,669 @@ var ProcTree = class {
9427
10319
  }
9428
10320
  if (leaf === void 0 && top === "mounts") {
9429
10321
  return { kind: "file", content: () => mountTable(this.source.mountList()) };
9430
- }
9431
- if (leaf === void 0 && SYSTEM_NAMES.includes(top)) {
9432
- return {
9433
- kind: "file",
9434
- content: () => systemFile(top, this.listing(), Date.now(), this.memory)
9435
- };
9436
- }
9437
- if (!/^[1-9]\d*$/.test(top) || rest2.length > 0 || !this.alive(Number(top))) return void 0;
9438
- if (leaf === void 0) return { kind: "dir", names: () => [...PID_NAMES] };
9439
- if (!PID_NAMES.includes(leaf)) return void 0;
9440
- return {
9441
- kind: "file",
9442
- content: () => {
9443
- const listing = this.listing();
9444
- const info = listing.processes.find((p) => p.pid === Number(top));
9445
- if (!info) throw syncError("ENOENT", `/proc/${top}/${leaf}`);
9446
- return pidFile(leaf, info, listing.boot);
9447
- }
9448
- };
9449
- }
9450
- parts(path, follow2) {
9451
- const parts2 = path.split("/").filter((p) => p !== "").slice(1);
9452
- if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
9453
- return parts2;
9454
- }
9455
- find(path, follow2) {
9456
- const node = this.node(this.parts(path, follow2));
9457
- if (!node) throw syncError("ENOENT", path);
9458
- return node;
9459
- }
9460
- stat(path, follow2 = true) {
9461
- const node = this.find(path, follow2);
9462
- const base = {
9463
- isFile: false,
9464
- isDirectory: false,
9465
- size: 0,
9466
- mtimeMs: Date.now(),
9467
- readonly: true
9468
- };
9469
- if (node.kind === "dir") return { ...base, isDirectory: true, mode: DIR_MODE };
9470
- if (node.kind === "link") {
9471
- return { ...base, isSymbolicLink: true, mode: LINK_MODE, size: node.target.length };
9472
- }
9473
- return { ...base, isFile: true, mode: FILE_MODE, size: this.bytes(node).length };
9474
- }
9475
- bytes(node) {
9476
- return new TextEncoder().encode(node.kind === "file" ? node.content() : "");
9477
- }
9478
- read(path) {
9479
- const node = this.find(path, true);
9480
- if (node.kind !== "file") throw syncError("EISDIR", path);
9481
- return this.bytes(node);
9482
- }
9483
- readdir(path) {
9484
- const node = this.find(path, true);
9485
- if (node.kind !== "dir") throw syncError("ENOTDIR", path);
9486
- return node.names();
9487
- }
9488
- readlink(path) {
9489
- const node = this.find(path, false);
9490
- if (node.kind !== "link") throw syncError("EINVAL", path);
9491
- return node.target;
9492
- }
9493
- exists(path) {
9494
- try {
9495
- this.find(path, true);
9496
- return true;
9497
- } catch {
9498
- return false;
9499
- }
9500
- }
9501
- };
9502
- var MAX_HOPS3 = 40;
9503
- function* procWalk(path, follow2) {
9504
- let done4 = [];
9505
- let todo = path.split("/").filter((part) => part !== "");
9506
- let hops = 0;
9507
- let broken;
9508
- while (todo.length > 0) {
9509
- const part = todo.shift();
9510
- if (part === ".") continue;
9511
- if (part === "..") {
9512
- done4.pop();
9513
- continue;
9514
- }
9515
- const at = `/${[...done4, part].join("/")}`;
9516
- const whole = normalize(`/${[...done4, part, ...todo].join("/")}`);
9517
- if (inProc(at) && inProc(whole)) return broken ?? whole;
9518
- if (!follow2 && todo.length === 0) return void 0;
9519
- const target = yield { at, check: todo.find((next2) => next2 !== ".") === ".." };
9520
- if (typeof target === "string") {
9521
- if (++hops > MAX_HOPS3) return void 0;
9522
- [done4, todo] = throughLink(target, done4, todo);
9523
- } else {
9524
- broken ??= refusalOf(target);
9525
- done4.push(part);
9526
- }
9527
- }
9528
- return void 0;
9529
- }
9530
- function failed(err) {
9531
- const code = err?.code;
9532
- return { refused: typeof code === "string" ? code : "ENOENT" };
9533
- }
9534
- function refusalOf(found) {
9535
- if (found === true) return void 0;
9536
- return found === false ? { refused: "ENOTDIR" } : found;
9537
- }
9538
- function throughLink(target, done4, todo) {
9539
- const parts2 = target.split("/").filter((part) => part !== "");
9540
- return [target.startsWith("/") ? [] : done4, [...parts2, ...todo]];
9541
- }
9542
- async function procAlias(fs, path, follow2) {
9543
- const { readlink, lstat } = fs;
9544
- if (!readlink || !lstat) return void 0;
9545
- const physical = await lstat.call(fs, "/proc").then(
9546
- () => true,
9547
- () => false
9548
- );
9549
- if (!physical) return void 0;
9550
- const walk2 = procWalk(path, follow2);
9551
- let step = walk2.next();
9552
- while (!step.done) {
9553
- const { at, check } = step.value;
9554
- const link2 = await readlink.call(fs, at).catch(() => void 0);
9555
- const isDir = (st) => st.isDirectory === true;
9556
- const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
9557
- step = walk2.next(target);
9558
- }
9559
- return step.value;
9560
- }
9561
-
9562
- // src/kernel/host-ops.ts
9563
- var HOST_OPS = [
9564
- "lock",
9565
- "unlock",
9566
- "async-submit",
9567
- "async-wait",
9568
- "async-take",
9569
- "async-resolve",
9570
- "async-hold",
9571
- "async-cancel",
9572
- "async-settle",
9573
- "async-close"
9574
- ];
9575
- var json3 = (value) => ({ ok: true, kind: "json", json: value });
9576
- var done2 = { ok: true, kind: "void" };
9577
- var LockTable = class {
9578
- held = /* @__PURE__ */ new Map();
9579
- lock(pid, path, exclusive, fd) {
9580
- const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
9581
- for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
9582
- const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
9583
- mine.exclusive = exclusive;
9584
- mine.fds.add(fd);
9585
- holders.set(pid, mine);
9586
- this.held.set(path, holders);
9587
- return true;
9588
- }
9589
- unlock(pid, path) {
9590
- const holders = this.held.get(path);
9591
- holders?.delete(pid);
9592
- if (holders?.size === 0) this.held.delete(path);
9593
- }
9594
- closed(pid, fd) {
9595
- for (const [path, holders] of [...this.held]) {
9596
- const mine = holders.get(pid);
9597
- if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
9598
- }
9599
- }
9600
- release(pid) {
9601
- for (const path of [...this.held.keys()]) this.unlock(pid, path);
9602
- }
9603
- };
9604
- var KEPT_OPS = /* @__PURE__ */ new Set(["fd-write"]);
9605
- var unknownSettled = false;
9606
- var AsyncOps = class {
9607
- next = 1;
9608
- results = /* @__PURE__ */ new Map();
9609
- ready = [];
9610
- running = /* @__PURE__ */ new Map();
9611
- waiters = /* @__PURE__ */ new Set();
9612
- taken = /* @__PURE__ */ new Map();
9613
- kept = /* @__PURE__ */ new Set();
9614
- closed = false;
9615
- run;
9616
- onReady;
9617
- constructor(run2, onReady) {
9618
- this.run = run2;
9619
- this.onReady = onReady;
9620
- }
9621
- submit(req) {
9622
- const id2 = this.hold();
9623
- if (KEPT_OPS.has(req.op)) this.kept.add(id2);
9624
- const running = new AbortController();
9625
- this.running.set(id2, running);
9626
- this.run(req, running.signal).then(
9627
- (result) => this.complete(id2, result),
9628
- (err) => this.complete(id2, { ok: false, errno: "EIO", message: String(err) })
9629
- );
9630
- return id2;
9631
- }
9632
- resolve(value) {
9633
- const id2 = this.hold();
9634
- this.complete(id2, json3(value));
9635
- return id2;
9636
- }
9637
- hold() {
9638
- const id2 = this.next++;
9639
- this.results.set(id2, void 0);
9640
- return id2;
9641
- }
9642
- take(id2) {
9643
- if (!this.results.has(id2)) throw new KernelError("EINVAL");
9644
- const result = this.results.get(id2);
9645
- if (!result) throw new KernelError("EAGAIN");
9646
- this.forget(id2);
9647
- if (this.kept.delete(id2)) this.taken.set(id2, result);
9648
- return result;
9649
- }
9650
- ack(ids) {
9651
- for (const id2 of ids) this.taken.delete(id2);
9652
- }
9653
- async settle(ids) {
9654
- const never = new AbortController().signal;
9655
- while (ids.some((id2) => this.running.has(id2))) await this.sleep(void 0, never);
9656
- return ids.map((id2) => {
9657
- const result = this.results.get(id2) ?? this.taken.get(id2) ?? null;
9658
- if (!result && !unknownSettled) {
9659
- unknownSettled = true;
9660
- console.warn(`async-settle: no result for op ${id2}; its write counts as made`);
10322
+ }
10323
+ if (leaf === void 0 && SYSTEM_NAMES.includes(top)) {
10324
+ return {
10325
+ kind: "file",
10326
+ content: () => systemFile(top, this.listing(), Date.now(), this.memory)
10327
+ };
10328
+ }
10329
+ if (!/^[1-9]\d*$/.test(top) || rest2.length > 0 || !this.alive(Number(top))) return void 0;
10330
+ if (leaf === void 0) return { kind: "dir", names: () => [...PID_NAMES] };
10331
+ if (!PID_NAMES.includes(leaf)) return void 0;
10332
+ return {
10333
+ kind: "file",
10334
+ content: () => {
10335
+ const listing = this.listing();
10336
+ const info = listing.processes.find((p) => p.pid === Number(top));
10337
+ if (!info) throw syncError("ENOENT", `/proc/${top}/${leaf}`);
10338
+ return pidFile(leaf, info, listing.boot);
9661
10339
  }
9662
- return result;
9663
- });
10340
+ };
9664
10341
  }
9665
- cancel(id2) {
9666
- this.kept.delete(id2);
9667
- this.running.get(id2)?.abort();
9668
- this.forget(id2);
10342
+ parts(path, follow2) {
10343
+ const parts2 = path.split("/").filter((p) => p !== "").slice(1);
10344
+ if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
10345
+ return parts2;
9669
10346
  }
9670
- forget(id2) {
9671
- this.running.delete(id2);
9672
- this.results.delete(id2);
9673
- const at = this.ready.indexOf(id2);
9674
- if (at >= 0) this.ready.splice(at, 1);
10347
+ find(path, follow2) {
10348
+ const node = this.node(this.parts(path, follow2));
10349
+ if (!node) throw syncError("ENOENT", path);
10350
+ return node;
9675
10351
  }
9676
- async wait(timeoutMs, blocking) {
9677
- let signal;
9678
- for (; ; ) {
9679
- if (this.closed) return -1;
9680
- const id2 = this.ready.shift();
9681
- if (id2 !== void 0) return id2;
9682
- if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
9683
- signal ??= blocking();
9684
- const woke = await this.sleep(timeoutMs, signal);
9685
- if (!woke) return 0;
10352
+ stat(path, follow2 = true) {
10353
+ const node = this.find(path, follow2);
10354
+ const base = {
10355
+ isFile: false,
10356
+ isDirectory: false,
10357
+ size: 0,
10358
+ mtimeMs: Date.now(),
10359
+ readonly: true
10360
+ };
10361
+ if (node.kind === "dir") return { ...base, isDirectory: true, mode: DIR_MODE };
10362
+ if (node.kind === "link") {
10363
+ return { ...base, isSymbolicLink: true, mode: LINK_MODE, size: node.target.length };
9686
10364
  }
10365
+ return { ...base, isFile: true, mode: FILE_MODE, size: this.bytes(node).length };
9687
10366
  }
9688
- close() {
9689
- this.closed = true;
9690
- this.wake();
9691
- }
9692
- sleep(timeoutMs, signal) {
9693
- return new Promise((resolve2, reject) => {
9694
- let timer;
9695
- const cleanup = () => {
9696
- clearTimeout(timer);
9697
- this.waiters.delete(wake);
9698
- signal.removeEventListener("abort", abort);
9699
- };
9700
- const wake = () => {
9701
- cleanup();
9702
- resolve2(true);
9703
- };
9704
- const abort = () => {
9705
- cleanup();
9706
- reject(new KernelError("EINTR"));
9707
- };
9708
- this.waiters.add(wake);
9709
- signal.addEventListener("abort", abort, { once: true });
9710
- if (timeoutMs !== void 0) {
9711
- timer = setTimeout(() => {
9712
- cleanup();
9713
- resolve2(false);
9714
- }, timeoutMs);
9715
- }
9716
- });
9717
- }
9718
- complete(id2, result) {
9719
- this.running.delete(id2);
9720
- if (!this.results.has(id2)) return;
9721
- this.results.set(id2, result);
9722
- this.ready.push(id2);
9723
- this.wake();
9724
- this.onReady?.();
10367
+ bytes(node) {
10368
+ return new TextEncoder().encode(node.kind === "file" ? node.content() : "");
9725
10369
  }
9726
- wake() {
9727
- for (const wake of [...this.waiters]) wake();
10370
+ read(path) {
10371
+ const node = this.find(path, true);
10372
+ if (node.kind !== "file") throw syncError("EISDIR", path);
10373
+ return this.bytes(node);
9728
10374
  }
9729
- };
9730
- async function hostSyscall(req, ctx) {
9731
- switch (req.op) {
9732
- case "lock":
9733
- if (!ctx.locks) throw new KernelError("ENOSYS");
9734
- if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
9735
- throw new KernelError("EAGAIN");
9736
- return done2;
9737
- case "unlock":
9738
- ctx.locks?.unlock(ctx.pid, req.path);
9739
- return done2;
9740
- case "async-submit":
9741
- if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
9742
- if (req.ack) ctx.ops.ack(req.ack);
9743
- return json3(ctx.ops.submit(req.req));
9744
- case "async-wait":
9745
- return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking));
9746
- case "async-take":
9747
- return ctx.ops.take(req.id);
9748
- case "async-resolve":
9749
- return json3(ctx.ops.resolve(req.value));
9750
- case "async-hold":
9751
- return json3(ctx.ops.hold());
9752
- case "async-cancel":
9753
- ctx.ops.cancel(req.id);
9754
- return done2;
9755
- case "async-settle":
9756
- return json3(await ctx.ops.settle(req.ids));
9757
- case "async-close":
9758
- ctx.ops.close();
9759
- return done2;
10375
+ readdir(path) {
10376
+ const node = this.find(path, true);
10377
+ if (node.kind !== "dir") throw syncError("ENOTDIR", path);
10378
+ return node.names();
9760
10379
  }
9761
- }
9762
-
9763
- // src/kernel/net/proxy-service.ts
9764
- var SEND_BUFFER2 = 1024 * 1024;
9765
- var TLS_HANDSHAKE = 22;
9766
- var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
9767
- var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
9768
- var REALM_PROXY_PORT = 3128;
9769
- var DEFAULT_LIMITS = {
9770
- maxConnections: 64,
9771
- maxHead: 64 * 1024,
9772
- idleMs: 12e4,
9773
- bodyBudget: 128 * 1024 * 1024,
9774
- closeWaitMs: 5e3
9775
- };
9776
- var HOP_BY_HOP = /* @__PURE__ */ new Set([
9777
- "connection",
9778
- "keep-alive",
9779
- "proxy-connection",
9780
- "proxy-authenticate",
9781
- "proxy-authorization",
9782
- "te",
9783
- "trailer",
9784
- "transfer-encoding",
9785
- "upgrade"
9786
- ]);
9787
- var REQUEST_OWN = /* @__PURE__ */ new Set(["host", "content-length", "expect"]);
9788
- var Budget = class {
9789
- free;
9790
- waiters = [];
9791
- constructor(free) {
9792
- this.free = free;
10380
+ readlink(path) {
10381
+ const node = this.find(path, false);
10382
+ if (node.kind !== "link") throw syncError("EINVAL", path);
10383
+ return node.target;
9793
10384
  }
9794
- async acquire(n, signal) {
9795
- while (this.free < n) {
9796
- signal.throwIfAborted();
9797
- await new Promise((resolve2) => {
9798
- const done4 = () => {
9799
- signal.removeEventListener("abort", done4);
9800
- resolve2();
9801
- };
9802
- this.waiters.push(done4);
9803
- signal.addEventListener("abort", done4, { once: true });
9804
- });
10385
+ exists(path) {
10386
+ try {
10387
+ this.find(path, true);
10388
+ return true;
10389
+ } catch {
10390
+ return false;
9805
10391
  }
9806
- this.free -= n;
9807
- }
9808
- release(n) {
9809
- this.free += n;
9810
- const waiters = this.waiters;
9811
- this.waiters = [];
9812
- for (const wake of waiters) wake();
9813
10392
  }
9814
10393
  };
9815
- function canonicalHost2(host) {
9816
- const bare = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
9817
- try {
9818
- return new URL(`http://${bare}/`).hostname;
9819
- } catch {
9820
- return void 0;
9821
- }
9822
- }
9823
- function localV4(a, b) {
9824
- return a === 127 || a === 0 || a === 169 && b === 254;
9825
- }
9826
- function hextets(address) {
9827
- const [head, tail, extra] = address.split("::");
9828
- if (extra !== void 0) return void 0;
9829
- const part = (text) => text ? text.split(":").map((h) => Number.parseInt(h, 16)) : [];
9830
- const front = part(head);
9831
- const back = part(tail);
9832
- const groups = tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
9833
- return groups.length === 8 && groups.every((g) => g >= 0 && g <= 65535) ? groups : void 0;
9834
- }
9835
- function localV6(address) {
9836
- const g = hextets(address);
9837
- if (!g) return true;
9838
- if ((g[0] & 65472) === 65152) return true;
9839
- const zeroPrefix = g.slice(0, 5).every((x) => x === 0);
9840
- if (!zeroPrefix) return false;
9841
- if (g[5] === 65535 || g[5] === 0 && g[6] !== 0) return localV4(g[6] >> 8, g[6] & 255);
9842
- return g[5] === 0 && g[6] === 0;
9843
- }
9844
- function isLoopbackHost(hostname) {
9845
- const canonical = canonicalHost2(hostname);
9846
- if (canonical === void 0) return false;
9847
- const host = canonical.replace(/\.+$/, "");
9848
- if (host === "localhost" || host.endsWith(".localhost")) return true;
9849
- if (host.startsWith("[")) return localV6(host.slice(1, -1));
9850
- const v4 = /^(\d+)\.(\d+)\.\d+\.\d+$/.exec(host);
9851
- return v4 ? localV4(Number(v4[1]), Number(v4[2])) : false;
9852
- }
9853
- function dropped(headers) {
9854
- return /* @__PURE__ */ new Set([...HOP_BY_HOP, ...fieldTokens(headers, "connection")]);
10394
+ var MAX_HOPS3 = 40;
10395
+ function* procWalk(path, follow2) {
10396
+ let done4 = [];
10397
+ let todo = path.split("/").filter((part) => part !== "");
10398
+ let hops = 0;
10399
+ let broken;
10400
+ while (todo.length > 0) {
10401
+ const part = todo.shift();
10402
+ if (part === ".") continue;
10403
+ if (part === "..") {
10404
+ done4.pop();
10405
+ continue;
10406
+ }
10407
+ const at = `/${[...done4, part].join("/")}`;
10408
+ const whole = normalize(`/${[...done4, part, ...todo].join("/")}`);
10409
+ if (inProc(at) && inProc(whole)) return broken ?? whole;
10410
+ if (!follow2 && todo.length === 0) return void 0;
10411
+ const target = yield { at, check: todo.find((next2) => next2 !== ".") === ".." };
10412
+ if (typeof target === "string") {
10413
+ if (++hops > MAX_HOPS3) return void 0;
10414
+ [done4, todo] = throughLink(target, done4, todo);
10415
+ } else {
10416
+ broken ??= refusalOf(target);
10417
+ done4.push(part);
10418
+ }
10419
+ }
10420
+ return void 0;
9855
10421
  }
9856
- function forwardRequestHeaders(headers) {
9857
- const drop = dropped(headers);
9858
- return headers.filter(([name2]) => {
9859
- const lower = name2.toLowerCase();
9860
- return !drop.has(lower) && !REQUEST_OWN.has(lower);
9861
- });
10422
+ function failed(err) {
10423
+ const code = err?.code;
10424
+ return { refused: typeof code === "string" ? code : "ENOENT" };
9862
10425
  }
9863
- function forwardResponseHeaders(headers, opts) {
9864
- const drop = dropped(headers);
9865
- if (!opts.encodedBodies) drop.add("content-encoding");
9866
- if (!(opts.encodedBodies && opts.bodiless)) drop.add("content-length");
9867
- return headers.filter(([name2]) => !drop.has(name2.toLowerCase()));
10426
+ function refusalOf(found) {
10427
+ if (found === true) return void 0;
10428
+ return found === false ? { refused: "ENOTDIR" } : found;
9868
10429
  }
9869
- function keepsAlive(req) {
9870
- const tokens = fieldTokens(req.headers, "connection");
9871
- if (tokens.includes("close")) return false;
9872
- return req.minor >= 1 || tokens.includes("keep-alive");
10430
+ function throughLink(target, done4, todo) {
10431
+ const parts2 = target.split("/").filter((part) => part !== "");
10432
+ return [target.startsWith("/") ? [] : done4, [...parts2, ...todo]];
9873
10433
  }
9874
- function tunnelRequestUrl(req, origin) {
9875
- const base = new URL(origin);
9876
- let url;
9877
- try {
9878
- url = new URL(req.target, req.target.startsWith("/") ? base : void 0);
9879
- } catch {
9880
- throw new HttpError(400, "malformed request target");
9881
- }
9882
- const hosts = fieldValues(req.headers, "host");
9883
- const hostOk = hosts.every(
9884
- (h) => URL.canParse(`https://${h}`) && new URL(`https://${h}`).host === base.host
10434
+ async function procAlias(fs, path, follow2) {
10435
+ const { readlink, lstat } = fs;
10436
+ if (!readlink || !lstat) return void 0;
10437
+ const physical = await lstat.call(fs, "/proc").then(
10438
+ () => true,
10439
+ () => false
9885
10440
  );
9886
- if (url.origin !== base.origin || !hostOk) {
9887
- throw new HttpError(421, `this tunnel is for ${base.host}`);
10441
+ if (!physical) return void 0;
10442
+ const walk2 = procWalk(path, follow2);
10443
+ let step = walk2.next();
10444
+ while (!step.done) {
10445
+ const { at, check } = step.value;
10446
+ const link2 = await readlink.call(fs, at).catch(() => void 0);
10447
+ const isDir = (st) => st.isDirectory === true;
10448
+ const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
10449
+ step = walk2.next(target);
9888
10450
  }
9889
- return url.href;
10451
+ return step.value;
9890
10452
  }
9891
- function requestUrl(req) {
9892
- if (req.target.startsWith("/")) {
9893
- throw new HttpError(400, "this is a proxy: send the absolute URL (GET http://host/path)");
10453
+
10454
+ // src/kernel/host-ops.ts
10455
+ var HOST_OPS = [
10456
+ "lock",
10457
+ "unlock",
10458
+ "async-submit",
10459
+ "async-wait",
10460
+ "async-take",
10461
+ "async-resolve",
10462
+ "async-hold",
10463
+ "async-cancel",
10464
+ "async-settle",
10465
+ "async-close"
10466
+ ];
10467
+ var json3 = (value) => ({ ok: true, kind: "json", json: value });
10468
+ var done2 = { ok: true, kind: "void" };
10469
+ var LockTable = class {
10470
+ held = /* @__PURE__ */ new Map();
10471
+ lock(pid, path, exclusive, fd) {
10472
+ const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
10473
+ for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
10474
+ const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
10475
+ mine.exclusive = exclusive;
10476
+ mine.fds.add(fd);
10477
+ holders.set(pid, mine);
10478
+ this.held.set(path, holders);
10479
+ return true;
9894
10480
  }
9895
- let url;
9896
- try {
9897
- url = new URL(req.target);
9898
- } catch {
9899
- throw new HttpError(400, "malformed request target");
10481
+ unlock(pid, path) {
10482
+ const holders = this.held.get(path);
10483
+ holders?.delete(pid);
10484
+ if (holders?.size === 0) this.held.delete(path);
9900
10485
  }
9901
- if (url.protocol !== "http:" && url.protocol !== "https:") {
9902
- throw new HttpError(400, `unsupported scheme ${url.protocol}`);
10486
+ closed(pid, fd) {
10487
+ for (const [path, holders] of [...this.held]) {
10488
+ const mine = holders.get(pid);
10489
+ if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
10490
+ }
9903
10491
  }
9904
- if (isLoopbackHost(url.hostname)) {
9905
- throw new HttpError(
9906
- 403,
9907
- `${url.hostname} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
9908
- );
10492
+ release(pid) {
10493
+ for (const path of [...this.held.keys()]) this.unlock(pid, path);
9909
10494
  }
9910
- return url.href;
9911
- }
9912
- function tunnelTarget(req) {
9913
- const match = /^([A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\]):(\d{1,5})$/.exec(req.target);
9914
- const port = Number(match?.[2]);
9915
- if (!match || port < 1 || port > 65535) {
9916
- throw new HttpError(400, "CONNECT needs host:port");
10495
+ };
10496
+ var KEPT_OPS = /* @__PURE__ */ new Set(["fd-write"]);
10497
+ var unknownSettled = false;
10498
+ var AsyncOps = class {
10499
+ next = 1;
10500
+ results = /* @__PURE__ */ new Map();
10501
+ ready = [];
10502
+ running = /* @__PURE__ */ new Map();
10503
+ waiters = /* @__PURE__ */ new Set();
10504
+ taken = /* @__PURE__ */ new Map();
10505
+ kept = /* @__PURE__ */ new Set();
10506
+ closed = false;
10507
+ run;
10508
+ onReady;
10509
+ constructor(run2, onReady) {
10510
+ this.run = run2;
10511
+ this.onReady = onReady;
9917
10512
  }
9918
- const host = canonicalHost2(match[1]);
9919
- if (host === void 0) throw new HttpError(400, "CONNECT needs host:port");
9920
- if (isLoopbackHost(host)) {
9921
- throw new HttpError(
9922
- 403,
9923
- `${host} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
10513
+ submit(req) {
10514
+ const id2 = this.hold();
10515
+ if (KEPT_OPS.has(req.op)) this.kept.add(id2);
10516
+ const running = new AbortController();
10517
+ this.running.set(id2, running);
10518
+ this.run(req, running.signal).then(
10519
+ (result) => this.complete(id2, result),
10520
+ (err) => this.complete(id2, { ok: false, errno: "EIO", message: String(err) })
9924
10521
  );
10522
+ return id2;
9925
10523
  }
9926
- return { host, port };
9927
- }
9928
- async function watchHangup(conn, abort, until) {
9929
- while (!until.aborted && !abort.signal.aborted) {
9930
- if (conn.poll().hangup) {
9931
- abort.abort(new KernelError("EPIPE"));
9932
- return;
9933
- }
9934
- try {
9935
- await conn.changed(until);
9936
- } catch {
9937
- return;
9938
- }
10524
+ resolve(value) {
10525
+ const id2 = this.hold();
10526
+ this.complete(id2, json3(value));
10527
+ return id2;
9939
10528
  }
9940
- }
9941
- function untilAborted(response, signal) {
9942
- return new Promise((resolve2, reject) => {
9943
- const onAbort = () => {
9944
- reject(signal.reason ?? new KernelError("EINTR"));
9945
- response.then((late) => late.cancel()).catch(() => void 0);
9946
- };
9947
- if (signal.aborted) return onAbort();
9948
- signal.addEventListener("abort", onAbort, { once: true });
9949
- response.then(resolve2, reject).finally(() => signal.removeEventListener("abort", onAbort));
9950
- });
9951
- }
9952
- function plainResponse(status, message3) {
9953
- const body = latin1Bytes(`slicc realm proxy: ${message3}
9954
- `);
9955
- return {
9956
- status,
9957
- statusText: REASON[status] ?? "",
9958
- headers: [["Content-Type", "text/plain; charset=utf-8"]],
9959
- body: (async function* () {
9960
- yield body;
9961
- })(),
9962
- cancel: async () => void 0
9963
- };
9964
- }
9965
- function timedSource(conn, idle, stop) {
9966
- return {
9967
- read: (max, signal) => {
9968
- const own = signal ? [stop, signal] : [stop];
9969
- if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
9970
- return conn.read(max, AbortSignal.any(own));
9971
- }
9972
- };
9973
- }
9974
- function webSocketTarget(req) {
9975
- return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
9976
- }
9977
- function wireCode(code) {
9978
- if (code === 1005) return 1e3;
9979
- return sendableCode(code) ? code : 1011;
9980
- }
9981
- function quietly(ms) {
9982
- return new Promise((resolve2) => {
9983
- AbortSignal.timeout(ms).addEventListener("abort", () => resolve2(), { once: true });
9984
- });
9985
- }
9986
- var RealmProxy = class {
9987
- options;
9988
- listener;
9989
- stop = new AbortController();
9990
- limits;
9991
- slots;
9992
- bodies;
9993
- connections = /* @__PURE__ */ new Set();
9994
- warnedWebSocket = false;
9995
- closed;
9996
- port;
9997
- constructor(options) {
9998
- this.options = options;
9999
- this.limits = { ...DEFAULT_LIMITS, ...options.limits };
10000
- this.slots = new Budget(this.limits.maxConnections);
10001
- this.bodies = new Budget(this.limits.bodyBudget);
10002
- this.listener = options.net.listen({
10003
- family: "inet",
10004
- host: options.host ?? "127.0.0.1",
10005
- port: options.port ?? REALM_PROXY_PORT
10006
- });
10007
- this.port = this.listener.local?.family === "inet" ? this.listener.local.port : 0;
10008
- this.closed = this.acceptLoop();
10529
+ hold() {
10530
+ const id2 = this.next++;
10531
+ this.results.set(id2, void 0);
10532
+ return id2;
10009
10533
  }
10010
- get stopped() {
10011
- return this.stop.signal.aborted;
10534
+ take(id2) {
10535
+ if (!this.results.has(id2)) throw new KernelError("EINVAL");
10536
+ const result = this.results.get(id2);
10537
+ if (!result) throw new KernelError("EAGAIN");
10538
+ this.forget(id2);
10539
+ if (this.kept.delete(id2)) this.taken.set(id2, result);
10540
+ return result;
10012
10541
  }
10013
- close() {
10014
- if (this.stop.signal.aborted) return;
10015
- this.stop.abort();
10016
- this.listener.close();
10017
- for (const conn of this.connections) conn.close();
10542
+ ack(ids) {
10543
+ for (const id2 of ids) this.taken.delete(id2);
10018
10544
  }
10019
- async acceptLoop() {
10020
- const served = /* @__PURE__ */ new Set();
10021
- try {
10022
- for (; ; ) {
10023
- await this.slots.acquire(1, this.stop.signal);
10024
- let conn;
10025
- try {
10026
- conn = await this.listener.accept(this.stop.signal);
10027
- } catch {
10028
- this.slots.release(1);
10029
- break;
10030
- }
10031
- const done4 = this.serve(conn).finally(() => {
10032
- this.slots.release(1);
10033
- served.delete(done4);
10034
- });
10035
- served.add(done4);
10545
+ async settle(ids) {
10546
+ const never = new AbortController().signal;
10547
+ while (ids.some((id2) => this.running.has(id2))) await this.sleep(void 0, never);
10548
+ return ids.map((id2) => {
10549
+ const result = this.results.get(id2) ?? this.taken.get(id2) ?? null;
10550
+ if (!result && !unknownSettled) {
10551
+ unknownSettled = true;
10552
+ console.warn(`async-settle: no result for op ${id2}; its write counts as made`);
10036
10553
  }
10037
- } catch {
10038
- }
10039
- await Promise.allSettled([...served]);
10554
+ return result;
10555
+ });
10040
10556
  }
10041
- async serve(conn) {
10042
- this.connections.add(conn);
10043
- const idle = { ms: this.limits.idleMs };
10044
- const incoming = new Incoming(timedSource(conn, idle, this.stop.signal));
10045
- try {
10046
- await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
10047
- } finally {
10048
- this.connections.delete(conn);
10049
- conn.close();
10050
- }
10557
+ cancel(id2) {
10558
+ this.kept.delete(id2);
10559
+ this.running.get(id2)?.abort();
10560
+ this.forget(id2);
10051
10561
  }
10052
- async requests(ctx) {
10053
- try {
10054
- for (; ; ) {
10055
- const head = await ctx.incoming.head(this.limits.maxHead);
10056
- if (!head || !await this.exchange(ctx, head)) break;
10057
- }
10058
- } catch (e) {
10059
- if (e instanceof HttpError) await this.refuse(ctx.sink, e);
10060
- }
10562
+ forget(id2) {
10563
+ this.running.delete(id2);
10564
+ this.results.delete(id2);
10565
+ const at = this.ready.indexOf(id2);
10566
+ if (at >= 0) this.ready.splice(at, 1);
10061
10567
  }
10062
- async refuse(sink, error) {
10063
- try {
10064
- await this.relay(sink, "GET", 1, plainResponse(error.status, error.message), false);
10065
- } catch {
10568
+ async wait(timeoutMs, blocking) {
10569
+ let signal;
10570
+ for (; ; ) {
10571
+ if (this.closed) return -1;
10572
+ const id2 = this.ready.shift();
10573
+ if (id2 !== void 0) return id2;
10574
+ if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
10575
+ signal ??= blocking();
10576
+ const woke = await this.sleep(timeoutMs, signal);
10577
+ if (!woke) return 0;
10066
10578
  }
10067
10579
  }
10068
- async exchange(ctx, head) {
10069
- const { sink: conn, incoming } = ctx;
10070
- const req = parseRequestHead(head);
10071
- if (req.method === "CONNECT") {
10072
- if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
10073
- await this.connect(ctx.conn, incoming, req, ctx.idle);
10074
- return false;
10075
- }
10076
- if (req.method === "GET" && isUpgrade(req.headers)) {
10077
- await this.websocket(ctx, req);
10078
- return false;
10079
- }
10080
- const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
10081
- const keep2 = keepsAlive(req);
10082
- const framing = requestFraming(req.headers);
10083
- const reserve = framing.kind === "length" ? framing.length : framing.kind === "chunked" ? this.cap() : 0;
10084
- if (reserve > this.cap()) throw new HttpError(413, `request body over ${this.cap()} bytes`);
10085
- await this.expectContinue(conn, req, framing.kind !== "none");
10086
- await this.bodies.acquire(reserve, this.stop.signal);
10087
- let response;
10088
- const abort = new AbortController();
10089
- const stopExchange = () => abort.abort();
10090
- this.stop.signal.addEventListener("abort", stopExchange, { once: true });
10091
- try {
10092
- try {
10093
- const body = await readBody(incoming, framing, this.cap());
10094
- const waiting = new AbortController();
10095
- void watchHangup(ctx.socket, abort, waiting.signal);
10096
- try {
10097
- response = await this.upstream(req, url, body, abort.signal, ctx.socket.peerCred?.pid);
10098
- } finally {
10099
- waiting.abort();
10100
- }
10101
- } finally {
10102
- this.bodies.release(reserve);
10580
+ close() {
10581
+ this.closed = true;
10582
+ this.wake();
10583
+ }
10584
+ sleep(timeoutMs, signal) {
10585
+ return new Promise((resolve2, reject) => {
10586
+ let timer;
10587
+ const cleanup = () => {
10588
+ clearTimeout(timer);
10589
+ this.waiters.delete(wake);
10590
+ signal.removeEventListener("abort", abort);
10591
+ };
10592
+ const wake = () => {
10593
+ cleanup();
10594
+ resolve2(true);
10595
+ };
10596
+ const abort = () => {
10597
+ cleanup();
10598
+ reject(new KernelError("EINTR"));
10599
+ };
10600
+ this.waiters.add(wake);
10601
+ signal.addEventListener("abort", abort, { once: true });
10602
+ if (timeoutMs !== void 0) {
10603
+ timer = setTimeout(() => {
10604
+ cleanup();
10605
+ resolve2(false);
10606
+ }, timeoutMs);
10103
10607
  }
10104
- return await this.relay(conn, req.method, req.minor, response, keep2);
10105
- } finally {
10106
- this.stop.signal.removeEventListener("abort", stopExchange);
10107
- abort.abort();
10108
- }
10608
+ });
10109
10609
  }
10110
- cap() {
10111
- return Math.min(this.options.transport.traits.maxRequestBody, this.limits.bodyBudget);
10610
+ complete(id2, result) {
10611
+ this.running.delete(id2);
10612
+ if (!this.results.has(id2)) return;
10613
+ this.results.set(id2, result);
10614
+ this.ready.push(id2);
10615
+ this.wake();
10616
+ this.onReady?.();
10112
10617
  }
10113
- async expectContinue(conn, req, hasBody) {
10114
- const expect = fieldTokens(req.headers, "expect");
10115
- if (expect.length === 0) return;
10116
- if (expect.length !== 1 || expect[0] !== "100-continue") {
10117
- throw new HttpError(417, `unsupported expectation: ${expect.join(", ")}`);
10118
- }
10119
- if (hasBody && req.minor >= 1) {
10120
- await conn.write(latin1Bytes("HTTP/1.1 100 Continue\r\n\r\n"), this.stop.signal);
10121
- }
10618
+ wake() {
10619
+ for (const wake of [...this.waiters]) wake();
10122
10620
  }
10123
- async upstream(req, url, body, signal, owner) {
10124
- try {
10125
- const fetching = this.options.transport.fetch(
10126
- {
10127
- url: toHostLoopback(url),
10128
- method: req.method.toUpperCase(),
10129
- headers: forwardRequestHeaders(req.headers),
10130
- body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
10131
- signal
10132
- },
10133
- owner
10134
- );
10135
- return await untilAborted(fetching, signal);
10136
- } catch (e) {
10137
- if (signal.aborted) throw e;
10138
- const message3 = e instanceof Error ? e.message : String(e);
10139
- const status = e?.status;
10140
- const own = typeof status === "number" && status >= 400 && status <= 599 ? status : 502;
10141
- return plainResponse(own, message3 || "upstream request failed");
10142
- }
10621
+ };
10622
+ async function hostSyscall(req, ctx) {
10623
+ switch (req.op) {
10624
+ case "lock":
10625
+ if (!ctx.locks) throw new KernelError("ENOSYS");
10626
+ if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
10627
+ throw new KernelError("EAGAIN");
10628
+ return done2;
10629
+ case "unlock":
10630
+ ctx.locks?.unlock(ctx.pid, req.path);
10631
+ return done2;
10632
+ case "async-submit":
10633
+ if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
10634
+ if (req.ack) ctx.ops.ack(req.ack);
10635
+ return json3(ctx.ops.submit(req.req));
10636
+ case "async-wait":
10637
+ return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking));
10638
+ case "async-take":
10639
+ return ctx.ops.take(req.id);
10640
+ case "async-resolve":
10641
+ return json3(ctx.ops.resolve(req.value));
10642
+ case "async-hold":
10643
+ return json3(ctx.ops.hold());
10644
+ case "async-cancel":
10645
+ ctx.ops.cancel(req.id);
10646
+ return done2;
10647
+ case "async-settle":
10648
+ return json3(await ctx.ops.settle(req.ids));
10649
+ case "async-close":
10650
+ ctx.ops.close();
10651
+ return done2;
10143
10652
  }
10144
- async relay(conn, method, minor, response, keepAlive) {
10145
- const status = response.status;
10146
- const bodiless2 = method === "HEAD" || status === 204 || status === 304 || status < 200;
10147
- const headers = forwardResponseHeaders(response.headers, {
10148
- encodedBodies: this.options.transport.traits.encodedBodies,
10149
- bodiless: bodiless2
10150
- });
10151
- const chunked2 = !bodiless2 && minor >= 1;
10152
- const keep2 = keepAlive && (bodiless2 || chunked2);
10153
- headers.push(["Connection", keep2 ? "keep-alive" : "close"]);
10154
- if (chunked2) headers.push(["Transfer-Encoding", "chunked"]);
10155
- const signal = this.stop.signal;
10156
- await conn.write(
10157
- responseHead(status, response.statusText || REASON[status] || "", headers),
10158
- signal
10159
- );
10160
- if (bodiless2) {
10161
- await response.cancel();
10162
- return keep2;
10163
- }
10653
+ }
10654
+
10655
+ // src/kernel/net/loopback-transport.ts
10656
+ var LOOPBACK_TRAITS = {
10657
+ manualRedirects: true,
10658
+ encodedBodies: true,
10659
+ maxRequestBody: 64 * 1024 * 1024,
10660
+ crossOrigin: "any",
10661
+ websocket: true
10662
+ };
10663
+ var MAX_HEAD2 = 64 * 1024;
10664
+ var OWN_HEADERS = /* @__PURE__ */ new Set([
10665
+ "host",
10666
+ "connection",
10667
+ "content-length",
10668
+ "transfer-encoding",
10669
+ "upgrade",
10670
+ "sec-websocket-key",
10671
+ "sec-websocket-version",
10672
+ "sec-websocket-protocol",
10673
+ "sec-websocket-extensions"
10674
+ ]);
10675
+ var OP_CLOSE = 8;
10676
+ var OP_PONG = 10;
10677
+ var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
10678
+ var SAFE_VALUE = /^[^\r\n\0]*$/;
10679
+ var CLOSE_WAIT_MS3 = 5e3;
10680
+ var MAX_UNREAD = 16 * 1024 * 1024;
10681
+ function errno(code, message3) {
10682
+ return Object.assign(new Error(`${code}: ${message3}`), { code });
10683
+ }
10684
+ function requestHead(method, url, headers, extra) {
10685
+ if (!TOKEN3.test(method)) throw errno("EINVAL", `method ${method}`);
10686
+ const lines2 = [`${method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
10687
+ for (const [name2, value] of headers) {
10688
+ if (OWN_HEADERS.has(name2.toLowerCase())) continue;
10689
+ if (!TOKEN3.test(name2) || !SAFE_VALUE.test(value)) throw errno("EINVAL", `header ${name2}`);
10690
+ lines2.push(`${name2}: ${value}`);
10691
+ }
10692
+ for (const [name2, value] of extra) lines2.push(`${name2}: ${value}`);
10693
+ return latin1Bytes(`${lines2.join("\r\n")}\r
10694
+ \r
10695
+ `);
10696
+ }
10697
+ function parseHead(bytes) {
10698
+ const [statusLine = "", ...lines2] = latin1(bytes).split(/\r?\n/);
10699
+ const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
10700
+ if (!match) throw errno("EPROTO", `not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
10701
+ const headers = [];
10702
+ for (const line of lines2) {
10703
+ const colon = line.indexOf(":");
10704
+ if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
10705
+ }
10706
+ return { status: Number(match[1]), statusText: match[2] ?? "", headers };
10707
+ }
10708
+ async function readHead(incoming, signal) {
10709
+ for (; ; ) {
10710
+ let raw;
10164
10711
  try {
10165
- for await (const piece of response.body) {
10166
- if (piece.length > 0) await conn.write(chunked2 ? chunk(piece) : piece, signal);
10167
- }
10168
- } catch {
10169
- await response.cancel().catch(() => void 0);
10170
- return false;
10712
+ raw = await incoming.head(MAX_HEAD2, signal);
10713
+ } catch (err) {
10714
+ if (err instanceof KernelError) throw err;
10715
+ throw errno("EPROTO", "the response head is malformed or too large");
10171
10716
  }
10172
- if (chunked2) await conn.write(LAST_CHUNK, signal);
10173
- return keep2;
10717
+ if (!raw) throw errno("ECONNRESET", "the connection closed before a response");
10718
+ const head = parseHead(raw);
10719
+ if (head.status < 100 || head.status >= 200 || head.status === 101) return head;
10174
10720
  }
10175
- async connect(conn, incoming, req, idle) {
10176
- const target = tunnelTarget(req);
10177
- const refused2 = target.port === 443 ? await this.tunnelReady(target) : void 0;
10178
- if (refused2) throw refused2;
10179
- await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
10180
- try {
10181
- const first = await incoming.peek(this.stop.signal);
10182
- if (first === void 0) return;
10183
- if (first !== TLS_HANDSHAKE) {
10184
- const origin = `http://${target.port === 80 ? target.host : `${target.host}:${target.port}`}`;
10185
- await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
10186
- return;
10187
- }
10188
- const tunnel = this.options.tunnel;
10189
- if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
10190
- await tunnel(
10191
- conn,
10192
- incoming,
10193
- target,
10194
- this.stop.signal,
10195
- (source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
10196
- );
10197
- } catch {
10721
+ }
10722
+ async function* chunked(incoming, signal) {
10723
+ for (; ; ) {
10724
+ const line = await incoming.line(4096, signal);
10725
+ const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(line);
10726
+ if (!size) throw errno("EPROTO", "a malformed chunk size");
10727
+ const n = Number.parseInt(size[1], 16);
10728
+ if (n === 0) {
10729
+ while (await incoming.line(MAX_HEAD2, signal) !== "") ;
10730
+ return;
10198
10731
  }
10732
+ yield await incoming.exactly(n, signal);
10733
+ if (await incoming.line(2, signal) !== "") throw errno("EPROTO", "a malformed chunk");
10199
10734
  }
10200
- async tunnelReady(target) {
10201
- const name2 = `CONNECT ${target.host}:${target.port}`;
10202
- if (!this.options.tunnel) return new HttpError(501, `${name2}: no tunnels through this proxy`);
10735
+ }
10736
+ async function* counted(incoming, length2, signal) {
10737
+ for (let left = length2; left > 0; ) {
10738
+ const data = await incoming.some(Math.min(left, 64 * 1024), signal);
10739
+ if (data.length === 0) throw errno("EPROTO", "the body ended early");
10740
+ left -= data.length;
10741
+ yield data;
10742
+ }
10743
+ }
10744
+ async function* untilClose(incoming, signal) {
10745
+ for (; ; ) {
10746
+ const data = await incoming.some(64 * 1024, signal);
10747
+ if (data.length === 0) return;
10748
+ yield data;
10749
+ }
10750
+ }
10751
+ function bodyOf(incoming, head, method, signal) {
10752
+ if (method === "HEAD" || head.status === 204 || head.status === 304) return [];
10753
+ if (fieldTokens(head.headers, "transfer-encoding").includes("chunked")) {
10754
+ return chunked(incoming, signal);
10755
+ }
10756
+ const lengths = fieldValues(head.headers, "content-length");
10757
+ if (lengths.length === 0) return untilClose(incoming, signal);
10758
+ const length2 = lengths[0]?.trim() ?? "";
10759
+ if (!/^\d{1,15}$/.test(length2) || lengths.some((l) => l.trim() !== length2)) {
10760
+ throw errno("EPROTO", `a malformed Content-Length: ${lengths.join(", ")}`);
10761
+ }
10762
+ return counted(incoming, Number(length2), signal);
10763
+ }
10764
+ var KernelLoopback = class {
10765
+ net;
10766
+ cred;
10767
+ constructor(net, cred) {
10768
+ this.net = net;
10769
+ this.cred = cred;
10770
+ }
10771
+ port(url) {
10772
+ return kernelLoopbackPort(url);
10773
+ }
10774
+ connect(url, port, signal) {
10775
+ if (url.protocol === "https:" || url.protocol === "wss:") {
10776
+ throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
10777
+ }
10778
+ signal.throwIfAborted();
10779
+ const socket = this.net.socket("inet");
10203
10780
  try {
10204
- await this.options.tunnelReady?.();
10205
- return void 0;
10206
- } catch (e) {
10207
- return new HttpError(501, `${name2}: ${e instanceof Error ? e.message : String(e)}`);
10781
+ void socket.connect({ family: "inet", host: "127.0.0.1", port }, false, this.cred());
10782
+ } catch (err) {
10783
+ socket.close();
10784
+ const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
10785
+ throw refused2 ? errno("ECONNREFUSED", `nothing listens on the kernel's port ${port}`) : err;
10208
10786
  }
10787
+ signal.addEventListener("abort", () => socket.close(), { once: true });
10788
+ return socket;
10209
10789
  }
10210
- async websocket(ctx, req) {
10211
- const href = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(webSocketTarget(req));
10212
- const { transport } = this.options;
10213
- if (!transport.traits.websocket || !transport.websocket) {
10214
- if (!this.warnedWebSocket) {
10215
- this.warnedWebSocket = true;
10216
- console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
10217
- }
10218
- throw new HttpError(501, NO_WEBSOCKET2);
10219
- }
10220
- const accept2 = await acceptKey(req.headers);
10221
- const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
10222
- const hangup = new AbortController();
10223
- const waiting = new AbortController();
10224
- void watchHangup(ctx.socket, hangup, waiting.signal);
10225
- let socket;
10790
+ async fetch(req, port) {
10791
+ const url = new URL(req.url);
10792
+ const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
10793
+ const length2 = body ? [["Content-Length", String(body.length)]] : [];
10794
+ const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
10795
+ const socket = this.connect(url, port, req.signal);
10226
10796
  try {
10227
- socket = await transport.websocket({
10228
- url: toHostLoopback(href).replace(/^http/, "ws"),
10229
- protocols,
10230
- headers: forwardRequestHeaders(req.headers),
10231
- signal: AbortSignal.any([this.stop.signal, hangup.signal])
10232
- });
10233
- } catch (e) {
10234
- waiting.abort();
10235
- throw new HttpError(502, e instanceof Error ? e.message : String(e));
10797
+ await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
10798
+ const incoming = new Incoming(socket);
10799
+ const response = await readHead(incoming, req.signal);
10800
+ const chunks2 = bodyOf(incoming, response, req.method, req.signal);
10801
+ return {
10802
+ url: req.url,
10803
+ status: response.status,
10804
+ statusText: response.statusText,
10805
+ headers: response.headers,
10806
+ body: closing(chunks2, socket),
10807
+ cancel: async () => socket.close()
10808
+ };
10809
+ } catch (err) {
10810
+ socket.close();
10811
+ throw err;
10236
10812
  }
10237
- ctx.idle.ms = Number.POSITIVE_INFINITY;
10813
+ }
10814
+ async websocket(req, port) {
10815
+ const url = new URL(req.url);
10816
+ const socket = this.connect(url, port, req.signal);
10238
10817
  try {
10239
- if (hangup.signal.aborted) throw hangup.signal.reason;
10240
- if (socket.protocol && !(protocols.includes(socket.protocol) && TOKEN2.test(socket.protocol))) {
10241
- throw new HttpError(502, "the far end chose a subprotocol the client did not offer");
10818
+ const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
10819
+ const extra = [
10820
+ ["Upgrade", "websocket"],
10821
+ ["Connection", "Upgrade"],
10822
+ ["Sec-WebSocket-Key", key],
10823
+ ["Sec-WebSocket-Version", "13"]
10824
+ ];
10825
+ if (req.protocols.length > 0)
10826
+ extra.push(["Sec-WebSocket-Protocol", req.protocols.join(", ")]);
10827
+ await writeAll2(socket, requestHead("GET", url, req.headers, extra), req.signal);
10828
+ const incoming = new Incoming(socket);
10829
+ const head = await readHead(incoming, req.signal);
10830
+ if (head.status !== 101) {
10831
+ throw errno(
10832
+ "EPROTO",
10833
+ `the server answered ${head.status} ${head.statusText}, not an upgrade`
10834
+ );
10242
10835
  }
10243
- await ctx.sink.write(switchingHead(accept2, socket.protocol), this.stop.signal);
10244
- await this.bridge(ctx, socket, hangup.signal);
10245
- } catch (e) {
10246
- socket.close(1001, "the client went away");
10247
- throw e;
10248
- } finally {
10249
- waiting.abort();
10836
+ const upgraded = fieldTokens(head.headers, "upgrade").includes("websocket") && fieldTokens(head.headers, "connection").includes("upgrade");
10837
+ const accept2 = fieldValues(head.headers, "sec-websocket-accept")[0];
10838
+ if (!upgraded || accept2 !== await acceptFor(key)) {
10839
+ throw errno("EPROTO", "the server did not accept the WebSocket key");
10840
+ }
10841
+ const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
10842
+ return new LoopbackWebSocket(socket, incoming, protocol);
10843
+ } catch (err) {
10844
+ socket.close();
10845
+ throw err;
10250
10846
  }
10251
10847
  }
10252
- async bridge(ctx, socket, hangup) {
10253
- const stop = this.stop.signal;
10254
- let chain2 = Promise.resolve();
10255
- const toGuest = (bytes) => {
10256
- chain2 = chain2.then(() => ctx.sink.write(bytes, stop));
10257
- return chain2;
10848
+ };
10849
+ async function writeAll2(socket, bytes, signal) {
10850
+ for (let at = 0; at < bytes.length; ) at += await socket.write(bytes.subarray(at), signal);
10851
+ }
10852
+ async function* closing(chunks2, socket) {
10853
+ try {
10854
+ yield* chunks2;
10855
+ } catch (err) {
10856
+ if (typeof err?.code === "string") throw err;
10857
+ throw errno("EPROTO", err instanceof Error ? err.message : String(err));
10858
+ } finally {
10859
+ socket.close();
10860
+ }
10861
+ }
10862
+ var LoopbackWebSocket = class {
10863
+ protocol;
10864
+ messages;
10865
+ closed;
10866
+ pending = 0;
10867
+ writes = Promise.resolve();
10868
+ sentClose = false;
10869
+ stop = new AbortController();
10870
+ socket;
10871
+ settle;
10872
+ unread = 0;
10873
+ room;
10874
+ constructor(socket, incoming, protocol) {
10875
+ this.socket = socket;
10876
+ this.protocol = protocol;
10877
+ this.closed = new Promise((resolve2) => {
10878
+ this.settle = resolve2;
10879
+ });
10880
+ this.stop.signal.addEventListener("abort", () => this.room?.(), { once: true });
10881
+ const queue = [];
10882
+ let wake;
10883
+ let ended = false;
10884
+ void this.pump(new WsReader(incoming, void 0, true, true), (message3) => {
10885
+ queue.push(message3);
10886
+ this.unread += message3.length;
10887
+ wake?.();
10888
+ }).finally(() => {
10889
+ ended = true;
10890
+ wake?.();
10891
+ });
10892
+ const taken = (message3) => {
10893
+ this.unread -= message3.length;
10894
+ const room = this.room;
10895
+ this.room = void 0;
10896
+ room?.();
10258
10897
  };
10259
- const done4 = new AbortController();
10260
- const reading = AbortSignal.any([stop, done4.signal, hangup]);
10261
- const stopping = () => socket.close(1001, "the proxy is stopping");
10262
- stop.addEventListener("abort", stopping, { once: true });
10263
- let guestDone = false;
10264
- let closeSent = false;
10265
- const outbound2 = (async () => {
10266
- for await (const data of socket.messages) {
10267
- if (guestDone) continue;
10268
- const text = typeof data === "string";
10269
- await toGuest(
10270
- frame(text ? OP_TEXT : OP_BINARY, text ? new TextEncoder().encode(data) : data)
10271
- );
10898
+ this.messages = {
10899
+ [Symbol.asyncIterator]: async function* () {
10900
+ for (; ; ) {
10901
+ const next2 = queue.shift();
10902
+ if (next2 !== void 0) {
10903
+ taken(next2);
10904
+ yield next2;
10905
+ continue;
10906
+ }
10907
+ if (ended) return;
10908
+ await new Promise((resolve2) => {
10909
+ wake = resolve2;
10910
+ });
10911
+ wake = void 0;
10912
+ }
10272
10913
  }
10273
- const closed = await socket.closed;
10274
- if (guestDone) return;
10275
- closeSent = true;
10276
- await toGuest(closeFrame(wireCode(closed.code), closed.reason));
10277
- AbortSignal.timeout(this.limits.closeWaitMs).addEventListener("abort", () => done4.abort(), {
10278
- once: true
10914
+ };
10915
+ }
10916
+ async space() {
10917
+ while (this.unread > MAX_UNREAD && !this.stop.signal.aborted) {
10918
+ await new Promise((resolve2) => {
10919
+ this.room = resolve2;
10279
10920
  });
10280
- })().catch(() => done4.abort());
10281
- const reader = new WsReader(ctx.incoming, MESSAGE_LIMIT, true);
10921
+ }
10922
+ }
10923
+ get buffered() {
10924
+ return this.pending;
10925
+ }
10926
+ write(bytes) {
10927
+ this.pending += bytes.length;
10928
+ const previous = this.writes;
10929
+ this.writes = (async () => {
10930
+ await previous;
10931
+ try {
10932
+ await writeAll2(this.socket, bytes, this.stop.signal);
10933
+ } catch {
10934
+ this.stop.abort();
10935
+ }
10936
+ this.pending -= bytes.length;
10937
+ })();
10938
+ }
10939
+ async pump(reader, deliver) {
10940
+ let close = { code: 1006, reason: "" };
10282
10941
  try {
10283
10942
  for (; ; ) {
10284
- const event = await reader.next(reading);
10943
+ await this.space();
10944
+ const event = await reader.next(this.stop.signal);
10285
10945
  if (!event) break;
10286
- if (event.kind === "ping") {
10287
- await toGuest(pongFrame(event.payload));
10288
- continue;
10289
- }
10290
- if (event.kind === "close") {
10291
- guestDone = true;
10292
- if (closeSent) break;
10293
- const code = wireCode(event.code);
10294
- socket.close(code, event.reason);
10295
- const reply = await Promise.race([
10296
- socket.closed,
10297
- quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
10298
- ]);
10299
- await toGuest(closeFrame(wireCode(reply.code), reply.reason));
10946
+ if (event.kind === "text") deliver(event.text);
10947
+ else if (event.kind === "binary") deliver(event.bytes);
10948
+ else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
10949
+ else {
10950
+ close = { code: event.code, reason: event.reason };
10951
+ if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
10300
10952
  break;
10301
10953
  }
10302
- while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
10303
- socket.send(event.kind === "text" ? event.text : event.bytes);
10304
- }
10305
- } catch (e) {
10306
- if (e instanceof WsError) {
10307
- guestDone = true;
10308
- socket.close(e.code, e.message);
10309
- await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
10310
10954
  }
10955
+ } catch (err) {
10956
+ if (err instanceof WsError) this.sendClose(err.code, err.message);
10957
+ close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
10311
10958
  } finally {
10312
- guestDone = true;
10313
- socket.close(1e3, "the client went away");
10314
- stop.removeEventListener("abort", stopping);
10315
- await Promise.race([outbound2, quietly(this.limits.closeWaitMs)]);
10316
- }
10959
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
10960
+ await this.writes;
10961
+ clearTimeout(late);
10962
+ this.socket.close();
10963
+ this.settle(close);
10964
+ }
10965
+ }
10966
+ sendClose(code, reason) {
10967
+ if (this.sentClose) return;
10968
+ this.sentClose = true;
10969
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
10970
+ void this.closed.then(() => clearTimeout(late));
10971
+ const bytes = new TextEncoder().encode(reason).subarray(0, 123);
10972
+ const payload = new Uint8Array(2 + bytes.length);
10973
+ new DataView(payload.buffer).setUint16(0, sendableCode(code) ? code : CLOSE.error);
10974
+ payload.set(bytes, 2);
10975
+ this.write(clientFrame(OP_CLOSE, payload));
10976
+ }
10977
+ send(data) {
10978
+ if (this.sentClose) throw errno("EPIPE", "the websocket is closing");
10979
+ const text = typeof data === "string";
10980
+ const payload = text ? new TextEncoder().encode(data) : data;
10981
+ this.write(clientFrame(text ? OP_TEXT : OP_BINARY, payload));
10982
+ }
10983
+ close(code = 1e3, reason = "") {
10984
+ this.sendClose(code, reason);
10317
10985
  }
10318
10986
  };
10319
10987
 
@@ -10990,7 +11658,7 @@ var WS_OPS = [
10990
11658
  "net-ws-send",
10991
11659
  "net-ws-close"
10992
11660
  ];
10993
- var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
11661
+ var TOKEN4 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
10994
11662
  function message(e) {
10995
11663
  return e instanceof Error ? e.message : String(e);
10996
11664
  }
@@ -10999,16 +11667,18 @@ function errnoOf(e) {
10999
11667
  const code = e?.code;
11000
11668
  return typeof code === "string" && ERRNO.test(code) ? code : "ECONNREFUSED";
11001
11669
  }
11002
- function fail(errno, text) {
11003
- return { ok: false, errno, message: text };
11670
+ function fail(errno2, text) {
11671
+ return { ok: false, errno: errno2, message: text };
11004
11672
  }
11005
11673
  var WsHandles = class {
11006
11674
  transport;
11007
11675
  open = /* @__PURE__ */ new Map();
11008
11676
  next;
11009
- constructor(transport, next2) {
11677
+ local;
11678
+ constructor(transport, next2, local) {
11010
11679
  this.transport = transport;
11011
11680
  this.next = next2;
11681
+ this.local = local;
11012
11682
  }
11013
11683
  owns(handle) {
11014
11684
  return this.open.has(handle);
@@ -11037,16 +11707,18 @@ var WsHandles = class {
11037
11707
  return { ok: true, kind: "json", json: handle };
11038
11708
  }
11039
11709
  async connect(handle, req, entry) {
11040
- const { transport } = this;
11041
- if (!transport.traits.websocket || !transport.websocket) return fail("ENOTSUP", NO_WEBSOCKET2);
11710
+ const { transport, local } = this;
11711
+ const port = local?.port(req.url);
11712
+ const open = { protocols: req.protocols, headers: req.headers, signal: entry.abort.signal };
11713
+ if (port === void 0 && (!transport.traits.websocket || !transport.websocket)) {
11714
+ return fail("ENOTSUP", NO_WEBSOCKET2);
11715
+ }
11042
11716
  let socket;
11043
11717
  try {
11044
- socket = await transport.websocket({
11718
+ socket = port === void 0 ? await transport.websocket({
11045
11719
  url: toHostLoopback(req.url),
11046
- protocols: req.protocols,
11047
- headers: req.headers,
11048
- signal: entry.abort.signal
11049
- });
11720
+ ...open
11721
+ }) : await local.websocket({ url: req.url, ...open }, port);
11050
11722
  } catch (e) {
11051
11723
  return fail(errnoOf(e), message(e));
11052
11724
  }
@@ -11054,7 +11726,7 @@ var WsHandles = class {
11054
11726
  socket.close(1001, "the program went away");
11055
11727
  return fail("EBADF", `no websocket ${handle}`);
11056
11728
  }
11057
- if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN3.test(socket.protocol))) {
11729
+ if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN4.test(socket.protocol))) {
11058
11730
  socket.close(1002, "subprotocol not offered");
11059
11731
  return fail("EPROTO", "the far end chose a subprotocol the program did not offer");
11060
11732
  }
@@ -11117,6 +11789,10 @@ var EMPTY = new Uint8Array(0);
11117
11789
  function message2(e) {
11118
11790
  return e instanceof Error ? e.message : String(e);
11119
11791
  }
11792
+ function codeOf2(e) {
11793
+ const code = e?.code;
11794
+ return typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "EIO";
11795
+ }
11120
11796
  async function* once(bytes) {
11121
11797
  yield bytes;
11122
11798
  }
@@ -11126,10 +11802,12 @@ var HttpHandles = class {
11126
11802
  nextHandle = 0;
11127
11803
  ws;
11128
11804
  owner;
11129
- constructor(transport, owner) {
11805
+ local;
11806
+ constructor(transport, owner, local) {
11130
11807
  this.transport = transport;
11131
11808
  this.owner = owner;
11132
- this.ws = new WsHandles(transport, () => ++this.nextHandle);
11809
+ this.local = local;
11810
+ this.ws = new WsHandles(transport, () => ++this.nextHandle, local);
11133
11811
  }
11134
11812
  syscall(req) {
11135
11813
  if (req.op.startsWith("net-ws-")) return this.ws.syscall(req);
@@ -11137,10 +11815,13 @@ var HttpHandles = class {
11137
11815
  if (req.op === "net-open") return Promise.resolve(this.opened(req));
11138
11816
  if (req.op === "net-head") return this.head(req.handle);
11139
11817
  if (req.op === "net-read") return this.read(req.handle, req.max);
11140
- if (req.op === "net-traits") return Promise.resolve(this.traits());
11818
+ if (req.op === "net-traits") return Promise.resolve(this.traits(req.url));
11141
11819
  return this.close(req.handle);
11142
11820
  }
11143
- traits() {
11821
+ traits(url) {
11822
+ if (url !== void 0 && this.local?.port(url) !== void 0) {
11823
+ return { ok: true, kind: "json", json: LOOPBACK_TRAITS };
11824
+ }
11144
11825
  const { unavailable, crossOrigin = "any", ...rest2 } = this.transport.traits;
11145
11826
  if (unavailable) return { ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT };
11146
11827
  return { ok: true, kind: "json", json: { ...rest2, crossOrigin } };
@@ -11171,28 +11852,30 @@ var HttpHandles = class {
11171
11852
  return { handle, head: entry.head };
11172
11853
  }
11173
11854
  async respond(handle, req, entry) {
11855
+ const port = this.local?.port(req.url);
11174
11856
  try {
11175
- const response = await this.transport.fetch(
11176
- {
11177
- url: req.url,
11178
- method: req.method,
11179
- headers: req.headers,
11180
- ...req.body ? { body: req.body } : {},
11181
- signal: entry.abort.signal
11182
- },
11183
- this.owner
11184
- );
11857
+ const target = port === void 0 ? toHostLoopback(req.url) : req.url;
11858
+ const request = {
11859
+ url: target,
11860
+ method: req.method,
11861
+ headers: req.headers,
11862
+ ...req.body ? { body: req.body } : {},
11863
+ signal: entry.abort.signal
11864
+ };
11865
+ const response = port === void 0 ? await this.transport.fetch(request, this.owner) : await this.local.fetch(request, port);
11185
11866
  entry.body = response.body[Symbol.asyncIterator]();
11186
11867
  entry.cancel = () => response.cancel();
11187
11868
  if (!this.open.has(handle)) await entry.cancel().catch(() => void 0);
11188
- const head = this.headOf(handle, response.url ?? req.url, response);
11869
+ const shown = response.url && response.url !== target ? response.url : req.url;
11870
+ const head = this.headOf(handle, shown, response);
11189
11871
  return response.redirected ? { ...head, json: { ...head.json, redirected: true } } : head;
11190
11872
  } catch (e) {
11191
11873
  const text = message2(e);
11874
+ if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
11192
11875
  const status = e.status;
11193
11876
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
11194
- const errno = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
11195
- return { ok: false, errno, message: text };
11877
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
11878
+ return { ok: false, errno: errno2, message: text };
11196
11879
  }
11197
11880
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
11198
11881
  return this.headOf(handle, req.url, {
@@ -12133,7 +12816,9 @@ async function socketSyscall(req, proc) {
12133
12816
  case "sock-accept":
12134
12817
  return ok(await accept(proc, req.fd, req.nonblock));
12135
12818
  case "sock-connect": {
12136
- const dialing = socketAt(fds, req.fd).connect(req.addr, true, proc.cred?.());
12819
+ const socket = socketAt(fds, req.fd);
12820
+ const stamp = net.facadeAt(req.addr) && await proc.stamp?.();
12821
+ const dialing = socket.connect(req.addr, true, proc.cred?.(), stamp || void 0);
12137
12822
  if (req.nonblock) {
12138
12823
  dialing?.catch(() => void 0);
12139
12824
  throw new KernelError("EINPROGRESS");
@@ -13312,6 +13997,9 @@ var WasmProcess = class {
13312
13997
  if (channel) channel.stamp = stamp;
13313
13998
  }
13314
13999
  }
14000
+ connectorStamp() {
14001
+ return this.sealedCaller();
14002
+ }
13315
14003
  async sealedCaller() {
13316
14004
  const shown = await this.options.identity?.();
13317
14005
  const broker = this.options.sealed;
@@ -13650,7 +14338,8 @@ var WasmProcess = class {
13650
14338
  net: this.net,
13651
14339
  blocking: () => this.blockingSignal(cancelled),
13652
14340
  resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking),
13653
- cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid })
14341
+ cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid }),
14342
+ stamp: () => this.connectorStamp()
13654
14343
  });
13655
14344
  }
13656
14345
  tty(fd) {
@@ -15022,7 +15711,7 @@ function scrubberOf(secrets) {
15022
15711
  const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
15023
15712
  return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
15024
15713
  }
15025
- async function writeAll2(file, bytes) {
15714
+ async function writeAll3(file, bytes) {
15026
15715
  const write = file.write;
15027
15716
  for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
15028
15717
  }
@@ -15056,7 +15745,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15056
15745
  const later = (bytes) => {
15057
15746
  if (bytes.length === 0) return;
15058
15747
  pending += 1;
15059
- tail = tail.then(() => writeAll2(file, bytes)).catch((err) => {
15748
+ tail = tail.then(() => writeAll3(file, bytes)).catch((err) => {
15060
15749
  if (failed3 === void 0) failed3 = err;
15061
15750
  }).finally(() => {
15062
15751
  pending -= 1;
@@ -15687,8 +16376,8 @@ var FSA_CAPABILITIES = {
15687
16376
  ranges: true,
15688
16377
  sessions: true
15689
16378
  };
15690
- var codeOf2 = (err) => err?.code;
15691
- var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
16379
+ var codeOf3 = (err) => err?.code;
16380
+ var denied = (err) => codeOf3(err) === "EACCES" || err?.name === "NotAllowedError";
15692
16381
  function attrOf(st) {
15693
16382
  return {
15694
16383
  kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
@@ -15710,7 +16399,7 @@ function kernelFsHandlers(fs) {
15710
16399
  try {
15711
16400
  return await fs.stat(path);
15712
16401
  } catch (err) {
15713
- if (codeOf2(err) === "ENOENT") return void 0;
16402
+ if (codeOf3(err) === "ENOENT") return void 0;
15714
16403
  throw err;
15715
16404
  }
15716
16405
  }
@@ -15903,7 +16592,7 @@ function parseFstab(text) {
15903
16592
  });
15904
16593
  return lines2;
15905
16594
  }
15906
- function codeOf3(err) {
16595
+ function codeOf4(err) {
15907
16596
  const code = err?.code;
15908
16597
  return typeof code === "string" ? code : "EIO";
15909
16598
  }
@@ -15941,18 +16630,18 @@ async function mountLine({ line, spec, invalid }, mount, signal, retries, discar
15941
16630
  break;
15942
16631
  } catch (err) {
15943
16632
  failed3 = err;
15944
- if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
16633
+ if (FINAL.has(codeOf4(err)) || attempt === retries.length) break;
15945
16634
  await wait(retries[attempt], signal);
15946
16635
  }
15947
16636
  }
15948
- const code = codeOf3(failed3);
16637
+ const code = codeOf4(failed3);
15949
16638
  return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
15950
16639
  }
15951
16640
 
15952
16641
  // src/mount/hostfs.ts
15953
16642
  var HOSTFS_IO = 16 * 1024 * 1024;
15954
- var codeOf4 = (err) => err?.code;
15955
- var unreachable = (err) => codeOf4(err) === "ENOTCONN";
16643
+ var codeOf5 = (err) => err?.code;
16644
+ var unreachable = (err) => codeOf5(err) === "ENOTCONN";
15956
16645
  function rel(path) {
15957
16646
  return path.replace(/^\/+/, "");
15958
16647
  }
@@ -16012,9 +16701,9 @@ var HostfsClient = class {
16012
16701
  throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
16013
16702
  return this.send(path, init, false, true);
16014
16703
  }
16015
- const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
16704
+ const errno2 = response.headers.get("X-Hostfs-Errno") ?? "EIO";
16016
16705
  const body = await response.json().catch(() => ({}));
16017
- throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
16706
+ throw fsError2(errno2, typeof body.message === "string" ? body.message : errno2);
16018
16707
  }
16019
16708
  async call(op, body = {}, handle = false) {
16020
16709
  const response = await this.send(
@@ -16129,7 +16818,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
16129
16818
  try {
16130
16819
  return { opened: await opening2(), restore: void 0 };
16131
16820
  } catch (err) {
16132
- if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
16821
+ if (!flags2.write || codeOf5(err) !== "EACCES") throw err;
16133
16822
  const was = await client.call("stat", { path: rel(path) });
16134
16823
  const mode = was.mode ?? 0;
16135
16824
  if (mode & 128) throw err;
@@ -16554,7 +17243,7 @@ var PortTransport = class {
16554
17243
  }
16555
17244
  };
16556
17245
  var errorOf = (err) => ({
16557
- code: typeof codeOf4(err) === "string" ? codeOf4(err) : "EIO",
17246
+ code: typeof codeOf5(err) === "string" ? codeOf5(err) : "EIO",
16558
17247
  message: err?.message ?? String(err)
16559
17248
  });
16560
17249
  function serveHostfsPort(port, transport) {
@@ -17145,8 +17834,8 @@ var RULE = new RegExp(
17145
17834
  var TERM = /^[A-Za-z0-9._+-]+$/;
17146
17835
  var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
17147
17836
  var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
17148
- function failed2(errno, error) {
17149
- return { error, errno };
17837
+ function failed2(errno2, error) {
17838
+ return { error, errno: errno2 };
17150
17839
  }
17151
17840
  function unescaped(double) {
17152
17841
  return double.replace(/\\(["\\$`])/g, "$1");
@@ -17757,6 +18446,7 @@ var Launcher = class {
17757
18446
  );
17758
18447
  net = new LoopbackNet();
17759
18448
  cdp;
18449
+ facades;
17760
18450
  ca;
17761
18451
  transport;
17762
18452
  uplink;
@@ -17831,10 +18521,16 @@ var Launcher = class {
17831
18521
  this.modulesDir = options.modules ?? "/node_modules";
17832
18522
  this.appended = appendedRoots(options.commands);
17833
18523
  this.cdp = new CdpHosts(options.cdp);
18524
+ this.facades = new FacadeHosts(options.facades);
17834
18525
  const hostname = options.hostname ?? DEFAULT_HOSTNAME;
17835
18526
  if (!isHostname(hostname)) throw new Error(`not a host name: ${String(hostname)}`);
17836
18527
  this.hostname = hostname;
17837
- this.env = { ...networkEnv(hostname), SLICC_CDP_URL: this.cdp.url, ...options.env };
18528
+ this.env = {
18529
+ ...networkEnv(hostname),
18530
+ SLICC_CDP_URL: this.cdp.url,
18531
+ ...this.facades.env(),
18532
+ ...options.env
18533
+ };
17838
18534
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
17839
18535
  this.transport = replayingHops(options.transport ?? missingTransport());
17840
18536
  this.announcedTraits = JSON.stringify(this.transport.traits);
@@ -17849,6 +18545,7 @@ var Launcher = class {
17849
18545
  ca: this.ca
17850
18546
  });
17851
18547
  enableCdp(this.net, this.cdp);
18548
+ this.facades.enable(this.net);
17852
18549
  const fs = withCommandDirs(
17853
18550
  this.base,
17854
18551
  async () => new Set((await this.commands()).keys()),
@@ -18432,6 +19129,12 @@ var Launcher = class {
18432
19129
  groupSession(pgid) {
18433
19130
  return this.jobs.sessionOf(pgid);
18434
19131
  }
19132
+ loopbackFor(pid) {
19133
+ return new KernelLoopback(this.net, () => {
19134
+ const cred = this.credOf(pid);
19135
+ return { pid, uid: cred.euid, gid: cred.egid };
19136
+ });
19137
+ }
18435
19138
  treeScrubs(req) {
18436
19139
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18437
19140
  return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
@@ -18509,7 +19212,7 @@ var Launcher = class {
18509
19212
  jobs: this.jobs,
18510
19213
  ptys: this.ptys,
18511
19214
  net: this.net,
18512
- http: new HttpHandles(this.transport, pid),
19215
+ http: new HttpHandles(this.transport, pid, this.loopbackFor(pid)),
18513
19216
  locks: this.locks,
18514
19217
  resolver: this.resolver,
18515
19218
  onReap: (child) => this.reaped(child, pid),
@@ -18782,6 +19485,9 @@ var Launcher = class {
18782
19485
  }
18783
19486
  };
18784
19487
  }
19488
+ connectorStamp(pid) {
19489
+ return this.sealed.stamp(this.identityOf(pid), this.credOf(pid), this.tagOf(pid));
19490
+ }
18785
19491
  identityOf(pid) {
18786
19492
  const parent = this.jobs.shownParent(pid);
18787
19493
  const alive = parent !== void 0 && this.running(parent);
@@ -19688,7 +20394,7 @@ import { connect as tls } from "node:tls";
19688
20394
  var PROBE_MS = 3e3;
19689
20395
  var PASSED = /* @__PURE__ */ new Set(["ECONNREFUSED", "EHOSTUNREACH", "ENETUNREACH", "ETIMEDOUT", "ECONNRESET"]);
19690
20396
  var NO_NAME = /* @__PURE__ */ new Set(["ENOTFOUND", "EAI_AGAIN", "EAI_NONAME", "EAI_FAIL", "ENODATA"]);
19691
- function codeOf5(e) {
20397
+ function codeOf6(e) {
19692
20398
  const code = e?.code;
19693
20399
  return typeof code === "string" ? code : void 0;
19694
20400
  }
@@ -19703,7 +20409,7 @@ function probe(secure, host, port, ms) {
19703
20409
  const timer = setTimeout(() => done4("ETIMEDOUT"), ms);
19704
20410
  socket.once(secure ? "secureConnect" : "connect", () => done4("EPROTO"));
19705
20411
  socket.once("error", (e) => {
19706
- const code = codeOf5(e) ?? "";
20412
+ const code = codeOf6(e) ?? "";
19707
20413
  done4(PASSED.has(code) ? code : "EPROTO");
19708
20414
  });
19709
20415
  });
@@ -19716,7 +20422,7 @@ async function diagnose(url, ms = PROBE_MS) {
19716
20422
  try {
19717
20423
  await lookup2(host);
19718
20424
  } catch (e) {
19719
- return NO_NAME.has(codeOf5(e) ?? "") ? "EAI_NONAME" : void 0;
20425
+ return NO_NAME.has(codeOf6(e) ?? "") ? "EAI_NONAME" : void 0;
19720
20426
  }
19721
20427
  return probe(secure, host, port, ms);
19722
20428
  }
@@ -19732,7 +20438,7 @@ function diagnosing(transport, check = diagnose) {
19732
20438
  try {
19733
20439
  return await open(request);
19734
20440
  } catch (e) {
19735
- if (request.signal.aborted || !(e instanceof Error) || codeOf5(e)) throw e;
20441
+ if (request.signal.aborted || !(e instanceof Error) || codeOf6(e)) throw e;
19736
20442
  const code = await check(request.url).catch(() => void 0);
19737
20443
  throw code ? Object.assign(e, { code }) : e;
19738
20444
  }
@@ -19990,7 +20696,7 @@ function dialStream(port, onClose) {
19990
20696
  }
19991
20697
 
19992
20698
  // src/client/loopback-fetch.ts
19993
- var OWN_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding"]);
20699
+ var OWN_HEADERS2 = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding"]);
19994
20700
  var encoder8 = new TextEncoder();
19995
20701
  var latin12 = new TextDecoder("latin1");
19996
20702
  var ByteSource = class {
@@ -20037,7 +20743,7 @@ function indexOf(haystack, needle) {
20037
20743
  }
20038
20744
  return -1;
20039
20745
  }
20040
- function parseHead(bytes) {
20746
+ function parseHead2(bytes) {
20041
20747
  const [statusLine = "", ...lines2] = latin12.decode(bytes).split("\r\n");
20042
20748
  const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
20043
20749
  if (!match) throw errorWithCode(`EPROTO: not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
@@ -20051,7 +20757,7 @@ function parseHead(bytes) {
20051
20757
  function header3(head, name2) {
20052
20758
  return head.headers.find(([key]) => key.toLowerCase() === name2)?.[1];
20053
20759
  }
20054
- async function* chunked(source) {
20760
+ async function* chunked2(source) {
20055
20761
  for (; ; ) {
20056
20762
  const line = await source.until("\r\n");
20057
20763
  if (!line) throw errorWithCode("EPROTO: the chunked body ended early");
@@ -20063,7 +20769,7 @@ async function* chunked(source) {
20063
20769
  yield data;
20064
20770
  }
20065
20771
  }
20066
- async function* counted(source, length2) {
20772
+ async function* counted2(source, length2) {
20067
20773
  let left = length2;
20068
20774
  while (left > 0) {
20069
20775
  const data = await source.some(left);
@@ -20072,19 +20778,19 @@ async function* counted(source, length2) {
20072
20778
  yield data;
20073
20779
  }
20074
20780
  }
20075
- async function* untilClose(source) {
20781
+ async function* untilClose2(source) {
20076
20782
  for (let data = await source.some(65536); data; data = await source.some(65536)) yield data;
20077
20783
  }
20078
- function bodyOf(source, head, method) {
20784
+ function bodyOf2(source, head, method) {
20079
20785
  if (method === "HEAD" || head.status === 204 || head.status === 304) return void 0;
20080
- if (header3(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked(source);
20786
+ if (header3(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked2(source);
20081
20787
  const length2 = header3(head, "content-length");
20082
- if (length2 === void 0) return untilClose(source);
20788
+ if (length2 === void 0) return untilClose2(source);
20083
20789
  const size = Number(length2);
20084
20790
  if (!/^\d+$/.test(length2.trim()) || !Number.isSafeInteger(size)) {
20085
20791
  throw errorWithCode(`EPROTO: a malformed Content-Length: ${length2}`);
20086
20792
  }
20087
- return counted(source, size);
20793
+ return counted2(source, size);
20088
20794
  }
20089
20795
  function stream(chunks2, socket, signal) {
20090
20796
  return new ReadableStream({
@@ -20113,7 +20819,7 @@ async function requestBytes(request) {
20113
20819
  const body = request.body ? new Uint8Array(await request.arrayBuffer()) : void 0;
20114
20820
  const lines2 = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
20115
20821
  request.headers.forEach((value, name2) => {
20116
- if (!OWN_HEADERS.has(name2)) lines2.push(`${name2}: ${value}`);
20822
+ if (!OWN_HEADERS2.has(name2)) lines2.push(`${name2}: ${value}`);
20117
20823
  });
20118
20824
  if (body) lines2.push(`Content-Length: ${body.length}`);
20119
20825
  lines2.push("Connection: close", "", "");
@@ -20141,9 +20847,9 @@ async function loopbackFetch(dial, input, options) {
20141
20847
  do {
20142
20848
  const raw = await source.until("\r\n\r\n");
20143
20849
  if (!raw) throw errorWithCode("ECONNRESET: the connection closed before a response");
20144
- head = parseHead(raw);
20850
+ head = parseHead2(raw);
20145
20851
  } while (head.status >= 100 && head.status < 200);
20146
- const chunks2 = bodyOf(source, head, request.method);
20852
+ const chunks2 = bodyOf2(source, head, request.method);
20147
20853
  if (!chunks2) socket.close();
20148
20854
  return new Response(chunks2 ? stream(chunks2, socket, options.signal) : null, {
20149
20855
  status: head.status,
@@ -20211,6 +20917,8 @@ var Channel = class {
20211
20917
  watching = /* @__PURE__ */ new Map();
20212
20918
  cdpHooks = /* @__PURE__ */ new Map();
20213
20919
  cdpOpen = /* @__PURE__ */ new Set();
20920
+ facadeHooks = /* @__PURE__ */ new Map();
20921
+ facadeOpen = /* @__PURE__ */ new Set();
20214
20922
  protocol = PROTOCOL;
20215
20923
  nextId = 0;
20216
20924
  port;
@@ -20233,6 +20941,8 @@ var Channel = class {
20233
20941
  this.remote?.fail(error);
20234
20942
  for (const close of [...this.cdpOpen]) close();
20235
20943
  this.cdpOpen.clear();
20944
+ for (const close of [...this.facadeOpen]) close();
20945
+ this.facadeOpen.clear();
20236
20946
  for (const call of this.pending.values()) call.reject(error);
20237
20947
  this.pending.clear();
20238
20948
  this.release();
@@ -20262,6 +20972,10 @@ var Channel = class {
20262
20972
  this.openCdp(data);
20263
20973
  return;
20264
20974
  }
20975
+ if (data.facadeOpen && data.port) {
20976
+ this.openFacade(data);
20977
+ return;
20978
+ }
20265
20979
  if (data.watch !== void 0) {
20266
20980
  this.watching.get(data.watch)?.(data.change);
20267
20981
  return;
@@ -20304,7 +21018,36 @@ var Channel = class {
20304
21018
  const close = serveCdpPort(port, hook, request, () => this.cdpOpen.delete(close));
20305
21019
  this.cdpOpen.add(close);
20306
21020
  }
21021
+ openFacade({ facadeOpen: { registration, ...request }, port }) {
21022
+ const hook = registration === void 0 ? void 0 : this.facadeHooks.get(registration);
21023
+ if (!hook || this.failure) {
21024
+ port.postMessage({ error: "this client no longer serves the facade" });
21025
+ port.close();
21026
+ return;
21027
+ }
21028
+ const close = serveFacadePort(port, hook, request, () => this.facadeOpen.delete(close));
21029
+ this.facadeOpen.add(close);
21030
+ }
20307
21031
  };
21032
+ async function serveFacadeOn(channel, name2, hook) {
21033
+ if (typeof hook !== "function") throw new TypeError("serveFacade: hook must be a function");
21034
+ await since(channel, 11, "facade serving", async () => {
21035
+ });
21036
+ const { id: id2, done: done4 } = channel.request({ op: "serve-facade", name: name2 });
21037
+ channel.facadeHooks.set(id2, hook);
21038
+ try {
21039
+ await done4;
21040
+ } catch (error) {
21041
+ channel.facadeHooks.delete(id2);
21042
+ throw error;
21043
+ }
21044
+ return {
21045
+ close: () => {
21046
+ if (!channel.facadeHooks.delete(id2)) return;
21047
+ void channel.call({ op: "unserve-facade", registration: id2 }).catch(() => void 0);
21048
+ }
21049
+ };
21050
+ }
20308
21051
  async function serveCdpOn(channel, hook, options) {
20309
21052
  await since(channel, 5, "CDP serving", async () => {
20310
21053
  });
@@ -20538,6 +21281,7 @@ async function attachKernel(port, options = {}) {
20538
21281
  loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
20539
21282
  fs: fsOn(channel),
20540
21283
  serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
21284
+ serveFacade: (name2, hook) => serveFacadeOn(channel, name2, hook),
20541
21285
  onTraits(listener) {
20542
21286
  channel.traitsListeners.add(listener);
20543
21287
  return () => channel.traitsListeners.delete(listener);
@@ -20654,6 +21398,7 @@ async function createNodeKernel(options = {}) {
20654
21398
  ...options.hostfs ? { hostfs: options.hostfs } : {},
20655
21399
  ...hostfsFetchOf(options),
20656
21400
  ...options.cdp ? { cdp: options.cdp } : {},
21401
+ ...options.facades ? { facades: options.facades } : {},
20657
21402
  ...options.hostname !== void 0 ? { hostname: options.hostname } : {},
20658
21403
  users: options.users === void 0 ? DEFAULT_USERS : options.users,
20659
21404
  ...options.onDenial ? { onDenial: options.onDenial } : {},