@ai-ecoverse/slicc-kernel 1.36.0 → 1.37.1

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.
@@ -4733,6 +4733,35 @@ async function scanFilesystems(fs, modules) {
4733
4733
  }
4734
4734
  return found;
4735
4735
  }
4736
+ var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
4737
+ async function scanBinfmts(fs, modules) {
4738
+ const found = /* @__PURE__ */ new Map();
4739
+ for (const pkg of await packagesIn(fs, modules)) {
4740
+ let manifest;
4741
+ try {
4742
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
4743
+ } catch {
4744
+ continue;
4745
+ }
4746
+ const declared = manifest.slicc?.binfmt;
4747
+ if (!declared || typeof declared !== "object") continue;
4748
+ for (const [suffix, command] of Object.entries(declared)) {
4749
+ if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME.test(command)) continue;
4750
+ if (!found.has(suffix)) found.set(suffix, command);
4751
+ }
4752
+ }
4753
+ return found;
4754
+ }
4755
+ function binfmtOf(binfmts, path) {
4756
+ const name2 = path.slice(path.lastIndexOf("/") + 1);
4757
+ let best;
4758
+ for (const suffix of binfmts.keys()) {
4759
+ if (name2.length > suffix.length && name2.endsWith(suffix)) {
4760
+ if (best === void 0 || suffix.length > best.length) best = suffix;
4761
+ }
4762
+ }
4763
+ return best === void 0 ? void 0 : binfmts.get(best);
4764
+ }
4736
4765
 
4737
4766
  // src/fs/commands.ts
4738
4767
  var S_IFDIR2 = 16384;
@@ -4849,13 +4878,193 @@ function withCommandDirs(fs, names) {
4849
4878
  };
4850
4879
  }
4851
4880
 
