@ai-ecoverse/slicc-kernel 1.88.1 → 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.
package/dist/node.js CHANGED
@@ -140,6 +140,14 @@ function inodeOf(path) {
140
140
  h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
141
141
  return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
142
142
  }
143
+ function redirected(of, to) {
144
+ return Object.assign(fsError("EXDEV", `${of} leads to the mount at ${to}`), {
145
+ redirect: { of: normalizePath(of), to: normalizePath(to) }
146
+ });
147
+ }
148
+ function redirectOf(err) {
149
+ return err?.redirect;
150
+ }
143
151
 
144
152
  // src/fs/owners.ts
145
153
  var ROOT_OWNER = Object.freeze({ uid: 0, gid: 0 });
@@ -311,7 +319,10 @@ var Perms = class {
311
319
  function parts(path) {
312
320
  return path.split("/").filter((part) => part !== "" && part !== ".");
313
321
  }
314
- async function walk(fs, cred, path, follow2) {
322
+ function trustedLink(perm, links) {
323
+ return links === void 0 || perm.uid === 0 || perm.uid === links;
324
+ }
325
+ async function walk(fs, cred, path, follow2, links) {
315
326
  const perms = new Perms(fs);
316
327
  await perms.learn(path);
317
328
  const queue = parts(path);
@@ -329,6 +340,7 @@ async function walk(fs, cred, path, follow2) {
329
340
  const perm = await perms.at(next2);
330
341
  const last = queue.length === 0;
331
342
  if (perm?.link !== void 0 && (!last || follow2)) {
343
+ if (!trustedLink(perm, links)) return { path, perm: void 0, denied: next2, link: true };
332
344
  if (++hops > MAX_LINKS2) return { path: next2, perm: void 0 };
333
345
  queue.unshift(...parts(perm.link));
334
346
  if (perm.link.startsWith("/")) at = "/";
@@ -353,9 +365,9 @@ async function finalOf(fs, walked, want, options) {
353
365
  return st ? [permOf(st), st.isDirectory] : [perm, false];
354
366
  }
355
367
  async function access(fs, cred, path, want, options = {}) {
356
- const walked = await walk(fs, cred, path, options.follow !== false);
368
+ const walked = await walk(fs, cred, path, options.follow !== false, options.links);
357
369
  if (walked.denied !== void 0) {
358
- if (await present(fs, walked.denied, true)) return walked;
370
+ if (walked.link || await present(fs, walked.denied, true)) return walked;
359
371
  return { path, perm: void 0, missing: true };
360
372
  }
361
373
  if (!walked.perm) return { ...walked, missing: true };
@@ -422,6 +434,9 @@ var Created = class {
422
434
  return this.paths.delete(normalizePath(path));
423
435
  }
424
436
  };
437
+ function linked(g, options) {
438
+ return g.links === void 0 ? options : { ...options, links: g.links };
439
+ }
425
440
  function deny(g, op, path, errno = "EACCES") {
426
441
  const tag = g.tag === void 0 ? {} : { tag: g.tag };
427
442
  g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
@@ -432,8 +447,8 @@ function ttyOpenable(g, tty) {
432
447
  deny(g, "open", tty.name ?? "/dev/tty");
433
448
  }
434
449
  async function need(g, op, path, want, options = {}, shown = path) {
435
- const walked = await access(g.fs, g.cred, path, want, options);
436
- if (walked.denied !== void 0) deny(g, op, shown);
450
+ const walked = await access(g.fs, g.cred, path, want, linked(g, options));
451
+ if (walked.denied !== void 0) deny(g, op, walked.link ? walked.denied : shown);
437
452
  return walked;
438
453
  }
439
454
  function entryIn(dir, path) {
@@ -452,14 +467,16 @@ async function inParent(g, op, path, old = false) {
452
467
  return dir2.denied === void 0 ? stickyEntry(g, op, path, dir2) : void 0;
453
468
  }
454
469
  if (!old) return void await need(g, op, parentOf(path), W_OK | X_OK, DIR, path);
455
- const dir = await access(g.fs, g.cred, parentOf(path), W_OK | X_OK, DIR);
470
+ const dir = await access(g.fs, g.cred, parentOf(path), W_OK | X_OK, linked(g, DIR));
456
471
  if (dir.denied === void 0) return stickyEntry(g, op, path, dir);
472
+ if (dir.link) return deny(g, op, dir.denied);
457
473
  const lookup3 = dir.denied === dir.path && dir.perm && permits(g.cred, dir.perm, X_OK);
458
474
  if (!lookup3 || await present(g.fs, entryIn(dir, path), false)) deny(g, op, path);
459
475
  }
460
476
  async function makeIn(g, op, path) {
461
477
  for (let dir = parentOf(path); ; dir = parentOf(dir)) {
462
- const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, DIR);
478
+ const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, linked(g, DIR));
479
+ if (walked.link) return deny(g, op, walked.denied);
463
480
  if (walked.denied !== void 0) {
464
481
  if (!await present(g.fs, path, true)) deny(g, op, path);
465
482
  return;
@@ -3086,39 +3103,47 @@ var Integrity = class {
3086
3103
  this.unknown = integrityUnknown(options);
3087
3104
  this.report = report;
3088
3105
  }
3089
- async real(path) {
3106
+ async real(path, asked) {
3090
3107
  const at = normalizePath(path);
3091
3108
  const real = await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
3092
- return isProtected(this.prefixes, real) ? real : void 0;
3093
- }
3094
- async key(path) {
3095
- const real = await this.real(path);
3096
- if (real === void 0 || !this.root.version) return void 0;
3109
+ if (isProtected(this.prefixes, real)) return { real, name: real };
3110
+ const names = asked === void 0 ? [] : await namesOf(this.root, asked);
3111
+ if (real !== at) names.push(...await namesOf(this.root, at));
3112
+ const name2 = names.findLast((p) => isProtected(this.prefixes, p));
3113
+ return name2 === void 0 ? void 0 : { real, name: name2 };
3114
+ }
3115
+ async key(path, asked) {
3116
+ const found = await this.real(path, asked);
3117
+ if (found === void 0) return void 0;
3118
+ const { real, name: name2 } = found;
3119
+ if (name2 !== real) return `${name2}\0${real}\0`;
3120
+ if (!this.root.version) return void 0;
3097
3121
  return `${real}\0${await this.root.version(real)}`;
3098
3122
  }
3099
- async read(path, caller, plain) {
3100
- return (await this.readKeyed(path, caller, plain)).bytes;
3123
+ async read(path, caller, plain, asked) {
3124
+ return (await this.readKeyed(path, caller, plain, asked)).bytes;
3101
3125
  }
3102
- async readKeyed(path, caller, plain) {
3103
- const real = await this.real(path);
3104
- if (real === void 0) return { bytes: await plain() };
3105
- const read = this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
3126
+ async readKeyed(path, caller, plain, asked) {
3127
+ const found = await this.real(path, asked);
3128
+ if (found === void 0) return { bytes: await plain() };
3129
+ const { real, name: name2 } = found;
3130
+ 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 };
3106
3131
  const { bytes } = read;
3107
3132
  const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
3108
3133
  const actual = await sha256(bytes);
3109
- const denial = { pid: caller.pid, uid: caller.uid, path: real, actual };
3134
+ const denial = { pid: caller.pid, uid: caller.uid, path: name2, actual };
3110
3135
  const tagged = caller.tag !== void 0 ? { tag: caller.tag } : {};
3111
3136
  let expected;
3112
3137
  try {
3113
- expected = await this.expected(real);
3138
+ expected = await this.expected(name2);
3114
3139
  } catch {
3115
3140
  this.say({ ...denial, ...tagged, op: "exec-integrity", errno: "EACCES" });
3116
- throw fsError("EACCES", `integrity lookup failed: ${real}`);
3141
+ throw fsError("EACCES", `integrity lookup failed: ${name2}`);
3117
3142
  }
3118
3143
  if (expected === void 0) {
3119
3144
  if (this.unknown === "allow") {
3120
- if (!this.unverified.has(real)) {
3121
- this.unverified.add(real);
3145
+ if (!this.unverified.has(name2)) {
3146
+ this.unverified.add(name2);
3122
3147
  this.say({ ...denial, ...tagged, op: "exec-unverified", errno: "ENODATA" });
3123
3148
  }
3124
3149
  return keyed;
@@ -3128,7 +3153,12 @@ var Integrity = class {
3128
3153
  }
3129
3154
  const known = expected !== void 0 ? { expected } : {};
3130
3155
  this.say({ ...denial, ...tagged, ...known, op: "exec-integrity", errno: "EACCES" });
3131
- throw fsError("EACCES", `integrity check failed: ${real}`);
3156
+ throw fsError("EACCES", `integrity check failed: ${name2}`);
3157
+ }
3158
+ forget(path) {
3159
+ for (const name2 of [...this.unverified]) {
3160
+ if (path === void 0 || isProtected([path], name2)) this.unverified.delete(name2);
3161
+ }
3132
3162
  }
3133
3163
  async expected(path) {
3134
3164
  const answer = await this.lookup(path);
@@ -3173,10 +3203,10 @@ function missing(err) {
3173
3203
  const code = err?.code;
3174
3204
  return code === "ENOENT" || code === "ENOTDIR";
3175
3205
  }
3176
- async function resolved(fs, path, follow2) {
3206
+ async function resolved(fs, path, follow2, hops = []) {
3177
3207
  let done4 = [];
3178
3208
  let todo = partsOf(path);
3179
- for (let hops = 0; todo.length > 0; ) {
3209
+ for (let links = 0; todo.length > 0; ) {
3180
3210
  const part = todo.shift();
3181
3211
  if (part === "..") {
3182
3212
  done4.pop();
@@ -3198,13 +3228,20 @@ async function resolved(fs, path, follow2) {
3198
3228
  done4.push(part);
3199
3229
  continue;
3200
3230
  }
3201
- if (++hops > MAX_LINKS4) throw fsError("ELOOP", path);
3231
+ if (++links > MAX_LINKS4) throw fsError("ELOOP", path);
3232
+ hops.push(normalizePath([at, ...todo].join("/")));
3202
3233
  const link2 = await fs.readlink(at);
3203
3234
  if (link2.startsWith("/")) done4 = [];
3204
3235
  todo = [...partsOf(link2), ...todo];
3205
3236
  }
3206
3237
  return `/${done4.join("/")}`;
3207
3238
  }
3239
+ async function namesOf(fs, path) {
3240
+ const lexical2 = normalizePath(path);
3241
+ const hops = [];
3242
+ await resolved(fs, lexical2, true, hops).catch(() => void 0);
3243
+ return [lexical2, ...hops];
3244
+ }
3208
3245
  var ProtectedFs = class {
3209
3246
  prefixes;
3210
3247
  constructor(prefixes2) {
@@ -3257,8 +3294,8 @@ var ProtectedFs = class {
3257
3294
  await check(path, true);
3258
3295
  return fs.utimes(path, atime, mtime);
3259
3296
  },
3260
- async mayChange(path, follow2) {
3261
- await check(path, follow2);
3297
+ async mayChange(path, follow2, whole = false) {
3298
+ await check(path, follow2, whole);
3262
3299
  },
3263
3300
  async stat(path) {
3264
3301
  return marked(path, await fs.stat(path));
@@ -3581,6 +3618,7 @@ var OpfsFs = class {
3581
3618
  remote;
3582
3619
  ranged;
3583
3620
  owners = false;
3621
+ mounts = {};
3584
3622
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
3585
3623
  this.root = root;
3586
3624
  this.meta = sidecar(meta);
@@ -3654,6 +3692,12 @@ var OpfsFs = class {
3654
3692
  trackOwners() {
3655
3693
  this.owners = true;
3656
3694
  }
3695
+ crossings(mounted) {
3696
+ this.mounts.mounted = mounted;
3697
+ }
3698
+ crossing(of, to) {
3699
+ if (this.mounts.mounted?.(to)) throw redirected(of, to);
3700
+ }
3657
3701
  async hasOwner(path) {
3658
3702
  return ownOf((await this.meta.get([path]))[0]) !== void 0;
3659
3703
  }
@@ -3848,12 +3892,13 @@ var OpfsFs = class {
3848
3892
  if (!link2) throw err;
3849
3893
  if (link2.path === current && !follow2) return { path: current, link: link2 };
3850
3894
  const target = `${link2.link}${current.slice(link2.path.length)}`;
3851
- current = target.split("/").includes("..") ? await this.physical(parentOf4(link2.path), target, hops + 1) : resolvePath(parentOf4(link2.path), target);
3895
+ current = target.split("/").includes("..") ? await this.physical(parentOf4(link2.path), target, hops + 1, path) : resolvePath(parentOf4(link2.path), target);
3896
+ this.crossing(path, current);
3852
3897
  }
3853
3898
  }
3854
3899
  throw fsError("ELOOP", path);
3855
3900
  }
3856
- async physical(base, path, used) {
3901
+ async physical(base, path, used, of) {
3857
3902
  let done4 = [];
3858
3903
  let todo = (path.startsWith("/") ? path : `${base}/${path}`).split("/").filter((p) => p !== "" && p !== ".");
3859
3904
  for (let hops = used; todo.length > 0; ) {
@@ -3862,6 +3907,7 @@ var OpfsFs = class {
3862
3907
  else if (!todo.includes("..")) done4.push(part);
3863
3908
  else {
3864
3909
  const at = `/${[...done4, part].join("/")}`;
3910
+ this.crossing(of, [at, ...todo].join("/"));
3865
3911
  const st = await this.lstat(at);
3866
3912
  if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
3867
3913
  if (!st.isSymbolicLink) done4.push(part);
@@ -5992,14 +6038,14 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5992
6038
  const own = await userDir(dir);
5993
6039
  return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
5994
6040
  }
5995
- async function linked(path) {
6041
+ async function linked2(path) {
5996
6042
  return await virtual(path) ?? virtual(await followLinks(fs, path));
5997
6043
  }
5998
6044
  async function stat(path, real) {
5999
6045
  try {
6000
6046
  return await real(path);
6001
6047
  } catch (err) {
6002
- const found = missing3(err) ? await linked(path) : void 0;
6048
+ const found = missing3(err) ? await linked2(path) : void 0;
6003
6049
  if (found) return found;
6004
6050
  throw err;
6005
6051
  }
@@ -6011,7 +6057,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6011
6057
  try {
6012
6058
  return await fs.readFileBuffer(path);
6013
6059
  } catch (err) {
6014
- if (missing3(err) && (await linked(path))?.isFile) return new Uint8Array(0);
6060
+ if (missing3(err) && (await linked2(path))?.isFile) return new Uint8Array(0);
6015
6061
  throw err;
6016
6062
  }
6017
6063
  },
@@ -6019,7 +6065,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6019
6065
  link: (from, to, follow2) => fs.link(from, to, follow2),
6020
6066
  links: (path) => mayLink(fs, path),
6021
6067
  async exists(path) {
6022
- return await fs.exists(path) || await linked(path) !== void 0;
6068
+ return await fs.exists(path) || await linked2(path) !== void 0;
6023
6069
  },
6024
6070
  stat: (path) => stat(path, (p) => fs.stat(p)),
6025
6071
  lstat: (path) => stat(path, (p) => fs.lstat(p)),
@@ -7389,6 +7435,7 @@ var MountTable = class {
7389
7435
  return { quota: st.blocks * st.bsize, usage: (st.blocks - st.bfree) * st.bsize };
7390
7436
  }
7391
7437
  wrap(base) {
7438
+ base.crossings?.((path) => this.locate(normalizePath(path)) !== void 0);
7392
7439
  return new MountFs(this, base);
7393
7440
  }
7394
7441
  };
@@ -7412,6 +7459,37 @@ var MountFs = class {
7412
7459
  writable(mount, path) {
7413
7460
  if (readonly(mount)) throw errnoError("EROFS", path);
7414
7461
  }
7462
+ async through(path) {
7463
+ const real = normalizePath(path);
7464
+ if (this.at(real)) return real;
7465
+ try {
7466
+ await this.base.stat(parentOf7(real));
7467
+ return real;
7468
+ } catch (err) {
7469
+ const moved2 = redirectOf(err);
7470
+ return moved2 ? normalizePath(moved2.to + real.slice(moved2.of.length)) : real;
7471
+ }
7472
+ }
7473
+ async across(from, to, redirects) {
7474
+ const moved2 = [await this.through(from), await this.through(to)];
7475
+ const same = moved2[0] === normalizePath(from) && moved2[1] === normalizePath(to);
7476
+ if (same || redirects >= MAX_LINKS7) throw errnoError("EXDEV", `${from} -> ${to}`);
7477
+ return moved2;
7478
+ }
7479
+ async based(paths, redirects, run2, again) {
7480
+ try {
7481
+ return await run2();
7482
+ } catch (err) {
7483
+ const moved2 = redirectOf(err);
7484
+ if (!moved2) throw err;
7485
+ if (redirects >= MAX_LINKS7) throw errnoError("ELOOP", paths[0]);
7486
+ const at = paths.map((path) => {
7487
+ const real = normalizePath(path);
7488
+ return within5(real, moved2.of) ? normalizePath(moved2.to + real.slice(moved2.of.length)) : real;
7489
+ });
7490
+ return again(at, redirects + 1);
7491
+ }
7492
+ }
7415
7493
  async follow(path, hops = 0, create = false) {
7416
7494
  const found = this.at(path);
7417
7495
  if (!found) return path;
@@ -7459,10 +7537,17 @@ var MountFs = class {
7459
7537
  async readFile(path) {
7460
7538
  return new TextDecoder().decode(await this.readFileBuffer(path));
7461
7539
  }
7462
- async readFileBuffer(path) {
7540
+ async readFileBuffer(path, redirects = 0) {
7463
7541
  const real = await this.follow(path);
7464
7542
  const found = this.at(real);
7465
- if (!found) return this.base.readFileBuffer(real);
7543
+ if (!found) {
7544
+ return this.based(
7545
+ [real],
7546
+ redirects,
7547
+ () => this.base.readFileBuffer(real),
7548
+ ([to], n) => this.readFileBuffer(to, n)
7549
+ );
7550
+ }
7466
7551
  for (let attempt = 1; ; attempt++) {
7467
7552
  try {
7468
7553
  return await this.readWhole(found.mount, real, found.rel);
@@ -7501,10 +7586,18 @@ var MountFs = class {
7501
7586
  }
7502
7587
  return concat3(chunks2, total);
7503
7588
  }
7504
- async writeFile(path, content, owner) {
7589
+ async writeFile(path, content, owner, redirects = 0) {
7505
7590
  const real = await this.follow(path, 0, true);
7506
7591
  const found = this.at(real);
7507
- if (!found) return this.base.writeFile(real, content, owner);
7592
+ if (!found) {
7593
+ const write = () => this.base.writeFile(real, content, owner);
7594
+ return this.based(
7595
+ [real],
7596
+ redirects,
7597
+ write,
7598
+ ([to], n) => this.writeFile(to, content, owner, n)
7599
+ );
7600
+ }
7508
7601
  const { mount, rel: rel2 } = found;
7509
7602
  this.writable(mount, real);
7510
7603
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
@@ -7538,10 +7631,18 @@ var MountFs = class {
7538
7631
  this.table.forget(mount, real);
7539
7632
  }
7540
7633
  }
7541
- async pread(path, offset, length2, version) {
7634
+ async pread(path, offset, length2, version, redirects = 0) {
7542
7635
  const real = await this.follow(path);
7543
7636
  const found = this.at(real);
7544
- if (!found && this.base.pread) return this.base.pread(real, offset, length2);
7637
+ const { pread } = this.base;
7638
+ if (!found && pread) {
7639
+ return this.based(
7640
+ [real],
7641
+ redirects,
7642
+ () => pread.call(this.base, real, offset, length2),
7643
+ ([to], n) => this.pread(to, offset, length2, version, n)
7644
+ );
7645
+ }
7545
7646
  if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
7546
7647
  const { mount, rel: rel2 } = found;
7547
7648
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
@@ -7588,10 +7689,18 @@ var MountFs = class {
7588
7689
  );
7589
7690
  }
7590
7691
  }
7591
- async pwrite(path, offset, bytes, transfer = false) {
7692
+ async pwrite(path, offset, bytes, transfer = false, redirects = 0) {
7592
7693
  const real = await this.follow(path, 0, true);
7593
7694
  const found = this.at(real);
7594
- if (!found && this.base.pwrite) return this.base.pwrite(real, offset, bytes);
7695
+ const { pwrite } = this.base;
7696
+ if (!found && pwrite) {
7697
+ return this.based(
7698
+ [real],
7699
+ redirects,
7700
+ () => pwrite.call(this.base, real, offset, bytes),
7701
+ ([to], n) => this.pwrite(to, offset, bytes, transfer, n)
7702
+ );
7703
+ }
7595
7704
  if (!found)
7596
7705
  return this.base.writeFile(
7597
7706
  real,
@@ -7631,10 +7740,18 @@ var MountFs = class {
7631
7740
  await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
7632
7741
  }
7633
7742
  }
7634
- async truncate(path, size) {
7743
+ async truncate(path, size, redirects = 0) {
7635
7744
  const real = await this.follow(path);
7636
7745
  const found = this.at(real);
7637
- if (!found && this.base.truncate) return this.base.truncate(real, size);
7746
+ const { truncate } = this.base;
7747
+ if (!found && truncate) {
7748
+ return this.based(
7749
+ [real],
7750
+ redirects,
7751
+ () => truncate.call(this.base, real, size),
7752
+ ([to], n) => this.truncate(to, size, n)
7753
+ );
7754
+ }
7638
7755
  if (!found?.mount.caps.ranges) {
7639
7756
  return this.writeFile(real, resized(await this.readFileBuffer(real), size));
7640
7757
  }
@@ -7650,8 +7767,15 @@ var MountFs = class {
7650
7767
  this.table.forget(mount, real);
7651
7768
  }
7652
7769
  }
7653
- async exists(path) {
7654
- if (!this.at(path)) return this.base.exists(path);
7770
+ async exists(path, redirects = 0) {
7771
+ if (!this.at(path)) {
7772
+ return this.based(
7773
+ [path],
7774
+ redirects,
7775
+ () => this.base.exists(path),
7776
+ ([to], n) => this.exists(to, n)
7777
+ );
7778
+ }
7655
7779
  try {
7656
7780
  await this.stat(path);
7657
7781
  return true;
@@ -7661,9 +7785,16 @@ var MountFs = class {
7661
7785
  throw err;
7662
7786
  }
7663
7787
  }
7664
- async lstat(path) {
7788
+ async lstat(path, redirects = 0) {
7665
7789
  const found = this.at(path);
7666
- if (!found) return this.base.lstat(path);
7790
+ if (!found) {
7791
+ return this.based(
7792
+ [path],
7793
+ redirects,
7794
+ () => this.base.lstat(path),
7795
+ ([to], n) => this.lstat(to, n)
7796
+ );
7797
+ }
7667
7798
  const real = normalizePath(path);
7668
7799
  if (this.table.writing(real)?.truncated) await this.table.commit(real);
7669
7800
  return this.statWriting(
@@ -7676,9 +7807,15 @@ var MountFs = class {
7676
7807
  const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
7677
7808
  return statOf({ ...attr2, size: Math.max(attr2.size, ...ends) }, real, mount);
7678
7809
  }
7679
- async stat(path) {
7810
+ async stat(path, redirects = 0) {
7680
7811
  const real = await this.follow(path);
7681
- return this.at(real) ? this.lstat(real) : this.base.stat(real);
7812
+ if (this.at(real)) return this.lstat(real);
7813
+ return this.based(
7814
+ [real],
7815
+ redirects,
7816
+ () => this.base.stat(real),
7817
+ ([to], n) => this.stat(to, n)
7818
+ );
7682
7819
  }
7683
7820
  async entries(path) {
7684
7821
  const real = await this.follow(path);
@@ -7690,12 +7827,26 @@ var MountFs = class {
7690
7827
  list: await this.table.readdir(found.mount, real, found.rel)
7691
7828
  };
7692
7829
  }
7693
- async readdir(path) {
7694
- if (!this.at(path)) return this.base.readdir(path);
7830
+ async readdir(path, redirects = 0) {
7831
+ if (!this.at(path)) {
7832
+ return this.based(
7833
+ [path],
7834
+ redirects,
7835
+ () => this.base.readdir(path),
7836
+ ([to], n) => this.readdir(to, n)
7837
+ );
7838
+ }
7695
7839
  return (await this.entries(path)).list.map((e) => e.name);
7696
7840
  }
7697
- async readdirStat(path) {
7698
- if (!this.at(path)) return this.base.readdirStat(path);
7841
+ async readdirStat(path, redirects = 0) {
7842
+ if (!this.at(path)) {
7843
+ return this.based(
7844
+ [path],
7845
+ redirects,
7846
+ () => this.base.readdirStat(path),
7847
+ ([to], n) => this.readdirStat(to, n)
7848
+ );
7849
+ }
7699
7850
  const { real, mount, list } = await this.entries(path);
7700
7851
  return Promise.all(
7701
7852
  list.map(async (entry) => {
@@ -7707,9 +7858,16 @@ var MountFs = class {
7707
7858
  })
7708
7859
  );
7709
7860
  }
7710
- async mkdir(path, options = {}) {
7861
+ async mkdir(path, options = {}, redirects = 0) {
7711
7862
  const found = this.at(path);
7712
- if (!found) return this.base.mkdir(path, options);
7863
+ if (!found) {
7864
+ return this.based(
7865
+ [path],
7866
+ redirects,
7867
+ () => this.base.mkdir(path, options),
7868
+ ([to], n) => this.mkdir(to, options, n)
7869
+ );
7870
+ }
7713
7871
  const { mount, rel: rel2 } = found;
7714
7872
  const real = normalizePath(path);
7715
7873
  this.writable(mount, real);
@@ -7734,11 +7892,18 @@ var MountFs = class {
7734
7892
  await mount.conn.call({ op: "mkdir", path: sub, ...made2 });
7735
7893
  }
7736
7894
  }
7737
- async rm(path, options = {}) {
7895
+ async rm(path, options = {}, redirects = 0) {
7738
7896
  const found = this.at(path);
7739
7897
  if (!found && options.recursive && this.table.under(normalizePath(path)))
7740
7898
  return this.rmAround(normalizePath(path), options);
7741
- if (!found) return this.base.rm(path, options);
7899
+ if (!found) {
7900
+ return this.based(
7901
+ [path],
7902
+ redirects,
7903
+ () => this.base.rm(path, options),
7904
+ ([to], n) => this.rm(to, options, n)
7905
+ );
7906
+ }
7742
7907
  const { mount, rel: rel2 } = found;
7743
7908
  const real = normalizePath(path);
7744
7909
  if (rel2 === "/" && options.recursive) await this.rmEach(real, mount, rel2, options);
@@ -7779,11 +7944,22 @@ var MountFs = class {
7779
7944
  }
7780
7945
  throw first ?? errnoError("EBUSY", `${path} holds a mount point`);
7781
7946
  }
7782
- async rename(from, to) {
7947
+ async rename(from, to, redirects = 0) {
7783
7948
  const a = this.at(from);
7784
7949
  const b = this.at(to);
7785
- if (!a && !b) return this.base.rename(from, to);
7786
- if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
7950
+ if (!a && !b) {
7951
+ return this.based(
7952
+ [from, to],
7953
+ redirects,
7954
+ () => this.base.rename(from, to),
7955
+ ([f, t], n) => this.rename(f, t, n)
7956
+ );
7957
+ }
7958
+ if (!a || !b) {
7959
+ const [f, t] = await this.across(from, to, redirects);
7960
+ return this.rename(f, t, redirects + 1);
7961
+ }
7962
+ if (a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
7787
7963
  this.writable(a.mount, normalizePath(to));
7788
7964
  await this.table.commitUnder(from);
7789
7965
  await this.table.commitUnder(to);
@@ -7824,9 +8000,17 @@ var MountFs = class {
7824
8000
  this.table.forget(a.mount, normalizePath(to));
7825
8001
  }
7826
8002
  }
7827
- async symlink(target, path, owner) {
8003
+ async symlink(target, path, owner, redirects = 0) {
7828
8004
  const found = this.at(path);
7829
- if (!found) return this.base.symlink(target, path, owner);
8005
+ if (!found) {
8006
+ const make = () => this.base.symlink(target, path, owner);
8007
+ return this.based(
8008
+ [path],
8009
+ redirects,
8010
+ make,
8011
+ ([to], n) => this.symlink(target, to, owner, n)
8012
+ );
8013
+ }
7830
8014
  this.writable(found.mount, normalizePath(path));
7831
8015
  if (!found.mount.caps.symlinks)
7832
8016
  throw errnoError("EPERM", `${path}: this file system has no symlinks`);
@@ -7836,8 +8020,11 @@ var MountFs = class {
7836
8020
  await found.mount.conn.call({ op: "symlink", target, path: found.rel, ...made2 });
7837
8021
  }
7838
8022
  async realpath(path, follow2) {
7839
- if (this.at(path) || !this.base.realpath) throw errnoError("ENOSYS", path);
7840
- const real = await this.base.realpath(path, follow2);
8023
+ const { realpath: realpath2 } = this.base;
8024
+ if (this.at(path) || !realpath2) throw errnoError("ENOSYS", path);
8025
+ const real = await realpath2.call(this.base, path, follow2).catch((err) => {
8026
+ throw redirectOf(err) ? errnoError("ENOSYS", path) : err;
8027
+ });
7841
8028
  if (this.at(real)) throw errnoError("ENOSYS", path);
7842
8029
  return real;
7843
8030
  }
@@ -7859,17 +8046,30 @@ var MountFs = class {
7859
8046
  }
7860
8047
  return out;
7861
8048
  }
7862
- async readlink(path) {
8049
+ async readlink(path, redirects = 0) {
7863
8050
  const found = this.at(path);
7864
- if (!found) return this.base.readlink(path);
8051
+ if (!found) {
8052
+ return this.based(
8053
+ [path],
8054
+ redirects,
8055
+ () => this.base.readlink(path),
8056
+ ([to], n) => this.readlink(to, n)
8057
+ );
8058
+ }
7865
8059
  return await found.mount.conn.call({ op: "readlink", path: found.rel });
7866
8060
  }
7867
- async chown(path, change) {
8061
+ async chown(path, change, redirects = 0) {
7868
8062
  const real = change.follow === false || change.kind === "new" ? path : await this.follow(path);
7869
8063
  const found = this.at(real);
7870
8064
  if (found) return this.chownMounted(found, normalizePath(real), change);
7871
- if (!this.base.chown) throw errnoError("EPERM", path);
7872
- return this.base.chown(real, change);
8065
+ const { chown: chown2 } = this.base;
8066
+ if (!chown2) throw errnoError("EPERM", path);
8067
+ return this.based(
8068
+ [real],
8069
+ redirects,
8070
+ () => chown2.call(this.base, real, change),
8071
+ ([to], n) => this.chown(to, change, n)
8072
+ );
7873
8073
  }
7874
8074
  async chownMounted(found, real, change) {
7875
8075
  const { mount, rel: rel2 } = found;
@@ -7889,24 +8089,47 @@ var MountFs = class {
7889
8089
  ...Object.fromEntries(attrs.filter(([, value]) => value !== void 0))
7890
8090
  });
7891
8091
  }
7892
- async chmod(path, mode) {
8092
+ async chmod(path, mode, redirects = 0) {
7893
8093
  const real = await this.follow(path);
7894
8094
  const found = this.at(real);
7895
- if (!found) return this.base.chmod(real, mode);
8095
+ if (!found) {
8096
+ return this.based(
8097
+ [real],
8098
+ redirects,
8099
+ () => this.base.chmod(real, mode),
8100
+ ([to], n) => this.chmod(to, mode, n)
8101
+ );
8102
+ }
7896
8103
  this.writable(found.mount, real);
7897
8104
  if (!found.mount.caps.chmod) return;
7898
8105
  this.table.forget(found.mount, real);
7899
8106
  await found.mount.conn.call({ op: "setattr", path: found.rel, mode });
7900
8107
  }
7901
- async utimes(path, atime, mtime) {
8108
+ async utimes(path, atime, mtime, redirects = 0) {
7902
8109
  const real = await this.follow(path);
7903
8110
  const found = this.at(real);
7904
- if (!found) return this.base.utimes(real, atime, mtime);
8111
+ if (!found) {
8112
+ const set2 = () => this.base.utimes(real, atime, mtime);
8113
+ return this.based(
8114
+ [real],
8115
+ redirects,
8116
+ set2,
8117
+ ([to], n) => this.utimes(to, atime, mtime, n)
8118
+ );
8119
+ }
7905
8120
  await this.setMtime(found, real, mtime);
7906
8121
  }
7907
- async lutimes(path, atime, mtime) {
8122
+ async lutimes(path, atime, mtime, redirects = 0) {
7908
8123
  const found = this.at(path);
7909
- if (!found) return lutimesOf(this.base, path, atime, mtime);
8124
+ if (!found) {
8125
+ const set2 = () => lutimesOf(this.base, path, atime, mtime);
8126
+ return this.based(
8127
+ [path],
8128
+ redirects,
8129
+ set2,
8130
+ ([to], n) => this.lutimes(to, atime, mtime, n)
8131
+ );
8132
+ }
7910
8133
  const real = normalizePath(path);
7911
8134
  if (!found.mount.caps.linkTimes && (await this.lstat(real)).isSymbolicLink) {
7912
8135
  throw errnoError("EOPNOTSUPP", path);
@@ -8125,7 +8348,8 @@ async function run(entry, path, req) {
8125
8348
  function guardOf(entry) {
8126
8349
  const cred = entry.dac && entry.cred?.();
8127
8350
  if (!cred || !entry.dac) return void 0;
8128
- return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac };
8351
+ const links = entry.links === void 0 ? {} : { links: entry.links };
8352
+ return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac, ...links };
8129
8353
  }
8130
8354
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
8131
8355
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
@@ -8186,9 +8410,15 @@ async function holdOp(entry, path, req) {
8186
8410
  entry.hold?.(path, on, open, void 0, req.handle);
8187
8411
  return done;
8188
8412
  }
8413
+ async function followed(g, path) {
8414
+ const st = await g.fs.lstat(path).catch(() => void 0);
8415
+ if (st?.isSymbolicLink && !trustedLink(permOf(st), g.links)) deny(g, "follow", path);
8416
+ return done;
8417
+ }
8189
8418
  async function accessOp(entry, path, req) {
8190
8419
  const g = guardOf(entry);
8191
8420
  if (!g) return done;
8421
+ if (req.arg2 === "follow") return followed(g, path);
8192
8422
  const open = req.arg2 === "open";
8193
8423
  const cred = open || req.arg2 === "eaccess" ? g.cred : realIds(g.cred);
8194
8424
  const options = { follow: req.nofollow !== true, ...open ? { directory: false } : {} };
@@ -8211,15 +8441,18 @@ var CHANGING = /* @__PURE__ */ new Set([
8211
8441
  "lchown"
8212
8442
  ]);
8213
8443
  var CONTENT = /* @__PURE__ */ new Set(["write", "pwrite", "truncate"]);
8444
+ var REMOVING = /* @__PURE__ */ new Set(["unlink", "rmdir", "rm", "rename"]);
8214
8445
  async function mayChange(entry, path, req) {
8215
8446
  const { mayChange: mayChange2 } = entry.fs;
8216
8447
  if (!mayChange2) return;
8217
8448
  const { op } = req;
8218
8449
  if (CONTENT.has(op) && req.handle === void 0) return mayChange2.call(entry.fs, path, true);
8219
8450
  if (!CHANGING.has(op)) return;
8220
- await mayChange2.call(entry.fs, path, op === "utimes" || op === "chmod" || op === "chown");
8451
+ const follow2 = op === "utimes" || op === "chmod" || op === "chown";
8452
+ await mayChange2.call(entry.fs, path, follow2, REMOVING.has(op));
8221
8453
  if (op === "rename" || op === "link") {
8222
- await mayChange2.call(entry.fs, entry.fs.resolvePath(entry.cwd, req.arg2 ?? ""), false);
8454
+ const to = entry.fs.resolvePath(entry.cwd, req.arg2 ?? "");
8455
+ await mayChange2.call(entry.fs, to, false, op === "rename");
8223
8456
  }
8224
8457
  }
8225
8458
  async function guarded(entry, path, req) {
@@ -8939,7 +9172,7 @@ function mountTable(mounts = []) {
8939
9172
  }
8940
9173
 
8941
9174
  // src/version.ts
8942
- var KERNEL_VERSION = true ? "1.88.1" : "0.0.0-dev";
9175
+ var KERNEL_VERSION = true ? "1.89.0" : "0.0.0-dev";
8943
9176
  var OS_RELEASE = "6.0.0-slicc";
8944
9177
  function procVersion() {
8945
9178
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -13031,7 +13264,7 @@ var WasmProcess = class {
13031
13264
  const { guard: guard2 } = this.options;
13032
13265
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
13033
13266
  if (!guard2 || inherited2 || req.pin !== void 0) return;
13034
- const g = { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
13267
+ const g = { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid, links: guard2.links };
13035
13268
  if (req.exclusive) return inParent(g, "open", req.path);
13036
13269
  if (req.created && guard2.created?.take(req.path)) return;
13037
13270
  const access2 = req.flags & WRITE_ACCESS;
@@ -13087,7 +13320,7 @@ var WasmProcess = class {
13087
13320
  }
13088
13321
  guarded() {
13089
13322
  const { guard: guard2 } = this.options;
13090
- return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
13323
+ return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid, links: guard2.links };
13091
13324
  }
13092
13325
  ttyOwner() {
13093
13326
  return { uid: this.cred.euid, gid: this.cred.egid, mode: TTY_MODE };
@@ -13813,6 +14046,7 @@ function processInit(opts, sab, umask, cred, lane) {
13813
14046
  umask,
13814
14047
  cred,
13815
14048
  ...opts.owners ? { owners: true } : {},
14049
+ ...opts.links !== void 0 ? { links: true } : {},
13816
14050
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
13817
14051
  };
13818
14052
  if (opts.program.abi === "js") {
@@ -13840,6 +14074,7 @@ function holdingToken(opts, holds, caller) {
13840
14074
  return mintSyncFsToken({
13841
14075
  ...opts.owners ? caller : {},
13842
14076
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
14077
+ ...opts.links !== void 0 ? { links: opts.links } : {},
13843
14078
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
13844
14079
  fs: opts.fs,
13845
14080
  cwd: opts.cwd,
@@ -13853,6 +14088,9 @@ function holdingToken(opts, holds, caller) {
13853
14088
  renamed: (from, to) => holds.renamed(from, to)
13854
14089
  });
13855
14090
  }
14091
+ function linksOf(opts) {
14092
+ return opts.links === void 0 ? {} : { links: opts.links };
14093
+ }
13856
14094
  function clipboardName(opts) {
13857
14095
  return opts.program.abi === "js" ? {} : { clipboardName: opts.argv0 };
13858
14096
  }
@@ -13864,7 +14102,7 @@ function processOptions(opts, header4, hooks, created3) {
13864
14102
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
13865
14103
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
13866
14104
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
13867
- ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
14105
+ ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3, ...linksOf(opts) } } : {},
13868
14106
  ...opts.identity ? { identity: opts.identity } : {},
13869
14107
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
13870
14108
  ...opts.settling ? { settling: opts.settling } : {},
@@ -13910,6 +14148,7 @@ function threadInit(opts, program, sab, cred, thread, main) {
13910
14148
  sab,
13911
14149
  cred: copyCred(cred),
13912
14150
  owners: opts.owners === true,
14151
+ ...opts.links !== void 0 ? { links: true } : {},
13913
14152
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
13914
14153
  thread,
13915
14154
  signals: main
@@ -17313,6 +17552,9 @@ var SUDO_SOURCE = [
17313
17552
  ].join("\n");
17314
17553
 
17315
17554
  // src/launcher.ts
17555
+ function linksOf2(req) {
17556
+ return req.links === void 0 ? {} : { links: req.links };
17557
+ }
17316
17558
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
17317
17559
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
17318
17560
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -17488,6 +17730,7 @@ var Launcher = class {
17488
17730
  warned = /* @__PURE__ */ new Set();
17489
17731
  compiled = /* @__PURE__ */ new Map();
17490
17732
  texts = /* @__PURE__ */ new Map();
17733
+ revocations = 0;
17491
17734
  integrity;
17492
17735
  scannedFs;
17493
17736
  get scanned() {
@@ -17500,6 +17743,7 @@ var Launcher = class {
17500
17743
  described = /* @__PURE__ */ new Map();
17501
17744
  creds = /* @__PURE__ */ new Map();
17502
17745
  tags = /* @__PURE__ */ new Map();
17746
+ links = /* @__PURE__ */ new Map();
17503
17747
  freshModules = new FreshModules();
17504
17748
  granted = /* @__PURE__ */ new Map();
17505
17749
  users;
@@ -17802,14 +18046,16 @@ var Launcher = class {
17802
18046
  }
17803
18047
  const path = this.fs.resolvePath(cwd, file);
17804
18048
  const glue = await followLinks(this.base, path);
17805
- const linked = COMMAND2.exec(glue)?.[1];
17806
- if (linked !== void 0) {
17807
- const target = await this.resolve(`/bin/${linked}`, argv0, cwd);
17808
- const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked)?.argv0Path === true;
18049
+ const asked = glue === path ? {} : { asked: path };
18050
+ const linked2 = COMMAND2.exec(glue)?.[1];
18051
+ if (linked2 !== void 0) {
18052
+ const found = await this.resolve(`/bin/${linked2}`, argv0, cwd);
18053
+ const target = found && { ...found, ...asked };
18054
+ const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked2)?.argv0Path === true;
17809
18055
  return target && byPath ? { ...target, argv0: path } : target;
17810
18056
  }
17811
18057
  const own = await this.commandAt(glue, scope);
17812
- if (own) return own.script ? void 0 : targetOf(own);
18058
+ if (own) return own.script ? void 0 : { ...targetOf(own), ...asked };
17813
18059
  const head = await this.head(glue);
17814
18060
  if (!head) return void 0;
17815
18061
  if (isWasm(head)) {
@@ -17817,13 +18063,14 @@ var Launcher = class {
17817
18063
  abi: "wasi",
17818
18064
  glue,
17819
18065
  wasm: glue,
17820
- argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
18066
+ argv0: baseName(argv0 || file).replace(/\.wasm$/, ""),
18067
+ ...asked
17821
18068
  };
17822
18069
  }
17823
18070
  if (interpreter(head) !== "node") return void 0;
17824
18071
  const wasm = modulePath(glue);
17825
18072
  if (!await this.base.exists(wasm)) return void 0;
17826
- return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
18073
+ return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file), ...asked };
17827
18074
  }
17828
18075
  async head(path) {
17829
18076
  try {
@@ -17841,6 +18088,8 @@ var Launcher = class {
17841
18088
  async interpreted(file, argv, cwd, scope, depth = 0) {
17842
18089
  const command = await this.scriptCommand(file, cwd, scope);
17843
18090
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
18091
+ const byPath = command && file.includes("/") && !COMMAND2.test(file);
18092
+ const named = byPath ? { [script]: this.fs.resolvePath(cwd, file) } : {};
17844
18093
  const head = await this.head(script);
17845
18094
  if (!head) return void 0;
17846
18095
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
@@ -17863,7 +18112,8 @@ var Launcher = class {
17863
18112
  target: withScriptEnv(inner.target, command),
17864
18113
  via,
17865
18114
  sources,
17866
- ...bins.length > 0 ? { bins } : {}
18115
+ ...bins.length > 0 ? { bins } : {},
18116
+ asked: { ...inner.asked, ...named }
17867
18117
  };
17868
18118
  }
17869
18119
  const target = withScriptEnv(found, command);
@@ -17872,7 +18122,8 @@ var Launcher = class {
17872
18122
  args: [...target.prefix ?? [], ...passed],
17873
18123
  via: [interp],
17874
18124
  sources: [script],
17875
- ...command?.bin ? { bins: [script] } : {}
18125
+ ...command?.bin ? { bins: [script] } : {},
18126
+ asked: named
17876
18127
  };
17877
18128
  }
17878
18129
  async plan(file, argv, cwd, scope, argv0) {
@@ -17886,7 +18137,14 @@ var Launcher = class {
17886
18137
  async mayExec(cred, req, planned) {
17887
18138
  const { dac } = this;
17888
18139
  if (!dac) return;
17889
- const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
18140
+ const g = {
18141
+ fs: this.fs,
18142
+ cred,
18143
+ pid: req.pid,
18144
+ dac,
18145
+ tag: req.tag,
18146
+ links: this.links.get(req.pid)
18147
+ };
17890
18148
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
17891
18149
  if (byPath(req.file)) {
17892
18150
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -18043,8 +18301,8 @@ var Launcher = class {
18043
18301
  readFile: (path) => this.integrity ? this.text(path, KERNEL_CALLER) : fs.readFile(path)
18044
18302
  });
18045
18303
  }
18046
- async cacheKey(path) {
18047
- const verified = await this.integrity?.key(path);
18304
+ async cacheKey(path, asked) {
18305
+ const verified = await this.integrity?.key(path, asked);
18048
18306
  if (verified !== void 0) return verified;
18049
18307
  const st = await this.base.stat(path);
18050
18308
  return `${path}:${st.size}:${st.mtime.getTime()}`;
@@ -18053,30 +18311,31 @@ var Launcher = class {
18053
18311
  const plain = () => fs.readFileBuffer(path);
18054
18312
  return this.integrity ? this.integrity.read(path, caller, plain) : plain();
18055
18313
  }
18056
- keyed(path, caller) {
18314
+ keyed(path, caller, asked) {
18057
18315
  const plain = async () => this.base.readFileBuffer(path);
18058
- return this.integrity ? this.integrity.readKeyed(path, caller, plain) : plain().then((bytes) => ({ bytes }));
18316
+ return this.integrity ? this.integrity.readKeyed(path, caller, plain, asked) : plain().then((bytes) => ({ bytes }));
18059
18317
  }
18060
18318
  cached(cache, key, load) {
18061
18319
  const hit = cache.get(key);
18062
18320
  if (hit !== void 0) return hit;
18321
+ const revocations = this.revocations;
18063
18322
  const loading2 = load();
18064
18323
  const found = loading2.then(({ value }) => value);
18065
18324
  cache.set(key, found);
18066
- loading2.then(
18067
- (done4) => {
18068
- if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
18069
- if (cache.get(key) === found) cache.delete(key);
18070
- if (done4.key !== void 0) cache.set(done4.key, found);
18071
- },
18072
- () => cache.delete(key)
18073
- );
18325
+ const drop = () => {
18326
+ if (cache.get(key) === found) cache.delete(key);
18327
+ };
18328
+ loading2.then((done4) => {
18329
+ if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
18330
+ drop();
18331
+ if (done4.key !== void 0 && revocations === this.revocations) cache.set(done4.key, found);
18332
+ }, drop);
18074
18333
  return found;
18075
18334
  }
18076
- async module(path, caller) {
18077
- const key = await this.cacheKey(path);
18335
+ async module(path, caller, asked) {
18336
+ const key = await this.cacheKey(path, asked);
18078
18337
  return this.cached(this.compiled, key, async () => {
18079
- const { bytes, key: read } = await this.keyed(path, caller);
18338
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18080
18339
  const memory = importedMemory(bytes);
18081
18340
  const foreign = foreignImports(bytes);
18082
18341
  const compiled = await WebAssembly.compile(bytes);
@@ -18085,22 +18344,30 @@ var Launcher = class {
18085
18344
  }
18086
18345
  async checkedSources(planned, caller) {
18087
18346
  const { target } = planned;
18088
- const files = [target.wasm, target.glue, target.imports, ...planned.sources ?? []];
18347
+ const files = [
18348
+ target.wasm,
18349
+ target.glue,
18350
+ target.imports,
18351
+ target.asked,
18352
+ ...planned.sources ?? []
18353
+ ];
18089
18354
  const manifests = /* @__PURE__ */ new Set();
18090
18355
  for (const file of files) {
18091
18356
  const root = file === void 0 ? void 0 : PACKAGE_ROOT.exec(file)?.[1];
18092
18357
  if (root !== void 0) manifests.add(`${root}/package.json`);
18093
18358
  }
18094
- for (const source of planned.sources ?? []) await this.text(source, caller);
18359
+ for (const source of planned.sources ?? []) {
18360
+ await this.text(source, caller, planned.asked?.[source]);
18361
+ }
18095
18362
  for (const manifest of manifests) {
18096
18363
  if (await this.base.exists(manifest)) await this.text(manifest, caller);
18097
18364
  }
18098
18365
  }
18099
- async text(path, caller) {
18366
+ async text(path, caller, asked) {
18100
18367
  if (!this.integrity) return this.base.readFile(path);
18101
- const key = await this.cacheKey(path);
18368
+ const key = await this.cacheKey(path, asked);
18102
18369
  return this.cached(this.texts, key, async () => {
18103
- const { bytes, key: read } = await this.keyed(path, caller);
18370
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18104
18371
  return { value: new TextDecoder().decode(bytes), key: read };
18105
18372
  });
18106
18373
  }
@@ -18117,17 +18384,18 @@ var Launcher = class {
18117
18384
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
18118
18385
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
18119
18386
  }
18387
+ const { asked } = target;
18120
18388
  if (target.abi === "js") {
18121
- return { abi: "js", glue: await this.text(target.wasm, caller), path: target.wasm };
18389
+ return { abi: "js", glue: await this.text(target.wasm, caller, asked), path: target.wasm };
18122
18390
  }
18123
18391
  if (target.abi !== "wasi") {
18124
18392
  const [glue, { module: module2 }] = await Promise.all([
18125
- this.text(target.glue, caller),
18126
- this.module(target.wasm, caller)
18393
+ this.text(target.glue, caller, asked),
18394
+ this.module(target.wasm, caller, asked && modulePath(asked))
18127
18395
  ]);
18128
18396
  return { glue, module: module2 };
18129
18397
  }
18130
- const { module, memory, foreign } = await this.module(target.wasm, caller);
18398
+ const { module, memory, foreign } = await this.module(target.wasm, caller, asked);
18131
18399
  const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
18132
18400
  const imports = target.imports ? await this.text(target.imports, caller) : void 0;
18133
18401
  return {
@@ -18168,6 +18436,10 @@ var Launcher = class {
18168
18436
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18169
18437
  return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
18170
18438
  }
18439
+ marks(pid, req) {
18440
+ if (req.tag !== void 0) this.tags.set(pid, req.tag);
18441
+ if (req.links !== void 0) this.links.set(pid, req.links);
18442
+ }
18171
18443
  start(req) {
18172
18444
  this.sampling();
18173
18445
  if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
@@ -18200,6 +18472,7 @@ var Launcher = class {
18200
18472
  cred: req.cred,
18201
18473
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
18202
18474
  ...this.owners ? { owners: true } : {},
18475
+ ...linksOf2(req),
18203
18476
  sealed: this.sealed,
18204
18477
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
18205
18478
  clipboard: this.clipboard,
@@ -18247,7 +18520,7 @@ var Launcher = class {
18247
18520
  if (scrubs) this.scrubs.set(pid, scrubs);
18248
18521
  const { cred } = req;
18249
18522
  this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
18250
- if (req.tag !== void 0) this.tags.set(pid, req.tag);
18523
+ this.marks(pid, req);
18251
18524
  if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
18252
18525
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
18253
18526
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -18300,6 +18573,7 @@ var Launcher = class {
18300
18573
  this.childTimes.delete(pid);
18301
18574
  this.creds.delete(pid);
18302
18575
  this.tags.delete(pid);
18576
+ this.links.delete(pid);
18303
18577
  this.granted.delete(pid);
18304
18578
  }
18305
18579
  reaped(pid, parent) {
@@ -18475,6 +18749,7 @@ var Launcher = class {
18475
18749
  umask: this.umaskOf(ppid),
18476
18750
  cred,
18477
18751
  tag: this.tagOf(ppid),
18752
+ links: grant ? grant.cred.euid : this.links.get(ppid),
18478
18753
  ...group,
18479
18754
  ...exec
18480
18755
  });
@@ -18536,7 +18811,8 @@ var Launcher = class {
18536
18811
  blocked: this.blockedBy(ppid),
18537
18812
  umask: this.umaskOf(ppid),
18538
18813
  cred: this.credOf(ppid),
18539
- tag: this.tagOf(ppid)
18814
+ tag: this.tagOf(ppid),
18815
+ links: this.links.get(ppid)
18540
18816
  })
18541
18817
  );
18542
18818
  }
@@ -18716,6 +18992,23 @@ var Launcher = class {
18716
18992
  }
18717
18993
  };
18718
18994
  }
18995
+ async revokeIntegrity(path) {
18996
+ if (path !== void 0 && (typeof path !== "string" || !path.startsWith("/"))) {
18997
+ throw new TypeError("integrity.revoke takes an absolute path");
18998
+ }
18999
+ if (!this.integrity) return;
19000
+ const at = path === void 0 ? void 0 : normalizePath(path);
19001
+ const real = at && await this.base.realpath?.(at, true).catch(() => void 0);
19002
+ const roots = [at, real].filter((p) => p !== void 0);
19003
+ this.revocations++;
19004
+ const hit = (key) => key.includes("\0") && (at === void 0 || key.split("\0").slice(0, 2).some((part) => isProtected(roots, part)));
19005
+ for (const cache of [this.compiled, this.texts]) {
19006
+ for (const key of [...cache.keys()]) if (hit(key)) cache.delete(key);
19007
+ }
19008
+ if (at === void 0) this.integrity.forget();
19009
+ for (const root of roots) this.integrity.forget(root);
19010
+ this.resetCatalogs();
19011
+ }
18719
19012
  attest(path) {
18720
19013
  return attest(this.rootFs, path, {
18721
19014
  users: this.usersMode,
@@ -19537,7 +19830,7 @@ var RemoteTransport = class {
19537
19830
  finish();
19538
19831
  throw e;
19539
19832
  }
19540
- const { status, statusText, headers, url, redirected, redirects } = head;
19833
+ const { status, statusText, headers, url, redirected: redirected2, redirects } = head;
19541
19834
  const read = async () => {
19542
19835
  if (finished) return void 0;
19543
19836
  const reply = await this.ask({ net: "read", nid }).catch((e) => {
@@ -19554,7 +19847,7 @@ var RemoteTransport = class {
19554
19847
  this.cancel(nid);
19555
19848
  };
19556
19849
  return {
19557
- ...redirected ? { url, redirected } : {},
19850
+ ...redirected2 ? { url, redirected: redirected2 } : {},
19558
19851
  ...redirects ? { redirects } : {},
19559
19852
  status,
19560
19853
  statusText,
@@ -20437,6 +20730,7 @@ async function createNodeKernel(options = {}) {
20437
20730
  await launcher.fs.writeFile(path, data);
20438
20731
  },
20439
20732
  chmod: async (path, mode) => launcher.fs.chmod(path, modeOption(mode)),
20733
+ integrity: { revoke: (path) => guard2(() => launcher.revokeIntegrity(path)) },
20440
20734
  readFile: async (path) => launcher.fs.readFileBuffer(path),
20441
20735
  mount: (spec) => guard2(() => launcher.mount(spec)),
20442
20736
  umount: (target) => guard2(async () => launcher.umount(target)),