@ai-ecoverse/slicc-kernel 1.88.1 → 1.90.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.
@@ -383,6 +383,14 @@ function inodeOf(path) {
383
383
  h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
384
384
  return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
385
385
  }
386
+ function redirected(of, to) {
387
+ return Object.assign(fsError("EXDEV", `${of} leads to the mount at ${to}`), {
388
+ redirect: { of: normalizePath(of), to: normalizePath(to) }
389
+ });
390
+ }
391
+ function redirectOf(err) {
392
+ return err?.redirect;
393
+ }
386
394
 
387
395
  // src/fs/owners.ts
388
396
  var ROOT_OWNER = Object.freeze({ uid: 0, gid: 0 });
@@ -476,39 +484,47 @@ var Integrity = class {
476
484
  this.unknown = integrityUnknown(options);
477
485
  this.report = report;
478
486
  }
479
- async real(path) {
487
+ async real(path, asked) {
480
488
  const at = normalizePath(path);
481
489
  const real = await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
482
- return isProtected(this.prefixes, real) ? real : void 0;
483
- }
484
- async key(path) {
485
- const real = await this.real(path);
486
- if (real === void 0 || !this.root.version) return void 0;
490
+ if (isProtected(this.prefixes, real)) return { real, name: real };
491
+ const names = asked === void 0 ? [] : await namesOf(this.root, asked);
492
+ if (real !== at) names.push(...await namesOf(this.root, at));
493
+ const name2 = names.findLast((p) => isProtected(this.prefixes, p));
494
+ return name2 === void 0 ? void 0 : { real, name: name2 };
495
+ }
496
+ async key(path, asked) {
497
+ const found = await this.real(path, asked);
498
+ if (found === void 0) return void 0;
499
+ const { real, name: name2 } = found;
500
+ if (name2 !== real) return `${name2}\0${real}\0`;
501
+ if (!this.root.version) return void 0;
487
502
  return `${real}\0${await this.root.version(real)}`;
488
503
  }
489
- async read(path, caller, plain) {
490
- return (await this.readKeyed(path, caller, plain)).bytes;
504
+ async read(path, caller, plain, asked) {
505
+ return (await this.readKeyed(path, caller, plain, asked)).bytes;
491
506
  }
492
- async readKeyed(path, caller, plain) {
493
- const real = await this.real(path);
494
- if (real === void 0) return { bytes: await plain() };
495
- const read = this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
507
+ async readKeyed(path, caller, plain, asked) {
508
+ const found = await this.real(path, asked);
509
+ if (found === void 0) return { bytes: await plain() };
510
+ const { real, name: name2 } = found;
511
+ const read = name2 !== real ? { bytes: await plain(), version: void 0 } : this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
496
512
  const { bytes } = read;
497
513
  const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
498
514
  const actual = await sha256(bytes);
499
- const denial = { pid: caller.pid, uid: caller.uid, path: real, actual };
515
+ const denial = { pid: caller.pid, uid: caller.uid, path: name2, actual };
500
516
  const tagged = caller.tag !== void 0 ? { tag: caller.tag } : {};
501
517
  let expected;
502
518
  try {
503
- expected = await this.expected(real);
519
+ expected = await this.expected(name2);
504
520
  } catch {
505
521
  this.say({ ...denial, ...tagged, op: "exec-integrity", errno: "EACCES" });
506
- throw fsError("EACCES", `integrity lookup failed: ${real}`);
522
+ throw fsError("EACCES", `integrity lookup failed: ${name2}`);
507
523
  }
508
524
  if (expected === void 0) {
509
525
  if (this.unknown === "allow") {
510
- if (!this.unverified.has(real)) {
511
- this.unverified.add(real);
526
+ if (!this.unverified.has(name2)) {
527
+ this.unverified.add(name2);
512
528
  this.say({ ...denial, ...tagged, op: "exec-unverified", errno: "ENODATA" });
513
529
  }
514
530
  return keyed;
@@ -518,7 +534,12 @@ var Integrity = class {
518
534
  }
519
535
  const known = expected !== void 0 ? { expected } : {};
520
536
  this.say({ ...denial, ...tagged, ...known, op: "exec-integrity", errno: "EACCES" });
521
- throw fsError("EACCES", `integrity check failed: ${real}`);
537
+ throw fsError("EACCES", `integrity check failed: ${name2}`);
538
+ }
539
+ forget(path) {
540
+ for (const name2 of [...this.unverified]) {
541
+ if (path === void 0 || isProtected([path], name2)) this.unverified.delete(name2);
542
+ }
522
543
  }
523
544
  async expected(path) {
524
545
  const answer = await this.lookup(path);
@@ -572,10 +593,10 @@ function missing(err) {
572
593
  const code = err?.code;
573
594
  return code === "ENOENT" || code === "ENOTDIR";
574
595
  }
575
- async function resolved(fs, path, follow2) {
596
+ async function resolved(fs, path, follow2, hops = []) {
576
597
  let done4 = [];
577
598
  let todo = partsOf(path);
578
- for (let hops = 0; todo.length > 0; ) {
599
+ for (let links = 0; todo.length > 0; ) {
579
600
  const part = todo.shift();
580
601
  if (part === "..") {
581
602
  done4.pop();
@@ -597,13 +618,20 @@ async function resolved(fs, path, follow2) {
597
618
  done4.push(part);
598
619
  continue;
599
620
  }
600
- if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
621
+ if (++links > MAX_LINKS) throw fsError("ELOOP", path);
622
+ hops.push(normalizePath([at, ...todo].join("/")));
601
623
  const link2 = await fs.readlink(at);
602
624
  if (link2.startsWith("/")) done4 = [];
603
625
  todo = [...partsOf(link2), ...todo];
604
626
  }
605
627
  return `/${done4.join("/")}`;
606
628
  }
629
+ async function namesOf(fs, path) {
630
+ const lexical2 = normalizePath(path);
631
+ const hops = [];
632
+ await resolved(fs, lexical2, true, hops).catch(() => void 0);
633
+ return [lexical2, ...hops];
634
+ }
607
635
  var ProtectedFs = class {
608
636
  prefixes;
609
637
  constructor(prefixes2) {
@@ -656,8 +684,8 @@ var ProtectedFs = class {
656
684
  await check(path, true);
657
685
  return fs.utimes(path, atime, mtime);
658
686
  },
659
- async mayChange(path, follow2) {
660
- await check(path, follow2);
687
+ async mayChange(path, follow2, whole = false) {
688
+ await check(path, follow2, whole);
661
689
  },
662
690
  async stat(path) {
663
691
  return marked(path, await fs.stat(path));
@@ -984,6 +1012,7 @@ var OpfsFs = class {
984
1012
  remote;
985
1013
  ranged;
986
1014
  owners = false;
1015
+ mounts = {};
987
1016
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
988
1017
  this.root = root;
989
1018
  this.meta = sidecar(meta);
@@ -1057,6 +1086,12 @@ var OpfsFs = class {
1057
1086
  trackOwners() {
1058
1087
  this.owners = true;
1059
1088
  }
1089
+ crossings(mounted) {
1090
+ this.mounts.mounted = mounted;
1091
+ }
1092
+ crossing(of, to) {
1093
+ if (this.mounts.mounted?.(to)) throw redirected(of, to);
1094
+ }
1060
1095
  async hasOwner(path) {
1061
1096
  return ownOf((await this.meta.get([path]))[0]) !== void 0;
1062
1097
  }
@@ -1251,12 +1286,13 @@ var OpfsFs = class {
1251
1286
  if (!link2) throw err;
1252
1287
  if (link2.path === current && !follow2) return { path: current, link: link2 };
1253
1288
  const target = `${link2.link}${current.slice(link2.path.length)}`;
1254
- current = target.split("/").includes("..") ? await this.physical(parentOf3(link2.path), target, hops + 1) : resolvePath(parentOf3(link2.path), target);
1289
+ current = target.split("/").includes("..") ? await this.physical(parentOf3(link2.path), target, hops + 1, path) : resolvePath(parentOf3(link2.path), target);
1290
+ this.crossing(path, current);
1255
1291
  }
1256
1292
  }
1257
1293
  throw fsError("ELOOP", path);
1258
1294
  }
1259
- async physical(base, path, used) {
1295
+ async physical(base, path, used, of) {
1260
1296
  let done4 = [];
1261
1297
  let todo = (path.startsWith("/") ? path : `${base}/${path}`).split("/").filter((p) => p !== "" && p !== ".");
1262
1298
  for (let hops = used; todo.length > 0; ) {
@@ -1265,6 +1301,7 @@ var OpfsFs = class {
1265
1301
  else if (!todo.includes("..")) done4.push(part);
1266
1302
  else {
1267
1303
  const at = `/${[...done4, part].join("/")}`;
1304
+ this.crossing(of, [at, ...todo].join("/"));
1268
1305
  const st = await this.lstat(at);
1269
1306
  if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
1270
1307
  if (!st.isSymbolicLink) done4.push(part);
@@ -2015,7 +2052,7 @@ function portCdpHook(ask, gone) {
2015
2052
  }
2016
2053
 
2017
2054
  // src/client/protocol.ts
2018
- var PROTOCOL = [1, 10];
2055
+ var PROTOCOL = [1, 11];
2019
2056
  var FS_METHODS = [
2020
2057
  "readFile",
2021
2058
  "writeFile",
@@ -2046,6 +2083,52 @@ function fresh(bytes, start2 = 0, end = bytes.length) {
2046
2083
  return new Uint8Array(bytes.subarray(start2, end));
2047
2084
  }
2048
2085
 
2086
+ // src/facade/port.ts
2087
+ var FACADE_GONE = "the facade host is gone";
2088
+ function portFacadeHook(ask, gone) {
2089
+ return (stamp, { name: name2 }) => new Promise((resolve2, reject) => {
2090
+ const { port1, port2 } = new MessageChannel();
2091
+ let closed = false;
2092
+ const shut = () => {
2093
+ closed = true;
2094
+ port1.close();
2095
+ };
2096
+ const conn = {
2097
+ send: (bytes) => {
2098
+ if (closed) return;
2099
+ const copy = bytes.slice();
2100
+ port1.postMessage({ b: copy }, [copy.buffer]);
2101
+ },
2102
+ onmessage: null,
2103
+ onclose: null,
2104
+ close: (reason) => {
2105
+ if (closed) return;
2106
+ port1.postMessage({ close: reason ?? true });
2107
+ shut();
2108
+ }
2109
+ };
2110
+ port1.addEventListener("message", ({ data }) => {
2111
+ if ("b" in data) conn.onmessage?.(data.b);
2112
+ else if ("open" in data) resolve2(conn);
2113
+ else if ("error" in data) {
2114
+ shut();
2115
+ reject(new Error(data.error));
2116
+ } else if (!closed) {
2117
+ shut();
2118
+ conn.onclose?.();
2119
+ }
2120
+ });
2121
+ port1.start();
2122
+ void gone?.then(() => {
2123
+ if (closed) return;
2124
+ shut();
2125
+ reject(new Error(FACADE_GONE));
2126
+ conn.onclose?.(FACADE_GONE);
2127
+ });
2128
+ ask({ name: name2, stamp }, port2);
2129
+ });
2130
+ }
2131
+
2049
2132
  // src/fs/walk.ts
2050
2133
  var MAX_LINKS3 = 40;
2051
2134
  function parentOf4(path) {
@@ -2159,7 +2242,10 @@ var Perms = class {
2159
2242
  function parts(path) {
2160
2243
  return path.split("/").filter((part) => part !== "" && part !== ".");
2161
2244
  }
2162
- async function walk(fs, cred, path, follow2) {
2245
+ function trustedLink(perm, links) {
2246
+ return links === void 0 || perm.uid === 0 || perm.uid === links;
2247
+ }
2248
+ async function walk(fs, cred, path, follow2, links) {
2163
2249
  const perms = new Perms(fs);
2164
2250
  await perms.learn(path);
2165
2251
  const queue = parts(path);
@@ -2177,6 +2263,7 @@ async function walk(fs, cred, path, follow2) {
2177
2263
  const perm = await perms.at(next2);
2178
2264
  const last = queue.length === 0;
2179
2265
  if (perm?.link !== void 0 && (!last || follow2)) {
2266
+ if (!trustedLink(perm, links)) return { path, perm: void 0, denied: next2, link: true };
2180
2267
  if (++hops > MAX_LINKS4) return { path: next2, perm: void 0 };
2181
2268
  queue.unshift(...parts(perm.link));
2182
2269
  if (perm.link.startsWith("/")) at = "/";
@@ -2201,9 +2288,9 @@ async function finalOf(fs, walked, want, options) {
2201
2288
  return st ? [permOf(st), st.isDirectory] : [perm, false];
2202
2289
  }
2203
2290
  async function access(fs, cred, path, want, options = {}) {
2204
- const walked = await walk(fs, cred, path, options.follow !== false);
2291
+ const walked = await walk(fs, cred, path, options.follow !== false, options.links);
2205
2292
  if (walked.denied !== void 0) {
2206
- if (await present(fs, walked.denied, true)) return walked;
2293
+ if (walked.link || await present(fs, walked.denied, true)) return walked;
2207
2294
  return { path, perm: void 0, missing: true };
2208
2295
  }
2209
2296
  if (!walked.perm) return { ...walked, missing: true };
@@ -2270,6 +2357,9 @@ var Created = class {
2270
2357
  return this.paths.delete(normalizePath(path));
2271
2358
  }
2272
2359
  };
2360
+ function linked(g, options) {
2361
+ return g.links === void 0 ? options : { ...options, links: g.links };
2362
+ }
2273
2363
  function deny(g, op, path, errno = "EACCES") {
2274
2364
  const tag = g.tag === void 0 ? {} : { tag: g.tag };
2275
2365
  g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
@@ -2280,8 +2370,8 @@ function ttyOpenable(g, tty) {
2280
2370
  deny(g, "open", tty.name ?? "/dev/tty");
2281
2371
  }
2282
2372
  async function need(g, op, path, want, options = {}, shown = path) {
2283
- const walked = await access(g.fs, g.cred, path, want, options);
2284
- if (walked.denied !== void 0) deny(g, op, shown);
2373
+ const walked = await access(g.fs, g.cred, path, want, linked(g, options));
2374
+ if (walked.denied !== void 0) deny(g, op, walked.link ? walked.denied : shown);
2285
2375
  return walked;
2286
2376
  }
2287
2377
  function entryIn(dir, path) {
@@ -2300,14 +2390,16 @@ async function inParent(g, op, path, old = false) {
2300
2390
  return dir2.denied === void 0 ? stickyEntry(g, op, path, dir2) : void 0;
2301
2391
  }
2302
2392
  if (!old) return void await need(g, op, parentOf4(path), W_OK | X_OK, DIR, path);
2303
- const dir = await access(g.fs, g.cred, parentOf4(path), W_OK | X_OK, DIR);
2393
+ const dir = await access(g.fs, g.cred, parentOf4(path), W_OK | X_OK, linked(g, DIR));
2304
2394
  if (dir.denied === void 0) return stickyEntry(g, op, path, dir);
2395
+ if (dir.link) return deny(g, op, dir.denied);
2305
2396
  const lookup = dir.denied === dir.path && dir.perm && permits(g.cred, dir.perm, X_OK);
2306
2397
  if (!lookup || await present(g.fs, entryIn(dir, path), false)) deny(g, op, path);
2307
2398
  }
2308
2399
  async function makeIn(g, op, path) {
2309
2400
  for (let dir = parentOf4(path); ; dir = parentOf4(dir)) {
2310
- const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, DIR);
2401
+ const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, linked(g, DIR));
2402
+ if (walked.link) return deny(g, op, walked.denied);
2311
2403
  if (walked.denied !== void 0) {
2312
2404
  if (!await present(g.fs, path, true)) deny(g, op, path);
2313
2405
  return;
@@ -3091,6 +3183,7 @@ var KernelSocket = class _KernelSocket {
3091
3183
  local;
3092
3184
  peer;
3093
3185
  peerCred;
3186
+ peerStamp;
3094
3187
  listenerCred;
3095
3188
  link;
3096
3189
  readShut = false;
@@ -3161,10 +3254,10 @@ var KernelSocket = class _KernelSocket {
3161
3254
  for (const wait2 of waits) wait2.catch(() => void 0);
3162
3255
  return pipeCall(() => Promise.race(waits));
3163
3256
  }
3164
- bind(addr) {
3257
+ bind(addr, kernel = false) {
3165
3258
  if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
3166
3259
  if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
3167
- this.local = this.net.bind(this, addr);
3260
+ this.local = this.net.bind(this, addr, kernel);
3168
3261
  this.boundAt = this.local;
3169
3262
  }
3170
3263
  listen(backlog, cred) {
@@ -3193,7 +3286,7 @@ var KernelSocket = class _KernelSocket {
3193
3286
  this.changes.wake();
3194
3287
  return true;
3195
3288
  }
3196
- connect(addr, viaUplink = true, cred) {
3289
+ connect(addr, viaUplink = true, cred, stamp) {
3197
3290
  if (this.state === "connected") throw new KernelError("EISCONN");
3198
3291
  if (this.state === "connecting") throw new KernelError("EALREADY");
3199
3292
  if (this.state !== "open") throw new KernelError("EINVAL");
@@ -3212,6 +3305,7 @@ var KernelSocket = class _KernelSocket {
3212
3305
  server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
3213
3306
  server.peer = this.local;
3214
3307
  server.peerCred = cred;
3308
+ server.peerStamp = stamp;
3215
3309
  this.peer = server.local;
3216
3310
  this.peerCred = target.listenerCred;
3217
3311
  link(this, server);
@@ -3352,6 +3446,8 @@ var LoopbackNet = class {
3352
3446
  bound = /* @__PURE__ */ new Map();
3353
3447
  activators = /* @__PURE__ */ new Map();
3354
3448
  internal = /* @__PURE__ */ new Set();
3449
+ reserved = /* @__PURE__ */ new Set();
3450
+ facades = /* @__PURE__ */ new Map();
3355
3451
  nextEphemeral = EPHEMERAL_FIRST;
3356
3452
  route;
3357
3453
  useUplink(route) {
@@ -3366,6 +3462,18 @@ var LoopbackNet = class {
3366
3462
  if (reserve) this.internal.add(port);
3367
3463
  return this.internal.has(port);
3368
3464
  }
3465
+ reserve(port) {
3466
+ this.reserved.add(port);
3467
+ }
3468
+ facade(port, name2) {
3469
+ this.reserve(port);
3470
+ this.kernelOnly(port, true);
3471
+ this.facades.set(port, name2);
3472
+ }
3473
+ facadeAt(addr) {
3474
+ if (addr.family !== "inet" || !isLoopback(canonicalHost(addr.host))) return void 0;
3475
+ return this.facades.get(addr.port);
3476
+ }
3369
3477
  activate(addr, start2) {
3370
3478
  this.activators.set(keyOf(addr), start2);
3371
3479
  }
@@ -3374,7 +3482,7 @@ var LoopbackNet = class {
3374
3482
  }
3375
3483
  listen(addr, backlog = 128) {
3376
3484
  const socket = this.socket(addr.family);
3377
- socket.bind(addr);
3485
+ socket.bind(addr, true);
3378
3486
  socket.listen(backlog);
3379
3487
  return socket;
3380
3488
  }
@@ -3383,11 +3491,13 @@ var LoopbackNet = class {
3383
3491
  void socket.connect(addr, false);
3384
3492
  return socket;
3385
3493
  }
3386
- bind(socket, addr) {
3494
+ bind(socket, addr, kernel = false) {
3387
3495
  if (addr.family === "unix") {
3388
3496
  if (addr.path === "") throw new KernelError("EINVAL");
3389
3497
  } else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
3390
3498
  throw new KernelError("EADDRNOTAVAIL");
3499
+ } else if (!kernel && this.reserved.has(addr.port)) {
3500
+ throw new KernelError("EADDRINUSE");
3391
3501
  }
3392
3502
  const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
3393
3503
  const key = keyOf(named);
@@ -3414,7 +3524,7 @@ var LoopbackNet = class {
3414
3524
  for (let i = 0; i < span; i++) {
3415
3525
  const port = this.nextEphemeral;
3416
3526
  this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
3417
- if (!this.bound.has(`inet:${port}`)) return port;
3527
+ if (!this.bound.has(`inet:${port}`) && !this.reserved.has(port)) return port;
3418
3528
  }
3419
3529
  throw new KernelError("EADDRINUSE");
3420
3530
  }
@@ -3710,12 +3820,12 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3710
3820
  }
3711
3821
  sockets.set(call.nid, socket);
3712
3822
  peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
3713
- void relay(call.nid, socket);
3823
+ void relay2(call.nid, socket);
3714
3824
  } catch (e) {
3715
3825
  fail2(call.nid, e);
3716
3826
  }
3717
3827
  };
3718
- const relay = async (nid, socket) => {
3828
+ const relay2 = async (nid, socket) => {
3719
3829
  let ending;
3720
3830
  let late;
3721
3831
  const unanswered = new Promise((resolve2) => {
@@ -4349,6 +4459,45 @@ function cdpRegistry(send, gone) {
4349
4459
  }
4350
4460
  };
4351
4461
  }
4462
+ function facadeRegistry(send, gone) {
4463
+ const registered = /* @__PURE__ */ new Map();
4464
+ const drop = (id2) => {
4465
+ registered.get(id2)?.();
4466
+ return registered.delete(id2);
4467
+ };
4468
+ return {
4469
+ serve(l, req) {
4470
+ const hook = portFacadeHook(
4471
+ (request2, facade) => send({ facadeOpen: { ...request2, registration: req.id }, port: facade }, [
4472
+ facade
4473
+ ]),
4474
+ gone
4475
+ );
4476
+ registered.set(req.id, l.facades.register(req.name, hook));
4477
+ return req.id;
4478
+ },
4479
+ drop,
4480
+ close: () => {
4481
+ for (const id2 of [...registered.keys()]) drop(id2);
4482
+ }
4483
+ };
4484
+ }
4485
+ function hostRegistries(send, gone) {
4486
+ const cdps = cdpRegistry(send, gone);
4487
+ const facades = facadeRegistry(send, gone);
4488
+ return {
4489
+ answer(l, req) {
4490
+ if (req.op === "serve-cdp") return cdps.serve(l, req);
4491
+ if (req.op === "unserve-cdp") return cdps.drop(req.registration);
4492
+ if (req.op === "serve-facade") return facades.serve(l, req);
4493
+ return facades.drop(req.registration);
4494
+ },
4495
+ close() {
4496
+ cdps.close();
4497
+ facades.close();
4498
+ }
4499
+ };
4500
+ }
4352
4501
  function visibleTo(g, notify) {
4353
4502
  if (!g?.dac.enforcing) return notify;
4354
4503
  let queue = Promise.resolve();
@@ -4392,7 +4541,7 @@ function serveClient(port, host) {
4392
4541
  if (state !== "closed") port.postMessage(message3, transfer);
4393
4542
  };
4394
4543
  const reply = (id2, body, transfer) => send({ id: id2, ...body }, transfer);
4395
- const cdps = cdpRegistry(send, ended.promise);
4544
+ const hosts = hostRegistries(send, ended.promise);
4396
4545
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
4397
4546
  if (state === "closed") return;
4398
4547
  send({ bye });
@@ -4400,7 +4549,7 @@ function serveClient(port, host) {
4400
4549
  net?.close();
4401
4550
  for (const unwatch of watches.values()) unwatch();
4402
4551
  watches.clear();
4403
- cdps.close();
4552
+ hosts.close();
4404
4553
  for (const session of terminals.values()) session.close();
4405
4554
  terminals.clear();
4406
4555
  if (kill) killGroups(host, [...groups]);
@@ -4497,16 +4646,16 @@ function serveClient(port, host) {
4497
4646
  case "mount":
4498
4647
  return { result: await l.mountAs(clientCred(self), req.spec) };
4499
4648
  case "umount":
4500
- await l.umountAs(clientCred(self), req.target);
4501
- return { result: true };
4649
+ return { result: await l.umountAs(clientCred(self), req.target).then(() => true) };
4502
4650
  case "mounts":
4503
4651
  return { result: l.mounts.list() };
4504
4652
  case "dial":
4505
4653
  return dialFor(l, req, watches);
4506
4654
  case "serve-cdp":
4507
- return { result: cdps.serve(l, req) };
4508
4655
  case "unserve-cdp":
4509
- return { result: cdps.drop(req.registration) };
4656
+ case "serve-facade":
4657
+ case "unserve-facade":
4658
+ return { result: hosts.answer(l, req) };
4510
4659
  case "detach":
4511
4660
  return { result: true };
4512
4661
  default:
@@ -6599,7 +6748,7 @@ var RemoteTransport = class {
6599
6748
  finish();
6600
6749
  throw e;
6601
6750
  }
6602
- const { status, statusText, headers, url, redirected, redirects } = head;
6751
+ const { status, statusText, headers, url, redirected: redirected2, redirects } = head;
6603
6752
  const read = async () => {
6604
6753
  if (finished) return void 0;
6605
6754
  const reply = await this.ask({ net: "read", nid }).catch((e) => {
@@ -6616,7 +6765,7 @@ var RemoteTransport = class {
6616
6765
  this.cancel(nid);
6617
6766
  };
6618
6767
  return {
6619
- ...redirected ? { url, redirected } : {},
6768
+ ...redirected2 ? { url, redirected: redirected2 } : {},
6620
6769
  ...redirects ? { redirects } : {},
6621
6770
  status,
6622
6771
  statusText,
@@ -7081,6 +7230,7 @@ var Relay = class {
7081
7230
  function enableCdp(net, hosts) {
7082
7231
  let facade;
7083
7232
  net.kernelOnly(CDP_PORT, true);
7233
+ net.reserve(CDP_PORT);
7084
7234
  net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
7085
7235
  const started = new CdpFacade({ net, hosts });
7086
7236
  facade = started;
@@ -7395,9 +7545,9 @@ async function scanFilesystems(fs, modules) {
7395
7545
  } catch {
7396
7546
  continue;
7397
7547
  }
7398
- const declared = manifest.slicc?.filesystems;
7399
- if (!declared || typeof declared !== "object") continue;
7400
- for (const [type, entry] of Object.entries(declared)) {
7548
+ const declared2 = manifest.slicc?.filesystems;
7549
+ if (!declared2 || typeof declared2 !== "object") continue;
7550
+ for (const [type, entry] of Object.entries(declared2)) {
7401
7551
  const module = inside(pkg, entry?.module);
7402
7552
  if (NAME2.test(type) && module && !found.has(type)) found.set(type, module);
7403
7553
  }
@@ -7415,9 +7565,9 @@ async function scanBinfmts(fs, modules, warn = () => {
7415
7565
  } catch {
7416
7566
  continue;
7417
7567
  }
7418
- const declared = manifest.slicc?.binfmt;
7419
- if (!declared || typeof declared !== "object") continue;
7420
- for (const [suffix, command] of Object.entries(declared)) {
7568
+ const declared2 = manifest.slicc?.binfmt;
7569
+ if (!declared2 || typeof declared2 !== "object") continue;
7570
+ for (const [suffix, command] of Object.entries(declared2)) {
7421
7571
  if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME2.test(command)) continue;
7422
7572
  const owner = owners.get(suffix);
7423
7573
  if (owner === void 0) {
@@ -7447,6 +7597,132 @@ function binfmtOf(binfmts, path) {
7447
7597
  return best === void 0 ? void 0 : binfmts.get(best);
7448
7598
  }
7449
7599
 
7600
+ // src/facade/types.ts
7601
+ var FACADE_NAME = /^[a-z][a-z0-9-]*$/;
7602
+ function facadeEnvName(name2) {
7603
+ return `SLICC_${name2.toUpperCase().replaceAll("-", "_")}_URL`;
7604
+ }
7605
+ function facadeUrl(port) {
7606
+ return `tcp://127.0.0.1:${port}`;
7607
+ }
7608
+
7609
+ // src/facade/hosts.ts
7610
+ var TAKEN_PORTS = /* @__PURE__ */ new Set([CDP_PORT, REALM_PROXY_PORT]);
7611
+ var READ_CHUNK = 64 * 1024;
7612
+ var FACADE_MAX_PENDING = 16 * 1024 * 1024;
7613
+ var FACADE_FLOODED = `slicc-kernel: more than ${FACADE_MAX_PENDING} bytes are waiting for the program to read`;
7614
+ function declared(specs) {
7615
+ const out = /* @__PURE__ */ new Map();
7616
+ const ports = /* @__PURE__ */ new Set();
7617
+ for (const [name2, spec] of Object.entries(specs ?? {})) {
7618
+ if (!FACADE_NAME.test(name2)) throw new TypeError(`facades: not a facade name: ${name2}`);
7619
+ const port = spec?.port;
7620
+ if (!Number.isInteger(port) || port < 1 || port > 65535 || TAKEN_PORTS.has(port)) {
7621
+ throw new TypeError(`facades.${name2}: not a free port: ${String(port)}`);
7622
+ }
7623
+ if (ports.has(port)) throw new TypeError(`facades.${name2}: port ${port} is taken twice`);
7624
+ if (spec.hook !== void 0 && typeof spec.hook !== "function") {
7625
+ throw new TypeError(`facades.${name2}: hook must be a function`);
7626
+ }
7627
+ ports.add(port);
7628
+ out.set(name2, { port, embedder: spec.hook, clients: [] });
7629
+ }
7630
+ return out;
7631
+ }
7632
+ var FacadeHosts = class {
7633
+ facades;
7634
+ constructor(specs) {
7635
+ this.facades = declared(specs);
7636
+ }
7637
+ env() {
7638
+ const env = {};
7639
+ for (const [name2, { port }] of this.facades) env[facadeEnvName(name2)] = facadeUrl(port);
7640
+ return env;
7641
+ }
7642
+ enable(net) {
7643
+ for (const [name2, { port }] of this.facades) {
7644
+ net.facade(port, name2);
7645
+ net.activate({ family: "inet", host: "127.0.0.1", port }, () => {
7646
+ const listener = net.listen({ family: "inet", host: "127.0.0.1", port });
7647
+ void acceptAll(listener, (socket) => this.serve(name2, socket));
7648
+ });
7649
+ }
7650
+ }
7651
+ register(name2, hook) {
7652
+ const entry = this.facades.get(name2);
7653
+ if (!entry || typeof hook !== "function") throw new KernelError("EINVAL");
7654
+ entry.clients.push(hook);
7655
+ return () => {
7656
+ const at = entry.clients.indexOf(hook);
7657
+ if (at >= 0) entry.clients.splice(at, 1);
7658
+ };
7659
+ }
7660
+ async serve(name2, socket) {
7661
+ const entry = this.facades.get(name2);
7662
+ const hook = entry.clients.at(-1) ?? entry.embedder;
7663
+ const stamp = socket.peerStamp;
7664
+ if (!hook || !stamp) return socket.close();
7665
+ let conn;
7666
+ try {
7667
+ conn = await hook(structuredClone(stamp), { name: name2 });
7668
+ } catch {
7669
+ return socket.close();
7670
+ }
7671
+ await relay(socket, conn);
7672
+ }
7673
+ };
7674
+ async function acceptAll(listener, serve) {
7675
+ for (; ; ) {
7676
+ const socket = await listener.accept();
7677
+ void serve(socket);
7678
+ }
7679
+ }
7680
+ async function relay(socket, conn) {
7681
+ let ended = false;
7682
+ let pending = 0;
7683
+ let writes = Promise.resolve();
7684
+ const end = () => {
7685
+ if (ended) return;
7686
+ ended = true;
7687
+ socket.close();
7688
+ };
7689
+ conn.onmessage = (bytes) => {
7690
+ if (ended) return;
7691
+ const chunk2 = bytes.slice();
7692
+ pending += chunk2.length;
7693
+ if (pending > FACADE_MAX_PENDING) {
7694
+ end();
7695
+ conn.close(FACADE_FLOODED);
7696
+ return;
7697
+ }
7698
+ writes = writes.then(async () => {
7699
+ if (ended) return;
7700
+ let at = 0;
7701
+ while (at < chunk2.length) at += await socket.write(chunk2.subarray(at));
7702
+ pending -= chunk2.length;
7703
+ });
7704
+ writes.catch(() => {
7705
+ end();
7706
+ conn.close();
7707
+ });
7708
+ };
7709
+ conn.onclose = () => {
7710
+ writes.then(end, end);
7711
+ };
7712
+ try {
7713
+ for (; ; ) {
7714
+ const bytes = await socket.read(READ_CHUNK);
7715
+ if (bytes.length === 0 || ended) break;
7716
+ conn.send(bytes);
7717
+ }
7718
+ } catch {
7719
+ }
7720
+ if (!ended) {
7721
+ end();
7722
+ conn.close();
7723
+ }
7724
+ }
7725
+
7450
7726
  // src/fs/attest.ts
7451
7727
  var ATTEST_MAX = 1024 * 1024;
7452
7728
  var UNOWNED = 1e3;
@@ -7641,14 +7917,14 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
7641
7917
  const own = await userDir(dir);
7642
7918
  return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
7643
7919
  }
7644
- async function linked(path) {
7920
+ async function linked2(path) {
7645
7921
  return await virtual(path) ?? virtual(await followLinks(fs, path));
7646
7922
  }
7647
7923
  async function stat(path, real) {
7648
7924
  try {
7649
7925
  return await real(path);
7650
7926
  } catch (err) {
7651
- const found = missing3(err) ? await linked(path) : void 0;
7927
+ const found = missing3(err) ? await linked2(path) : void 0;
7652
7928
  if (found) return found;
7653
7929
  throw err;
7654
7930
  }
@@ -7660,7 +7936,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
7660
7936
  try {
7661
7937
  return await fs.readFileBuffer(path);
7662
7938
  } catch (err) {
7663
- if (missing3(err) && (await linked(path))?.isFile) return new Uint8Array(0);
7939
+ if (missing3(err) && (await linked2(path))?.isFile) return new Uint8Array(0);
7664
7940
  throw err;
7665
7941
  }
7666
7942
  },
@@ -7668,7 +7944,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
7668
7944
  link: (from, to, follow2) => fs.link(from, to, follow2),
7669
7945
  links: (path) => mayLink(fs, path),
7670
7946
  async exists(path) {
7671
- return await fs.exists(path) || await linked(path) !== void 0;
7947
+ return await fs.exists(path) || await linked2(path) !== void 0;
7672
7948
  },
7673
7949
  stat: (path) => stat(path, (p) => fs.stat(p)),
7674
7950
  lstat: (path) => stat(path, (p) => fs.lstat(p)),
@@ -9093,6 +9369,7 @@ var MountTable = class {
9093
9369
  return { quota: st.blocks * st.bsize, usage: (st.blocks - st.bfree) * st.bsize };
9094
9370
  }
9095
9371
  wrap(base) {
9372
+ base.crossings?.((path) => this.locate(normalizePath(path)) !== void 0);
9096
9373
  return new MountFs(this, base);
9097
9374
  }
9098
9375
  };
@@ -9116,6 +9393,37 @@ var MountFs = class {
9116
9393
  writable(mount, path) {
9117
9394
  if (readonly(mount)) throw errnoError("EROFS", path);
9118
9395
  }
9396
+ async through(path) {
9397
+ const real = normalizePath(path);
9398
+ if (this.at(real)) return real;
9399
+ try {
9400
+ await this.base.stat(parentOf7(real));
9401
+ return real;
9402
+ } catch (err) {
9403
+ const moved2 = redirectOf(err);
9404
+ return moved2 ? normalizePath(moved2.to + real.slice(moved2.of.length)) : real;
9405
+ }
9406
+ }
9407
+ async across(from, to, redirects) {
9408
+ const moved2 = [await this.through(from), await this.through(to)];
9409
+ const same = moved2[0] === normalizePath(from) && moved2[1] === normalizePath(to);
9410
+ if (same || redirects >= MAX_LINKS7) throw errnoError("EXDEV", `${from} -> ${to}`);
9411
+ return moved2;
9412
+ }
9413
+ async based(paths, redirects, run2, again) {
9414
+ try {
9415
+ return await run2();
9416
+ } catch (err) {
9417
+ const moved2 = redirectOf(err);
9418
+ if (!moved2) throw err;
9419
+ if (redirects >= MAX_LINKS7) throw errnoError("ELOOP", paths[0]);
9420
+ const at = paths.map((path) => {
9421
+ const real = normalizePath(path);
9422
+ return within6(real, moved2.of) ? normalizePath(moved2.to + real.slice(moved2.of.length)) : real;
9423
+ });
9424
+ return again(at, redirects + 1);
9425
+ }
9426
+ }
9119
9427
  async follow(path, hops = 0, create = false) {
9120
9428
  const found = this.at(path);
9121
9429
  if (!found) return path;
@@ -9163,10 +9471,17 @@ var MountFs = class {
9163
9471
  async readFile(path) {
9164
9472
  return new TextDecoder().decode(await this.readFileBuffer(path));
9165
9473
  }
9166
- async readFileBuffer(path) {
9474
+ async readFileBuffer(path, redirects = 0) {
9167
9475
  const real = await this.follow(path);
9168
9476
  const found = this.at(real);
9169
- if (!found) return this.base.readFileBuffer(real);
9477
+ if (!found) {
9478
+ return this.based(
9479
+ [real],
9480
+ redirects,
9481
+ () => this.base.readFileBuffer(real),
9482
+ ([to], n) => this.readFileBuffer(to, n)
9483
+ );
9484
+ }
9170
9485
  for (let attempt = 1; ; attempt++) {
9171
9486
  try {
9172
9487
  return await this.readWhole(found.mount, real, found.rel);
@@ -9205,10 +9520,18 @@ var MountFs = class {
9205
9520
  }
9206
9521
  return concat4(chunks, total);
9207
9522
  }
9208
- async writeFile(path, content, owner) {
9523
+ async writeFile(path, content, owner, redirects = 0) {
9209
9524
  const real = await this.follow(path, 0, true);
9210
9525
  const found = this.at(real);
9211
- if (!found) return this.base.writeFile(real, content, owner);
9526
+ if (!found) {
9527
+ const write = () => this.base.writeFile(real, content, owner);
9528
+ return this.based(
9529
+ [real],
9530
+ redirects,
9531
+ write,
9532
+ ([to], n) => this.writeFile(to, content, owner, n)
9533
+ );
9534
+ }
9212
9535
  const { mount, rel: rel2 } = found;
9213
9536
  this.writable(mount, real);
9214
9537
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
@@ -9242,10 +9565,18 @@ var MountFs = class {
9242
9565
  this.table.forget(mount, real);
9243
9566
  }
9244
9567
  }
9245
- async pread(path, offset, length2, version) {
9568
+ async pread(path, offset, length2, version, redirects = 0) {
9246
9569
  const real = await this.follow(path);
9247
9570
  const found = this.at(real);
9248
- if (!found && this.base.pread) return this.base.pread(real, offset, length2);
9571
+ const { pread } = this.base;
9572
+ if (!found && pread) {
9573
+ return this.based(
9574
+ [real],
9575
+ redirects,
9576
+ () => pread.call(this.base, real, offset, length2),
9577
+ ([to], n) => this.pread(to, offset, length2, version, n)
9578
+ );
9579
+ }
9249
9580
  if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
9250
9581
  const { mount, rel: rel2 } = found;
9251
9582
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
@@ -9292,10 +9623,18 @@ var MountFs = class {
9292
9623
  );
9293
9624
  }
9294
9625
  }
9295
- async pwrite(path, offset, bytes, transfer = false) {
9626
+ async pwrite(path, offset, bytes, transfer = false, redirects = 0) {
9296
9627
  const real = await this.follow(path, 0, true);
9297
9628
  const found = this.at(real);
9298
- if (!found && this.base.pwrite) return this.base.pwrite(real, offset, bytes);
9629
+ const { pwrite } = this.base;
9630
+ if (!found && pwrite) {
9631
+ return this.based(
9632
+ [real],
9633
+ redirects,
9634
+ () => pwrite.call(this.base, real, offset, bytes),
9635
+ ([to], n) => this.pwrite(to, offset, bytes, transfer, n)
9636
+ );
9637
+ }
9299
9638
  if (!found)
9300
9639
  return this.base.writeFile(
9301
9640
  real,
@@ -9335,10 +9674,18 @@ var MountFs = class {
9335
9674
  await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
9336
9675
  }
9337
9676
  }
9338
- async truncate(path, size) {
9677
+ async truncate(path, size, redirects = 0) {
9339
9678
  const real = await this.follow(path);
9340
9679
  const found = this.at(real);
9341
- if (!found && this.base.truncate) return this.base.truncate(real, size);
9680
+ const { truncate } = this.base;
9681
+ if (!found && truncate) {
9682
+ return this.based(
9683
+ [real],
9684
+ redirects,
9685
+ () => truncate.call(this.base, real, size),
9686
+ ([to], n) => this.truncate(to, size, n)
9687
+ );
9688
+ }
9342
9689
  if (!found?.mount.caps.ranges) {
9343
9690
  return this.writeFile(real, resized(await this.readFileBuffer(real), size));
9344
9691
  }
@@ -9354,8 +9701,15 @@ var MountFs = class {
9354
9701
  this.table.forget(mount, real);
9355
9702
  }
9356
9703
  }
9357
- async exists(path) {
9358
- if (!this.at(path)) return this.base.exists(path);
9704
+ async exists(path, redirects = 0) {
9705
+ if (!this.at(path)) {
9706
+ return this.based(
9707
+ [path],
9708
+ redirects,
9709
+ () => this.base.exists(path),
9710
+ ([to], n) => this.exists(to, n)
9711
+ );
9712
+ }
9359
9713
  try {
9360
9714
  await this.stat(path);
9361
9715
  return true;
@@ -9365,9 +9719,16 @@ var MountFs = class {
9365
9719
  throw err;
9366
9720
  }
9367
9721
  }
9368
- async lstat(path) {
9722
+ async lstat(path, redirects = 0) {
9369
9723
  const found = this.at(path);
9370
- if (!found) return this.base.lstat(path);
9724
+ if (!found) {
9725
+ return this.based(
9726
+ [path],
9727
+ redirects,
9728
+ () => this.base.lstat(path),
9729
+ ([to], n) => this.lstat(to, n)
9730
+ );
9731
+ }
9371
9732
  const real = normalizePath(path);
9372
9733
  if (this.table.writing(real)?.truncated) await this.table.commit(real);
9373
9734
  return this.statWriting(
@@ -9380,9 +9741,15 @@ var MountFs = class {
9380
9741
  const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
9381
9742
  return statOf({ ...attr2, size: Math.max(attr2.size, ...ends) }, real, mount);
9382
9743
  }
9383
- async stat(path) {
9744
+ async stat(path, redirects = 0) {
9384
9745
  const real = await this.follow(path);
9385
- return this.at(real) ? this.lstat(real) : this.base.stat(real);
9746
+ if (this.at(real)) return this.lstat(real);
9747
+ return this.based(
9748
+ [real],
9749
+ redirects,
9750
+ () => this.base.stat(real),
9751
+ ([to], n) => this.stat(to, n)
9752
+ );
9386
9753
  }
9387
9754
  async entries(path) {
9388
9755
  const real = await this.follow(path);
@@ -9394,12 +9761,26 @@ var MountFs = class {
9394
9761
  list: await this.table.readdir(found.mount, real, found.rel)
9395
9762
  };
9396
9763
  }
9397
- async readdir(path) {
9398
- if (!this.at(path)) return this.base.readdir(path);
9764
+ async readdir(path, redirects = 0) {
9765
+ if (!this.at(path)) {
9766
+ return this.based(
9767
+ [path],
9768
+ redirects,
9769
+ () => this.base.readdir(path),
9770
+ ([to], n) => this.readdir(to, n)
9771
+ );
9772
+ }
9399
9773
  return (await this.entries(path)).list.map((e) => e.name);
9400
9774
  }
9401
- async readdirStat(path) {
9402
- if (!this.at(path)) return this.base.readdirStat(path);
9775
+ async readdirStat(path, redirects = 0) {
9776
+ if (!this.at(path)) {
9777
+ return this.based(
9778
+ [path],
9779
+ redirects,
9780
+ () => this.base.readdirStat(path),
9781
+ ([to], n) => this.readdirStat(to, n)
9782
+ );
9783
+ }
9403
9784
  const { real, mount, list } = await this.entries(path);
9404
9785
  return Promise.all(
9405
9786
  list.map(async (entry) => {
@@ -9411,9 +9792,16 @@ var MountFs = class {
9411
9792
  })
9412
9793
  );
9413
9794
  }
9414
- async mkdir(path, options = {}) {
9795
+ async mkdir(path, options = {}, redirects = 0) {
9415
9796
  const found = this.at(path);
9416
- if (!found) return this.base.mkdir(path, options);
9797
+ if (!found) {
9798
+ return this.based(
9799
+ [path],
9800
+ redirects,
9801
+ () => this.base.mkdir(path, options),
9802
+ ([to], n) => this.mkdir(to, options, n)
9803
+ );
9804
+ }
9417
9805
  const { mount, rel: rel2 } = found;
9418
9806
  const real = normalizePath(path);
9419
9807
  this.writable(mount, real);
@@ -9438,11 +9826,18 @@ var MountFs = class {
9438
9826
  await mount.conn.call({ op: "mkdir", path: sub, ...made2 });
9439
9827
  }
9440
9828
  }
9441
- async rm(path, options = {}) {
9829
+ async rm(path, options = {}, redirects = 0) {
9442
9830
  const found = this.at(path);
9443
9831
  if (!found && options.recursive && this.table.under(normalizePath(path)))
9444
9832
  return this.rmAround(normalizePath(path), options);
9445
- if (!found) return this.base.rm(path, options);
9833
+ if (!found) {
9834
+ return this.based(
9835
+ [path],
9836
+ redirects,
9837
+ () => this.base.rm(path, options),
9838
+ ([to], n) => this.rm(to, options, n)
9839
+ );
9840
+ }
9446
9841
  const { mount, rel: rel2 } = found;
9447
9842
  const real = normalizePath(path);
9448
9843
  if (rel2 === "/" && options.recursive) await this.rmEach(real, mount, rel2, options);
@@ -9483,11 +9878,22 @@ var MountFs = class {
9483
9878
  }
9484
9879
  throw first ?? errnoError("EBUSY", `${path} holds a mount point`);
9485
9880
  }
9486
- async rename(from, to) {
9881
+ async rename(from, to, redirects = 0) {
9487
9882
  const a = this.at(from);
9488
9883
  const b = this.at(to);
9489
- if (!a && !b) return this.base.rename(from, to);
9490
- if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
9884
+ if (!a && !b) {
9885
+ return this.based(
9886
+ [from, to],
9887
+ redirects,
9888
+ () => this.base.rename(from, to),
9889
+ ([f, t], n) => this.rename(f, t, n)
9890
+ );
9891
+ }
9892
+ if (!a || !b) {
9893
+ const [f, t] = await this.across(from, to, redirects);
9894
+ return this.rename(f, t, redirects + 1);
9895
+ }
9896
+ if (a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
9491
9897
  this.writable(a.mount, normalizePath(to));
9492
9898
  await this.table.commitUnder(from);
9493
9899
  await this.table.commitUnder(to);
@@ -9528,9 +9934,17 @@ var MountFs = class {
9528
9934
  this.table.forget(a.mount, normalizePath(to));
9529
9935
  }
9530
9936
  }
9531
- async symlink(target, path, owner) {
9937
+ async symlink(target, path, owner, redirects = 0) {
9532
9938
  const found = this.at(path);
9533
- if (!found) return this.base.symlink(target, path, owner);
9939
+ if (!found) {
9940
+ const make = () => this.base.symlink(target, path, owner);
9941
+ return this.based(
9942
+ [path],
9943
+ redirects,
9944
+ make,
9945
+ ([to], n) => this.symlink(target, to, owner, n)
9946
+ );
9947
+ }
9534
9948
  this.writable(found.mount, normalizePath(path));
9535
9949
  if (!found.mount.caps.symlinks)
9536
9950
  throw errnoError("EPERM", `${path}: this file system has no symlinks`);
@@ -9540,8 +9954,11 @@ var MountFs = class {
9540
9954
  await found.mount.conn.call({ op: "symlink", target, path: found.rel, ...made2 });
9541
9955
  }
9542
9956
  async realpath(path, follow2) {
9543
- if (this.at(path) || !this.base.realpath) throw errnoError("ENOSYS", path);
9544
- const real = await this.base.realpath(path, follow2);
9957
+ const { realpath: realpath2 } = this.base;
9958
+ if (this.at(path) || !realpath2) throw errnoError("ENOSYS", path);
9959
+ const real = await realpath2.call(this.base, path, follow2).catch((err) => {
9960
+ throw redirectOf(err) ? errnoError("ENOSYS", path) : err;
9961
+ });
9545
9962
  if (this.at(real)) throw errnoError("ENOSYS", path);
9546
9963
  return real;
9547
9964
  }
@@ -9563,17 +9980,30 @@ var MountFs = class {
9563
9980
  }
9564
9981
  return out;
9565
9982
  }
9566
- async readlink(path) {
9983
+ async readlink(path, redirects = 0) {
9567
9984
  const found = this.at(path);
9568
- if (!found) return this.base.readlink(path);
9985
+ if (!found) {
9986
+ return this.based(
9987
+ [path],
9988
+ redirects,
9989
+ () => this.base.readlink(path),
9990
+ ([to], n) => this.readlink(to, n)
9991
+ );
9992
+ }
9569
9993
  return await found.mount.conn.call({ op: "readlink", path: found.rel });
9570
9994
  }
9571
- async chown(path, change) {
9995
+ async chown(path, change, redirects = 0) {
9572
9996
  const real = change.follow === false || change.kind === "new" ? path : await this.follow(path);
9573
9997
  const found = this.at(real);
9574
9998
  if (found) return this.chownMounted(found, normalizePath(real), change);
9575
- if (!this.base.chown) throw errnoError("EPERM", path);
9576
- return this.base.chown(real, change);
9999
+ const { chown: chown2 } = this.base;
10000
+ if (!chown2) throw errnoError("EPERM", path);
10001
+ return this.based(
10002
+ [real],
10003
+ redirects,
10004
+ () => chown2.call(this.base, real, change),
10005
+ ([to], n) => this.chown(to, change, n)
10006
+ );
9577
10007
  }
9578
10008
  async chownMounted(found, real, change) {
9579
10009
  const { mount, rel: rel2 } = found;
@@ -9593,24 +10023,47 @@ var MountFs = class {
9593
10023
  ...Object.fromEntries(attrs.filter(([, value]) => value !== void 0))
9594
10024
  });
9595
10025
  }
9596
- async chmod(path, mode) {
10026
+ async chmod(path, mode, redirects = 0) {
9597
10027
  const real = await this.follow(path);
9598
10028
  const found = this.at(real);
9599
- if (!found) return this.base.chmod(real, mode);
10029
+ if (!found) {
10030
+ return this.based(
10031
+ [real],
10032
+ redirects,
10033
+ () => this.base.chmod(real, mode),
10034
+ ([to], n) => this.chmod(to, mode, n)
10035
+ );
10036
+ }
9600
10037
  this.writable(found.mount, real);
9601
10038
  if (!found.mount.caps.chmod) return;
9602
10039
  this.table.forget(found.mount, real);
9603
10040
  await found.mount.conn.call({ op: "setattr", path: found.rel, mode });
9604
10041
  }
9605
- async utimes(path, atime, mtime) {
10042
+ async utimes(path, atime, mtime, redirects = 0) {
9606
10043
  const real = await this.follow(path);
9607
10044
  const found = this.at(real);
9608
- if (!found) return this.base.utimes(real, atime, mtime);
10045
+ if (!found) {
10046
+ const set2 = () => this.base.utimes(real, atime, mtime);
10047
+ return this.based(
10048
+ [real],
10049
+ redirects,
10050
+ set2,
10051
+ ([to], n) => this.utimes(to, atime, mtime, n)
10052
+ );
10053
+ }
9609
10054
  await this.setMtime(found, real, mtime);
9610
10055
  }
9611
- async lutimes(path, atime, mtime) {
10056
+ async lutimes(path, atime, mtime, redirects = 0) {
9612
10057
  const found = this.at(path);
9613
- if (!found) return lutimesOf(this.base, path, atime, mtime);
10058
+ if (!found) {
10059
+ const set2 = () => lutimesOf(this.base, path, atime, mtime);
10060
+ return this.based(
10061
+ [path],
10062
+ redirects,
10063
+ set2,
10064
+ ([to], n) => this.lutimes(to, atime, mtime, n)
10065
+ );
10066
+ }
9614
10067
  const real = normalizePath(path);
9615
10068
  if (!found.mount.caps.linkTimes && (await this.lstat(real)).isSymbolicLink) {
9616
10069
  throw errnoError("EOPNOTSUPP", path);
@@ -9829,7 +10282,8 @@ async function run(entry, path, req) {
9829
10282
  function guardOf(entry) {
9830
10283
  const cred = entry.dac && entry.cred?.();
9831
10284
  if (!cred || !entry.dac) return void 0;
9832
- return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac };
10285
+ const links = entry.links === void 0 ? {} : { links: entry.links };
10286
+ return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac, ...links };
9833
10287
  }
9834
10288
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
9835
10289
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
@@ -9890,9 +10344,15 @@ async function holdOp(entry, path, req) {
9890
10344
  entry.hold?.(path, on, open, void 0, req.handle);
9891
10345
  return done;
9892
10346
  }
10347
+ async function followed(g, path) {
10348
+ const st = await g.fs.lstat(path).catch(() => void 0);
10349
+ if (st?.isSymbolicLink && !trustedLink(permOf(st), g.links)) deny(g, "follow", path);
10350
+ return done;
10351
+ }
9893
10352
  async function accessOp(entry, path, req) {
9894
10353
  const g = guardOf(entry);
9895
10354
  if (!g) return done;
10355
+ if (req.arg2 === "follow") return followed(g, path);
9896
10356
  const open = req.arg2 === "open";
9897
10357
  const cred = open || req.arg2 === "eaccess" ? g.cred : realIds(g.cred);
9898
10358
  const options = { follow: req.nofollow !== true, ...open ? { directory: false } : {} };
@@ -9915,15 +10375,18 @@ var CHANGING = /* @__PURE__ */ new Set([
9915
10375
  "lchown"
9916
10376
  ]);
9917
10377
  var CONTENT = /* @__PURE__ */ new Set(["write", "pwrite", "truncate"]);
10378
+ var REMOVING = /* @__PURE__ */ new Set(["unlink", "rmdir", "rm", "rename"]);
9918
10379
  async function mayChange(entry, path, req) {
9919
10380
  const { mayChange: mayChange2 } = entry.fs;
9920
10381
  if (!mayChange2) return;
9921
10382
  const { op } = req;
9922
10383
  if (CONTENT.has(op) && req.handle === void 0) return mayChange2.call(entry.fs, path, true);
9923
10384
  if (!CHANGING.has(op)) return;
9924
- await mayChange2.call(entry.fs, path, op === "utimes" || op === "chmod" || op === "chown");
10385
+ const follow2 = op === "utimes" || op === "chmod" || op === "chown";
10386
+ await mayChange2.call(entry.fs, path, follow2, REMOVING.has(op));
9925
10387
  if (op === "rename" || op === "link") {
9926
- await mayChange2.call(entry.fs, entry.fs.resolvePath(entry.cwd, req.arg2 ?? ""), false);
10388
+ const to = entry.fs.resolvePath(entry.cwd, req.arg2 ?? "");
10389
+ await mayChange2.call(entry.fs, to, false, op === "rename");
9927
10390
  }
9928
10391
  }
9929
10392
  async function guarded(entry, path, req) {
@@ -10616,7 +11079,7 @@ function mountTable(mounts = []) {
10616
11079
  }
10617
11080
 
10618
11081
  // src/version.ts
10619
- var KERNEL_VERSION = true ? "1.88.1" : "0.0.0-dev";
11082
+ var KERNEL_VERSION = true ? "1.90.0" : "0.0.0-dev";
10620
11083
  var OS_RELEASE = "6.0.0-slicc";
10621
11084
  function procVersion() {
10622
11085
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -12256,7 +12719,9 @@ async function socketSyscall(req, proc) {
12256
12719
  case "sock-accept":
12257
12720
  return ok(await accept(proc, req.fd, req.nonblock));
12258
12721
  case "sock-connect": {
12259
- const dialing = socketAt(fds, req.fd).connect(req.addr, true, proc.cred?.());
12722
+ const socket = socketAt(fds, req.fd);
12723
+ const stamp = net.facadeAt(req.addr) && await proc.stamp?.();
12724
+ const dialing = socket.connect(req.addr, true, proc.cred?.(), stamp || void 0);
12260
12725
  if (req.nonblock) {
12261
12726
  dialing?.catch(() => void 0);
12262
12727
  throw new KernelError("EINPROGRESS");
@@ -13387,7 +13852,7 @@ var WasmProcess = class {
13387
13852
  const { guard: guard2 } = this.options;
13388
13853
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
13389
13854
  if (!guard2 || inherited2 || req.pin !== void 0) return;
13390
- const g = { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
13855
+ const g = { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid, links: guard2.links };
13391
13856
  if (req.exclusive) return inParent(g, "open", req.path);
13392
13857
  if (req.created && guard2.created?.take(req.path)) return;
13393
13858
  const access2 = req.flags & WRITE_ACCESS;
@@ -13435,6 +13900,9 @@ var WasmProcess = class {
13435
13900
  if (channel) channel.stamp = stamp;
13436
13901
  }
13437
13902
  }
13903
+ connectorStamp() {
13904
+ return this.sealedCaller();
13905
+ }
13438
13906
  async sealedCaller() {
13439
13907
  const shown = await this.options.identity?.();
13440
13908
  const broker = this.options.sealed;
@@ -13443,7 +13911,7 @@ var WasmProcess = class {
13443
13911
  }
13444
13912
  guarded() {
13445
13913
  const { guard: guard2 } = this.options;
13446
- return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
13914
+ return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid, links: guard2.links };
13447
13915
  }
13448
13916
  ttyOwner() {
13449
13917
  return { uid: this.cred.euid, gid: this.cred.egid, mode: TTY_MODE };
@@ -13773,7 +14241,8 @@ var WasmProcess = class {
13773
14241
  net: this.net,
13774
14242
  blocking: () => this.blockingSignal(cancelled),
13775
14243
  resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking),
13776
- cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid })
14244
+ cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid }),
14245
+ stamp: () => this.connectorStamp()
13777
14246
  });
13778
14247
  }
13779
14248
  tty(fd) {
@@ -14169,6 +14638,7 @@ function processInit(opts, sab, umask, cred, lane) {
14169
14638
  umask,
14170
14639
  cred,
14171
14640
  ...opts.owners ? { owners: true } : {},
14641
+ ...opts.links !== void 0 ? { links: true } : {},
14172
14642
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
14173
14643
  };
14174
14644
  if (opts.program.abi === "js") {
@@ -14196,6 +14666,7 @@ function holdingToken(opts, holds, caller) {
14196
14666
  return mintSyncFsToken({
14197
14667
  ...opts.owners ? caller : {},
14198
14668
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
14669
+ ...opts.links !== void 0 ? { links: opts.links } : {},
14199
14670
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
14200
14671
  fs: opts.fs,
14201
14672
  cwd: opts.cwd,
@@ -14209,6 +14680,9 @@ function holdingToken(opts, holds, caller) {
14209
14680
  renamed: (from, to) => holds.renamed(from, to)
14210
14681
  });
14211
14682
  }
14683
+ function linksOf(opts) {
14684
+ return opts.links === void 0 ? {} : { links: opts.links };
14685
+ }
14212
14686
  function clipboardName(opts) {
14213
14687
  return opts.program.abi === "js" ? {} : { clipboardName: opts.argv0 };
14214
14688
  }
@@ -14220,7 +14694,7 @@ function processOptions(opts, header3, hooks, created3) {
14220
14694
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
14221
14695
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
14222
14696
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
14223
- ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
14697
+ ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3, ...linksOf(opts) } } : {},
14224
14698
  ...opts.identity ? { identity: opts.identity } : {},
14225
14699
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
14226
14700
  ...opts.settling ? { settling: opts.settling } : {},
@@ -14266,6 +14740,7 @@ function threadInit(opts, program, sab, cred, thread, main) {
14266
14740
  sab,
14267
14741
  cred: copyCred(cred),
14268
14742
  owners: opts.owners === true,
14743
+ ...opts.links !== void 0 ? { links: true } : {},
14269
14744
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
14270
14745
  thread,
14271
14746
  signals: main
@@ -17566,6 +18041,9 @@ var SUDO_SOURCE = [
17566
18041
  ].join("\n");
17567
18042
 
17568
18043
  // src/launcher.ts
18044
+ function linksOf2(req) {
18045
+ return req.links === void 0 ? {} : { links: req.links };
18046
+ }
17569
18047
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
17570
18048
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
17571
18049
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -17741,6 +18219,7 @@ var Launcher = class {
17741
18219
  warned = /* @__PURE__ */ new Set();
17742
18220
  compiled = /* @__PURE__ */ new Map();
17743
18221
  texts = /* @__PURE__ */ new Map();
18222
+ revocations = 0;
17744
18223
  integrity;
17745
18224
  scannedFs;
17746
18225
  get scanned() {
@@ -17753,6 +18232,7 @@ var Launcher = class {
17753
18232
  described = /* @__PURE__ */ new Map();
17754
18233
  creds = /* @__PURE__ */ new Map();
17755
18234
  tags = /* @__PURE__ */ new Map();
18235
+ links = /* @__PURE__ */ new Map();
17756
18236
  freshModules = new FreshModules();
17757
18237
  granted = /* @__PURE__ */ new Map();
17758
18238
  users;
@@ -17766,6 +18246,7 @@ var Launcher = class {
17766
18246
  );
17767
18247
  net = new LoopbackNet();
17768
18248
  cdp;
18249
+ facades;
17769
18250
  ca;
17770
18251
  transport;
17771
18252
  uplink;
@@ -17840,10 +18321,16 @@ var Launcher = class {
17840
18321
  this.modulesDir = options.modules ?? "/node_modules";
17841
18322
  this.appended = appendedRoots(options.commands);
17842
18323
  this.cdp = new CdpHosts(options.cdp);
18324
+ this.facades = new FacadeHosts(options.facades);
17843
18325
  const hostname = options.hostname ?? DEFAULT_HOSTNAME;
17844
18326
  if (!isHostname(hostname)) throw new Error(`not a host name: ${String(hostname)}`);
17845
18327
  this.hostname = hostname;
17846
- this.env = { ...networkEnv(hostname), SLICC_CDP_URL: this.cdp.url, ...options.env };
18328
+ this.env = {
18329
+ ...networkEnv(hostname),
18330
+ SLICC_CDP_URL: this.cdp.url,
18331
+ ...this.facades.env(),
18332
+ ...options.env
18333
+ };
17847
18334
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
17848
18335
  this.transport = replayingHops(options.transport ?? missingTransport());
17849
18336
  this.announcedTraits = JSON.stringify(this.transport.traits);
@@ -17858,6 +18345,7 @@ var Launcher = class {
17858
18345
  ca: this.ca
17859
18346
  });
17860
18347
  enableCdp(this.net, this.cdp);
18348
+ this.facades.enable(this.net);
17861
18349
  const fs = withCommandDirs(
17862
18350
  this.base,
17863
18351
  async () => new Set((await this.commands()).keys()),
@@ -18055,14 +18543,16 @@ var Launcher = class {
18055
18543
  }
18056
18544
  const path = this.fs.resolvePath(cwd, file);
18057
18545
  const glue = await followLinks(this.base, path);
18058
- const linked = COMMAND2.exec(glue)?.[1];
18059
- if (linked !== void 0) {
18060
- const target = await this.resolve(`/bin/${linked}`, argv0, cwd);
18061
- const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked)?.argv0Path === true;
18546
+ const asked = glue === path ? {} : { asked: path };
18547
+ const linked2 = COMMAND2.exec(glue)?.[1];
18548
+ if (linked2 !== void 0) {
18549
+ const found = await this.resolve(`/bin/${linked2}`, argv0, cwd);
18550
+ const target = found && { ...found, ...asked };
18551
+ const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked2)?.argv0Path === true;
18062
18552
  return target && byPath ? { ...target, argv0: path } : target;
18063
18553
  }
18064
18554
  const own = await this.commandAt(glue, scope);
18065
- if (own) return own.script ? void 0 : targetOf(own);
18555
+ if (own) return own.script ? void 0 : { ...targetOf(own), ...asked };
18066
18556
  const head = await this.head(glue);
18067
18557
  if (!head) return void 0;
18068
18558
  if (isWasm(head)) {
@@ -18070,13 +18560,14 @@ var Launcher = class {
18070
18560
  abi: "wasi",
18071
18561
  glue,
18072
18562
  wasm: glue,
18073
- argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
18563
+ argv0: baseName(argv0 || file).replace(/\.wasm$/, ""),
18564
+ ...asked
18074
18565
  };
18075
18566
  }
18076
18567
  if (interpreter(head) !== "node") return void 0;
18077
18568
  const wasm = modulePath(glue);
18078
18569
  if (!await this.base.exists(wasm)) return void 0;
18079
- return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
18570
+ return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file), ...asked };
18080
18571
  }
18081
18572
  async head(path) {
18082
18573
  try {
@@ -18094,6 +18585,8 @@ var Launcher = class {
18094
18585
  async interpreted(file, argv, cwd, scope, depth = 0) {
18095
18586
  const command = await this.scriptCommand(file, cwd, scope);
18096
18587
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
18588
+ const byPath = command && file.includes("/") && !COMMAND2.test(file);
18589
+ const named = byPath ? { [script]: this.fs.resolvePath(cwd, file) } : {};
18097
18590
  const head = await this.head(script);
18098
18591
  if (!head) return void 0;
18099
18592
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
@@ -18116,7 +18609,8 @@ var Launcher = class {
18116
18609
  target: withScriptEnv(inner.target, command),
18117
18610
  via,
18118
18611
  sources,
18119
- ...bins.length > 0 ? { bins } : {}
18612
+ ...bins.length > 0 ? { bins } : {},
18613
+ asked: { ...inner.asked, ...named }
18120
18614
  };
18121
18615
  }
18122
18616
  const target = withScriptEnv(found, command);
@@ -18125,7 +18619,8 @@ var Launcher = class {
18125
18619
  args: [...target.prefix ?? [], ...passed],
18126
18620
  via: [interp],
18127
18621
  sources: [script],
18128
- ...command?.bin ? { bins: [script] } : {}
18622
+ ...command?.bin ? { bins: [script] } : {},
18623
+ asked: named
18129
18624
  };
18130
18625
  }
18131
18626
  async plan(file, argv, cwd, scope, argv0) {
@@ -18139,7 +18634,14 @@ var Launcher = class {
18139
18634
  async mayExec(cred, req, planned) {
18140
18635
  const { dac } = this;
18141
18636
  if (!dac) return;
18142
- const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
18637
+ const g = {
18638
+ fs: this.fs,
18639
+ cred,
18640
+ pid: req.pid,
18641
+ dac,
18642
+ tag: req.tag,
18643
+ links: this.links.get(req.pid)
18644
+ };
18143
18645
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
18144
18646
  if (byPath(req.file)) {
18145
18647
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -18296,8 +18798,8 @@ var Launcher = class {
18296
18798
  readFile: (path) => this.integrity ? this.text(path, KERNEL_CALLER) : fs.readFile(path)
18297
18799
  });
18298
18800
  }
18299
- async cacheKey(path) {
18300
- const verified = await this.integrity?.key(path);
18801
+ async cacheKey(path, asked) {
18802
+ const verified = await this.integrity?.key(path, asked);
18301
18803
  if (verified !== void 0) return verified;
18302
18804
  const st = await this.base.stat(path);
18303
18805
  return `${path}:${st.size}:${st.mtime.getTime()}`;
@@ -18306,30 +18808,31 @@ var Launcher = class {
18306
18808
  const plain = () => fs.readFileBuffer(path);
18307
18809
  return this.integrity ? this.integrity.read(path, caller, plain) : plain();
18308
18810
  }
18309
- keyed(path, caller) {
18811
+ keyed(path, caller, asked) {
18310
18812
  const plain = async () => this.base.readFileBuffer(path);
18311
- return this.integrity ? this.integrity.readKeyed(path, caller, plain) : plain().then((bytes) => ({ bytes }));
18813
+ return this.integrity ? this.integrity.readKeyed(path, caller, plain, asked) : plain().then((bytes) => ({ bytes }));
18312
18814
  }
18313
18815
  cached(cache, key, load) {
18314
18816
  const hit = cache.get(key);
18315
18817
  if (hit !== void 0) return hit;
18818
+ const revocations = this.revocations;
18316
18819
  const loading2 = load();
18317
18820
  const found = loading2.then(({ value }) => value);
18318
18821
  cache.set(key, found);
18319
- loading2.then(
18320
- (done4) => {
18321
- if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
18322
- if (cache.get(key) === found) cache.delete(key);
18323
- if (done4.key !== void 0) cache.set(done4.key, found);
18324
- },
18325
- () => cache.delete(key)
18326
- );
18822
+ const drop = () => {
18823
+ if (cache.get(key) === found) cache.delete(key);
18824
+ };
18825
+ loading2.then((done4) => {
18826
+ if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
18827
+ drop();
18828
+ if (done4.key !== void 0 && revocations === this.revocations) cache.set(done4.key, found);
18829
+ }, drop);
18327
18830
  return found;
18328
18831
  }
18329
- async module(path, caller) {
18330
- const key = await this.cacheKey(path);
18832
+ async module(path, caller, asked) {
18833
+ const key = await this.cacheKey(path, asked);
18331
18834
  return this.cached(this.compiled, key, async () => {
18332
- const { bytes, key: read } = await this.keyed(path, caller);
18835
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18333
18836
  const memory = importedMemory(bytes);
18334
18837
  const foreign = foreignImports(bytes);
18335
18838
  const compiled = await WebAssembly.compile(bytes);
@@ -18338,22 +18841,30 @@ var Launcher = class {
18338
18841
  }
18339
18842
  async checkedSources(planned, caller) {
18340
18843
  const { target } = planned;
18341
- const files = [target.wasm, target.glue, target.imports, ...planned.sources ?? []];
18844
+ const files = [
18845
+ target.wasm,
18846
+ target.glue,
18847
+ target.imports,
18848
+ target.asked,
18849
+ ...planned.sources ?? []
18850
+ ];
18342
18851
  const manifests = /* @__PURE__ */ new Set();
18343
18852
  for (const file of files) {
18344
18853
  const root = file === void 0 ? void 0 : PACKAGE_ROOT.exec(file)?.[1];
18345
18854
  if (root !== void 0) manifests.add(`${root}/package.json`);
18346
18855
  }
18347
- for (const source of planned.sources ?? []) await this.text(source, caller);
18856
+ for (const source of planned.sources ?? []) {
18857
+ await this.text(source, caller, planned.asked?.[source]);
18858
+ }
18348
18859
  for (const manifest of manifests) {
18349
18860
  if (await this.base.exists(manifest)) await this.text(manifest, caller);
18350
18861
  }
18351
18862
  }
18352
- async text(path, caller) {
18863
+ async text(path, caller, asked) {
18353
18864
  if (!this.integrity) return this.base.readFile(path);
18354
- const key = await this.cacheKey(path);
18865
+ const key = await this.cacheKey(path, asked);
18355
18866
  return this.cached(this.texts, key, async () => {
18356
- const { bytes, key: read } = await this.keyed(path, caller);
18867
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18357
18868
  return { value: new TextDecoder().decode(bytes), key: read };
18358
18869
  });
18359
18870
  }
@@ -18370,17 +18881,18 @@ var Launcher = class {
18370
18881
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
18371
18882
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
18372
18883
  }
18884
+ const { asked } = target;
18373
18885
  if (target.abi === "js") {
18374
- return { abi: "js", glue: await this.text(target.wasm, caller), path: target.wasm };
18886
+ return { abi: "js", glue: await this.text(target.wasm, caller, asked), path: target.wasm };
18375
18887
  }
18376
18888
  if (target.abi !== "wasi") {
18377
18889
  const [glue, { module: module2 }] = await Promise.all([
18378
- this.text(target.glue, caller),
18379
- this.module(target.wasm, caller)
18890
+ this.text(target.glue, caller, asked),
18891
+ this.module(target.wasm, caller, asked && modulePath(asked))
18380
18892
  ]);
18381
18893
  return { glue, module: module2 };
18382
18894
  }
18383
- const { module, memory, foreign } = await this.module(target.wasm, caller);
18895
+ const { module, memory, foreign } = await this.module(target.wasm, caller, asked);
18384
18896
  const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
18385
18897
  const imports = target.imports ? await this.text(target.imports, caller) : void 0;
18386
18898
  return {
@@ -18421,6 +18933,10 @@ var Launcher = class {
18421
18933
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18422
18934
  return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
18423
18935
  }
18936
+ marks(pid, req) {
18937
+ if (req.tag !== void 0) this.tags.set(pid, req.tag);
18938
+ if (req.links !== void 0) this.links.set(pid, req.links);
18939
+ }
18424
18940
  start(req) {
18425
18941
  this.sampling();
18426
18942
  if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
@@ -18453,6 +18969,7 @@ var Launcher = class {
18453
18969
  cred: req.cred,
18454
18970
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
18455
18971
  ...this.owners ? { owners: true } : {},
18972
+ ...linksOf2(req),
18456
18973
  sealed: this.sealed,
18457
18974
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
18458
18975
  clipboard: this.clipboard,
@@ -18500,7 +19017,7 @@ var Launcher = class {
18500
19017
  if (scrubs) this.scrubs.set(pid, scrubs);
18501
19018
  const { cred } = req;
18502
19019
  this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
18503
- if (req.tag !== void 0) this.tags.set(pid, req.tag);
19020
+ this.marks(pid, req);
18504
19021
  if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
18505
19022
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
18506
19023
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -18553,6 +19070,7 @@ var Launcher = class {
18553
19070
  this.childTimes.delete(pid);
18554
19071
  this.creds.delete(pid);
18555
19072
  this.tags.delete(pid);
19073
+ this.links.delete(pid);
18556
19074
  this.granted.delete(pid);
18557
19075
  }
18558
19076
  reaped(pid, parent) {
@@ -18728,6 +19246,7 @@ var Launcher = class {
18728
19246
  umask: this.umaskOf(ppid),
18729
19247
  cred,
18730
19248
  tag: this.tagOf(ppid),
19249
+ links: grant ? grant.cred.euid : this.links.get(ppid),
18731
19250
  ...group,
18732
19251
  ...exec
18733
19252
  });
@@ -18760,6 +19279,9 @@ var Launcher = class {
18760
19279
  }
18761
19280
  };
18762
19281
  }
19282
+ connectorStamp(pid) {
19283
+ return this.sealed.stamp(this.identityOf(pid), this.credOf(pid), this.tagOf(pid));
19284
+ }
18763
19285
  identityOf(pid) {
18764
19286
  const parent = this.jobs.shownParent(pid);
18765
19287
  const alive = parent !== void 0 && this.running(parent);
@@ -18789,7 +19311,8 @@ var Launcher = class {
18789
19311
  blocked: this.blockedBy(ppid),
18790
19312
  umask: this.umaskOf(ppid),
18791
19313
  cred: this.credOf(ppid),
18792
- tag: this.tagOf(ppid)
19314
+ tag: this.tagOf(ppid),
19315
+ links: this.links.get(ppid)
18793
19316
  })
18794
19317
  );
18795
19318
  }
@@ -18969,6 +19492,23 @@ var Launcher = class {
18969
19492
  }
18970
19493
  };
18971
19494
  }
19495
+ async revokeIntegrity(path) {
19496
+ if (path !== void 0 && (typeof path !== "string" || !path.startsWith("/"))) {
19497
+ throw new TypeError("integrity.revoke takes an absolute path");
19498
+ }
19499
+ if (!this.integrity) return;
19500
+ const at = path === void 0 ? void 0 : normalizePath(path);
19501
+ const real = at && await this.base.realpath?.(at, true).catch(() => void 0);
19502
+ const roots = [at, real].filter((p) => p !== void 0);
19503
+ this.revocations++;
19504
+ const hit = (key) => key.includes("\0") && (at === void 0 || key.split("\0").slice(0, 2).some((part) => isProtected(roots, part)));
19505
+ for (const cache of [this.compiled, this.texts]) {
19506
+ for (const key of [...cache.keys()]) if (hit(key)) cache.delete(key);
19507
+ }
19508
+ if (at === void 0) this.integrity.forget();
19509
+ for (const root of roots) this.integrity.forget(root);
19510
+ this.resetCatalogs();
19511
+ }
18972
19512
  attest(path) {
18973
19513
  return attest(this.rootFs, path, {
18974
19514
  users: this.usersMode,
@@ -19413,6 +19953,20 @@ function cdpOption(req, port) {
19413
19953
  )
19414
19954
  };
19415
19955
  }
19956
+ function pageHooks(req, port) {
19957
+ return { ...cdpOption(req, port), ...facadesOption(req, port) };
19958
+ }
19959
+ function facadesOption(req, port) {
19960
+ if (!req.facades) return {};
19961
+ const hook = portFacadeHook(
19962
+ (request2, facade) => port.postMessage({ facadeOpen: request2, port: facade }, [facade])
19963
+ );
19964
+ const facades = {};
19965
+ for (const [name2, spec] of Object.entries(req.facades)) {
19966
+ facades[name2] = { port: spec.port, ...spec.hook ? { hook } : {} };
19967
+ }
19968
+ return { facades };
19969
+ }
19416
19970
  function remotes(port, req) {
19417
19971
  return {
19418
19972
  ...req.transport ? { transport: new RemoteTransport(port, req.transport) } : {},
@@ -19466,7 +20020,8 @@ var ATTEST_OPS = /* @__PURE__ */ new Set([
19466
20020
  "attest",
19467
20021
  "watch-files",
19468
20022
  "unwatch-files",
19469
- "chmod"
20023
+ "chmod",
20024
+ "integrity-revoke"
19470
20025
  ]);
19471
20026
  function isAttestRequest(req) {
19472
20027
  return ATTEST_OPS.has(req.op);
@@ -19484,6 +20039,7 @@ function attestOps(port) {
19484
20039
  return async (l, req) => {
19485
20040
  if (req.op === "attest") return l.attest(req.path);
19486
20041
  if (req.op === "chmod") return l.fs.chmod(req.path, modeOption(req.mode)).then(() => true);
20042
+ if (req.op === "integrity-revoke") return l.revokeIntegrity(req.path).then(() => true);
19487
20043
  if (req.op === "unwatch-files") {
19488
20044
  watches.get(req.watch)?.();
19489
20045
  return watches.delete(req.watch);
@@ -19624,7 +20180,7 @@ function serveKernel(port, deps) {
19624
20180
  ...media ? { media } : {},
19625
20181
  onMountPending: (medium) => port.postMessage({ medium }),
19626
20182
  ...req.hostfs ? { hostfs: askGrant } : {},
19627
- ...cdpOption(req, port),
20183
+ ...pageHooks(req, port),
19628
20184
  ...mountPolicy(req.processMounts),
19629
20185
  caStore: caStore(deps.metadata ? caDb(req) : false)
19630
20186
  });