4881
+ // src/fs/unlinked.ts
4882
+ var UNLINKED_DIR = ".slicc-unlinked";
4883
+ function webLocks() {
4884
+ return globalThis.navigator?.locks;
4885
+ }
4886
+ var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
4887
+ var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
4888
+ var lockName = (instance) => `slicc-unlinked:${instance}`;
4889
+ var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
4890
+ var PathGate = class {
4891
+ tails = /* @__PURE__ */ new Map();
4892
+ async acquire(path) {
4893
+ const prev = this.tails.get(path) ?? Promise.resolve();
4894
+ const { promise, resolve } = Promise.withResolvers();
4895
+ const tail = prev.then(() => promise);
4896
+ this.tails.set(path, tail);
4897
+ await prev;
4898
+ return () => {
4899
+ resolve();
4900
+ if (this.tails.get(path) === tail) this.tails.delete(path);
4901
+ };
4902
+ }
4903
+ async run(path, op) {
4904
+ const release = await this.acquire(path);
4905
+ try {
4906
+ return await op();
4907
+ } finally {
4908
+ release();
4909
+ }
4910
+ }
4911
+ };
4912
+ var UnlinkedKeeper = class {
4913
+ fs;
4914
+ holders;
4915
+ rootOf;
4916
+ locks;
4917
+ busy;
4918
+ waiting = /* @__PURE__ */ new Set();
4919
+ instance = crypto.randomUUID();
4920
+ gate = new PathGate();
4921
+ queue = Promise.resolve();
4922
+ count = 0;
4923
+ constructor(fs, holders, rootOf, locks, busy = () => false) {
4924
+ this.fs = fs;
4925
+ this.holders = holders;
4926
+ this.rootOf = rootOf;
4927
+ this.locks = locks;
4928
+ this.busy = busy;
4929
+ void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
4930
+ }
4931
+ serial(op) {
4932
+ const next = this.queue.then(op);
4933
+ this.queue = next.catch(() => void 0);
4934
+ return next;
4935
+ }
4936
+ wrap() {
4937
+ const fs = this.fs;
4938
+ const keeper = this;
4939
+ const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
4940
+ return {
4941
+ ...fs,
4942
+ async rm(path, options) {
4943
+ const target = fs.resolvePath("/", path);
4944
+ const pinned = await keeper.pin(target);
4945
+ if (pinned.moved.has(target)) pinned.commit();
4946
+ else await keeper.settle(pinned, () => fs.rm(path, options));
4947
+ },
4948
+ async rename(from, to) {
4949
+ const pinned = await keeper.pin(fs.resolvePath("/", to));
4950
+ await keeper.settle(pinned, () => fs.rename(from, to));
4951
+ },
4952
+ readdir: async (path) => visible(await fs.readdir(path)),
4953
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
4954
+ };
4955
+ }
4956
+ async settle(pinned, op) {
4957
+ try {
4958
+ await op();
4959
+ } catch (err) {
4960
+ await pinned.rollback();
4961
+ throw err;
4962
+ }
4963
+ pinned.commit();
4964
+ }
4965
+ async pin(target) {
4966
+ const held = /* @__PURE__ */ new Map();
4967
+ for (const holder of this.holders) {
4968
+ if (holder.owns(target)) continue;
4969
+ for (const path of holder.opens.keys()) {
4970
+ if (!within3(path, target) || holder.unlinked.has(path)) continue;
4971
+ held.set(path, [...held.get(path) ?? [], holder]);
4972
+ }
4973
+ }
4974
+ const paths = [...held.keys()].sort();
4975
+ const releases = [];
4976
+ for (const path of paths) releases.push(await this.gate.acquire(path));
4977
+ const hidden = /* @__PURE__ */ new Map();
4978
+ for (const path of paths) {
4979
+ const at = await this.hide(path);
4980
+ if (at !== void 0) hidden.set(path, at);
4981
+ }
4982
+ const done4 = () => {
4983
+ for (const release of releases) release();
4984
+ };
4985
+ return {
4986
+ moved: new Set(hidden.keys()),
4987
+ commit: () => {
4988
+ for (const [path, at] of hidden) {
4989
+ const holders = held.get(path);
4990
+ const file = this.keptFile(at, { refs: holders.length });
4991
+ for (const holder of holders) holder.unlinked.set(path, file);
4992
+ }
4993
+ done4();
4994
+ },
4995
+ rollback: async () => {
4996
+ for (const [path, at] of hidden) {
4997
+ await this.serial(async () => {
4998
+ await Promise.allSettled([this.fs.rename(at, path)]);
4999
+ await this.tidy(parentOf3(at));
5000
+ });
5001
+ }
5002
+ done4();
5003
+ }
5004
+ };
5005
+ }
5006
+ keptFile(hidden, entry) {
5007
+ return {
5008
+ hidden,
5009
+ release: () => {
5010
+ if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
5011
+ }
5012
+ };
5013
+ }
5014
+ hide(path) {
5015
+ return this.serial(async () => {
5016
+ const dir = dirOf(this.rootOf(path));
5017
+ const hidden = `${dir}/${this.instance}.${++this.count}`;
5018
+ try {
5019
+ await this.fs.mkdir(dir, { recursive: true });
5020
+ await this.fs.rename(path, hidden);
5021
+ return hidden;
5022
+ } catch {
5023
+ await this.tidy(dir);
5024
+ return void 0;
5025
+ }
5026
+ });
5027
+ }
5028
+ closed(path) {
5029
+ if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
5030
+ }
5031
+ async drop(hidden) {
5032
+ if (this.busy(hidden)) {
5033
+ this.waiting.add(hidden);
5034
+ return;
5035
+ }
5036
+ await this.fs.rm(hidden).catch(() => void 0);
5037
+ await this.tidy(parentOf3(hidden));
5038
+ }
5039
+ async tidy(dir) {
5040
+ const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
5041
+ if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
5042
+ }
5043
+ sweep(root) {
5044
+ const query = this.locks?.query?.bind(this.locks);
5045
+ if (!query) return Promise.resolve();
5046
+ return this.serial(async () => {
5047
+ const dir = dirOf(root);
5048
+ const names = await this.fs.readdir(dir).catch(() => void 0);
5049
+ if (!names) return;
5050
+ const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
5051
+ for (const name2 of names) {
5052
+ const owner = name2.slice(0, name2.lastIndexOf("."));
5053
+ if (owner === this.instance || held.has(lockName(owner))) continue;
5054
+ await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
5055
+ }
5056
+ await this.tidy(dir);
5057
+ });
5058
+ }
5059
+ };
5060
+
4852
5061
  // src/fs/watch.ts
