@ai-ecoverse/slicc-kernel 1.88.0 → 1.89.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);
@@ -2159,7 +2196,10 @@ var Perms = class {
2159
2196
  function parts(path) {
2160
2197
  return path.split("/").filter((part) => part !== "" && part !== ".");
2161
2198
  }
2162
- async function walk(fs, cred, path, follow2) {
2199
+ function trustedLink(perm, links) {
2200
+ return links === void 0 || perm.uid === 0 || perm.uid === links;
2201
+ }
2202
+ async function walk(fs, cred, path, follow2, links) {
2163
2203
  const perms = new Perms(fs);
2164
2204
  await perms.learn(path);
2165
2205
  const queue = parts(path);
@@ -2177,6 +2217,7 @@ async function walk(fs, cred, path, follow2) {
2177
2217
  const perm = await perms.at(next2);
2178
2218
  const last = queue.length === 0;
2179
2219
  if (perm?.link !== void 0 && (!last || follow2)) {
2220
+ if (!trustedLink(perm, links)) return { path, perm: void 0, denied: next2, link: true };
2180
2221
  if (++hops > MAX_LINKS4) return { path: next2, perm: void 0 };
2181
2222
  queue.unshift(...parts(perm.link));
2182
2223
  if (perm.link.startsWith("/")) at = "/";
@@ -2201,9 +2242,9 @@ async function finalOf(fs, walked, want, options) {
2201
2242
  return st ? [permOf(st), st.isDirectory] : [perm, false];
2202
2243
  }
2203
2244
  async function access(fs, cred, path, want, options = {}) {
2204
- const walked = await walk(fs, cred, path, options.follow !== false);
2245
+ const walked = await walk(fs, cred, path, options.follow !== false, options.links);
2205
2246
  if (walked.denied !== void 0) {
2206
- if (await present(fs, walked.denied, true)) return walked;
2247
+ if (walked.link || await present(fs, walked.denied, true)) return walked;
2207
2248
  return { path, perm: void 0, missing: true };
2208
2249
  }
2209
2250
  if (!walked.perm) return { ...walked, missing: true };
@@ -2270,6 +2311,9 @@ var Created = class {
2270
2311
  return this.paths.delete(normalizePath(path));
2271
2312
  }
2272
2313
  };
2314
+ function linked(g, options) {
2315
+ return g.links === void 0 ? options : { ...options, links: g.links };
2316
+ }
2273
2317
  function deny(g, op, path, errno = "EACCES") {
2274
2318
  const tag = g.tag === void 0 ? {} : { tag: g.tag };
2275
2319
  g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
@@ -2280,8 +2324,8 @@ function ttyOpenable(g, tty) {
2280
2324
  deny(g, "open", tty.name ?? "/dev/tty");
2281
2325
  }
2282
2326
  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);
2327
+ const walked = await access(g.fs, g.cred, path, want, linked(g, options));
2328
+ if (walked.denied !== void 0) deny(g, op, walked.link ? walked.denied : shown);
2285
2329
  return walked;
2286
2330
  }
2287
2331
  function entryIn(dir, path) {
@@ -2300,14 +2344,16 @@ async function inParent(g, op, path, old = false) {
2300
2344
  return dir2.denied === void 0 ? stickyEntry(g, op, path, dir2) : void 0;
2301
2345
  }
2302
2346
  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);
2347
+ const dir = await access(g.fs, g.cred, parentOf4(path), W_OK | X_OK, linked(g, DIR));
2304
2348
  if (dir.denied === void 0) return stickyEntry(g, op, path, dir);
2349
+ if (dir.link) return deny(g, op, dir.denied);
2305
2350
  const lookup = dir.denied === dir.path && dir.perm && permits(g.cred, dir.perm, X_OK);
2306
2351
  if (!lookup || await present(g.fs, entryIn(dir, path), false)) deny(g, op, path);
2307
2352
  }
2308
2353
  async function makeIn(g, op, path) {
2309
2354
  for (let dir = parentOf4(path); ; dir = parentOf4(dir)) {
2310
- const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, DIR);
2355
+ const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, linked(g, DIR));
2356
+ if (walked.link) return deny(g, op, walked.denied);
2311
2357
  if (walked.denied !== void 0) {
2312
2358
  if (!await present(g.fs, path, true)) deny(g, op, path);
2313
2359
  return;
@@ -4580,12 +4626,12 @@ async function interruptible(work, signal) {
4580
4626
  if (signal.aborted) throw new KernelError("EINTR");
4581
4627
  let stop = () => {
4582
4628
  };
4583
- const interrupted2 = new Promise((_, reject) => {
4629
+ const interrupted3 = new Promise((_, reject) => {
4584
4630
  stop = () => reject(new KernelError("EINTR"));
4585
4631
  signal.addEventListener("abort", stop, { once: true });
4586
4632
  });
4587
4633
  try {
4588
- return await Promise.race([work, interrupted2]);
4634
+ return await Promise.race([work, interrupted3]);
4589
4635
  } finally {
4590
4636
  signal.removeEventListener("abort", stop);
4591
4637
  }
@@ -6599,7 +6645,7 @@ var RemoteTransport = class {
6599
6645
  finish();
6600
6646
  throw e;
6601
6647
  }
6602
- const { status, statusText, headers, url, redirected, redirects } = head;
6648
+ const { status, statusText, headers, url, redirected: redirected2, redirects } = head;
6603
6649
  const read = async () => {
6604
6650
  if (finished) return void 0;
6605
6651
  const reply = await this.ask({ net: "read", nid }).catch((e) => {
@@ -6616,7 +6662,7 @@ var RemoteTransport = class {
6616
6662
  this.cancel(nid);
6617
6663
  };
6618
6664
  return {
6619
- ...redirected ? { url, redirected } : {},
6665
+ ...redirected2 ? { url, redirected: redirected2 } : {},
6620
6666
  ...redirects ? { redirects } : {},
6621
6667
  status,
6622
6668
  statusText,
@@ -7641,14 +7687,14 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
7641
7687
  const own = await userDir(dir);
7642
7688
  return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
7643
7689
  }
7644
- async function linked(path) {
7690
+ async function linked2(path) {
7645
7691
  return await virtual(path) ?? virtual(await followLinks(fs, path));
7646
7692
  }
7647
7693
  async function stat(path, real) {
7648
7694
  try {
7649
7695
  return await real(path);
7650
7696
  } catch (err) {
7651
- const found = missing3(err) ? await linked(path) : void 0;
7697
+ const found = missing3(err) ? await linked2(path) : void 0;
7652
7698
  if (found) return found;
7653
7699
  throw err;
7654
7700
  }
@@ -7660,7 +7706,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
7660
7706
  try {
7661
7707
  return await fs.readFileBuffer(path);
7662
7708
  } catch (err) {
7663
- if (missing3(err) && (await linked(path))?.isFile) return new Uint8Array(0);
7709
+ if (missing3(err) && (await linked2(path))?.isFile) return new Uint8Array(0);
7664
7710
  throw err;
7665
7711
  }
7666
7712
  },
@@ -7668,7 +7714,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
7668
7714
  link: (from, to, follow2) => fs.link(from, to, follow2),
7669
7715
  links: (path) => mayLink(fs, path),
7670
7716
  async exists(path) {
7671
- return await fs.exists(path) || await linked(path) !== void 0;
7717
+ return await fs.exists(path) || await linked2(path) !== void 0;
7672
7718
  },
7673
7719
  stat: (path) => stat(path, (p) => fs.stat(p)),
7674
7720
  lstat: (path) => stat(path, (p) => fs.lstat(p)),
@@ -9093,6 +9139,7 @@ var MountTable = class {
9093
9139
  return { quota: st.blocks * st.bsize, usage: (st.blocks - st.bfree) * st.bsize };
9094
9140
  }
9095
9141
  wrap(base) {
9142
+ base.crossings?.((path) => this.locate(normalizePath(path)) !== void 0);
9096
9143
  return new MountFs(this, base);
9097
9144
  }
9098
9145
  };
@@ -9116,6 +9163,37 @@ var MountFs = class {
9116
9163
  writable(mount, path) {
9117
9164
  if (readonly(mount)) throw errnoError("EROFS", path);
9118
9165
  }
9166
+ async through(path) {
9167
+ const real = normalizePath(path);
9168
+ if (this.at(real)) return real;
9169
+ try {
9170
+ await this.base.stat(parentOf7(real));
9171
+ return real;
9172
+ } catch (err) {
9173
+ const moved2 = redirectOf(err);
9174
+ return moved2 ? normalizePath(moved2.to + real.slice(moved2.of.length)) : real;
9175
+ }
9176
+ }
9177
+ async across(from, to, redirects) {
9178
+ const moved2 = [await this.through(from), await this.through(to)];
9179
+ const same = moved2[0] === normalizePath(from) && moved2[1] === normalizePath(to);
9180
+ if (same || redirects >= MAX_LINKS7) throw errnoError("EXDEV", `${from} -> ${to}`);
9181
+ return moved2;
9182
+ }
9183
+ async based(paths, redirects, run2, again) {
9184
+ try {
9185
+ return await run2();
9186
+ } catch (err) {
9187
+ const moved2 = redirectOf(err);
9188
+ if (!moved2) throw err;
9189
+ if (redirects >= MAX_LINKS7) throw errnoError("ELOOP", paths[0]);
9190
+ const at = paths.map((path) => {
9191
+ const real = normalizePath(path);
9192
+ return within6(real, moved2.of) ? normalizePath(moved2.to + real.slice(moved2.of.length)) : real;
9193
+ });
9194
+ return again(at, redirects + 1);
9195
+ }
9196
+ }
9119
9197
  async follow(path, hops = 0, create = false) {
9120
9198
  const found = this.at(path);
9121
9199
  if (!found) return path;
@@ -9163,10 +9241,17 @@ var MountFs = class {
9163
9241
  async readFile(path) {
9164
9242
  return new TextDecoder().decode(await this.readFileBuffer(path));
9165
9243
  }
9166
- async readFileBuffer(path) {
9244
+ async readFileBuffer(path, redirects = 0) {
9167
9245
  const real = await this.follow(path);
9168
9246
  const found = this.at(real);
9169
- if (!found) return this.base.readFileBuffer(real);
9247
+ if (!found) {
9248
+ return this.based(
9249
+ [real],
9250
+ redirects,
9251
+ () => this.base.readFileBuffer(real),
9252
+ ([to], n) => this.readFileBuffer(to, n)
9253
+ );
9254
+ }
9170
9255
  for (let attempt = 1; ; attempt++) {
9171
9256
  try {
9172
9257
  return await this.readWhole(found.mount, real, found.rel);
@@ -9205,10 +9290,18 @@ var MountFs = class {
9205
9290
  }
9206
9291
  return concat4(chunks, total);
9207
9292
  }
9208
- async writeFile(path, content, owner) {
9293
+ async writeFile(path, content, owner, redirects = 0) {
9209
9294
  const real = await this.follow(path, 0, true);
9210
9295
  const found = this.at(real);
9211
- if (!found) return this.base.writeFile(real, content, owner);
9296
+ if (!found) {
9297
+ const write = () => this.base.writeFile(real, content, owner);
9298
+ return this.based(
9299
+ [real],
9300
+ redirects,
9301
+ write,
9302
+ ([to], n) => this.writeFile(to, content, owner, n)
9303
+ );
9304
+ }
9212
9305
  const { mount, rel: rel2 } = found;
9213
9306
  this.writable(mount, real);
9214
9307
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
@@ -9242,10 +9335,18 @@ var MountFs = class {
9242
9335
  this.table.forget(mount, real);
9243
9336
  }
9244
9337
  }
9245
- async pread(path, offset, length2, version) {
9338
+ async pread(path, offset, length2, version, redirects = 0) {
9246
9339
  const real = await this.follow(path);
9247
9340
  const found = this.at(real);
9248
- if (!found && this.base.pread) return this.base.pread(real, offset, length2);
9341
+ const { pread } = this.base;
9342
+ if (!found && pread) {
9343
+ return this.based(
9344
+ [real],
9345
+ redirects,
9346
+ () => pread.call(this.base, real, offset, length2),
9347
+ ([to], n) => this.pread(to, offset, length2, version, n)
9348
+ );
9349
+ }
9249
9350
  if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
9250
9351
  const { mount, rel: rel2 } = found;
9251
9352
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
@@ -9292,10 +9393,18 @@ var MountFs = class {
9292
9393
  );
9293
9394
  }
9294
9395
  }
9295
- async pwrite(path, offset, bytes, transfer = false) {
9396
+ async pwrite(path, offset, bytes, transfer = false, redirects = 0) {
9296
9397
  const real = await this.follow(path, 0, true);
9297
9398
  const found = this.at(real);
9298
- if (!found && this.base.pwrite) return this.base.pwrite(real, offset, bytes);
9399
+ const { pwrite } = this.base;
9400
+ if (!found && pwrite) {
9401
+ return this.based(
9402
+ [real],
9403
+ redirects,
9404
+ () => pwrite.call(this.base, real, offset, bytes),
9405
+ ([to], n) => this.pwrite(to, offset, bytes, transfer, n)
9406
+ );
9407
+ }
9299
9408
  if (!found)
9300
9409
  return this.base.writeFile(
9301
9410
  real,
@@ -9335,10 +9444,18 @@ var MountFs = class {
9335
9444
  await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
9336
9445
  }
9337
9446
  }
9338
- async truncate(path, size) {
9447
+ async truncate(path, size, redirects = 0) {
9339
9448
  const real = await this.follow(path);
9340
9449
  const found = this.at(real);
9341
- if (!found && this.base.truncate) return this.base.truncate(real, size);
9450
+ const { truncate } = this.base;
9451
+ if (!found && truncate) {
9452
+ return this.based(
9453
+ [real],
9454
+ redirects,
9455
+ () => truncate.call(this.base, real, size),
9456
+ ([to], n) => this.truncate(to, size, n)
9457
+ );
9458
+ }
9342
9459
  if (!found?.mount.caps.ranges) {
9343
9460
  return this.writeFile(real, resized(await this.readFileBuffer(real), size));
9344
9461
  }
@@ -9354,8 +9471,15 @@ var MountFs = class {
9354
9471
  this.table.forget(mount, real);
9355
9472
  }
9356
9473
  }
9357
- async exists(path) {
9358
- if (!this.at(path)) return this.base.exists(path);
9474
+ async exists(path, redirects = 0) {
9475
+ if (!this.at(path)) {
9476
+ return this.based(
9477
+ [path],
9478
+ redirects,
9479
+ () => this.base.exists(path),
9480
+ ([to], n) => this.exists(to, n)
9481
+ );
9482
+ }
9359
9483
  try {
9360
9484
  await this.stat(path);
9361
9485
  return true;
@@ -9365,9 +9489,16 @@ var MountFs = class {
9365
9489
  throw err;
9366
9490
  }
9367
9491
  }
9368
- async lstat(path) {
9492
+ async lstat(path, redirects = 0) {
9369
9493
  const found = this.at(path);
9370
- if (!found) return this.base.lstat(path);
9494
+ if (!found) {
9495
+ return this.based(
9496
+ [path],
9497
+ redirects,
9498
+ () => this.base.lstat(path),
9499
+ ([to], n) => this.lstat(to, n)
9500
+ );
9501
+ }
9371
9502
  const real = normalizePath(path);
9372
9503
  if (this.table.writing(real)?.truncated) await this.table.commit(real);
9373
9504
  return this.statWriting(
@@ -9380,9 +9511,15 @@ var MountFs = class {
9380
9511
  const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
9381
9512
  return statOf({ ...attr2, size: Math.max(attr2.size, ...ends) }, real, mount);
9382
9513
  }
9383
- async stat(path) {
9514
+ async stat(path, redirects = 0) {
9384
9515
  const real = await this.follow(path);
9385
- return this.at(real) ? this.lstat(real) : this.base.stat(real);
9516
+ if (this.at(real)) return this.lstat(real);
9517
+ return this.based(
9518
+ [real],
9519
+ redirects,
9520
+ () => this.base.stat(real),
9521
+ ([to], n) => this.stat(to, n)
9522
+ );
9386
9523
  }
9387
9524
  async entries(path) {
9388
9525
  const real = await this.follow(path);
@@ -9394,12 +9531,26 @@ var MountFs = class {
9394
9531
  list: await this.table.readdir(found.mount, real, found.rel)
9395
9532
  };
9396
9533
  }
9397
- async readdir(path) {
9398
- if (!this.at(path)) return this.base.readdir(path);
9534
+ async readdir(path, redirects = 0) {
9535
+ if (!this.at(path)) {
9536
+ return this.based(
9537
+ [path],
9538
+ redirects,
9539
+ () => this.base.readdir(path),
9540
+ ([to], n) => this.readdir(to, n)
9541
+ );
9542
+ }
9399
9543
  return (await this.entries(path)).list.map((e) => e.name);
9400
9544
  }
9401
- async readdirStat(path) {
9402
- if (!this.at(path)) return this.base.readdirStat(path);
9545
+ async readdirStat(path, redirects = 0) {
9546
+ if (!this.at(path)) {
9547
+ return this.based(
9548
+ [path],
9549
+ redirects,
9550
+ () => this.base.readdirStat(path),
9551
+ ([to], n) => this.readdirStat(to, n)
9552
+ );
9553
+ }
9403
9554
  const { real, mount, list } = await this.entries(path);
9404
9555
  return Promise.all(
9405
9556
  list.map(async (entry) => {
@@ -9411,9 +9562,16 @@ var MountFs = class {
9411
9562
  })
9412
9563
  );
9413
9564
  }
9414
- async mkdir(path, options = {}) {
9565
+ async mkdir(path, options = {}, redirects = 0) {
9415
9566
  const found = this.at(path);
9416
- if (!found) return this.base.mkdir(path, options);
9567
+ if (!found) {
9568
+ return this.based(
9569
+ [path],
9570
+ redirects,
9571
+ () => this.base.mkdir(path, options),
9572
+ ([to], n) => this.mkdir(to, options, n)
9573
+ );
9574
+ }
9417
9575
  const { mount, rel: rel2 } = found;
9418
9576
  const real = normalizePath(path);
9419
9577
  this.writable(mount, real);
@@ -9438,11 +9596,18 @@ var MountFs = class {
9438
9596
  await mount.conn.call({ op: "mkdir", path: sub, ...made2 });
9439
9597
  }
9440
9598
  }
9441
- async rm(path, options = {}) {
9599
+ async rm(path, options = {}, redirects = 0) {
9442
9600
  const found = this.at(path);
9443
9601
  if (!found && options.recursive && this.table.under(normalizePath(path)))
9444
9602
  return this.rmAround(normalizePath(path), options);
9445
- if (!found) return this.base.rm(path, options);
9603
+ if (!found) {
9604
+ return this.based(
9605
+ [path],
9606
+ redirects,
9607
+ () => this.base.rm(path, options),
9608
+ ([to], n) => this.rm(to, options, n)
9609
+ );
9610
+ }
9446
9611
  const { mount, rel: rel2 } = found;
9447
9612
  const real = normalizePath(path);
9448
9613
  if (rel2 === "/" && options.recursive) await this.rmEach(real, mount, rel2, options);
@@ -9483,11 +9648,22 @@ var MountFs = class {
9483
9648
  }
9484
9649
  throw first ?? errnoError("EBUSY", `${path} holds a mount point`);
9485
9650
  }
9486
- async rename(from, to) {
9651
+ async rename(from, to, redirects = 0) {
9487
9652
  const a = this.at(from);
9488
9653
  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}`);
9654
+ if (!a && !b) {
9655
+ return this.based(
9656
+ [from, to],
9657
+ redirects,
9658
+ () => this.base.rename(from, to),
9659
+ ([f, t], n) => this.rename(f, t, n)
9660
+ );
9661
+ }
9662
+ if (!a || !b) {
9663
+ const [f, t] = await this.across(from, to, redirects);
9664
+ return this.rename(f, t, redirects + 1);
9665
+ }
9666
+ if (a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
9491
9667
  this.writable(a.mount, normalizePath(to));
9492
9668
  await this.table.commitUnder(from);
9493
9669
  await this.table.commitUnder(to);
@@ -9528,9 +9704,17 @@ var MountFs = class {
9528
9704
  this.table.forget(a.mount, normalizePath(to));
9529
9705
  }
9530
9706
  }
9531
- async symlink(target, path, owner) {
9707
+ async symlink(target, path, owner, redirects = 0) {
9532
9708
  const found = this.at(path);
9533
- if (!found) return this.base.symlink(target, path, owner);
9709
+ if (!found) {
9710
+ const make = () => this.base.symlink(target, path, owner);
9711
+ return this.based(
9712
+ [path],
9713
+ redirects,
9714
+ make,
9715
+ ([to], n) => this.symlink(target, to, owner, n)
9716
+ );
9717
+ }
9534
9718
  this.writable(found.mount, normalizePath(path));
9535
9719
  if (!found.mount.caps.symlinks)
9536
9720
  throw errnoError("EPERM", `${path}: this file system has no symlinks`);
@@ -9540,8 +9724,11 @@ var MountFs = class {
9540
9724
  await found.mount.conn.call({ op: "symlink", target, path: found.rel, ...made2 });
9541
9725
  }
9542
9726
  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);
9727
+ const { realpath: realpath2 } = this.base;
9728
+ if (this.at(path) || !realpath2) throw errnoError("ENOSYS", path);
9729
+ const real = await realpath2.call(this.base, path, follow2).catch((err) => {
9730
+ throw redirectOf(err) ? errnoError("ENOSYS", path) : err;
9731
+ });
9545
9732
  if (this.at(real)) throw errnoError("ENOSYS", path);
9546
9733
  return real;
9547
9734
  }
@@ -9563,17 +9750,30 @@ var MountFs = class {
9563
9750
  }
9564
9751
  return out;
9565
9752
  }
9566
- async readlink(path) {
9753
+ async readlink(path, redirects = 0) {
9567
9754
  const found = this.at(path);
9568
- if (!found) return this.base.readlink(path);
9755
+ if (!found) {
9756
+ return this.based(
9757
+ [path],
9758
+ redirects,
9759
+ () => this.base.readlink(path),
9760
+ ([to], n) => this.readlink(to, n)
9761
+ );
9762
+ }
9569
9763
  return await found.mount.conn.call({ op: "readlink", path: found.rel });
9570
9764
  }
9571
- async chown(path, change) {
9765
+ async chown(path, change, redirects = 0) {
9572
9766
  const real = change.follow === false || change.kind === "new" ? path : await this.follow(path);
9573
9767
  const found = this.at(real);
9574
9768
  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);
9769
+ const { chown: chown2 } = this.base;
9770
+ if (!chown2) throw errnoError("EPERM", path);
9771
+ return this.based(
9772
+ [real],
9773
+ redirects,
9774
+ () => chown2.call(this.base, real, change),
9775
+ ([to], n) => this.chown(to, change, n)
9776
+ );
9577
9777
  }
9578
9778
  async chownMounted(found, real, change) {
9579
9779
  const { mount, rel: rel2 } = found;
@@ -9593,23 +9793,47 @@ var MountFs = class {
9593
9793
  ...Object.fromEntries(attrs.filter(([, value]) => value !== void 0))
9594
9794
  });
9595
9795
  }
9596
- async chmod(path, mode) {
9597
- const found = this.at(path);
9598
- if (!found) return this.base.chmod(path, mode);
9599
- this.writable(found.mount, normalizePath(path));
9796
+ async chmod(path, mode, redirects = 0) {
9797
+ const real = await this.follow(path);
9798
+ const found = this.at(real);
9799
+ if (!found) {
9800
+ return this.based(
9801
+ [real],
9802
+ redirects,
9803
+ () => this.base.chmod(real, mode),
9804
+ ([to], n) => this.chmod(to, mode, n)
9805
+ );
9806
+ }
9807
+ this.writable(found.mount, real);
9600
9808
  if (!found.mount.caps.chmod) return;
9601
- this.table.forget(found.mount, normalizePath(path));
9809
+ this.table.forget(found.mount, real);
9602
9810
  await found.mount.conn.call({ op: "setattr", path: found.rel, mode });
9603
9811
  }
9604
- async utimes(path, atime, mtime) {
9812
+ async utimes(path, atime, mtime, redirects = 0) {
9605
9813
  const real = await this.follow(path);
9606
9814
  const found = this.at(real);
9607
- if (!found) return this.base.utimes(real, atime, mtime);
9815
+ if (!found) {
9816
+ const set2 = () => this.base.utimes(real, atime, mtime);
9817
+ return this.based(
9818
+ [real],
9819
+ redirects,
9820
+ set2,
9821
+ ([to], n) => this.utimes(to, atime, mtime, n)
9822
+ );
9823
+ }
9608
9824
  await this.setMtime(found, real, mtime);
9609
9825
  }
9610
- async lutimes(path, atime, mtime) {
9826
+ async lutimes(path, atime, mtime, redirects = 0) {
9611
9827
  const found = this.at(path);
9612
- if (!found) return lutimesOf(this.base, path, atime, mtime);
9828
+ if (!found) {
9829
+ const set2 = () => lutimesOf(this.base, path, atime, mtime);
9830
+ return this.based(
9831
+ [path],
9832
+ redirects,
9833
+ set2,
9834
+ ([to], n) => this.lutimes(to, atime, mtime, n)
9835
+ );
9836
+ }
9613
9837
  const real = normalizePath(path);
9614
9838
  if (!found.mount.caps.linkTimes && (await this.lstat(real)).isSymbolicLink) {
9615
9839
  throw errnoError("EOPNOTSUPP", path);
@@ -9828,7 +10052,8 @@ async function run(entry, path, req) {
9828
10052
  function guardOf(entry) {
9829
10053
  const cred = entry.dac && entry.cred?.();
9830
10054
  if (!cred || !entry.dac) return void 0;
9831
- return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac };
10055
+ const links = entry.links === void 0 ? {} : { links: entry.links };
10056
+ return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac, ...links };
9832
10057
  }
9833
10058
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
9834
10059
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
@@ -9889,9 +10114,15 @@ async function holdOp(entry, path, req) {
9889
10114
  entry.hold?.(path, on, open, void 0, req.handle);
9890
10115
  return done;
9891
10116
  }
10117
+ async function followed(g, path) {
10118
+ const st = await g.fs.lstat(path).catch(() => void 0);
10119
+ if (st?.isSymbolicLink && !trustedLink(permOf(st), g.links)) deny(g, "follow", path);
10120
+ return done;
10121
+ }
9892
10122
  async function accessOp(entry, path, req) {
9893
10123
  const g = guardOf(entry);
9894
10124
  if (!g) return done;
10125
+ if (req.arg2 === "follow") return followed(g, path);
9895
10126
  const open = req.arg2 === "open";
9896
10127
  const cred = open || req.arg2 === "eaccess" ? g.cred : realIds(g.cred);
9897
10128
  const options = { follow: req.nofollow !== true, ...open ? { directory: false } : {} };
@@ -9914,15 +10145,18 @@ var CHANGING = /* @__PURE__ */ new Set([
9914
10145
  "lchown"
9915
10146
  ]);
9916
10147
  var CONTENT = /* @__PURE__ */ new Set(["write", "pwrite", "truncate"]);
10148
+ var REMOVING = /* @__PURE__ */ new Set(["unlink", "rmdir", "rm", "rename"]);
9917
10149
  async function mayChange(entry, path, req) {
9918
10150
  const { mayChange: mayChange2 } = entry.fs;
9919
10151
  if (!mayChange2) return;
9920
10152
  const { op } = req;
9921
10153
  if (CONTENT.has(op) && req.handle === void 0) return mayChange2.call(entry.fs, path, true);
9922
10154
  if (!CHANGING.has(op)) return;
9923
- await mayChange2.call(entry.fs, path, op === "utimes" || op === "chmod" || op === "chown");
10155
+ const follow2 = op === "utimes" || op === "chmod" || op === "chown";
10156
+ await mayChange2.call(entry.fs, path, follow2, REMOVING.has(op));
9924
10157
  if (op === "rename" || op === "link") {
9925
- await mayChange2.call(entry.fs, entry.fs.resolvePath(entry.cwd, req.arg2 ?? ""), false);
10158
+ const to = entry.fs.resolvePath(entry.cwd, req.arg2 ?? "");
10159
+ await mayChange2.call(entry.fs, to, false, op === "rename");
9926
10160
  }
9927
10161
  }
9928
10162
  async function guarded(entry, path, req) {
@@ -10615,7 +10849,7 @@ function mountTable(mounts = []) {
10615
10849
  }
10616
10850
 
10617
10851
  // src/version.ts
10618
- var KERNEL_VERSION = true ? "1.88.0" : "0.0.0-dev";
10852
+ var KERNEL_VERSION = true ? "1.89.0" : "0.0.0-dev";
10619
10853
  var OS_RELEASE = "6.0.0-slicc";
10620
10854
  function procVersion() {
10621
10855
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -12182,16 +12416,16 @@ function interruption(signal) {
12182
12416
  }
12183
12417
  async function selectFds(fds, read, write, timeoutMs, interrupt) {
12184
12418
  const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
12185
- const interrupted2 = interruption(interrupt);
12186
- interrupted2.promise.catch(() => {
12419
+ const interrupted3 = interruption(interrupt);
12420
+ interrupted3.promise.catch(() => {
12187
12421
  });
12188
12422
  try {
12189
- return await waitReady(fds, read, write, deadline, interrupted2.promise);
12423
+ return await waitReady(fds, read, write, deadline, interrupted3.promise);
12190
12424
  } finally {
12191
- interrupted2.done();
12425
+ interrupted3.done();
12192
12426
  }
12193
12427
  }
12194
- async function waitReady(fds, read, write, deadline, interrupted2) {
12428
+ async function waitReady(fds, read, write, deadline, interrupted3) {
12195
12429
  for (; ; ) {
12196
12430
  const now = ready(fds, read, write);
12197
12431
  if (now.read.length > 0 || now.write.length > 0 || Date.now() >= deadline) return now;
@@ -12203,7 +12437,7 @@ async function waitReady(fds, read, write, deadline, interrupted2) {
12203
12437
  let timer;
12204
12438
  const expiry = deadline === Number.POSITIVE_INFINITY ? [] : [new Promise((resolve2) => timer = setTimeout(resolve2, deadline - Date.now()))];
12205
12439
  try {
12206
- await Promise.race([...changes, ...expiry, interrupted2]);
12440
+ await Promise.race([...changes, ...expiry, interrupted3]);
12207
12441
  } finally {
12208
12442
  round.abort();
12209
12443
  clearTimeout(timer);
@@ -13386,7 +13620,7 @@ var WasmProcess = class {
13386
13620
  const { guard: guard2 } = this.options;
13387
13621
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
13388
13622
  if (!guard2 || inherited2 || req.pin !== void 0) return;
13389
- const g = { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
13623
+ const g = { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid, links: guard2.links };
13390
13624
  if (req.exclusive) return inParent(g, "open", req.path);
13391
13625
  if (req.created && guard2.created?.take(req.path)) return;
13392
13626
  const access2 = req.flags & WRITE_ACCESS;
@@ -13442,7 +13676,7 @@ var WasmProcess = class {
13442
13676
  }
13443
13677
  guarded() {
13444
13678
  const { guard: guard2 } = this.options;
13445
- return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
13679
+ return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid, links: guard2.links };
13446
13680
  }
13447
13681
  ttyOwner() {
13448
13682
  return { uid: this.cred.euid, gid: this.cred.egid, mode: TTY_MODE };
@@ -14168,6 +14402,7 @@ function processInit(opts, sab, umask, cred, lane) {
14168
14402
  umask,
14169
14403
  cred,
14170
14404
  ...opts.owners ? { owners: true } : {},
14405
+ ...opts.links !== void 0 ? { links: true } : {},
14171
14406
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
14172
14407
  };
14173
14408
  if (opts.program.abi === "js") {
@@ -14195,6 +14430,7 @@ function holdingToken(opts, holds, caller) {
14195
14430
  return mintSyncFsToken({
14196
14431
  ...opts.owners ? caller : {},
14197
14432
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
14433
+ ...opts.links !== void 0 ? { links: opts.links } : {},
14198
14434
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
14199
14435
  fs: opts.fs,
14200
14436
  cwd: opts.cwd,
@@ -14208,6 +14444,9 @@ function holdingToken(opts, holds, caller) {
14208
14444
  renamed: (from, to) => holds.renamed(from, to)
14209
14445
  });
14210
14446
  }
14447
+ function linksOf(opts) {
14448
+ return opts.links === void 0 ? {} : { links: opts.links };
14449
+ }
14211
14450
  function clipboardName(opts) {
14212
14451
  return opts.program.abi === "js" ? {} : { clipboardName: opts.argv0 };
14213
14452
  }
@@ -14219,7 +14458,7 @@ function processOptions(opts, header3, hooks, created3) {
14219
14458
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
14220
14459
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
14221
14460
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
14222
- ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
14461
+ ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3, ...linksOf(opts) } } : {},
14223
14462
  ...opts.identity ? { identity: opts.identity } : {},
14224
14463
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
14225
14464
  ...opts.settling ? { settling: opts.settling } : {},
@@ -14265,6 +14504,7 @@ function threadInit(opts, program, sab, cred, thread, main) {
14265
14504
  sab,
14266
14505
  cred: copyCred(cred),
14267
14506
  owners: opts.owners === true,
14507
+ ...opts.links !== void 0 ? { links: true } : {},
14268
14508
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
14269
14509
  thread,
14270
14510
  signals: main
@@ -15132,63 +15372,137 @@ function compile(secrets, options = {}) {
15132
15372
 
15133
15373
  // src/kernel/scrub-file.ts
15134
15374
  var SCRUB_IDLE_MS = 50;
15375
+ var SCRUB_NONBLOCK_MAX = 64 * 1024;
15135
15376
  var encoder5 = new TextEncoder();
15136
15377
  function scrubberOf(secrets) {
15137
15378
  const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
15138
15379
  return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
15139
15380
  }
15140
- async function writeAll2(file, bytes, signal) {
15381
+ async function writeAll2(file, bytes) {
15141
15382
  const write = file.write;
15142
- for (let at = 0; at < bytes.length; ) {
15143
- at += await write.call(file, bytes.subarray(at), at === 0 ? signal : void 0);
15144
- }
15383
+ for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
15145
15384
  }
15146
15385
  function bound(file, key) {
15147
15386
  const method = file[key];
15148
15387
  return typeof method === "function" ? { [key]: method.bind(file) } : {};
15149
15388
  }
15389
+ function interrupted2(err) {
15390
+ const code = err?.code;
15391
+ return code === "EINTR" || code === "EAGAIN";
15392
+ }
15150
15393
  function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15151
15394
  const file = inner.file;
15152
15395
  const stream = scrubber.stream();
15153
- let queue = Promise.resolve();
15396
+ let tail = Promise.resolve();
15397
+ let pending = 0;
15398
+ let busy = false;
15154
15399
  let timer;
15155
15400
  let failed3;
15401
+ const waiters = /* @__PURE__ */ new Set();
15402
+ const quiet = () => !busy && pending === 0;
15403
+ const wake = () => {
15404
+ for (const waiter of [...waiters]) waiter();
15405
+ };
15156
15406
  const raise = () => {
15157
15407
  if (failed3 === void 0) return;
15158
15408
  const err = failed3;
15159
15409
  failed3 = void 0;
15160
15410
  throw err;
15161
15411
  };
15162
- const enqueue = (bytes, signal) => {
15163
- const next2 = queue.then(() => bytes.length > 0 ? writeAll2(file, bytes, signal) : void 0);
15164
- queue = next2.catch(() => void 0);
15165
- return next2;
15412
+ const later = (bytes) => {
15413
+ if (bytes.length === 0) return;
15414
+ pending += 1;
15415
+ tail = tail.then(() => writeAll2(file, bytes)).catch((err) => {
15416
+ if (failed3 === void 0) failed3 = err;
15417
+ }).finally(() => {
15418
+ pending -= 1;
15419
+ if (quiet()) wake();
15420
+ });
15421
+ };
15422
+ const settling = (signal) => new Promise((resolve2, reject) => {
15423
+ const done4 = () => {
15424
+ waiters.delete(done4);
15425
+ signal?.removeEventListener("abort", stop);
15426
+ resolve2();
15427
+ };
15428
+ const stop = () => {
15429
+ waiters.delete(done4);
15430
+ reject(new KernelError("EINTR"));
15431
+ };
15432
+ if (signal?.aborted) return stop();
15433
+ waiters.add(done4);
15434
+ signal?.addEventListener("abort", stop, { once: true });
15435
+ });
15436
+ const settled = async () => {
15437
+ while (!quiet()) await settling();
15438
+ };
15439
+ const changed = async (signal) => {
15440
+ const round = new AbortController();
15441
+ const stop = signal ? AbortSignal.any([signal, round.signal]) : round.signal;
15442
+ try {
15443
+ const own = settling(stop);
15444
+ await Promise.race(file.changed ? [own, file.changed(stop)] : [own]);
15445
+ } catch (err) {
15446
+ throw signal?.aborted ? new KernelError("EINTR") : err;
15447
+ } finally {
15448
+ round.abort();
15449
+ }
15450
+ };
15451
+ const turn = async (signal) => {
15452
+ for (let waited = false; ; waited = true) {
15453
+ if (quiet() && (pollFile(file).writable || !file.changed)) {
15454
+ busy = true;
15455
+ return;
15456
+ }
15457
+ if (signal?.aborted) throw new KernelError(waited ? "EINTR" : "EAGAIN");
15458
+ await changed(signal);
15459
+ }
15460
+ };
15461
+ const send = async (out, signal) => {
15462
+ const write = file.write;
15463
+ for (let at = 0; at < out.length; ) {
15464
+ try {
15465
+ at += await write.call(file, out.subarray(at), signal);
15466
+ } catch (err) {
15467
+ if (!signal?.aborted || !interrupted2(err)) throw err;
15468
+ }
15469
+ if (at < out.length && signal?.aborted) return later(out.subarray(at));
15470
+ }
15166
15471
  };
15167
15472
  const release = () => {
15168
15473
  timer = void 0;
15169
- enqueue(stream.idle()).catch((err) => {
15170
- failed3 = err;
15171
- });
15474
+ later(stream.idle());
15172
15475
  };
15173
15476
  return new OpenFile({
15174
15477
  ...bound(file, "read"),
15175
15478
  ...bound(file, "peek"),
15176
- ...bound(file, "poll"),
15177
- ...bound(file, "changed"),
15178
15479
  ...bound(file, "stat"),
15480
+ poll: () => {
15481
+ const state = pollFile(file);
15482
+ return quiet() ? state : { ...state, writable: false };
15483
+ },
15484
+ changed,
15179
15485
  ...file.tty ? { tty: file.tty } : {},
15180
15486
  ...file.clipboard ? { clipboard: file.clipboard } : {},
15181
15487
  ...file.write ? {
15182
15488
  write: async (bytes, signal) => {
15183
- if (timer !== void 0) clearTimeout(timer);
15184
- raise();
15185
- await enqueue(stream.push(bytes), signal);
15489
+ await turn(signal);
15490
+ const taken = signal?.aborted && bytes.length > SCRUB_NONBLOCK_MAX ? bytes.subarray(0, SCRUB_NONBLOCK_MAX) : bytes;
15491
+ try {
15492
+ raise();
15493
+ if (timer !== void 0) clearTimeout(timer);
15494
+ timer = void 0;
15495
+ await send(stream.push(taken), signal);
15496
+ } finally {
15497
+ busy = false;
15498
+ if (quiet()) wake();
15499
+ }
15186
15500
  timer = setTimeout(release, idleMs);
15187
- return bytes.length;
15501
+ return taken.length;
15188
15502
  }
15189
15503
  } : {},
15190
15504
  flush: async () => {
15191
- await queue;
15505
+ await settled();
15192
15506
  raise();
15193
15507
  await file.flush?.();
15194
15508
  },
@@ -15196,7 +15510,9 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15196
15510
  if (timer !== void 0) clearTimeout(timer);
15197
15511
  let failure2;
15198
15512
  try {
15199
- await enqueue(stream.end());
15513
+ await settled();
15514
+ later(stream.end());
15515
+ await settled();
15200
15516
  raise();
15201
15517
  } catch (err) {
15202
15518
  failure2 = { err };
@@ -15372,12 +15688,12 @@ var SealedChannel = class {
15372
15688
  if (signal.aborted) throw new KernelError("EINTR");
15373
15689
  let stop = () => {
15374
15690
  };
15375
- const interrupted2 = new Promise((_, reject) => {
15691
+ const interrupted3 = new Promise((_, reject) => {
15376
15692
  stop = () => reject(new KernelError("EINTR"));
15377
15693
  signal.addEventListener("abort", stop, { once: true });
15378
15694
  });
15379
15695
  try {
15380
- await Promise.race([flight.done, interrupted2]);
15696
+ await Promise.race([flight.done, interrupted3]);
15381
15697
  } finally {
15382
15698
  signal.removeEventListener("abort", stop);
15383
15699
  }
@@ -17489,6 +17805,9 @@ var SUDO_SOURCE = [
17489
17805
  ].join("\n");
17490
17806
 
17491
17807
  // src/launcher.ts
17808
+ function linksOf2(req) {
17809
+ return req.links === void 0 ? {} : { links: req.links };
17810
+ }
17492
17811
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
17493
17812
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
17494
17813
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -17664,6 +17983,7 @@ var Launcher = class {
17664
17983
  warned = /* @__PURE__ */ new Set();
17665
17984
  compiled = /* @__PURE__ */ new Map();
17666
17985
  texts = /* @__PURE__ */ new Map();
17986
+ revocations = 0;
17667
17987
  integrity;
17668
17988
  scannedFs;
17669
17989
  get scanned() {
@@ -17676,6 +17996,7 @@ var Launcher = class {
17676
17996
  described = /* @__PURE__ */ new Map();
17677
17997
  creds = /* @__PURE__ */ new Map();
17678
17998
  tags = /* @__PURE__ */ new Map();
17999
+ links = /* @__PURE__ */ new Map();
17679
18000
  freshModules = new FreshModules();
17680
18001
  granted = /* @__PURE__ */ new Map();
17681
18002
  users;
@@ -17978,14 +18299,16 @@ var Launcher = class {
17978
18299
  }
17979
18300
  const path = this.fs.resolvePath(cwd, file);
17980
18301
  const glue = await followLinks(this.base, path);
17981
- const linked = COMMAND2.exec(glue)?.[1];
17982
- if (linked !== void 0) {
17983
- const target = await this.resolve(`/bin/${linked}`, argv0, cwd);
17984
- const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked)?.argv0Path === true;
18302
+ const asked = glue === path ? {} : { asked: path };
18303
+ const linked2 = COMMAND2.exec(glue)?.[1];
18304
+ if (linked2 !== void 0) {
18305
+ const found = await this.resolve(`/bin/${linked2}`, argv0, cwd);
18306
+ const target = found && { ...found, ...asked };
18307
+ const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked2)?.argv0Path === true;
17985
18308
  return target && byPath ? { ...target, argv0: path } : target;
17986
18309
  }
17987
18310
  const own = await this.commandAt(glue, scope);
17988
- if (own) return own.script ? void 0 : targetOf(own);
18311
+ if (own) return own.script ? void 0 : { ...targetOf(own), ...asked };
17989
18312
  const head = await this.head(glue);
17990
18313
  if (!head) return void 0;
17991
18314
  if (isWasm(head)) {
@@ -17993,13 +18316,14 @@ var Launcher = class {
17993
18316
  abi: "wasi",
17994
18317
  glue,
17995
18318
  wasm: glue,
17996
- argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
18319
+ argv0: baseName(argv0 || file).replace(/\.wasm$/, ""),
18320
+ ...asked
17997
18321
  };
17998
18322
  }
17999
18323
  if (interpreter(head) !== "node") return void 0;
18000
18324
  const wasm = modulePath(glue);
18001
18325
  if (!await this.base.exists(wasm)) return void 0;
18002
- return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
18326
+ return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file), ...asked };
18003
18327
  }
18004
18328
  async head(path) {
18005
18329
  try {
@@ -18017,6 +18341,8 @@ var Launcher = class {
18017
18341
  async interpreted(file, argv, cwd, scope, depth = 0) {
18018
18342
  const command = await this.scriptCommand(file, cwd, scope);
18019
18343
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
18344
+ const byPath = command && file.includes("/") && !COMMAND2.test(file);
18345
+ const named = byPath ? { [script]: this.fs.resolvePath(cwd, file) } : {};
18020
18346
  const head = await this.head(script);
18021
18347
  if (!head) return void 0;
18022
18348
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
@@ -18039,7 +18365,8 @@ var Launcher = class {
18039
18365
  target: withScriptEnv(inner.target, command),
18040
18366
  via,
18041
18367
  sources,
18042
- ...bins.length > 0 ? { bins } : {}
18368
+ ...bins.length > 0 ? { bins } : {},
18369
+ asked: { ...inner.asked, ...named }
18043
18370
  };
18044
18371
  }
18045
18372
  const target = withScriptEnv(found, command);
@@ -18048,7 +18375,8 @@ var Launcher = class {
18048
18375
  args: [...target.prefix ?? [], ...passed],
18049
18376
  via: [interp],
18050
18377
  sources: [script],
18051
- ...command?.bin ? { bins: [script] } : {}
18378
+ ...command?.bin ? { bins: [script] } : {},
18379
+ asked: named
18052
18380
  };
18053
18381
  }
18054
18382
  async plan(file, argv, cwd, scope, argv0) {
@@ -18062,7 +18390,14 @@ var Launcher = class {
18062
18390
  async mayExec(cred, req, planned) {
18063
18391
  const { dac } = this;
18064
18392
  if (!dac) return;
18065
- const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
18393
+ const g = {
18394
+ fs: this.fs,
18395
+ cred,
18396
+ pid: req.pid,
18397
+ dac,
18398
+ tag: req.tag,
18399
+ links: this.links.get(req.pid)
18400
+ };
18066
18401
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
18067
18402
  if (byPath(req.file)) {
18068
18403
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -18219,8 +18554,8 @@ var Launcher = class {
18219
18554
  readFile: (path) => this.integrity ? this.text(path, KERNEL_CALLER) : fs.readFile(path)
18220
18555
  });
18221
18556
  }
18222
- async cacheKey(path) {
18223
- const verified = await this.integrity?.key(path);
18557
+ async cacheKey(path, asked) {
18558
+ const verified = await this.integrity?.key(path, asked);
18224
18559
  if (verified !== void 0) return verified;
18225
18560
  const st = await this.base.stat(path);
18226
18561
  return `${path}:${st.size}:${st.mtime.getTime()}`;
@@ -18229,30 +18564,31 @@ var Launcher = class {
18229
18564
  const plain = () => fs.readFileBuffer(path);
18230
18565
  return this.integrity ? this.integrity.read(path, caller, plain) : plain();
18231
18566
  }
18232
- keyed(path, caller) {
18567
+ keyed(path, caller, asked) {
18233
18568
  const plain = async () => this.base.readFileBuffer(path);
18234
- return this.integrity ? this.integrity.readKeyed(path, caller, plain) : plain().then((bytes) => ({ bytes }));
18569
+ return this.integrity ? this.integrity.readKeyed(path, caller, plain, asked) : plain().then((bytes) => ({ bytes }));
18235
18570
  }
18236
18571
  cached(cache, key, load) {
18237
18572
  const hit = cache.get(key);
18238
18573
  if (hit !== void 0) return hit;
18574
+ const revocations = this.revocations;
18239
18575
  const loading2 = load();
18240
18576
  const found = loading2.then(({ value }) => value);
18241
18577
  cache.set(key, found);
18242
- loading2.then(
18243
- (done4) => {
18244
- if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
18245
- if (cache.get(key) === found) cache.delete(key);
18246
- if (done4.key !== void 0) cache.set(done4.key, found);
18247
- },
18248
- () => cache.delete(key)
18249
- );
18578
+ const drop = () => {
18579
+ if (cache.get(key) === found) cache.delete(key);
18580
+ };
18581
+ loading2.then((done4) => {
18582
+ if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
18583
+ drop();
18584
+ if (done4.key !== void 0 && revocations === this.revocations) cache.set(done4.key, found);
18585
+ }, drop);
18250
18586
  return found;
18251
18587
  }
18252
- async module(path, caller) {
18253
- const key = await this.cacheKey(path);
18588
+ async module(path, caller, asked) {
18589
+ const key = await this.cacheKey(path, asked);
18254
18590
  return this.cached(this.compiled, key, async () => {
18255
- const { bytes, key: read } = await this.keyed(path, caller);
18591
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18256
18592
  const memory = importedMemory(bytes);
18257
18593
  const foreign = foreignImports(bytes);
18258
18594
  const compiled = await WebAssembly.compile(bytes);
@@ -18261,22 +18597,30 @@ var Launcher = class {
18261
18597
  }
18262
18598
  async checkedSources(planned, caller) {
18263
18599
  const { target } = planned;
18264
- const files = [target.wasm, target.glue, target.imports, ...planned.sources ?? []];
18600
+ const files = [
18601
+ target.wasm,
18602
+ target.glue,
18603
+ target.imports,
18604
+ target.asked,
18605
+ ...planned.sources ?? []
18606
+ ];
18265
18607
  const manifests = /* @__PURE__ */ new Set();
18266
18608
  for (const file of files) {
18267
18609
  const root = file === void 0 ? void 0 : PACKAGE_ROOT.exec(file)?.[1];
18268
18610
  if (root !== void 0) manifests.add(`${root}/package.json`);
18269
18611
  }
18270
- for (const source of planned.sources ?? []) await this.text(source, caller);
18612
+ for (const source of planned.sources ?? []) {
18613
+ await this.text(source, caller, planned.asked?.[source]);
18614
+ }
18271
18615
  for (const manifest of manifests) {
18272
18616
  if (await this.base.exists(manifest)) await this.text(manifest, caller);
18273
18617
  }
18274
18618
  }
18275
- async text(path, caller) {
18619
+ async text(path, caller, asked) {
18276
18620
  if (!this.integrity) return this.base.readFile(path);
18277
- const key = await this.cacheKey(path);
18621
+ const key = await this.cacheKey(path, asked);
18278
18622
  return this.cached(this.texts, key, async () => {
18279
- const { bytes, key: read } = await this.keyed(path, caller);
18623
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18280
18624
  return { value: new TextDecoder().decode(bytes), key: read };
18281
18625
  });
18282
18626
  }
@@ -18293,17 +18637,18 @@ var Launcher = class {
18293
18637
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
18294
18638
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
18295
18639
  }
18640
+ const { asked } = target;
18296
18641
  if (target.abi === "js") {
18297
- return { abi: "js", glue: await this.text(target.wasm, caller), path: target.wasm };
18642
+ return { abi: "js", glue: await this.text(target.wasm, caller, asked), path: target.wasm };
18298
18643
  }
18299
18644
  if (target.abi !== "wasi") {
18300
18645
  const [glue, { module: module2 }] = await Promise.all([
18301
- this.text(target.glue, caller),
18302
- this.module(target.wasm, caller)
18646
+ this.text(target.glue, caller, asked),
18647
+ this.module(target.wasm, caller, asked && modulePath(asked))
18303
18648
  ]);
18304
18649
  return { glue, module: module2 };
18305
18650
  }
18306
- const { module, memory, foreign } = await this.module(target.wasm, caller);
18651
+ const { module, memory, foreign } = await this.module(target.wasm, caller, asked);
18307
18652
  const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
18308
18653
  const imports = target.imports ? await this.text(target.imports, caller) : void 0;
18309
18654
  return {
@@ -18340,11 +18685,13 @@ var Launcher = class {
18340
18685
  groupSession(pgid) {
18341
18686
  return this.jobs.sessionOf(pgid);
18342
18687
  }
18343
- treeScrubs(pid, req) {
18688
+ treeScrubs(req) {
18344
18689
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18345
- const scrubs = req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
18346
- if (scrubs) this.scrubs.set(pid, scrubs);
18347
- return scrubs;
18690
+ return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
18691
+ }
18692
+ marks(pid, req) {
18693
+ if (req.tag !== void 0) this.tags.set(pid, req.tag);
18694
+ if (req.links !== void 0) this.links.set(pid, req.links);
18348
18695
  }
18349
18696
  start(req) {
18350
18697
  this.sampling();
@@ -18352,7 +18699,7 @@ var Launcher = class {
18352
18699
  throw new SpawnError("ESRCH");
18353
18700
  }
18354
18701
  const pid = req.pid ?? this.nextPid++;
18355
- const scrubs = this.treeScrubs(pid, req);
18702
+ const scrubs = this.treeScrubs(req);
18356
18703
  const stack = this.stackFor(req.tag);
18357
18704
  const terminal = req.fds.stdioTerminal();
18358
18705
  const decided = req.decided ?? Promise.resolve(true);
@@ -18378,6 +18725,7 @@ var Launcher = class {
18378
18725
  cred: req.cred,
18379
18726
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
18380
18727
  ...this.owners ? { owners: true } : {},
18728
+ ...linksOf2(req),
18381
18729
  sealed: this.sealed,
18382
18730
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
18383
18731
  clipboard: this.clipboard,
@@ -18422,9 +18770,10 @@ var Launcher = class {
18422
18770
  ...this.shownIds(req)
18423
18771
  });
18424
18772
  this.processes.set(pid, handle);
18773
+ if (scrubs) this.scrubs.set(pid, scrubs);
18425
18774
  const { cred } = req;
18426
18775
  this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
18427
- if (req.tag !== void 0) this.tags.set(pid, req.tag);
18776
+ this.marks(pid, req);
18428
18777
  if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
18429
18778
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
18430
18779
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -18477,6 +18826,7 @@ var Launcher = class {
18477
18826
  this.childTimes.delete(pid);
18478
18827
  this.creds.delete(pid);
18479
18828
  this.tags.delete(pid);
18829
+ this.links.delete(pid);
18480
18830
  this.granted.delete(pid);
18481
18831
  }
18482
18832
  reaped(pid, parent) {
@@ -18652,6 +19002,7 @@ var Launcher = class {
18652
19002
  umask: this.umaskOf(ppid),
18653
19003
  cred,
18654
19004
  tag: this.tagOf(ppid),
19005
+ links: grant ? grant.cred.euid : this.links.get(ppid),
18655
19006
  ...group,
18656
19007
  ...exec
18657
19008
  });
@@ -18713,7 +19064,8 @@ var Launcher = class {
18713
19064
  blocked: this.blockedBy(ppid),
18714
19065
  umask: this.umaskOf(ppid),
18715
19066
  cred: this.credOf(ppid),
18716
- tag: this.tagOf(ppid)
19067
+ tag: this.tagOf(ppid),
19068
+ links: this.links.get(ppid)
18717
19069
  })
18718
19070
  );
18719
19071
  }
@@ -18893,6 +19245,23 @@ var Launcher = class {
18893
19245
  }
18894
19246
  };
18895
19247
  }
19248
+ async revokeIntegrity(path) {
19249
+ if (path !== void 0 && (typeof path !== "string" || !path.startsWith("/"))) {
19250
+ throw new TypeError("integrity.revoke takes an absolute path");
19251
+ }
19252
+ if (!this.integrity) return;
19253
+ const at = path === void 0 ? void 0 : normalizePath(path);
19254
+ const real = at && await this.base.realpath?.(at, true).catch(() => void 0);
19255
+ const roots = [at, real].filter((p) => p !== void 0);
19256
+ this.revocations++;
19257
+ const hit = (key) => key.includes("\0") && (at === void 0 || key.split("\0").slice(0, 2).some((part) => isProtected(roots, part)));
19258
+ for (const cache of [this.compiled, this.texts]) {
19259
+ for (const key of [...cache.keys()]) if (hit(key)) cache.delete(key);
19260
+ }
19261
+ if (at === void 0) this.integrity.forget();
19262
+ for (const root of roots) this.integrity.forget(root);
19263
+ this.resetCatalogs();
19264
+ }
18896
19265
  attest(path) {
18897
19266
  return attest(this.rootFs, path, {
18898
19267
  users: this.usersMode,
@@ -19390,7 +19759,8 @@ var ATTEST_OPS = /* @__PURE__ */ new Set([
19390
19759
  "attest",
19391
19760
  "watch-files",
19392
19761
  "unwatch-files",
19393
- "chmod"
19762
+ "chmod",
19763
+ "integrity-revoke"
19394
19764
  ]);
19395
19765
  function isAttestRequest(req) {
19396
19766
  return ATTEST_OPS.has(req.op);
@@ -19408,6 +19778,7 @@ function attestOps(port) {
19408
19778
  return async (l, req) => {
19409
19779
  if (req.op === "attest") return l.attest(req.path);
19410
19780
  if (req.op === "chmod") return l.fs.chmod(req.path, modeOption(req.mode)).then(() => true);
19781
+ if (req.op === "integrity-revoke") return l.revokeIntegrity(req.path).then(() => true);
19411
19782
  if (req.op === "unwatch-files") {
19412
19783
  watches.get(req.watch)?.();
19413
19784
  return watches.delete(req.watch);