@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.
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)),
@@ -6585,12 +6631,12 @@ async function interruptible(work, signal) {
6585
6631
  if (signal.aborted) throw new KernelError("EINTR");
6586
6632
  let stop = () => {
6587
6633
  };
6588
- const interrupted2 = new Promise((_, reject) => {
6634
+ const interrupted3 = new Promise((_, reject) => {
6589
6635
  stop = () => reject(new KernelError("EINTR"));
6590
6636
  signal.addEventListener("abort", stop, { once: true });
6591
6637
  });
6592
6638
  try {
6593
- return await Promise.race([work, interrupted2]);
6639
+ return await Promise.race([work, interrupted3]);
6594
6640
  } finally {
6595
6641
  signal.removeEventListener("abort", stop);
6596
6642
  }
@@ -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,23 +8089,47 @@ var MountFs = class {
7889
8089
  ...Object.fromEntries(attrs.filter(([, value]) => value !== void 0))
7890
8090
  });
7891
8091
  }
7892
- async chmod(path, mode) {
7893
- const found = this.at(path);
7894
- if (!found) return this.base.chmod(path, mode);
7895
- this.writable(found.mount, normalizePath(path));
8092
+ async chmod(path, mode, redirects = 0) {
8093
+ const real = await this.follow(path);
8094
+ const found = this.at(real);
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
+ }
8103
+ this.writable(found.mount, real);
7896
8104
  if (!found.mount.caps.chmod) return;
7897
- this.table.forget(found.mount, normalizePath(path));
8105
+ this.table.forget(found.mount, real);
7898
8106
  await found.mount.conn.call({ op: "setattr", path: found.rel, mode });
7899
8107
  }
7900
- async utimes(path, atime, mtime) {
8108
+ async utimes(path, atime, mtime, redirects = 0) {
7901
8109
  const real = await this.follow(path);
7902
8110
  const found = this.at(real);
7903
- 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
+ }
7904
8120
  await this.setMtime(found, real, mtime);
7905
8121
  }
7906
- async lutimes(path, atime, mtime) {
8122
+ async lutimes(path, atime, mtime, redirects = 0) {
7907
8123
  const found = this.at(path);
7908
- 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
+ }
7909
8133
  const real = normalizePath(path);
7910
8134
  if (!found.mount.caps.linkTimes && (await this.lstat(real)).isSymbolicLink) {
7911
8135
  throw errnoError("EOPNOTSUPP", path);
@@ -8124,7 +8348,8 @@ async function run(entry, path, req) {
8124
8348
  function guardOf(entry) {
8125
8349
  const cred = entry.dac && entry.cred?.();
8126
8350
  if (!cred || !entry.dac) return void 0;
8127
- 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 };
8128
8353
  }
8129
8354
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
8130
8355
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
@@ -8185,9 +8410,15 @@ async function holdOp(entry, path, req) {
8185
8410
  entry.hold?.(path, on, open, void 0, req.handle);
8186
8411
  return done;
8187
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
+ }
8188
8418
  async function accessOp(entry, path, req) {
8189
8419
  const g = guardOf(entry);
8190
8420
  if (!g) return done;
8421
+ if (req.arg2 === "follow") return followed(g, path);
8191
8422
  const open = req.arg2 === "open";
8192
8423
  const cred = open || req.arg2 === "eaccess" ? g.cred : realIds(g.cred);
8193
8424
  const options = { follow: req.nofollow !== true, ...open ? { directory: false } : {} };
@@ -8210,15 +8441,18 @@ var CHANGING = /* @__PURE__ */ new Set([
8210
8441
  "lchown"
8211
8442
  ]);
8212
8443
  var CONTENT = /* @__PURE__ */ new Set(["write", "pwrite", "truncate"]);
8444
+ var REMOVING = /* @__PURE__ */ new Set(["unlink", "rmdir", "rm", "rename"]);
8213
8445
  async function mayChange(entry, path, req) {
8214
8446
  const { mayChange: mayChange2 } = entry.fs;
8215
8447
  if (!mayChange2) return;
8216
8448
  const { op } = req;
8217
8449
  if (CONTENT.has(op) && req.handle === void 0) return mayChange2.call(entry.fs, path, true);
8218
8450
  if (!CHANGING.has(op)) return;
8219
- 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));
8220
8453
  if (op === "rename" || op === "link") {
8221
- 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");
8222
8456
  }
8223
8457
  }
8224
8458
  async function guarded(entry, path, req) {
@@ -8938,7 +9172,7 @@ function mountTable(mounts = []) {
8938
9172
  }
8939
9173
 
8940
9174
  // src/version.ts
8941
- var KERNEL_VERSION = true ? "1.88.0" : "0.0.0-dev";
9175
+ var KERNEL_VERSION = true ? "1.89.0" : "0.0.0-dev";
8942
9176
  var OS_RELEASE = "6.0.0-slicc";
8943
9177
  function procVersion() {
8944
9178
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -11826,16 +12060,16 @@ function interruption(signal) {
11826
12060
  }
11827
12061
  async function selectFds(fds, read, write, timeoutMs, interrupt) {
11828
12062
  const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
11829
- const interrupted2 = interruption(interrupt);
11830
- interrupted2.promise.catch(() => {
12063
+ const interrupted3 = interruption(interrupt);
12064
+ interrupted3.promise.catch(() => {
11831
12065
  });
11832
12066
  try {
11833
- return await waitReady(fds, read, write, deadline, interrupted2.promise);
12067
+ return await waitReady(fds, read, write, deadline, interrupted3.promise);
11834
12068
  } finally {
11835
- interrupted2.done();
12069
+ interrupted3.done();
11836
12070
  }
11837
12071
  }
11838
- async function waitReady(fds, read, write, deadline, interrupted2) {
12072
+ async function waitReady(fds, read, write, deadline, interrupted3) {
11839
12073
  for (; ; ) {
11840
12074
  const now = ready(fds, read, write);
11841
12075
  if (now.read.length > 0 || now.write.length > 0 || Date.now() >= deadline) return now;
@@ -11847,7 +12081,7 @@ async function waitReady(fds, read, write, deadline, interrupted2) {
11847
12081
  let timer;
11848
12082
  const expiry = deadline === Number.POSITIVE_INFINITY ? [] : [new Promise((resolve2) => timer = setTimeout(resolve2, deadline - Date.now()))];
11849
12083
  try {
11850
- await Promise.race([...changes, ...expiry, interrupted2]);
12084
+ await Promise.race([...changes, ...expiry, interrupted3]);
11851
12085
  } finally {
11852
12086
  round.abort();
11853
12087
  clearTimeout(timer);
@@ -13030,7 +13264,7 @@ var WasmProcess = class {
13030
13264
  const { guard: guard2 } = this.options;
13031
13265
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
13032
13266
  if (!guard2 || inherited2 || req.pin !== void 0) return;
13033
- 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 };
13034
13268
  if (req.exclusive) return inParent(g, "open", req.path);
13035
13269
  if (req.created && guard2.created?.take(req.path)) return;
13036
13270
  const access2 = req.flags & WRITE_ACCESS;
@@ -13086,7 +13320,7 @@ var WasmProcess = class {
13086
13320
  }
13087
13321
  guarded() {
13088
13322
  const { guard: guard2 } = this.options;
13089
- 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 };
13090
13324
  }
13091
13325
  ttyOwner() {
13092
13326
  return { uid: this.cred.euid, gid: this.cred.egid, mode: TTY_MODE };
@@ -13812,6 +14046,7 @@ function processInit(opts, sab, umask, cred, lane) {
13812
14046
  umask,
13813
14047
  cred,
13814
14048
  ...opts.owners ? { owners: true } : {},
14049
+ ...opts.links !== void 0 ? { links: true } : {},
13815
14050
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
13816
14051
  };
13817
14052
  if (opts.program.abi === "js") {
@@ -13839,6 +14074,7 @@ function holdingToken(opts, holds, caller) {
13839
14074
  return mintSyncFsToken({
13840
14075
  ...opts.owners ? caller : {},
13841
14076
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
14077
+ ...opts.links !== void 0 ? { links: opts.links } : {},
13842
14078
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
13843
14079
  fs: opts.fs,
13844
14080
  cwd: opts.cwd,
@@ -13852,6 +14088,9 @@ function holdingToken(opts, holds, caller) {
13852
14088
  renamed: (from, to) => holds.renamed(from, to)
13853
14089
  });
13854
14090
  }
14091
+ function linksOf(opts) {
14092
+ return opts.links === void 0 ? {} : { links: opts.links };
14093
+ }
13855
14094
  function clipboardName(opts) {
13856
14095
  return opts.program.abi === "js" ? {} : { clipboardName: opts.argv0 };
13857
14096
  }
@@ -13863,7 +14102,7 @@ function processOptions(opts, header4, hooks, created3) {
13863
14102
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
13864
14103
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
13865
14104
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
13866
- ...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) } } : {},
13867
14106
  ...opts.identity ? { identity: opts.identity } : {},
13868
14107
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
13869
14108
  ...opts.settling ? { settling: opts.settling } : {},
@@ -13909,6 +14148,7 @@ function threadInit(opts, program, sab, cred, thread, main) {
13909
14148
  sab,
13910
14149
  cred: copyCred(cred),
13911
14150
  owners: opts.owners === true,
14151
+ ...opts.links !== void 0 ? { links: true } : {},
13912
14152
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
13913
14153
  thread,
13914
14154
  signals: main
@@ -14776,63 +15016,137 @@ function compile(secrets, options = {}) {
14776
15016
 
14777
15017
  // src/kernel/scrub-file.ts
14778
15018
  var SCRUB_IDLE_MS = 50;
15019
+ var SCRUB_NONBLOCK_MAX = 64 * 1024;
14779
15020
  var encoder5 = new TextEncoder();
14780
15021
  function scrubberOf(secrets) {
14781
15022
  const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
14782
15023
  return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
14783
15024
  }
14784
- async function writeAll2(file, bytes, signal) {
15025
+ async function writeAll2(file, bytes) {
14785
15026
  const write = file.write;
14786
- for (let at = 0; at < bytes.length; ) {
14787
- at += await write.call(file, bytes.subarray(at), at === 0 ? signal : void 0);
14788
- }
15027
+ for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
14789
15028
  }
14790
15029
  function bound(file, key) {
14791
15030
  const method = file[key];
14792
15031
  return typeof method === "function" ? { [key]: method.bind(file) } : {};
14793
15032
  }
15033
+ function interrupted2(err) {
15034
+ const code = err?.code;
15035
+ return code === "EINTR" || code === "EAGAIN";
15036
+ }
14794
15037
  function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
14795
15038
  const file = inner.file;
14796
15039
  const stream2 = scrubber.stream();
14797
- let queue = Promise.resolve();
15040
+ let tail = Promise.resolve();
15041
+ let pending = 0;
15042
+ let busy = false;
14798
15043
  let timer;
14799
15044
  let failed3;
15045
+ const waiters = /* @__PURE__ */ new Set();
15046
+ const quiet2 = () => !busy && pending === 0;
15047
+ const wake = () => {
15048
+ for (const waiter of [...waiters]) waiter();
15049
+ };
14800
15050
  const raise = () => {
14801
15051
  if (failed3 === void 0) return;
14802
15052
  const err = failed3;
14803
15053
  failed3 = void 0;
14804
15054
  throw err;
14805
15055
  };
14806
- const enqueue = (bytes, signal) => {
14807
- const next2 = queue.then(() => bytes.length > 0 ? writeAll2(file, bytes, signal) : void 0);
14808
- queue = next2.catch(() => void 0);
14809
- return next2;
15056
+ const later = (bytes) => {
15057
+ if (bytes.length === 0) return;
15058
+ pending += 1;
15059
+ tail = tail.then(() => writeAll2(file, bytes)).catch((err) => {
15060
+ if (failed3 === void 0) failed3 = err;
15061
+ }).finally(() => {
15062
+ pending -= 1;
15063
+ if (quiet2()) wake();
15064
+ });
15065
+ };
15066
+ const settling = (signal) => new Promise((resolve2, reject) => {
15067
+ const done4 = () => {
15068
+ waiters.delete(done4);
15069
+ signal?.removeEventListener("abort", stop);
15070
+ resolve2();
15071
+ };
15072
+ const stop = () => {
15073
+ waiters.delete(done4);
15074
+ reject(new KernelError("EINTR"));
15075
+ };
15076
+ if (signal?.aborted) return stop();
15077
+ waiters.add(done4);
15078
+ signal?.addEventListener("abort", stop, { once: true });
15079
+ });
15080
+ const settled = async () => {
15081
+ while (!quiet2()) await settling();
15082
+ };
15083
+ const changed = async (signal) => {
15084
+ const round = new AbortController();
15085
+ const stop = signal ? AbortSignal.any([signal, round.signal]) : round.signal;
15086
+ try {
15087
+ const own = settling(stop);
15088
+ await Promise.race(file.changed ? [own, file.changed(stop)] : [own]);
15089
+ } catch (err) {
15090
+ throw signal?.aborted ? new KernelError("EINTR") : err;
15091
+ } finally {
15092
+ round.abort();
15093
+ }
15094
+ };
15095
+ const turn = async (signal) => {
15096
+ for (let waited = false; ; waited = true) {
15097
+ if (quiet2() && (pollFile(file).writable || !file.changed)) {
15098
+ busy = true;
15099
+ return;
15100
+ }
15101
+ if (signal?.aborted) throw new KernelError(waited ? "EINTR" : "EAGAIN");
15102
+ await changed(signal);
15103
+ }
15104
+ };
15105
+ const send = async (out, signal) => {
15106
+ const write = file.write;
15107
+ for (let at = 0; at < out.length; ) {
15108
+ try {
15109
+ at += await write.call(file, out.subarray(at), signal);
15110
+ } catch (err) {
15111
+ if (!signal?.aborted || !interrupted2(err)) throw err;
15112
+ }
15113
+ if (at < out.length && signal?.aborted) return later(out.subarray(at));
15114
+ }
14810
15115
  };
14811
15116
  const release = () => {
14812
15117
  timer = void 0;
14813
- enqueue(stream2.idle()).catch((err) => {
14814
- failed3 = err;
14815
- });
15118
+ later(stream2.idle());
14816
15119
  };
14817
15120
  return new OpenFile({
14818
15121
  ...bound(file, "read"),
14819
15122
  ...bound(file, "peek"),
14820
- ...bound(file, "poll"),
14821
- ...bound(file, "changed"),
14822
15123
  ...bound(file, "stat"),
15124
+ poll: () => {
15125
+ const state = pollFile(file);
15126
+ return quiet2() ? state : { ...state, writable: false };
15127
+ },
15128
+ changed,
14823
15129
  ...file.tty ? { tty: file.tty } : {},
14824
15130
  ...file.clipboard ? { clipboard: file.clipboard } : {},
14825
15131
  ...file.write ? {
14826
15132
  write: async (bytes, signal) => {
14827
- if (timer !== void 0) clearTimeout(timer);
14828
- raise();
14829
- await enqueue(stream2.push(bytes), signal);
15133
+ await turn(signal);
15134
+ const taken = signal?.aborted && bytes.length > SCRUB_NONBLOCK_MAX ? bytes.subarray(0, SCRUB_NONBLOCK_MAX) : bytes;
15135
+ try {
15136
+ raise();
15137
+ if (timer !== void 0) clearTimeout(timer);
15138
+ timer = void 0;
15139
+ await send(stream2.push(taken), signal);
15140
+ } finally {
15141
+ busy = false;
15142
+ if (quiet2()) wake();
15143
+ }
14830
15144
  timer = setTimeout(release, idleMs);
14831
- return bytes.length;
15145
+ return taken.length;
14832
15146
  }
14833
15147
  } : {},
14834
15148
  flush: async () => {
14835
- await queue;
15149
+ await settled();
14836
15150
  raise();
14837
15151
  await file.flush?.();
14838
15152
  },
@@ -14840,7 +15154,9 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
14840
15154
  if (timer !== void 0) clearTimeout(timer);
14841
15155
  let failure;
14842
15156
  try {
14843
- await enqueue(stream2.end());
15157
+ await settled();
15158
+ later(stream2.end());
15159
+ await settled();
14844
15160
  raise();
14845
15161
  } catch (err) {
14846
15162
  failure = { err };
@@ -15016,12 +15332,12 @@ var SealedChannel = class {
15016
15332
  if (signal.aborted) throw new KernelError("EINTR");
15017
15333
  let stop = () => {
15018
15334
  };
15019
- const interrupted2 = new Promise((_, reject) => {
15335
+ const interrupted3 = new Promise((_, reject) => {
15020
15336
  stop = () => reject(new KernelError("EINTR"));
15021
15337
  signal.addEventListener("abort", stop, { once: true });
15022
15338
  });
15023
15339
  try {
15024
- await Promise.race([flight.done, interrupted2]);
15340
+ await Promise.race([flight.done, interrupted3]);
15025
15341
  } finally {
15026
15342
  signal.removeEventListener("abort", stop);
15027
15343
  }
@@ -17236,6 +17552,9 @@ var SUDO_SOURCE = [
17236
17552
  ].join("\n");
17237
17553
 
17238
17554
  // src/launcher.ts
17555
+ function linksOf2(req) {
17556
+ return req.links === void 0 ? {} : { links: req.links };
17557
+ }
17239
17558
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
17240
17559
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
17241
17560
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -17411,6 +17730,7 @@ var Launcher = class {
17411
17730
  warned = /* @__PURE__ */ new Set();
17412
17731
  compiled = /* @__PURE__ */ new Map();
17413
17732
  texts = /* @__PURE__ */ new Map();
17733
+ revocations = 0;
17414
17734
  integrity;
17415
17735
  scannedFs;
17416
17736
  get scanned() {
@@ -17423,6 +17743,7 @@ var Launcher = class {
17423
17743
  described = /* @__PURE__ */ new Map();
17424
17744
  creds = /* @__PURE__ */ new Map();
17425
17745
  tags = /* @__PURE__ */ new Map();
17746
+ links = /* @__PURE__ */ new Map();
17426
17747
  freshModules = new FreshModules();
17427
17748
  granted = /* @__PURE__ */ new Map();
17428
17749
  users;
@@ -17725,14 +18046,16 @@ var Launcher = class {
17725
18046
  }
17726
18047
  const path = this.fs.resolvePath(cwd, file);
17727
18048
  const glue = await followLinks(this.base, path);
17728
- const linked = COMMAND2.exec(glue)?.[1];
17729
- if (linked !== void 0) {
17730
- const target = await this.resolve(`/bin/${linked}`, argv0, cwd);
17731
- 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;
17732
18055
  return target && byPath ? { ...target, argv0: path } : target;
17733
18056
  }
17734
18057
  const own = await this.commandAt(glue, scope);
17735
- if (own) return own.script ? void 0 : targetOf(own);
18058
+ if (own) return own.script ? void 0 : { ...targetOf(own), ...asked };
17736
18059
  const head = await this.head(glue);
17737
18060
  if (!head) return void 0;
17738
18061
  if (isWasm(head)) {
@@ -17740,13 +18063,14 @@ var Launcher = class {
17740
18063
  abi: "wasi",
17741
18064
  glue,
17742
18065
  wasm: glue,
17743
- argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
18066
+ argv0: baseName(argv0 || file).replace(/\.wasm$/, ""),
18067
+ ...asked
17744
18068
  };
17745
18069
  }
17746
18070
  if (interpreter(head) !== "node") return void 0;
17747
18071
  const wasm = modulePath(glue);
17748
18072
  if (!await this.base.exists(wasm)) return void 0;
17749
- return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
18073
+ return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file), ...asked };
17750
18074
  }
17751
18075
  async head(path) {
17752
18076
  try {
@@ -17764,6 +18088,8 @@ var Launcher = class {
17764
18088
  async interpreted(file, argv, cwd, scope, depth = 0) {
17765
18089
  const command = await this.scriptCommand(file, cwd, scope);
17766
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) } : {};
17767
18093
  const head = await this.head(script);
17768
18094
  if (!head) return void 0;
17769
18095
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
@@ -17786,7 +18112,8 @@ var Launcher = class {
17786
18112
  target: withScriptEnv(inner.target, command),
17787
18113
  via,
17788
18114
  sources,
17789
- ...bins.length > 0 ? { bins } : {}
18115
+ ...bins.length > 0 ? { bins } : {},
18116
+ asked: { ...inner.asked, ...named }
17790
18117
  };
17791
18118
  }
17792
18119
  const target = withScriptEnv(found, command);
@@ -17795,7 +18122,8 @@ var Launcher = class {
17795
18122
  args: [...target.prefix ?? [], ...passed],
17796
18123
  via: [interp],
17797
18124
  sources: [script],
17798
- ...command?.bin ? { bins: [script] } : {}
18125
+ ...command?.bin ? { bins: [script] } : {},
18126
+ asked: named
17799
18127
  };
17800
18128
  }
17801
18129
  async plan(file, argv, cwd, scope, argv0) {
@@ -17809,7 +18137,14 @@ var Launcher = class {
17809
18137
  async mayExec(cred, req, planned) {
17810
18138
  const { dac } = this;
17811
18139
  if (!dac) return;
17812
- 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
+ };
17813
18148
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
17814
18149
  if (byPath(req.file)) {
17815
18150
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -17966,8 +18301,8 @@ var Launcher = class {
17966
18301
  readFile: (path) => this.integrity ? this.text(path, KERNEL_CALLER) : fs.readFile(path)
17967
18302
  });
17968
18303
  }
17969
- async cacheKey(path) {
17970
- const verified = await this.integrity?.key(path);
18304
+ async cacheKey(path, asked) {
18305
+ const verified = await this.integrity?.key(path, asked);
17971
18306
  if (verified !== void 0) return verified;
17972
18307
  const st = await this.base.stat(path);
17973
18308
  return `${path}:${st.size}:${st.mtime.getTime()}`;
@@ -17976,30 +18311,31 @@ var Launcher = class {
17976
18311
  const plain = () => fs.readFileBuffer(path);
17977
18312
  return this.integrity ? this.integrity.read(path, caller, plain) : plain();
17978
18313
  }
17979
- keyed(path, caller) {
18314
+ keyed(path, caller, asked) {
17980
18315
  const plain = async () => this.base.readFileBuffer(path);
17981
- 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 }));
17982
18317
  }
17983
18318
  cached(cache, key, load) {
17984
18319
  const hit = cache.get(key);
17985
18320
  if (hit !== void 0) return hit;
18321
+ const revocations = this.revocations;
17986
18322
  const loading2 = load();
17987
18323
  const found = loading2.then(({ value }) => value);
17988
18324
  cache.set(key, found);
17989
- loading2.then(
17990
- (done4) => {
17991
- if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
17992
- if (cache.get(key) === found) cache.delete(key);
17993
- if (done4.key !== void 0) cache.set(done4.key, found);
17994
- },
17995
- () => cache.delete(key)
17996
- );
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);
17997
18333
  return found;
17998
18334
  }
17999
- async module(path, caller) {
18000
- const key = await this.cacheKey(path);
18335
+ async module(path, caller, asked) {
18336
+ const key = await this.cacheKey(path, asked);
18001
18337
  return this.cached(this.compiled, key, async () => {
18002
- const { bytes, key: read } = await this.keyed(path, caller);
18338
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18003
18339
  const memory = importedMemory(bytes);
18004
18340
  const foreign = foreignImports(bytes);
18005
18341
  const compiled = await WebAssembly.compile(bytes);
@@ -18008,22 +18344,30 @@ var Launcher = class {
18008
18344
  }
18009
18345
  async checkedSources(planned, caller) {
18010
18346
  const { target } = planned;
18011
- 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
+ ];
18012
18354
  const manifests = /* @__PURE__ */ new Set();
18013
18355
  for (const file of files) {
18014
18356
  const root = file === void 0 ? void 0 : PACKAGE_ROOT.exec(file)?.[1];
18015
18357
  if (root !== void 0) manifests.add(`${root}/package.json`);
18016
18358
  }
18017
- 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
+ }
18018
18362
  for (const manifest of manifests) {
18019
18363
  if (await this.base.exists(manifest)) await this.text(manifest, caller);
18020
18364
  }
18021
18365
  }
18022
- async text(path, caller) {
18366
+ async text(path, caller, asked) {
18023
18367
  if (!this.integrity) return this.base.readFile(path);
18024
- const key = await this.cacheKey(path);
18368
+ const key = await this.cacheKey(path, asked);
18025
18369
  return this.cached(this.texts, key, async () => {
18026
- const { bytes, key: read } = await this.keyed(path, caller);
18370
+ const { bytes, key: read } = await this.keyed(path, caller, asked);
18027
18371
  return { value: new TextDecoder().decode(bytes), key: read };
18028
18372
  });
18029
18373
  }
@@ -18040,17 +18384,18 @@ var Launcher = class {
18040
18384
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
18041
18385
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
18042
18386
  }
18387
+ const { asked } = target;
18043
18388
  if (target.abi === "js") {
18044
- 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 };
18045
18390
  }
18046
18391
  if (target.abi !== "wasi") {
18047
18392
  const [glue, { module: module2 }] = await Promise.all([
18048
- this.text(target.glue, caller),
18049
- this.module(target.wasm, caller)
18393
+ this.text(target.glue, caller, asked),
18394
+ this.module(target.wasm, caller, asked && modulePath(asked))
18050
18395
  ]);
18051
18396
  return { glue, module: module2 };
18052
18397
  }
18053
- const { module, memory, foreign } = await this.module(target.wasm, caller);
18398
+ const { module, memory, foreign } = await this.module(target.wasm, caller, asked);
18054
18399
  const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
18055
18400
  const imports = target.imports ? await this.text(target.imports, caller) : void 0;
18056
18401
  return {
@@ -18087,11 +18432,13 @@ var Launcher = class {
18087
18432
  groupSession(pgid) {
18088
18433
  return this.jobs.sessionOf(pgid);
18089
18434
  }
18090
- treeScrubs(pid, req) {
18435
+ treeScrubs(req) {
18091
18436
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
18092
- const scrubs = req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
18093
- if (scrubs) this.scrubs.set(pid, scrubs);
18094
- return scrubs;
18437
+ return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
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);
18095
18442
  }
18096
18443
  start(req) {
18097
18444
  this.sampling();
@@ -18099,7 +18446,7 @@ var Launcher = class {
18099
18446
  throw new SpawnError("ESRCH");
18100
18447
  }
18101
18448
  const pid = req.pid ?? this.nextPid++;
18102
- const scrubs = this.treeScrubs(pid, req);
18449
+ const scrubs = this.treeScrubs(req);
18103
18450
  const stack = this.stackFor(req.tag);
18104
18451
  const terminal = req.fds.stdioTerminal();
18105
18452
  const decided = req.decided ?? Promise.resolve(true);
@@ -18125,6 +18472,7 @@ var Launcher = class {
18125
18472
  cred: req.cred,
18126
18473
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
18127
18474
  ...this.owners ? { owners: true } : {},
18475
+ ...linksOf2(req),
18128
18476
  sealed: this.sealed,
18129
18477
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
18130
18478
  clipboard: this.clipboard,
@@ -18169,9 +18517,10 @@ var Launcher = class {
18169
18517
  ...this.shownIds(req)
18170
18518
  });
18171
18519
  this.processes.set(pid, handle);
18520
+ if (scrubs) this.scrubs.set(pid, scrubs);
18172
18521
  const { cred } = req;
18173
18522
  this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
18174
- if (req.tag !== void 0) this.tags.set(pid, req.tag);
18523
+ this.marks(pid, req);
18175
18524
  if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
18176
18525
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
18177
18526
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -18224,6 +18573,7 @@ var Launcher = class {
18224
18573
  this.childTimes.delete(pid);
18225
18574
  this.creds.delete(pid);
18226
18575
  this.tags.delete(pid);
18576
+ this.links.delete(pid);
18227
18577
  this.granted.delete(pid);
18228
18578
  }
18229
18579
  reaped(pid, parent) {
@@ -18399,6 +18749,7 @@ var Launcher = class {
18399
18749
  umask: this.umaskOf(ppid),
18400
18750
  cred,
18401
18751
  tag: this.tagOf(ppid),
18752
+ links: grant ? grant.cred.euid : this.links.get(ppid),
18402
18753
  ...group,
18403
18754
  ...exec
18404
18755
  });
@@ -18460,7 +18811,8 @@ var Launcher = class {
18460
18811
  blocked: this.blockedBy(ppid),
18461
18812
  umask: this.umaskOf(ppid),
18462
18813
  cred: this.credOf(ppid),
18463
- tag: this.tagOf(ppid)
18814
+ tag: this.tagOf(ppid),
18815
+ links: this.links.get(ppid)
18464
18816
  })
18465
18817
  );
18466
18818
  }
@@ -18640,6 +18992,23 @@ var Launcher = class {
18640
18992
  }
18641
18993
  };
18642
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
+ }
18643
19012
  attest(path) {
18644
19013
  return attest(this.rootFs, path, {
18645
19014
  users: this.usersMode,
@@ -19461,7 +19830,7 @@ var RemoteTransport = class {
19461
19830
  finish();
19462
19831
  throw e;
19463
19832
  }
19464
- const { status, statusText, headers, url, redirected, redirects } = head;
19833
+ const { status, statusText, headers, url, redirected: redirected2, redirects } = head;
19465
19834
  const read = async () => {
19466
19835
  if (finished) return void 0;
19467
19836
  const reply = await this.ask({ net: "read", nid }).catch((e) => {
@@ -19478,7 +19847,7 @@ var RemoteTransport = class {
19478
19847
  this.cancel(nid);
19479
19848
  };
19480
19849
  return {
19481
- ...redirected ? { url, redirected } : {},
19850
+ ...redirected2 ? { url, redirected: redirected2 } : {},
19482
19851
  ...redirects ? { redirects } : {},
19483
19852
  status,
19484
19853
  statusText,
@@ -20361,6 +20730,7 @@ async function createNodeKernel(options = {}) {
20361
20730
  await launcher.fs.writeFile(path, data);
20362
20731
  },
20363
20732
  chmod: async (path, mode) => launcher.fs.chmod(path, modeOption(mode)),
20733
+ integrity: { revoke: (path) => guard2(() => launcher.revokeIntegrity(path)) },
20364
20734
  readFile: async (path) => launcher.fs.readFileBuffer(path),
20365
20735
  mount: (spec) => guard2(() => launcher.mount(spec)),
20366
20736
  umount: (target) => guard2(async () => launcher.umount(target)),