4853
5062
  var WATCH_BATCH_LIMIT = 1e3;
4854
5063
  var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
4855
- var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5064
+ var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
4856
5065
  function touches(watcher, changed) {
4857
5066
  return watcher.paths.some(
4858
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
5067
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
4859
5068
  );
4860
5069
  }
4861
5070
  var FsWatchers = class {
@@ -5237,7 +5446,7 @@ function parseSize(text) {
5237
5446
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
5238
5447
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
5239
5448
  }
5240
- var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
5449
+ var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
5241
5450
  var HeldSet = class extends Set {
5242
5451
  idle;
5243
5452
  constructor(idle) {
@@ -5250,18 +5459,47 @@ var HeldSet = class extends Set {
5250
5459
  }
5251
5460
  delete(holds) {
5252
5461
  const removed = super.delete(holds);
5253
- for (const path of holds.keys()) this.idle(path);
5462
+ for (const path of /* @__PURE__ */ new Set([...holds.keys(), ...holds.opens.keys()])) this.idle(path);
5463
+ for (const kept of holds.unlinked.values()) kept.release();
5464
+ holds.unlinked.clear();
5254
5465
  return removed;
5255
5466
  }
5256
5467
  };
5257
5468
  function heldUnder(held, target) {
5258
- for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
5469
+ for (const paths of held) for (const path of paths.keys()) if (within4(path, target)) return true;
5259
5470
  return false;
5260
5471
  }
5261
5472
  var HeldPaths = class extends Map {
5262
5473
  revoked = /* @__PURE__ */ new Set();
5474
+ opens = /* @__PURE__ */ new Map();
5475
+ unlinked = /* @__PURE__ */ new Map();
5476
+ aliases = /* @__PURE__ */ new Map();
5477
+ owning;
5263
5478
  onIdle;
5264
- hold(path, on) {
5479
+ keyOf(path) {
5480
+ return this.aliases.get(path) ?? path;
5481
+ }
5482
+ keptAt(path) {
5483
+ return this.unlinked.get(this.keyOf(path));
5484
+ }
5485
+ holding(path) {
5486
+ return this.opens.has(this.keyOf(path));
5487
+ }
5488
+ hold(path, on, open = false, real) {
5489
+ if (open) {
5490
+ const key = real ?? this.keyOf(path);
5491
+ if (key !== path) this.aliases.set(path, key);
5492
+ const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
5493
+ if (count2 > 0) this.opens.set(key, count2);
5494
+ else {
5495
+ this.opens.delete(key);
5496
+ this.unlinked.get(key)?.release();
5497
+ this.unlinked.delete(key);
5498
+ for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
5499
+ this.onIdle?.(key);
5500
+ }
5501
+ return;
5502
+ }
5265
5503
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
5266
5504
  if (count > 0) this.set(path, count);
5267
5505
  else {
@@ -5270,23 +5508,40 @@ var HeldPaths = class extends Map {
5270
5508
  this.onIdle?.(path);
5271
5509
  }
5272
5510
  }
5511
+ async own(path, op) {
5512
+ this.owning = path;
5513
+ try {
5514
+ return await op();
5515
+ } finally {
5516
+ this.owning = void 0;
5517
+ }
5518
+ }
5519
+ owns(path) {
5520
+ return this.owning === path;
5521
+ }
5273
5522
  revoke(prefix) {
5274
- for (const path of this.keys()) if (within3(path, prefix)) this.revoked.add(path);
5523
+ for (const path of this.keys()) if (within4(path, prefix)) this.revoked.add(path);
5275
5524
  }
5276
5525
  renamed(from, to) {
5277
5526
  for (const [path, count] of [...this]) {
5278
- if (!within3(path, from)) continue;
5527
+ if (!within4(path, from)) continue;
5279
5528
  const moved2 = to + path.slice(from.length);
5280
5529
  this.delete(path);
5281
5530
  this.set(moved2, (this.get(moved2) ?? 0) + count);
5282
5531
  if (this.revoked.delete(path)) this.revoked.add(moved2);
5283
5532
  }
5533
+ for (const [path, count] of [...this.opens]) {
5534
+ if (!within4(path, from)) continue;
5535
+ const moved2 = to + path.slice(from.length);
5536
+ this.opens.delete(path);
5537
+ this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
5538
+ }
5284
5539
  }
5285
5540
  isRevoked(path) {
5286
5541
  return this.revoked.has(path);
5287
5542
  }
5288
5543
  };
5289
- var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5544
+ var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5290
5545
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
5291
5546
  function statOf(attr, path, mount) {
5292
5547
  const mtime = new Date(attr.mtime);
@@ -5375,7 +5630,7 @@ var MountTable = class {
5375
5630
  locate(path) {
5376
5631
  let best;
5377
5632
  for (const mount of this.mounts.values()) {
5378
- if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
5633
+ if (within4(path, mount.target) && (!best || mount.target.length > best.target.length))
5379
5634
  best = mount;
5380
5635
  }
5381
5636
  if (!best) return void 0;
@@ -5514,7 +5769,7 @@ var MountTable = class {
5514
5769
  async commitUnder(prefix) {
5515
5770
  const at = normalizePath(prefix);
5516
5771
  await Promise.all(
5517
- [...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
5772
+ [...this.sessions.keys()].filter((p) => within4(p, at)).map((p) => this.commit(p))
5518
5773
  );
5519
5774
  }
5520
5775
  async commitOverlapping(real, offset, length2) {
@@ -5548,10 +5803,10 @@ var MountTable = class {
5548
5803
  this.deps.changed?.(...full);
5549
5804
  }
5550
5805
  forget(mount, path) {
5551
- for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
5806
+ for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
5552
5807
  mount.lists.delete(path);
5553
- mount.lists.delete(parentOf4(path));
5554
- for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
5808
+ mount.lists.delete(parentOf5(path));
5809
+ for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
5555
5810
  }
5556
5811
  async getattr(mount, path, rel2) {
5557
5812
  if (mount.conn.failure) throw mount.conn.failure;
@@ -5616,7 +5871,7 @@ var MountFs = class {
5616
5871
  if (attr.kind !== "symlink") return real;
5617
5872
  if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
5618
5873
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
5619
- return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
5874
+ return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
5620
5875
  }
5621
5876
  async open(mount, rel2, write) {
5622
5877
  return await mount.conn.call({
@@ -6130,6 +6385,8 @@ async function ranged(fs, path, req) {
6130
6385
  else await ops.truncate(path, req.length ?? 0);
6131
6386
  return done;
6132
6387
  }
6388
+ var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
6389
+ var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
6133
6390
  async function dispatchSyncFs(req) {
6134
6391
  const entry = resolveSyncFsToken(req.token);
6135
6392
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -6138,11 +6395,22 @@ async function dispatchSyncFs(req) {
6138
6395
  const path = fs.resolvePath(cwd, req.path);
6139
6396
  if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
6140
6397
  if (req.op === "hold") {
6141
- entry.hold?.(path, req.mode === 1);
6398
+ const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
6399
+ const real = on && open ? await fs.realpath?.(path, true).catch(() => path) : void 0;
6400
+ entry.hold?.(path, on, open, real);
6142
6401
  return done;
6143
6402
  }
6144
6403
  if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
6145
- return await run(entry, path, req);
6404
+ if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
6405
+ const serve = async () => {
6406
+ const kept = entry.unlinked?.(path);
6407
+ return run(entry, kept && !await fs.exists(path) ? kept.hidden : path, req);
6408
+ };
6409
+ return await (entry.gate ? entry.gate(path, serve) : serve());
6410
+ }
6411
+ if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
6412
+ const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
6413
+ return await entry.own(target, () => run(entry, path, req));
6146
6414
  } catch (err) {
6147
6415
  return toErrno(err);
6148
6416
  }
@@ -7530,7 +7798,7 @@ function isMissing(err) {
7530
7798
  if (err?.code === "ENOENT") return true;
7531
7799
  return err instanceof Error && err.message.startsWith("ENOENT");
7532
7800
  }
7533
- function within4(path, root) {
7801
+ function within5(path, root) {
7534
7802
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
7535
7803
  }
7536
7804
  var VfsNode = class {
@@ -7735,7 +8003,7 @@ var VfsNodes = class {
7735
8003
  return run2;
7736
8004
  }
7737
8005
  async entryKey(path) {
7738
- const parent = parentOf5(path);
8006
+ const parent = parentOf6(path);
7739
8007
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
7740
8008
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
7741
8009
  }
@@ -7783,12 +8051,12 @@ var VfsNodes = class {
7783
8051
  return node;
7784
8052
  }
7785
8053
  holds(prefix) {
7786
- for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
8054
+ for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
7787
8055
  return false;
7788
8056
  }
7789
8057
  writes(prefix) {
7790
8058
  for (const [path, node] of this.byPath)
7791
- if (node.writers > 0 && within4(path, prefix)) return true;
8059
+ if (node.writers > 0 && within5(path, prefix)) return true;
7792
8060
  return false;
7793
8061
  }
7794
8062
  closed(node, writable = false) {
@@ -7802,24 +8070,24 @@ var VfsNodes = class {
7802
8070
  this.onClosed?.(node.path);
7803
8071
  }
7804
8072
  async flush(path) {
7805
- for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
8073
+ for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
7806
8074
  }
7807
8075
  async unlinking(path) {
7808
8076
  for (const [held, node] of [...this.byPath]) {
7809
- if (!within4(held, path)) continue;
8077
+ if (!within5(held, path)) continue;
7810
8078
  await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
7811
8079
  }
7812
8080
  }
7813
8081
  revoke(prefix) {
7814
8082
  for (const [path, node] of this.byPath) {
7815
- if (!within4(path, prefix)) continue;
8083
+ if (!within5(path, prefix)) continue;
7816
8084
  node.revoke();
7817
8085
  this.byPath.delete(path);
7818
8086
  }
7819
8087
  }
7820
8088
  unlinked(path) {
7821
8089
  for (const [held, node] of [...this.byPath]) {
7822
- if (!within4(held, path)) continue;
8090
+ if (!within5(held, path)) continue;
7823
8091
  node.orphaned = true;
7824
8092
  this.byPath.delete(held);
7825
8093
  }
@@ -7828,8 +8096,8 @@ var VfsNodes = class {
7828
8096
  if (from === to) return;
7829
8097
  const moved2 = [];
7830
8098
  for (const [p, node] of this.byPath) {
7831
- if (within4(p, from)) moved2.push(node);
7832
- else if (within4(p, to)) {
8099
+ if (within5(p, from)) moved2.push(node);
8100
+ else if (within5(p, to)) {
7833
8101
  node.orphaned = true;
7834
8102
  this.byPath.delete(p);
7835
8103
  }
@@ -7852,7 +8120,7 @@ async function present(fs, path, follow = false) {
7852
8120
  }
7853
8121
  }
7854
8122
  var MAX_LINKS5 = 40;
7855
- function parentOf5(path) {
8123
+ function parentOf6(path) {
7856
8124
  return path.slice(0, path.lastIndexOf("/")) || "/";
7857
8125
  }
7858
8126
  async function linkOf(fs, path) {
@@ -7865,7 +8133,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
7865
8133
  const part = queue.shift();
7866
8134
  if (part === ".") continue;
7867
8135
  if (part === "..") {
7868
- at = parentOf5(at);
8136
+ at = parentOf6(at);
7869
8137
  continue;
7870
8138
  }
7871
8139
  const next = at === "/" ? `/${part}` : `${at}/${part}`;
@@ -7889,7 +8157,7 @@ function joinName(dir, name2) {
7889
8157
  }
7890
8158
  async function realTarget(fs, path) {
7891
8159
  const budget = { hops: 0 };
7892
- let dir = await realDir(fs, parentOf5(path), budget);
8160
+ let dir = await realDir(fs, parentOf6(path), budget);
7893
8161
  let name2 = path.slice(path.lastIndexOf("/") + 1);
7894
8162
  for (; ; ) {
7895
8163
  const link2 = await linkOf(fs, joinName(dir, name2));
@@ -7897,13 +8165,13 @@ async function realTarget(fs, path) {
7897
8165
  if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
7898
8166
  budget.hops++;
7899
8167
  const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
7900
- dir = await realDir(fs, parentOf5(target), budget);
8168
+ dir = await realDir(fs, parentOf6(target), budget);
7901
8169
  name2 = target.slice(target.lastIndexOf("/") + 1);
7902
8170
  }
7903
8171
  return joinName(dir, name2);
7904
8172
  }
7905
8173
  async function targetDir(fs, path) {
7906
- return parentOf5(await realTarget(fs, path).catch(() => path));
8174
+ return parentOf6(await realTarget(fs, path).catch(() => path));
7907
8175
  }
7908
8176
  async function refuseReadonly(fs, path, flags, opts = {}) {
7909
8177
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -8292,7 +8560,7 @@ var WasmProcess = class {
8292
8560
  const fd = this.fds.install(heldFile(), 3);
8293
8561
  try {
8294
8562
  const nodes = this.nodes;
8295
- const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : await nodes.targetKey(req.path);
8563
+ const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : req.create ? await nodes.targetKey(req.path) : await this.keptOr(await nodes.targetKey(req.path));
8296
8564
  const open = { ...req, path };
8297
8565
  if (req.exclusive) {
8298
8566
  await nodes.createExclusive(path);
@@ -8448,6 +8716,11 @@ var WasmProcess = class {
8448
8716
  return { ok: true, kind: "json", json: await file.stat() };
8449
8717
  }
8450
8718
  }
8719
+ keptPath;
8720
+ async keptOr(path) {
8721
+ const kept = this.keptPath?.(path);
8722
+ return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
8723
+ }
8451
8724
  async promote(req) {
8452
8725
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8453
8726
  if (req.share !== void 0) {
@@ -8455,7 +8728,7 @@ var WasmProcess = class {
8455
8728
  return;
8456
8729
  }
8457
8730
  if (req.path === void 0) throw new KernelError("EINVAL");
8458
- const path = req.orphan ? req.path : await this.nodes.targetKey(req.path);
8731
+ const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path));
8459
8732
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8460
8733
  const file = vfsFile(
8461
8734
  this.options.fs,
@@ -8814,6 +9087,19 @@ function inherited(opts) {
8814
9087
  ...opts.umask !== void 0 ? { umask: opts.umask } : {}
8815
9088
  };
8816
9089
  }
9090
+ function holdingToken(opts, holds) {
9091
+ return mintSyncFsToken({
9092
+ fs: opts.fs,
9093
+ cwd: opts.cwd,
9094
+ ...opts.statfs ? { statfs: opts.statfs } : {},
9095
+ hold: (path, on, open, real) => holds.hold(path, on, open, real),
9096
+ unlinked: (path) => holds.keptAt(path),
9097
+ gate: (path, op) => opts.unlinkGate && holds.holding(path) ? opts.unlinkGate.run(holds.keyOf(path), op) : op(),
9098
+ own: (path, op) => holds.own(path, op),
9099
+ revoked: (path) => holds.isRevoked(path),
9100
+ renamed: (from, to) => holds.renamed(from, to)
9101
+ });
9102
+ }
8817
9103
  function spawnWasmProcess(opts) {
8818
9104
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
8819
9105
  const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
@@ -8858,14 +9144,8 @@ function spawnWasmProcess(opts) {
8858
9144
  });
8859
9145
  const holds = new HeldPaths();
8860
9146
  opts.held?.add(holds);
8861
- const token = mintSyncFsToken({
8862
- fs: opts.fs,
8863
- cwd: opts.cwd,
8864
- ...opts.statfs ? { statfs: opts.statfs } : {},
8865
- hold: (path, on) => holds.hold(path, on),
8866
- revoked: (path) => holds.isRevoked(path),
8867
- renamed: (from, to) => holds.renamed(from, to)
8868
- });
9147
+ const token = holdingToken(opts, holds);
9148
+ process.keptPath = (path) => holds.keptAt(path)?.hidden;
8869
9149
  const worker = opts.createWorker();
8870
9150
  const dying = new Dying();
8871
9151
  const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
@@ -10383,6 +10663,7 @@ var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
10383
10663
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
10384
10664
  var WASM_MAGIC = [0, 97, 115, 109];
10385
10665
  var SHEBANG_MAX = 256;
10666
+ var MAX_INTERPRETERS = 4;
10386
10667
  var NOT_FOUND = 127;
10387
10668
  var DECIDE_MS = 100;
10388
10669
  var INIT_PID = 1;
@@ -10434,6 +10715,17 @@ function targetOf(command) {
10434
10715
  ...command.preopenRoot ? { preopenRoot: true } : {}
10435
10716
  };
10436
10717
  }
10718
+ function withScriptEnv(target, command) {
10719
+ if (!command) return target;
10720
+ return {
10721
+ ...target,
10722
+ env: { ...target.env, ...command.env },
10723
+ unset: [
10724
+ ...(target.unset ?? []).filter((key) => !Object.hasOwn(command.env ?? {}, key)),
10725
+ ...command.unset ?? []
10726
+ ]
10727
+ };
10728
+ }
10437
10729
  function childHandle(handle) {
10438
10730
  return {
10439
10731
  pid: handle.pid,
@@ -10458,6 +10750,7 @@ var Launcher = class {
10458
10750
  modulesDir;
10459
10751
  env;
10460
10752
  catalog;
10753
+ binfmts;
10461
10754
  compiled = /* @__PURE__ */ new Map();
10462
10755
  processes = /* @__PURE__ */ new Map();
10463
10756
  zombies = /* @__PURE__ */ new Set();
@@ -10492,6 +10785,7 @@ var Launcher = class {
10492
10785
  deciding = /* @__PURE__ */ new Map();
10493
10786
  identities = { asked: 0, timedOut: 0 };
10494
10787
  nodes;
10788
+ unlinked;
10495
10789
  held = new HeldSet((path) => this.released(path));
10496
10790
  processMounts;
10497
10791
  booting = new AbortController();
@@ -10536,9 +10830,18 @@ var Launcher = class {
10536
10830
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10537
10831
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
10538
10832
  this.openFiles.add(this.nodes);
10539
- this.fs = keepingOpen(fs, this.nodes);
10833
+ this.unlinked = new UnlinkedKeeper(
10834
+ fs,
10835
+ this.held,
10836
+ (path) => this.mountRoot(path),
10837
+ webLocks(),
10838
+ (path) => [...this.openFiles].some((nodes) => nodes.holds(path))
10839
+ );
10840
+ this.fs = keepingOpen(this.unlinked.wrap(), this.nodes);
10841
+ void this.unlinked.sweep("/");
10540
10842
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
10541
10843
  this.catalog = void 0;
10844
+ this.binfmts = void 0;
10542
10845
  });
10543
10846
  }
10544
10847
  setRoutes(table) {
@@ -10548,6 +10851,10 @@ var Launcher = class {
10548
10851
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
10549
10852
  return this.catalog;
10550
10853
  }
10854
+ interpreters() {
10855
+ this.binfmts ??= this.roots().then((roots) => scanBinfmts(this.base, roots));
10856
+ return this.binfmts;
10857
+ }
10551
10858
  async roots() {
10552
10859
  return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
10553
10860
  }
@@ -10593,30 +10900,28 @@ var Launcher = class {
10593
10900
  const command = (await this.commands()).get(name2);
10594
10901
  return command?.script ? command : void 0;
10595
10902
  }
10596
- async interpreted(file, argv, cwd) {
10903
+ async interpreted(file, argv, cwd, depth = 0) {
10597
10904
  const command = await this.scriptCommand(file);
10598
10905
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
10599
- const words = shebang(await this.head(script) ?? new Uint8Array());
10906
+ const head = await this.head(script);
10907
+ if (!head) return void 0;
10908
+ const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
10909
+ const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
10600
10910
  if (!words) return void 0;
10601
10911
  if (baseName(words[0]) === "env") words.shift();
10602
10912
  const [interp, ...rest] = words;
10603
- const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10604
- if (!found) return void 0;
10605
- const target = command ? {
10606
- ...found,
10607
- env: { ...found.env, ...command.env },
10608
- unset: [...found.unset ?? [], ...command.unset ?? []]
10609
- } : found;
10610
10913
  const arg = rest.join(" ");
10611
- return {
10612
- target,
10613
- args: [
10614
- ...target.prefix ?? [],
10615
- ...arg ? [arg] : [],
10616
- command ? script : file,
10617
- ...argv.slice(1)
10618
- ]
10619
- };
10914
+ const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
10915
+ const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10916
+ if (!found) {
10917
+ if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
10918
+ return void 0;
10919
+ }
10920
+ const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
10921
+ return inner && { ...inner, target: withScriptEnv(inner.target, command) };
10922
+ }
10923
+ const target = withScriptEnv(found, command);
10924
+ return { target, args: [...target.prefix ?? [], ...passed] };
10620
10925
  }
10621
10926
  async plan(file, argv, cwd) {
10622
10927
  const direct = await this.resolve(file, argv[0] ?? file, cwd);
@@ -10624,8 +10929,10 @@ var Launcher = class {
10624
10929
  return this.interpreted(file, argv, cwd);
10625
10930
  }
10626
10931
  async unrunnable(file, cwd) {
10627
- const head = await this.base.readFileBuffer(this.fs.resolvePath(cwd, file)).catch(() => null);
10628
- return head && !(head[0] === 35 && head[1] === 33) ? "ENOEXEC" : "ENOENT";
10932
+ const path = this.fs.resolvePath(cwd, file);
10933
+ const head = await this.base.readFileBuffer(path).catch(() => null);
10934
+ if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
10935
+ return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
10629
10936
  }
10630
10937
  module(path) {
10631
10938
  return this.base.stat(path).then((st) => {
@@ -10741,6 +11048,7 @@ var Launcher = class {
10741
11048
  openFiles: this.openFiles,
10742
11049
  nodes: this.nodes,
10743
11050
  held: this.held,
11051
+ unlinkGate: this.unlinked.gate,
10744
11052
  statfs: (path) => this.mounts.statfs(path),
10745
11053
  mounts: () => this.mounts.list(),
10746
11054
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -10898,6 +11206,7 @@ var Launcher = class {
10898
11206
  }
10899
11207
  released(path) {
10900
11208
  const at = normalizePath(path);
11209
+ this.unlinked.closed(at);
10901
11210
  const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10902
11211
  if (!open) void this.mounts.commit(at).catch(() => void 0);
10903
11212
  }
@@ -10993,10 +11302,23 @@ var Launcher = class {
10993
11302
  }
10994
11303
  };
10995
11304
  }
10996
- mount(spec) {
10997
- if (spec.type !== "fsa" || spec.source.startsWith("fsa:"))
10998
- return this.mounts.mount(spec, this.fs);
10999
- return this.mounts.mount({ ...spec, source: `fsa:${crypto.randomUUID()}` }, this.fs);
11305
+ async mount(spec) {
11306
+ const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
11307
+ const entry = await this.mounts.mount(
11308
+ fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
11309
+ this.fs
11310
+ );
11311
+ void this.unlinked.sweep(entry.target);
11312
+ return entry;
11313
+ }
11314
+ mountRoot(path) {
11315
+ let root = "/";
11316
+ for (const { target } of this.mounts.list()) {
11317
+ if (target.length > root.length && (path === target || path.startsWith(`${target}/`))) {
11318
+ root = target;
11319
+ }
11320
+ }
11321
+ return root;
11000
11322
  }
11001
11323
  async insert(target, handle, source) {
11002
11324
  const at = normalizePath(target);
@@ -11010,6 +11332,7 @@ var Launcher = class {
11010
11332
  await this.media.put(slot.id, handle);
11011
11333
  slot.medium.insert(handle);
11012
11334
  slot.served.invalidate(true);
11335
+ void this.unlinked.sweep(at);
11013
11336
  }
11014
11337
  umount(target, detach = false) {
11015
11338
  const at = normalizePath(target);
@@ -11085,6 +11408,7 @@ var Launcher = class {
11085
11408
  }
11086
11409
  async starting(argv, dir) {
11087
11410
  this.catalog = void 0;
11411
+ this.binfmts = void 0;
11088
11412
  const cwd = this.fs.resolvePath("/", dir ?? "/");
11089
11413
  const [file = ""] = argv;
11090
11414
  await this.base.mkdir(cwd, { recursive: true });