@ai-ecoverse/slicc-kernel 1.90.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.
@@ -2299,8 +2299,8 @@ async function access(fs, cred, path, want, options = {}) {
2299
2299
  if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
2300
2300
  return { ...walked, missing: true };
2301
2301
  }
2302
- function refused(path, errno = "EACCES") {
2303
- return fsError(errno, path);
2302
+ function refused(path, errno2 = "EACCES") {
2303
+ return fsError(errno2, path);
2304
2304
  }
2305
2305
 
2306
2306
  // src/fs/dac.ts
@@ -2360,9 +2360,9 @@ var Created = class {
2360
2360
  function linked(g, options) {
2361
2361
  return g.links === void 0 ? options : { ...options, links: g.links };
2362
2362
  }
2363
- function deny(g, op, path, errno = "EACCES") {
2363
+ function deny(g, op, path, errno2 = "EACCES") {
2364
2364
  const tag = g.tag === void 0 ? {} : { tag: g.tag };
2365
- g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
2365
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno: errno2, ...tag });
2366
2366
  }
2367
2367
  var TTY_MODE = 400;
2368
2368
  function ttyOpenable(g, tty) {
@@ -3029,7 +3029,7 @@ var WASI_SIGNAL_TO_POSIX = Object.fromEntries(
3029
3029
 
3030
3030
  // src/process/wasi-errno.ts
3031
3031
  var WASI_ERRNO = {
3032
- ...Object.fromEntries(Object.entries(E).map(([name2, errno]) => [`E${name2}`, errno])),
3032
+ ...Object.fromEntries(Object.entries(E).map(([name2, errno2]) => [`E${name2}`, errno2])),
3033
3033
  EWOULDBLOCK: E.AGAIN,
3034
3034
  ENOTSUP: 138,
3035
3035
  EOPNOTSUPP: 138,
@@ -3126,7 +3126,7 @@ function dialError(e, timedOut, closed) {
3126
3126
  if (timedOut) return new KernelError("ETIMEDOUT");
3127
3127
  if (closed) return new KernelError("ECONNABORTED");
3128
3128
  const code = e?.code;
3129
- const known = DIAL_ERRORS.find((errno) => errno === code);
3129
+ const known = DIAL_ERRORS.find((errno2) => errno2 === code);
3130
3130
  return new KernelError(known ?? "EHOSTUNREACH");
3131
3131
  }
3132
3132
  function isLoopback(host) {
@@ -3793,11 +3793,11 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3793
3793
  };
3794
3794
  const sockets = /* @__PURE__ */ new Map();
3795
3795
  const credit = /* @__PURE__ */ new Map();
3796
- const refill = (nid, n, closing = false) => {
3796
+ const refill = (nid, n, closing2 = false) => {
3797
3797
  const flow = credit.get(nid);
3798
3798
  if (!flow) return;
3799
3799
  flow.inflight -= n;
3800
- if (closing && !flow.closing) {
3800
+ if (closing2 && !flow.closing) {
3801
3801
  flow.closing = true;
3802
3802
  flow.deadline();
3803
3803
  }
@@ -4928,6 +4928,33 @@ function toHostLoopback(href) {
4928
4928
  url.hostname = "127.0.0.1";
4929
4929
  return url.href;
4930
4930
  }
4931
+ var LOOPBACK_V4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
4932
+ var LOOPBACK_V6 = /* @__PURE__ */ new Set(["::1", "::", "0:0:0:0:0:0:0:1"]);
4933
+ var MAPPED_V4 = /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/;
4934
+ var KERNEL_PORT = /^(\d{1,5})\.kernel\.localhost$/;
4935
+ var SCHEMES = {
4936
+ "http:": 80,
4937
+ "ws:": 80,
4938
+ "https:": 443,
4939
+ "wss:": 443
4940
+ };
4941
+ function isKernelLoopbackHost(hostname) {
4942
+ const host = hostname.replace(/^\[(.*)\]$/, "$1").replace(/\.$/, "").toLowerCase();
4943
+ return host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0" || LOOPBACK_V4.test(host) || LOOPBACK_V6.has(host) || MAPPED_V4.test(host);
4944
+ }
4945
+ function kernelLoopbackPort(href) {
4946
+ let url;
4947
+ try {
4948
+ url = new URL(href);
4949
+ } catch {
4950
+ return void 0;
4951
+ }
4952
+ const fallback = SCHEMES[url.protocol];
4953
+ if (fallback === void 0 || !isKernelLoopbackHost(url.hostname)) return void 0;
4954
+ const named = KERNEL_PORT.exec(url.hostname.replace(/\.$/, "").toLowerCase())?.[1];
4955
+ const port = Number(named ?? (url.port || fallback));
4956
+ return port >= 1 && port <= 65535 ? port : void 0;
4957
+ }
4931
4958
  var DEFAULT_HOSTNAME = "slicc";
4932
4959
  var HOSTNAME_ADDRESS = "127.0.1.1";
4933
4960
  var LIBC_HOSTNAMES = ["emscripten", "wasmer.sh"];
@@ -5233,6 +5260,9 @@ async function acceptKey(headers) {
5233
5260
  }
5234
5261
  const key = field(headers, "sec-websocket-key") ?? "";
5235
5262
  if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
5263
+ return acceptFor(key);
5264
+ }
5265
+ async function acceptFor(key) {
5236
5266
  const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
5237
5267
  return btoa(String.fromCharCode(...new Uint8Array(digest)));
5238
5268
  }
@@ -5256,6 +5286,19 @@ function frame(opcode, payload) {
5256
5286
  out.set(payload, size);
5257
5287
  return out;
5258
5288
  }
5289
+ function clientFrame(opcode, payload) {
5290
+ const plain = frame(opcode, payload);
5291
+ const head = plain.length - payload.length;
5292
+ const mask = crypto.getRandomValues(new Uint8Array(4));
5293
+ const out = new Uint8Array(plain.length + 4);
5294
+ out.set(plain.subarray(0, head));
5295
+ out[1] = out[1] | 128;
5296
+ out.set(mask, head);
5297
+ for (let i = 0; i < payload.length; i++) {
5298
+ out[head + 4 + i] = payload[i] ^ mask[i & 3];
5299
+ }
5300
+ return out;
5301
+ }
5259
5302
  function closeFrame(code, reason = "") {
5260
5303
  let bytes = new TextEncoder().encode(reason);
5261
5304
  if (bytes.length > 123) {
@@ -5295,10 +5338,12 @@ var WsReader = class {
5295
5338
  continuing = false;
5296
5339
  binaryMessage = false;
5297
5340
  binary;
5298
- constructor(incoming, max = MAX_MESSAGE, binary) {
5341
+ fromServer;
5342
+ constructor(incoming, max = MAX_MESSAGE, binary, fromServer = false) {
5299
5343
  this.incoming = incoming;
5300
5344
  this.max = max;
5301
5345
  this.binary = binary;
5346
+ this.fromServer = fromServer;
5302
5347
  }
5303
5348
  async next(signal) {
5304
5349
  for (; ; ) {
@@ -5310,7 +5355,7 @@ var WsReader = class {
5310
5355
  const fin = (head[0] & 128) !== 0;
5311
5356
  const opcode = head[0] & 15;
5312
5357
  if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
5313
- if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
5358
+ const masked = this.masking(head[1]);
5314
5359
  const length2 = await this.length(head[1] & 127, signal);
5315
5360
  const control = opcode >= 8;
5316
5361
  if (control && (length2 > 125 || !fin)) {
@@ -5319,15 +5364,19 @@ var WsReader = class {
5319
5364
  if (!control && this.size + length2 > this.max) {
5320
5365
  throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
5321
5366
  }
5322
- const mask = new Uint8Array(4);
5323
- await fill(this.incoming, mask, signal);
5324
- const payload = new Uint8Array(length2);
5325
- await fill(this.incoming, payload, signal);
5326
- for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5367
+ const payload = await this.payload(length2, masked, signal);
5327
5368
  const event = this.take(opcode, fin, payload);
5328
5369
  if (event) return event;
5329
5370
  }
5330
5371
  }
5372
+ masking(second) {
5373
+ const masked = (second & 128) !== 0;
5374
+ if (masked !== this.fromServer) return masked;
5375
+ throw new WsError(
5376
+ CLOSE.protocol,
5377
+ this.fromServer ? "server frames must not be masked" : "client frames must be masked"
5378
+ );
5379
+ }
5331
5380
  data(opcode, fin, payload) {
5332
5381
  if (opcode === OP.continuation !== this.continuing) {
5333
5382
  throw new WsError(CLOSE.protocol, "unexpected continuation frame");
@@ -5342,6 +5391,14 @@ var WsReader = class {
5342
5391
  this.size = 0;
5343
5392
  return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
5344
5393
  }
5394
+ async payload(length2, masked, signal) {
5395
+ const mask = new Uint8Array(4);
5396
+ if (masked) await fill(this.incoming, mask, signal);
5397
+ const payload = new Uint8Array(length2);
5398
+ await fill(this.incoming, payload, signal);
5399
+ if (masked) for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
5400
+ return payload;
5401
+ }
5345
5402
  async length(short, signal) {
5346
5403
  if (short < 126) return short;
5347
5404
  const ext = new Uint8Array(short === 126 ? 2 : 8);
@@ -5777,10 +5834,10 @@ var RealmProxy = class {
5777
5834
  encodedBodies: this.options.transport.traits.encodedBodies,
5778
5835
  bodiless: bodiless2
5779
5836
  });
5780
- const chunked = !bodiless2 && minor >= 1;
5781
- const keep2 = keepAlive && (bodiless2 || chunked);
5837
+ const chunked2 = !bodiless2 && minor >= 1;
5838
+ const keep2 = keepAlive && (bodiless2 || chunked2);
5782
5839
  headers.push(["Connection", keep2 ? "keep-alive" : "close"]);
5783
- if (chunked) headers.push(["Transfer-Encoding", "chunked"]);
5840
+ if (chunked2) headers.push(["Transfer-Encoding", "chunked"]);
5784
5841
  const signal = this.stop.signal;
5785
5842
  await conn.write(
5786
5843
  responseHead(status, response.statusText || REASON[status] || "", headers),
@@ -5792,13 +5849,13 @@ var RealmProxy = class {
5792
5849
  }
5793
5850
  try {
5794
5851
  for await (const piece of response.body) {
5795
- if (piece.length > 0) await conn.write(chunked ? chunk(piece) : piece, signal);
5852
+ if (piece.length > 0) await conn.write(chunked2 ? chunk(piece) : piece, signal);
5796
5853
  }
5797
5854
  } catch {
5798
5855
  await response.cancel().catch(() => void 0);
5799
5856
  return false;
5800
5857
  }
5801
- if (chunked) await conn.write(LAST_CHUNK, signal);
5858
+ if (chunked2) await conn.write(LAST_CHUNK, signal);
5802
5859
  return keep2;
5803
5860
  }
5804
5861
  async connect(conn, incoming, req, idle) {
@@ -8225,15 +8282,15 @@ var UnlinkedKeeper = class {
8225
8282
  wrap(fs = this.fs) {
8226
8283
  const keeper = this;
8227
8284
  const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
8228
- const counted = (st) => {
8285
+ const counted2 = (st) => {
8229
8286
  const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
8230
8287
  return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
8231
8288
  };
8232
8289
  return {
8233
8290
  ...fs,
8234
8291
  links: (path) => mayLink(fs, path),
8235
- stat: async (path) => counted(await fs.stat(path)),
8236
- lstat: async (path) => counted(await fs.lstat(path)),
8292
+ stat: async (path) => counted2(await fs.stat(path)),
8293
+ lstat: async (path) => counted2(await fs.lstat(path)),
8237
8294
  async rm(path, options) {
8238
8295
  const target = fs.resolvePath("/", path);
8239
8296
  const pinned = await keeper.pin(target);
@@ -8247,7 +8304,7 @@ var UnlinkedKeeper = class {
8247
8304
  await keeper.settle(pinned, () => fs.rename(from, to));
8248
8305
  },
8249
8306
  readdir: async (path) => visible(await fs.readdir(path)),
8250
- readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted(st)])
8307
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
8251
8308
  };
8252
8309
  }
8253
8310
  async settle(pinned, op) {
@@ -11079,7 +11136,7 @@ function mountTable(mounts = []) {
11079
11136
  }
11080
11137
 
11081
11138
  // src/version.ts
11082
- var KERNEL_VERSION = true ? "1.90.0" : "0.0.0-dev";
11139
+ var KERNEL_VERSION = true ? "1.91.0" : "0.0.0-dev";
11083
11140
  var OS_RELEASE = "6.0.0-slicc";
11084
11141
  function procVersion() {
11085
11142
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -11667,6 +11724,339 @@ async function hostSyscall(req, ctx) {
11667
11724
  }
11668
11725
  }
11669
11726
 
11727
+ // src/kernel/net/loopback-transport.ts
11728
+ var LOOPBACK_TRAITS = {
11729
+ manualRedirects: true,
11730
+ encodedBodies: true,
11731
+ maxRequestBody: 64 * 1024 * 1024,
11732
+ crossOrigin: "any",
11733
+ websocket: true
11734
+ };
11735
+ var MAX_HEAD2 = 64 * 1024;
11736
+ var OWN_HEADERS = /* @__PURE__ */ new Set([
11737
+ "host",
11738
+ "connection",
11739
+ "content-length",
11740
+ "transfer-encoding",
11741
+ "upgrade",
11742
+ "sec-websocket-key",
11743
+ "sec-websocket-version",
11744
+ "sec-websocket-protocol",
11745
+ "sec-websocket-extensions"
11746
+ ]);
11747
+ var OP_CLOSE = 8;
11748
+ var OP_PONG = 10;
11749
+ var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
11750
+ var SAFE_VALUE = /^[^\r\n\0]*$/;
11751
+ var CLOSE_WAIT_MS3 = 5e3;
11752
+ var MAX_UNREAD = 16 * 1024 * 1024;
11753
+ function errno(code, message3) {
11754
+ return Object.assign(new Error(`${code}: ${message3}`), { code });
11755
+ }
11756
+ function requestHead(method, url, headers, extra) {
11757
+ if (!TOKEN3.test(method)) throw errno("EINVAL", `method ${method}`);
11758
+ const lines2 = [`${method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
11759
+ for (const [name2, value] of headers) {
11760
+ if (OWN_HEADERS.has(name2.toLowerCase())) continue;
11761
+ if (!TOKEN3.test(name2) || !SAFE_VALUE.test(value)) throw errno("EINVAL", `header ${name2}`);
11762
+ lines2.push(`${name2}: ${value}`);
11763
+ }
11764
+ for (const [name2, value] of extra) lines2.push(`${name2}: ${value}`);
11765
+ return latin1Bytes(`${lines2.join("\r\n")}\r
11766
+ \r
11767
+ `);
11768
+ }
11769
+ function parseHead(bytes) {
11770
+ const [statusLine = "", ...lines2] = latin1(bytes).split(/\r?\n/);
11771
+ const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
11772
+ if (!match) throw errno("EPROTO", `not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
11773
+ const headers = [];
11774
+ for (const line of lines2) {
11775
+ const colon = line.indexOf(":");
11776
+ if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
11777
+ }
11778
+ return { status: Number(match[1]), statusText: match[2] ?? "", headers };
11779
+ }
11780
+ async function readHead(incoming, signal) {
11781
+ for (; ; ) {
11782
+ let raw;
11783
+ try {
11784
+ raw = await incoming.head(MAX_HEAD2, signal);
11785
+ } catch (err) {
11786
+ if (err instanceof KernelError) throw err;
11787
+ throw errno("EPROTO", "the response head is malformed or too large");
11788
+ }
11789
+ if (!raw) throw errno("ECONNRESET", "the connection closed before a response");
11790
+ const head = parseHead(raw);
11791
+ if (head.status < 100 || head.status >= 200 || head.status === 101) return head;
11792
+ }
11793
+ }
11794
+ async function* chunked(incoming, signal) {
11795
+ for (; ; ) {
11796
+ const line = await incoming.line(4096, signal);
11797
+ const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(line);
11798
+ if (!size) throw errno("EPROTO", "a malformed chunk size");
11799
+ const n = Number.parseInt(size[1], 16);
11800
+ if (n === 0) {
11801
+ while (await incoming.line(MAX_HEAD2, signal) !== "") ;
11802
+ return;
11803
+ }
11804
+ yield await incoming.exactly(n, signal);
11805
+ if (await incoming.line(2, signal) !== "") throw errno("EPROTO", "a malformed chunk");
11806
+ }
11807
+ }
11808
+ async function* counted(incoming, length2, signal) {
11809
+ for (let left = length2; left > 0; ) {
11810
+ const data = await incoming.some(Math.min(left, 64 * 1024), signal);
11811
+ if (data.length === 0) throw errno("EPROTO", "the body ended early");
11812
+ left -= data.length;
11813
+ yield data;
11814
+ }
11815
+ }
11816
+ async function* untilClose(incoming, signal) {
11817
+ for (; ; ) {
11818
+ const data = await incoming.some(64 * 1024, signal);
11819
+ if (data.length === 0) return;
11820
+ yield data;
11821
+ }
11822
+ }
11823
+ function bodyOf(incoming, head, method, signal) {
11824
+ if (method === "HEAD" || head.status === 204 || head.status === 304) return [];
11825
+ if (fieldTokens(head.headers, "transfer-encoding").includes("chunked")) {
11826
+ return chunked(incoming, signal);
11827
+ }
11828
+ const lengths = fieldValues(head.headers, "content-length");
11829
+ if (lengths.length === 0) return untilClose(incoming, signal);
11830
+ const length2 = lengths[0]?.trim() ?? "";
11831
+ if (!/^\d{1,15}$/.test(length2) || lengths.some((l) => l.trim() !== length2)) {
11832
+ throw errno("EPROTO", `a malformed Content-Length: ${lengths.join(", ")}`);
11833
+ }
11834
+ return counted(incoming, Number(length2), signal);
11835
+ }
11836
+ var KernelLoopback = class {
11837
+ net;
11838
+ cred;
11839
+ constructor(net, cred) {
11840
+ this.net = net;
11841
+ this.cred = cred;
11842
+ }
11843
+ port(url) {
11844
+ return kernelLoopbackPort(url);
11845
+ }
11846
+ connect(url, port, signal) {
11847
+ if (url.protocol === "https:" || url.protocol === "wss:") {
11848
+ throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
11849
+ }
11850
+ signal.throwIfAborted();
11851
+ const socket = this.net.socket("inet");
11852
+ try {
11853
+ void socket.connect({ family: "inet", host: "127.0.0.1", port }, false, this.cred());
11854
+ } catch (err) {
11855
+ socket.close();
11856
+ const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
11857
+ throw refused2 ? errno("ECONNREFUSED", `nothing listens on the kernel's port ${port}`) : err;
11858
+ }
11859
+ signal.addEventListener("abort", () => socket.close(), { once: true });
11860
+ return socket;
11861
+ }
11862
+ async fetch(req, port) {
11863
+ const url = new URL(req.url);
11864
+ const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
11865
+ const length2 = body ? [["Content-Length", String(body.length)]] : [];
11866
+ const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
11867
+ const socket = this.connect(url, port, req.signal);
11868
+ try {
11869
+ await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
11870
+ const incoming = new Incoming(socket);
11871
+ const response = await readHead(incoming, req.signal);
11872
+ const chunks = bodyOf(incoming, response, req.method, req.signal);
11873
+ return {
11874
+ url: req.url,
11875
+ status: response.status,
11876
+ statusText: response.statusText,
11877
+ headers: response.headers,
11878
+ body: closing(chunks, socket),
11879
+ cancel: async () => socket.close()
11880
+ };
11881
+ } catch (err) {
11882
+ socket.close();
11883
+ throw err;
11884
+ }
11885
+ }
11886
+ async websocket(req, port) {
11887
+ const url = new URL(req.url);
11888
+ const socket = this.connect(url, port, req.signal);
11889
+ try {
11890
+ const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
11891
+ const extra = [
11892
+ ["Upgrade", "websocket"],
11893
+ ["Connection", "Upgrade"],
11894
+ ["Sec-WebSocket-Key", key],
11895
+ ["Sec-WebSocket-Version", "13"]
11896
+ ];
11897
+ if (req.protocols.length > 0)
11898
+ extra.push(["Sec-WebSocket-Protocol", req.protocols.join(", ")]);
11899
+ await writeAll2(socket, requestHead("GET", url, req.headers, extra), req.signal);
11900
+ const incoming = new Incoming(socket);
11901
+ const head = await readHead(incoming, req.signal);
11902
+ if (head.status !== 101) {
11903
+ throw errno(
11904
+ "EPROTO",
11905
+ `the server answered ${head.status} ${head.statusText}, not an upgrade`
11906
+ );
11907
+ }
11908
+ const upgraded = fieldTokens(head.headers, "upgrade").includes("websocket") && fieldTokens(head.headers, "connection").includes("upgrade");
11909
+ const accept2 = fieldValues(head.headers, "sec-websocket-accept")[0];
11910
+ if (!upgraded || accept2 !== await acceptFor(key)) {
11911
+ throw errno("EPROTO", "the server did not accept the WebSocket key");
11912
+ }
11913
+ const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
11914
+ return new LoopbackWebSocket(socket, incoming, protocol);
11915
+ } catch (err) {
11916
+ socket.close();
11917
+ throw err;
11918
+ }
11919
+ }
11920
+ };
11921
+ async function writeAll2(socket, bytes, signal) {
11922
+ for (let at = 0; at < bytes.length; ) at += await socket.write(bytes.subarray(at), signal);
11923
+ }
11924
+ async function* closing(chunks, socket) {
11925
+ try {
11926
+ yield* chunks;
11927
+ } catch (err) {
11928
+ if (typeof err?.code === "string") throw err;
11929
+ throw errno("EPROTO", err instanceof Error ? err.message : String(err));
11930
+ } finally {
11931
+ socket.close();
11932
+ }
11933
+ }
11934
+ var LoopbackWebSocket = class {
11935
+ protocol;
11936
+ messages;
11937
+ closed;
11938
+ pending = 0;
11939
+ writes = Promise.resolve();
11940
+ sentClose = false;
11941
+ stop = new AbortController();
11942
+ socket;
11943
+ settle;
11944
+ unread = 0;
11945
+ room;
11946
+ constructor(socket, incoming, protocol) {
11947
+ this.socket = socket;
11948
+ this.protocol = protocol;
11949
+ this.closed = new Promise((resolve2) => {
11950
+ this.settle = resolve2;
11951
+ });
11952
+ this.stop.signal.addEventListener("abort", () => this.room?.(), { once: true });
11953
+ const queue = [];
11954
+ let wake;
11955
+ let ended = false;
11956
+ void this.pump(new WsReader(incoming, void 0, true, true), (message3) => {
11957
+ queue.push(message3);
11958
+ this.unread += message3.length;
11959
+ wake?.();
11960
+ }).finally(() => {
11961
+ ended = true;
11962
+ wake?.();
11963
+ });
11964
+ const taken = (message3) => {
11965
+ this.unread -= message3.length;
11966
+ const room = this.room;
11967
+ this.room = void 0;
11968
+ room?.();
11969
+ };
11970
+ this.messages = {
11971
+ [Symbol.asyncIterator]: async function* () {
11972
+ for (; ; ) {
11973
+ const next2 = queue.shift();
11974
+ if (next2 !== void 0) {
11975
+ taken(next2);
11976
+ yield next2;
11977
+ continue;
11978
+ }
11979
+ if (ended) return;
11980
+ await new Promise((resolve2) => {
11981
+ wake = resolve2;
11982
+ });
11983
+ wake = void 0;
11984
+ }
11985
+ }
11986
+ };
11987
+ }
11988
+ async space() {
11989
+ while (this.unread > MAX_UNREAD && !this.stop.signal.aborted) {
11990
+ await new Promise((resolve2) => {
11991
+ this.room = resolve2;
11992
+ });
11993
+ }
11994
+ }
11995
+ get buffered() {
11996
+ return this.pending;
11997
+ }
11998
+ write(bytes) {
11999
+ this.pending += bytes.length;
12000
+ const previous = this.writes;
12001
+ this.writes = (async () => {
12002
+ await previous;
12003
+ try {
12004
+ await writeAll2(this.socket, bytes, this.stop.signal);
12005
+ } catch {
12006
+ this.stop.abort();
12007
+ }
12008
+ this.pending -= bytes.length;
12009
+ })();
12010
+ }
12011
+ async pump(reader, deliver) {
12012
+ let close = { code: 1006, reason: "" };
12013
+ try {
12014
+ for (; ; ) {
12015
+ await this.space();
12016
+ const event = await reader.next(this.stop.signal);
12017
+ if (!event) break;
12018
+ if (event.kind === "text") deliver(event.text);
12019
+ else if (event.kind === "binary") deliver(event.bytes);
12020
+ else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
12021
+ else {
12022
+ close = { code: event.code, reason: event.reason };
12023
+ if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
12024
+ break;
12025
+ }
12026
+ }
12027
+ } catch (err) {
12028
+ if (err instanceof WsError) this.sendClose(err.code, err.message);
12029
+ close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
12030
+ } finally {
12031
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12032
+ await this.writes;
12033
+ clearTimeout(late);
12034
+ this.socket.close();
12035
+ this.settle(close);
12036
+ }
12037
+ }
12038
+ sendClose(code, reason) {
12039
+ if (this.sentClose) return;
12040
+ this.sentClose = true;
12041
+ const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12042
+ void this.closed.then(() => clearTimeout(late));
12043
+ const bytes = new TextEncoder().encode(reason).subarray(0, 123);
12044
+ const payload = new Uint8Array(2 + bytes.length);
12045
+ new DataView(payload.buffer).setUint16(0, sendableCode(code) ? code : CLOSE.error);
12046
+ payload.set(bytes, 2);
12047
+ this.write(clientFrame(OP_CLOSE, payload));
12048
+ }
12049
+ send(data) {
12050
+ if (this.sentClose) throw errno("EPIPE", "the websocket is closing");
12051
+ const text = typeof data === "string";
12052
+ const payload = text ? new TextEncoder().encode(data) : data;
12053
+ this.write(clientFrame(text ? OP_TEXT : OP_BINARY, payload));
12054
+ }
12055
+ close(code = 1e3, reason = "") {
12056
+ this.sendClose(code, reason);
12057
+ }
12058
+ };
12059
+
11670
12060
  // src/kernel/net/ws-syscalls.ts
11671
12061
  var WS_OPS = [
11672
12062
  "net-ws-open",
@@ -11676,7 +12066,7 @@ var WS_OPS = [
11676
12066
  "net-ws-send",
11677
12067
  "net-ws-close"
11678
12068
  ];
11679
- var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
12069
+ var TOKEN4 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
11680
12070
  function message(e) {
11681
12071
  return e instanceof Error ? e.message : String(e);
11682
12072
  }
@@ -11685,16 +12075,18 @@ function errnoOf(e) {
11685
12075
  const code = e?.code;
11686
12076
  return typeof code === "string" && ERRNO.test(code) ? code : "ECONNREFUSED";
11687
12077
  }
11688
- function fail(errno, text) {
11689
- return { ok: false, errno, message: text };
12078
+ function fail(errno2, text) {
12079
+ return { ok: false, errno: errno2, message: text };
11690
12080
  }
11691
12081
  var WsHandles = class {
11692
12082
  transport;
11693
12083
  open = /* @__PURE__ */ new Map();
11694
12084
  next;
11695
- constructor(transport, next2) {
12085
+ local;
12086
+ constructor(transport, next2, local) {
11696
12087
  this.transport = transport;
11697
12088
  this.next = next2;
12089
+ this.local = local;
11698
12090
  }
11699
12091
  owns(handle) {
11700
12092
  return this.open.has(handle);
@@ -11723,16 +12115,18 @@ var WsHandles = class {
11723
12115
  return { ok: true, kind: "json", json: handle };
11724
12116
  }
11725
12117
  async connect(handle, req, entry) {
11726
- const { transport } = this;
11727
- if (!transport.traits.websocket || !transport.websocket) return fail("ENOTSUP", NO_WEBSOCKET2);
12118
+ const { transport, local } = this;
12119
+ const port = local?.port(req.url);
12120
+ const open = { protocols: req.protocols, headers: req.headers, signal: entry.abort.signal };
12121
+ if (port === void 0 && (!transport.traits.websocket || !transport.websocket)) {
12122
+ return fail("ENOTSUP", NO_WEBSOCKET2);
12123
+ }
11728
12124
  let socket;
11729
12125
  try {
11730
- socket = await transport.websocket({
12126
+ socket = port === void 0 ? await transport.websocket({
11731
12127
  url: toHostLoopback(req.url),
11732
- protocols: req.protocols,
11733
- headers: req.headers,
11734
- signal: entry.abort.signal
11735
- });
12128
+ ...open
12129
+ }) : await local.websocket({ url: req.url, ...open }, port);
11736
12130
  } catch (e) {
11737
12131
  return fail(errnoOf(e), message(e));
11738
12132
  }
@@ -11740,7 +12134,7 @@ var WsHandles = class {
11740
12134
  socket.close(1001, "the program went away");
11741
12135
  return fail("EBADF", `no websocket ${handle}`);
11742
12136
  }
11743
- if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN3.test(socket.protocol))) {
12137
+ if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN4.test(socket.protocol))) {
11744
12138
  socket.close(1002, "subprotocol not offered");
11745
12139
  return fail("EPROTO", "the far end chose a subprotocol the program did not offer");
11746
12140
  }
@@ -11803,6 +12197,10 @@ var EMPTY = new Uint8Array(0);
11803
12197
  function message2(e) {
11804
12198
  return e instanceof Error ? e.message : String(e);
11805
12199
  }
12200
+ function codeOf2(e) {
12201
+ const code = e?.code;
12202
+ return typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "EIO";
12203
+ }
11806
12204
  async function* once(bytes) {
11807
12205
  yield bytes;
11808
12206
  }
@@ -11812,10 +12210,12 @@ var HttpHandles = class {
11812
12210
  nextHandle = 0;
11813
12211
  ws;
11814
12212
  owner;
11815
- constructor(transport, owner) {
12213
+ local;
12214
+ constructor(transport, owner, local) {
11816
12215
  this.transport = transport;
11817
12216
  this.owner = owner;
11818
- this.ws = new WsHandles(transport, () => ++this.nextHandle);
12217
+ this.local = local;
12218
+ this.ws = new WsHandles(transport, () => ++this.nextHandle, local);
11819
12219
  }
11820
12220
  syscall(req) {
11821
12221
  if (req.op.startsWith("net-ws-")) return this.ws.syscall(req);
@@ -11823,10 +12223,13 @@ var HttpHandles = class {
11823
12223
  if (req.op === "net-open") return Promise.resolve(this.opened(req));
11824
12224
  if (req.op === "net-head") return this.head(req.handle);
11825
12225
  if (req.op === "net-read") return this.read(req.handle, req.max);
11826
- if (req.op === "net-traits") return Promise.resolve(this.traits());
12226
+ if (req.op === "net-traits") return Promise.resolve(this.traits(req.url));
11827
12227
  return this.close(req.handle);
11828
12228
  }
11829
- traits() {
12229
+ traits(url) {
12230
+ if (url !== void 0 && this.local?.port(url) !== void 0) {
12231
+ return { ok: true, kind: "json", json: LOOPBACK_TRAITS };
12232
+ }
11830
12233
  const { unavailable, crossOrigin = "any", ...rest2 } = this.transport.traits;
11831
12234
  if (unavailable) return { ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT };
11832
12235
  return { ok: true, kind: "json", json: { ...rest2, crossOrigin } };
@@ -11857,28 +12260,30 @@ var HttpHandles = class {
11857
12260
  return { handle, head: entry.head };
11858
12261
  }
11859
12262
  async respond(handle, req, entry) {
12263
+ const port = this.local?.port(req.url);
11860
12264
  try {
11861
- const response = await this.transport.fetch(
11862
- {
11863
- url: req.url,
11864
- method: req.method,
11865
- headers: req.headers,
11866
- ...req.body ? { body: req.body } : {},
11867
- signal: entry.abort.signal
11868
- },
11869
- this.owner
11870
- );
12265
+ const target = port === void 0 ? toHostLoopback(req.url) : req.url;
12266
+ const request2 = {
12267
+ url: target,
12268
+ method: req.method,
12269
+ headers: req.headers,
12270
+ ...req.body ? { body: req.body } : {},
12271
+ signal: entry.abort.signal
12272
+ };
12273
+ const response = port === void 0 ? await this.transport.fetch(request2, this.owner) : await this.local.fetch(request2, port);
11871
12274
  entry.body = response.body[Symbol.asyncIterator]();
11872
12275
  entry.cancel = () => response.cancel();
11873
12276
  if (!this.open.has(handle)) await entry.cancel().catch(() => void 0);
11874
- const head = this.headOf(handle, response.url ?? req.url, response);
12277
+ const shown = response.url && response.url !== target ? response.url : req.url;
12278
+ const head = this.headOf(handle, shown, response);
11875
12279
  return response.redirected ? { ...head, json: { ...head.json, redirected: true } } : head;
11876
12280
  } catch (e) {
11877
12281
  const text = message2(e);
12282
+ if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
11878
12283
  const status = e.status;
11879
12284
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
11880
- const errno = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
11881
- return { ok: false, errno, message: text };
12285
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
12286
+ return { ok: false, errno: errno2, message: text };
11882
12287
  }
11883
12288
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
11884
12289
  return this.headOf(handle, req.url, {
@@ -15614,7 +16019,7 @@ function scrubberOf(secrets) {
15614
16019
  const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
15615
16020
  return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
15616
16021
  }
15617
- async function writeAll2(file, bytes) {
16022
+ async function writeAll3(file, bytes) {
15618
16023
  const write = file.write;
15619
16024
  for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
15620
16025
  }
@@ -15648,7 +16053,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15648
16053
  const later = (bytes) => {
15649
16054
  if (bytes.length === 0) return;
15650
16055
  pending += 1;
15651
- tail = tail.then(() => writeAll2(file, bytes)).catch((err) => {
16056
+ tail = tail.then(() => writeAll3(file, bytes)).catch((err) => {
15652
16057
  if (failed3 === void 0) failed3 = err;
15653
16058
  }).finally(() => {
15654
16059
  pending -= 1;
@@ -16279,8 +16684,8 @@ var FSA_CAPABILITIES = {
16279
16684
  ranges: true,
16280
16685
  sessions: true
16281
16686
  };
16282
- var codeOf2 = (err) => err?.code;
16283
- var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
16687
+ var codeOf3 = (err) => err?.code;
16688
+ var denied = (err) => codeOf3(err) === "EACCES" || err?.name === "NotAllowedError";
16284
16689
  function attrOf(st) {
16285
16690
  return {
16286
16691
  kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
@@ -16302,7 +16707,7 @@ function kernelFsHandlers(fs) {
16302
16707
  try {
16303
16708
  return await fs.stat(path);
16304
16709
  } catch (err) {
16305
- if (codeOf2(err) === "ENOENT") return void 0;
16710
+ if (codeOf3(err) === "ENOENT") return void 0;
16306
16711
  throw err;
16307
16712
  }
16308
16713
  }
@@ -16495,7 +16900,7 @@ function parseFstab(text) {
16495
16900
  });
16496
16901
  return lines2;
16497
16902
  }
16498
- function codeOf3(err) {
16903
+ function codeOf4(err) {
16499
16904
  const code = err?.code;
16500
16905
  return typeof code === "string" ? code : "EIO";
16501
16906
  }
@@ -16533,18 +16938,18 @@ async function mountLine({ line, spec, invalid }, mount, signal, retries, discar
16533
16938
  break;
16534
16939
  } catch (err) {
16535
16940
  failed3 = err;
16536
- if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
16941
+ if (FINAL.has(codeOf4(err)) || attempt === retries.length) break;
16537
16942
  await wait(retries[attempt], signal);
16538
16943
  }
16539
16944
  }
16540
- const code = codeOf3(failed3);
16945
+ const code = codeOf4(failed3);
16541
16946
  return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
16542
16947
  }
16543
16948
 
16544
16949
  // src/mount/hostfs.ts
16545
16950
  var HOSTFS_IO = 16 * 1024 * 1024;
16546
- var codeOf4 = (err) => err?.code;
16547
- var unreachable = (err) => codeOf4(err) === "ENOTCONN";
16951
+ var codeOf5 = (err) => err?.code;
16952
+ var unreachable = (err) => codeOf5(err) === "ENOTCONN";
16548
16953
  function rel(path) {
16549
16954
  return path.replace(/^\/+/, "");
16550
16955
  }
@@ -16604,9 +17009,9 @@ var HostfsClient = class {
16604
17009
  throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
16605
17010
  return this.send(path, init, false, true);
16606
17011
  }
16607
- const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
17012
+ const errno2 = response.headers.get("X-Hostfs-Errno") ?? "EIO";
16608
17013
  const body = await response.json().catch(() => ({}));
16609
- throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
17014
+ throw fsError2(errno2, typeof body.message === "string" ? body.message : errno2);
16610
17015
  }
16611
17016
  async call(op, body = {}, handle = false) {
16612
17017
  const response = await this.send(
@@ -16721,7 +17126,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
16721
17126
  try {
16722
17127
  return { opened: await opening2(), restore: void 0 };
16723
17128
  } catch (err) {
16724
- if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
17129
+ if (!flags2.write || codeOf5(err) !== "EACCES") throw err;
16725
17130
  const was = await client.call("stat", { path: rel(path) });
16726
17131
  const mode = was.mode ?? 0;
16727
17132
  if (mode & 128) throw err;
@@ -17634,8 +18039,8 @@ var RULE = new RegExp(
17634
18039
  var TERM = /^[A-Za-z0-9._+-]+$/;
17635
18040
  var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
17636
18041
  var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
17637
- function failed2(errno, error) {
17638
- return { error, errno };
18042
+ function failed2(errno2, error) {
18043
+ return { error, errno: errno2 };
17639
18044
  }
17640
18045
  function unescaped(double) {
17641
18046
  return double.replace(/\\(["\\$`])/g, "$1");
@@ -18929,6 +19334,12 @@ var Launcher = class {
18929
19334
  groupSession(pgid) {
18930
19335
  return this.jobs.sessionOf(pgid);
18931
19336
  }
19337
+ loopbackFor(pid) {
19338
+ return new KernelLoopback(this.net, () => {
19339
+ const cred = this.credOf(pid);
19340
+ return { pid, uid: cred.euid, gid: cred.egid };
19341
+ });
19342
+ }
18932
19343
  treeScrubs(req) {
18933
19344
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18934
19345
  return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
@@ -19006,7 +19417,7 @@ var Launcher = class {
19006
19417
  jobs: this.jobs,
19007
19418
  ptys: this.ptys,
19008
19419
  net: this.net,
19009
- http: new HttpHandles(this.transport, pid),
19420
+ http: new HttpHandles(this.transport, pid, this.loopbackFor(pid)),
19010
19421
  locks: this.locks,
19011
19422
  resolver: this.resolver,
19012
19423
  onReap: (child) => this.reaped(child, pid),