@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.
package/dist/node.js CHANGED
@@ -3249,6 +3249,35 @@ async function scanFilesystems(fs, modules) {
3249
3249
  }
3250
3250
  return found;
3251
3251
  }
3252
+ var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
3253
+ async function scanBinfmts(fs, modules) {
3254
+ const found = /* @__PURE__ */ new Map();
3255
+ for (const pkg of await packagesIn(fs, modules)) {
3256
+ let manifest;
3257
+ try {
3258
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
3259
+ } catch {
3260
+ continue;
3261
+ }
3262
+ const declared = manifest.slicc?.binfmt;
3263
+ if (!declared || typeof declared !== "object") continue;
3264
+ for (const [suffix, command] of Object.entries(declared)) {
3265
+ if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME.test(command)) continue;
3266
+ if (!found.has(suffix)) found.set(suffix, command);
3267
+ }
3268
+ }
3269
+ return found;
3270
+ }
3271
+ function binfmtOf(binfmts, path) {
3272
+ const name2 = path.slice(path.lastIndexOf("/") + 1);
3273
+ let best;
3274
+ for (const suffix of binfmts.keys()) {
3275
+ if (name2.length > suffix.length && name2.endsWith(suffix)) {
3276
+ if (best === void 0 || suffix.length > best.length) best = suffix;
3277
+ }
3278
+ }
3279
+ return best === void 0 ? void 0 : binfmts.get(best);
3280
+ }
3252
3281
 
3253
3282
  // src/fs/commands.ts
3254
3283
  var S_IFDIR2 = 16384;
@@ -3365,13 +3394,193 @@ function withCommandDirs(fs, names) {
3365
3394
  };
3366
3395
  }
3367
3396
 
3397
+ // src/fs/unlinked.ts
3398
+ var UNLINKED_DIR = ".slicc-unlinked";
3399
+ function webLocks() {
3400
+ return globalThis.navigator?.locks;
3401
+ }
3402
+ var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3403
+ var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3404
+ var lockName = (instance) => `slicc-unlinked:${instance}`;
3405
+ var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
3406
+ var PathGate = class {
3407
+ tails = /* @__PURE__ */ new Map();
3408
+ async acquire(path) {
3409
+ const prev = this.tails.get(path) ?? Promise.resolve();
3410
+ const { promise, resolve } = Promise.withResolvers();
3411
+ const tail = prev.then(() => promise);
3412
+ this.tails.set(path, tail);
3413
+ await prev;
3414
+ return () => {
3415
+ resolve();
3416
+ if (this.tails.get(path) === tail) this.tails.delete(path);
3417
+ };
3418
+ }
3419
+ async run(path, op) {
3420
+ const release = await this.acquire(path);
3421
+ try {
3422
+ return await op();
3423
+ } finally {
3424
+ release();
3425
+ }
3426
+ }
3427
+ };
3428
+ var UnlinkedKeeper = class {
3429
+ fs;
3430
+ holders;
3431
+ rootOf;
3432
+ locks;
3433
+ busy;
3434
+ waiting = /* @__PURE__ */ new Set();
3435
+ instance = crypto.randomUUID();
3436
+ gate = new PathGate();
3437
+ queue = Promise.resolve();
3438
+ count = 0;
3439
+ constructor(fs, holders, rootOf, locks, busy = () => false) {
3440
+ this.fs = fs;
3441
+ this.holders = holders;
3442
+ this.rootOf = rootOf;
3443
+ this.locks = locks;
3444
+ this.busy = busy;
3445
+ void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
3446
+ }
3447
+ serial(op) {
3448
+ const next = this.queue.then(op);
3449
+ this.queue = next.catch(() => void 0);
3450
+ return next;
3451
+ }
3452
+ wrap() {
3453
+ const fs = this.fs;
3454
+ const keeper = this;
3455
+ const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
3456
+ return {
3457
+ ...fs,
3458
+ async rm(path, options) {
3459
+ const target = fs.resolvePath("/", path);
3460
+ const pinned = await keeper.pin(target);
3461
+ if (pinned.moved.has(target)) pinned.commit();
3462
+ else await keeper.settle(pinned, () => fs.rm(path, options));
3463
+ },
3464
+ async rename(from, to) {
3465
+ const pinned = await keeper.pin(fs.resolvePath("/", to));
3466
+ await keeper.settle(pinned, () => fs.rename(from, to));
3467
+ },
3468
+ readdir: async (path) => visible(await fs.readdir(path)),
3469
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
3470
+ };
3471
+ }
3472
+ async settle(pinned, op) {
3473
+ try {
3474
+ await op();
3475
+ } catch (err) {
3476
+ await pinned.rollback();
3477
+ throw err;
3478
+ }
3479
+ pinned.commit();
3480
+ }
3481
+ async pin(target) {
3482
+ const held = /* @__PURE__ */ new Map();
3483
+ for (const holder of this.holders) {
3484
+ if (holder.owns(target)) continue;
3485
+ for (const path of holder.opens.keys()) {
3486
+ if (!within2(path, target) || holder.unlinked.has(path)) continue;
3487
+ held.set(path, [...held.get(path) ?? [], holder]);
3488
+ }
3489
+ }
3490
+ const paths = [...held.keys()].sort();
3491
+ const releases = [];
3492
+ for (const path of paths) releases.push(await this.gate.acquire(path));
3493
+ const hidden = /* @__PURE__ */ new Map();
3494
+ for (const path of paths) {
3495
+ const at = await this.hide(path);
3496
+ if (at !== void 0) hidden.set(path, at);
3497
+ }
3498
+ const done4 = () => {
3499
+ for (const release of releases) release();
3500
+ };
3501
+ return {
3502
+ moved: new Set(hidden.keys()),
3503
+ commit: () => {
3504
+ for (const [path, at] of hidden) {
3505
+ const holders = held.get(path);
3506
+ const file = this.keptFile(at, { refs: holders.length });
3507
+ for (const holder of holders) holder.unlinked.set(path, file);
3508
+ }
3509
+ done4();
3510
+ },
3511
+ rollback: async () => {
3512
+ for (const [path, at] of hidden) {
3513
+ await this.serial(async () => {
3514
+ await Promise.allSettled([this.fs.rename(at, path)]);
3515
+ await this.tidy(parentOf3(at));
3516
+ });
3517
+ }
3518
+ done4();
3519
+ }
3520
+ };
3521
+ }
3522
+ keptFile(hidden, entry) {
3523
+ return {
3524
+ hidden,
3525
+ release: () => {
3526
+ if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
3527
+ }
3528
+ };
3529
+ }
3530
+ hide(path) {
3531
+ return this.serial(async () => {
3532
+ const dir = dirOf(this.rootOf(path));
3533
+ const hidden = `${dir}/${this.instance}.${++this.count}`;
3534
+ try {
3535
+ await this.fs.mkdir(dir, { recursive: true });
3536
+ await this.fs.rename(path, hidden);
3537
+ return hidden;
3538
+ } catch {
3539
+ await this.tidy(dir);
3540
+ return void 0;
3541
+ }
3542
+ });
3543
+ }
3544
+ closed(path) {
3545
+ if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
3546
+ }
3547
+ async drop(hidden) {
3548
+ if (this.busy(hidden)) {
3549
+ this.waiting.add(hidden);
3550
+ return;
3551
+ }
3552
+ await this.fs.rm(hidden).catch(() => void 0);
3553
+ await this.tidy(parentOf3(hidden));
3554
+ }
3555
+ async tidy(dir) {
3556
+ const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
3557
+ if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
3558
+ }
3559
+ sweep(root) {
3560
+ const query = this.locks?.query?.bind(this.locks);
3561
+ if (!query) return Promise.resolve();
3562
+ return this.serial(async () => {
3563
+ const dir = dirOf(root);
3564
+ const names = await this.fs.readdir(dir).catch(() => void 0);
3565
+ if (!names) return;
3566
+ const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
3567
+ for (const name2 of names) {
3568
+ const owner = name2.slice(0, name2.lastIndexOf("."));
3569
+ if (owner === this.instance || held.has(lockName(owner))) continue;
3570
+ await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
3571
+ }
3572
+ await this.tidy(dir);
3573
+ });
3574
+ }
3575
+ };
3576
+
3368
3577
  // src/fs/watch.ts
3369
3578
  var WATCH_BATCH_LIMIT = 1e3;
3370
3579
  var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
3371
- var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3580
+ var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3372
3581
  function touches(watcher, changed) {
3373
3582
  return watcher.paths.some(
3374
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
3583
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
3375
3584
  );
3376
3585
  }
3377
3586
  var FsWatchers = class {
@@ -3753,7 +3962,7 @@ function parseSize(text) {
3753
3962
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
3754
3963
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
3755
3964
  }
3756
- var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3965
+ var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3757
3966
  var HeldSet = class extends Set {
3758
3967
  idle;
3759
3968
  constructor(idle) {
@@ -3766,18 +3975,47 @@ var HeldSet = class extends Set {
3766
3975
  }
3767
3976
  delete(holds) {
3768
3977
  const removed = super.delete(holds);
3769
- for (const path of holds.keys()) this.idle(path);
3978
+ for (const path of /* @__PURE__ */ new Set([...holds.keys(), ...holds.opens.keys()])) this.idle(path);
3979
+ for (const kept of holds.unlinked.values()) kept.release();
3980
+ holds.unlinked.clear();
3770
3981
  return removed;
3771
3982
  }
3772
3983
  };
3773
3984
  function heldUnder(held, target) {
3774
- for (const paths of held) for (const path of paths.keys()) if (within2(path, target)) return true;
3985
+ for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
3775
3986
  return false;
3776
3987
  }
3777
3988
  var HeldPaths = class extends Map {
3778
3989
  revoked = /* @__PURE__ */ new Set();
3990
+ opens = /* @__PURE__ */ new Map();
3991
+ unlinked = /* @__PURE__ */ new Map();
3992
+ aliases = /* @__PURE__ */ new Map();
3993
+ owning;
3779
3994
  onIdle;
3780
- hold(path, on) {
3995
+ keyOf(path) {
3996
+ return this.aliases.get(path) ?? path;
3997
+ }
3998
+ keptAt(path) {
3999
+ return this.unlinked.get(this.keyOf(path));
4000
+ }
4001
+ holding(path) {
4002
+ return this.opens.has(this.keyOf(path));
4003
+ }
4004
+ hold(path, on, open = false, real) {
4005
+ if (open) {
4006
+ const key = real ?? this.keyOf(path);
4007
+ if (key !== path) this.aliases.set(path, key);
4008
+ const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
4009
+ if (count2 > 0) this.opens.set(key, count2);
4010
+ else {
4011
+ this.opens.delete(key);
4012
+ this.unlinked.get(key)?.release();
4013
+ this.unlinked.delete(key);
4014
+ for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
4015
+ this.onIdle?.(key);
4016
+ }
4017
+ return;
4018
+ }
3781
4019
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
3782
4020
  if (count > 0) this.set(path, count);
3783
4021
  else {
@@ -3786,23 +4024,40 @@ var HeldPaths = class extends Map {
3786
4024
  this.onIdle?.(path);
3787
4025
  }
3788
4026
  }
4027
+ async own(path, op) {
4028
+ this.owning = path;
4029
+ try {
4030
+ return await op();
4031
+ } finally {
4032
+ this.owning = void 0;
4033
+ }
4034
+ }
4035
+ owns(path) {
4036
+ return this.owning === path;
4037
+ }
3789
4038
  revoke(prefix) {
3790
- for (const path of this.keys()) if (within2(path, prefix)) this.revoked.add(path);
4039
+ for (const path of this.keys()) if (within3(path, prefix)) this.revoked.add(path);
3791
4040
  }
3792
4041
  renamed(from, to) {
3793
4042
  for (const [path, count] of [...this]) {
3794
- if (!within2(path, from)) continue;
4043
+ if (!within3(path, from)) continue;
3795
4044
  const moved2 = to + path.slice(from.length);
3796
4045
  this.delete(path);
3797
4046
  this.set(moved2, (this.get(moved2) ?? 0) + count);
3798
4047
  if (this.revoked.delete(path)) this.revoked.add(moved2);
3799
4048
  }
4049
+ for (const [path, count] of [...this.opens]) {
4050
+ if (!within3(path, from)) continue;
4051
+ const moved2 = to + path.slice(from.length);
4052
+ this.opens.delete(path);
4053
+ this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
4054
+ }
3800
4055
  }
3801
4056
  isRevoked(path) {
3802
4057
  return this.revoked.has(path);
3803
4058
  }
3804
4059
  };
3805
- var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
4060
+ var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3806
4061
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
3807
4062
  function statOf(attr, path, mount) {
3808
4063
  const mtime = new Date(attr.mtime);
@@ -3891,7 +4146,7 @@ var MountTable = class {
3891
4146
  locate(path) {
3892
4147
  let best;
3893
4148
  for (const mount of this.mounts.values()) {
3894
- if (within2(path, mount.target) && (!best || mount.target.length > best.target.length))
4149
+ if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
3895
4150
  best = mount;
3896
4151
  }
3897
4152
  if (!best) return void 0;
@@ -4030,7 +4285,7 @@ var MountTable = class {
4030
4285
  async commitUnder(prefix) {
4031
4286
  const at = normalizePath(prefix);
4032
4287
  await Promise.all(
4033
- [...this.sessions.keys()].filter((p) => within2(p, at)).map((p) => this.commit(p))
4288
+ [...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
4034
4289
  );
4035
4290
  }
4036
4291
  async commitOverlapping(real, offset, length2) {
@@ -4064,10 +4319,10 @@ var MountTable = class {
4064
4319
  this.deps.changed?.(...full);
4065
4320
  }
4066
4321
  forget(mount, path) {
4067
- for (const key of [...mount.attrs.keys()]) if (within2(key, path)) mount.attrs.delete(key);
4322
+ for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
4068
4323
  mount.lists.delete(path);
4069
- mount.lists.delete(parentOf4(path));
4070
- for (const key of [...mount.lists.keys()]) if (within2(key, path)) mount.lists.delete(key);
4324
+ mount.lists.delete(parentOf5(path));
4325
+ for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
4071
4326
  }
4072
4327
  async getattr(mount, path, rel2) {
4073
4328
  if (mount.conn.failure) throw mount.conn.failure;
@@ -4132,7 +4387,7 @@ var MountFs = class {
4132
4387
  if (attr.kind !== "symlink") return real;
4133
4388
  if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
4134
4389
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
4135
- return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
4390
+ return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
4136
4391
  }
4137
4392
  async open(mount, rel2, write) {
4138
4393
  return await mount.conn.call({
@@ -4646,6 +4901,8 @@ async function ranged(fs, path, req) {
4646
4901
  else await ops.truncate(path, req.length ?? 0);
4647
4902
  return done;
4648
4903
  }
4904
+ var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
4905
+ var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
4649
4906
  async function dispatchSyncFs(req) {
4650
4907
  const entry = resolveSyncFsToken(req.token);
4651
4908
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -4654,11 +4911,22 @@ async function dispatchSyncFs(req) {
4654
4911
  const path = fs.resolvePath(cwd, req.path);
4655
4912
  if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
4656
4913
  if (req.op === "hold") {
4657
- entry.hold?.(path, req.mode === 1);
4914
+ const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
4915
+ const real = on && open ? await fs.realpath?.(path, true).catch(() => path) : void 0;
4916
+ entry.hold?.(path, on, open, real);
4658
4917
  return done;
4659
4918
  }
4660
4919
  if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
4661
- return await run(entry, path, req);
4920
+ if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
4921
+ const serve = async () => {
4922
+ const kept = entry.unlinked?.(path);
4923
+ return run(entry, kept && !await fs.exists(path) ? kept.hidden : path, req);
4924
+ };
4925
+ return await (entry.gate ? entry.gate(path, serve) : serve());
4926
+ }
4927
+ if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
4928
+ const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
4929
+ return await entry.own(target, () => run(entry, path, req));
4662
4930
  } catch (err) {
4663
4931
  return toErrno(err);
4664
4932
  }
@@ -6237,7 +6505,7 @@ function parsePrefix(text) {
6237
6505
  if (length2 < 0 || length2 > max) return void 0;
6238
6506
  return { ...parsed, length: length2 };
6239
6507
  }
6240
- function within3(prefix, addr) {
6508
+ function within4(prefix, addr) {
6241
6509
  if (prefix.v6 !== addr.v6) return false;
6242
6510
  const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
6243
6511
  return addr.bits >> shift === prefix.bits >> shift;
@@ -6277,9 +6545,9 @@ var Routes = class {
6277
6545
  if (!addr) return "unreachable";
6278
6546
  if (addr.v6 && addr.bits === 1n) return "loopback";
6279
6547
  if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
6280
- if (RESERVED.some((p) => within3(p, addr))) return "reserved";
6548
+ if (RESERVED.some((p) => within4(p, addr))) return "reserved";
6281
6549
  if (addr.v6 && !this.ipv6) return "unreachable";
6282
- if (this.exit || this.table.some((p) => within3(p, addr))) return "uplink";
6550
+ if (this.exit || this.table.some((p) => within4(p, addr))) return "uplink";
6283
6551
  return "unreachable";
6284
6552
  }
6285
6553
  };
@@ -7248,7 +7516,7 @@ function isMissing(err) {
7248
7516
  if (err?.code === "ENOENT") return true;
7249
7517
  return err instanceof Error && err.message.startsWith("ENOENT");
7250
7518
  }
7251
- function within4(path, root) {
7519
+ function within5(path, root) {
7252
7520
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
7253
7521
  }
7254
7522
  var VfsNode = class {
@@ -7453,7 +7721,7 @@ var VfsNodes = class {
7453
7721
  return run2;
7454
7722
  }
7455
7723
  async entryKey(path) {
7456
- const parent = parentOf5(path);
7724
+ const parent = parentOf6(path);
7457
7725
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
7458
7726
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
7459
7727
  }
@@ -7501,12 +7769,12 @@ var VfsNodes = class {
7501
7769
  return node;
7502
7770
  }
7503
7771
  holds(prefix) {
7504
- for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
7772
+ for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
7505
7773
  return false;
7506
7774
  }
7507
7775
  writes(prefix) {
7508
7776
  for (const [path, node] of this.byPath)
7509
- if (node.writers > 0 && within4(path, prefix)) return true;
7777
+ if (node.writers > 0 && within5(path, prefix)) return true;
7510
7778
  return false;
7511
7779
  }
7512
7780
  closed(node, writable = false) {
@@ -7520,24 +7788,24 @@ var VfsNodes = class {
7520
7788
  this.onClosed?.(node.path);
7521
7789
  }
7522
7790
  async flush(path) {
7523
- for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
7791
+ for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
7524
7792
  }
7525
7793
  async unlinking(path) {
7526
7794
  for (const [held, node] of [...this.byPath]) {
7527
- if (!within4(held, path)) continue;
7795
+ if (!within5(held, path)) continue;
7528
7796
  await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
7529
7797
  }
7530
7798
  }
7531
7799
  revoke(prefix) {
7532
7800
  for (const [path, node] of this.byPath) {
7533
- if (!within4(path, prefix)) continue;
7801
+ if (!within5(path, prefix)) continue;
7534
7802
  node.revoke();
7535
7803
  this.byPath.delete(path);
7536
7804
  }
7537
7805
  }
7538
7806
  unlinked(path) {
7539
7807
  for (const [held, node] of [...this.byPath]) {
7540
- if (!within4(held, path)) continue;
7808
+ if (!within5(held, path)) continue;
7541
7809
  node.orphaned = true;
7542
7810
  this.byPath.delete(held);
7543
7811
  }
@@ -7546,8 +7814,8 @@ var VfsNodes = class {
7546
7814
  if (from === to) return;
7547
7815
  const moved2 = [];
7548
7816
  for (const [p, node] of this.byPath) {
7549
- if (within4(p, from)) moved2.push(node);
7550
- else if (within4(p, to)) {
7817
+ if (within5(p, from)) moved2.push(node);
7818
+ else if (within5(p, to)) {
7551
7819
  node.orphaned = true;
7552
7820
  this.byPath.delete(p);
7553
7821
  }
@@ -7570,7 +7838,7 @@ async function present(fs, path, follow = false) {
7570
7838
  }
7571
7839
  }
7572
7840
  var MAX_LINKS5 = 40;
7573
- function parentOf5(path) {
7841
+ function parentOf6(path) {
7574
7842
  return path.slice(0, path.lastIndexOf("/")) || "/";
7575
7843
  }
7576
7844
  async function linkOf(fs, path) {
@@ -7583,7 +7851,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
7583
7851
  const part = queue.shift();
7584
7852
  if (part === ".") continue;
7585
7853
  if (part === "..") {
7586
- at = parentOf5(at);
7854
+ at = parentOf6(at);
7587
7855
  continue;
7588
7856
  }
7589
7857
  const next = at === "/" ? `/${part}` : `${at}/${part}`;
@@ -7607,7 +7875,7 @@ function joinName(dir, name2) {
7607
7875
  }
7608
7876
  async function realTarget(fs, path) {
7609
7877
  const budget = { hops: 0 };
7610
- let dir = await realDir(fs, parentOf5(path), budget);
7878
+ let dir = await realDir(fs, parentOf6(path), budget);
7611
7879
  let name2 = path.slice(path.lastIndexOf("/") + 1);
7612
7880
  for (; ; ) {
7613
7881
  const link2 = await linkOf(fs, joinName(dir, name2));
@@ -7615,13 +7883,13 @@ async function realTarget(fs, path) {
7615
7883
  if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
7616
7884
  budget.hops++;
7617
7885
  const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
7618
- dir = await realDir(fs, parentOf5(target), budget);
7886
+ dir = await realDir(fs, parentOf6(target), budget);
7619
7887
  name2 = target.slice(target.lastIndexOf("/") + 1);
7620
7888
  }
7621
7889
  return joinName(dir, name2);
7622
7890
  }
7623
7891
  async function targetDir(fs, path) {
7624
- return parentOf5(await realTarget(fs, path).catch(() => path));
7892
+ return parentOf6(await realTarget(fs, path).catch(() => path));
7625
7893
  }
7626
7894
  async function refuseReadonly(fs, path, flags, opts = {}) {
7627
7895
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -8010,7 +8278,7 @@ var WasmProcess = class {
8010
8278
  const fd = this.fds.install(heldFile(), 3);
8011
8279
  try {
8012
8280
  const nodes = this.nodes;
8013
- const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : await nodes.targetKey(req.path);
8281
+ 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));
8014
8282
  const open = { ...req, path };
8015
8283
  if (req.exclusive) {
8016
8284
  await nodes.createExclusive(path);
@@ -8166,6 +8434,11 @@ var WasmProcess = class {
8166
8434
  return { ok: true, kind: "json", json: await file.stat() };
8167
8435
  }
8168
8436
  }
8437
+ keptPath;
8438
+ async keptOr(path) {
8439
+ const kept = this.keptPath?.(path);
8440
+ return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
8441
+ }
8169
8442
  async promote(req) {
8170
8443
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8171
8444
  if (req.share !== void 0) {
@@ -8173,7 +8446,7 @@ var WasmProcess = class {
8173
8446
  return;
8174
8447
  }
8175
8448
  if (req.path === void 0) throw new KernelError("EINVAL");
8176
- const path = req.orphan ? req.path : await this.nodes.targetKey(req.path);
8449
+ const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path));
8177
8450
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8178
8451
  const file = vfsFile(
8179
8452
  this.options.fs,
@@ -8532,6 +8805,19 @@ function inherited(opts) {
8532
8805
  ...opts.umask !== void 0 ? { umask: opts.umask } : {}
8533
8806
  };
8534
8807
  }
8808
+ function holdingToken(opts, holds) {
8809
+ return mintSyncFsToken({
8810
+ fs: opts.fs,
8811
+ cwd: opts.cwd,
8812
+ ...opts.statfs ? { statfs: opts.statfs } : {},
8813
+ hold: (path, on, open, real) => holds.hold(path, on, open, real),
8814
+ unlinked: (path) => holds.keptAt(path),
8815
+ gate: (path, op) => opts.unlinkGate && holds.holding(path) ? opts.unlinkGate.run(holds.keyOf(path), op) : op(),
8816
+ own: (path, op) => holds.own(path, op),
8817
+ revoked: (path) => holds.isRevoked(path),
8818
+ renamed: (from, to) => holds.renamed(from, to)
8819
+ });
8820
+ }
8535
8821
  function spawnWasmProcess(opts) {
8536
8822
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
8537
8823
  const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
@@ -8576,14 +8862,8 @@ function spawnWasmProcess(opts) {
8576
8862
  });
8577
8863
  const holds = new HeldPaths();
8578
8864
  opts.held?.add(holds);
8579
- const token = mintSyncFsToken({
8580
- fs: opts.fs,
8581
- cwd: opts.cwd,
8582
- ...opts.statfs ? { statfs: opts.statfs } : {},
8583
- hold: (path, on) => holds.hold(path, on),
8584
- revoked: (path) => holds.isRevoked(path),
8585
- renamed: (from, to) => holds.renamed(from, to)
8586
- });
8865
+ const token = holdingToken(opts, holds);
8866
+ process.keptPath = (path) => holds.keptAt(path)?.hidden;
8587
8867
  const worker = opts.createWorker();
8588
8868
  const dying = new Dying();
8589
8869
  const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
@@ -10110,6 +10390,7 @@ var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
10110
10390
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
10111
10391
  var WASM_MAGIC = [0, 97, 115, 109];
10112
10392
  var SHEBANG_MAX = 256;
10393
+ var MAX_INTERPRETERS = 4;
10113
10394
  var NOT_FOUND = 127;
10114
10395
  var DECIDE_MS = 100;
10115
10396
  var INIT_PID = 1;
@@ -10161,6 +10442,17 @@ function targetOf(command) {
10161
10442
  ...command.preopenRoot ? { preopenRoot: true } : {}
10162
10443
  };
10163
10444
  }
10445
+ function withScriptEnv(target, command) {
10446
+ if (!command) return target;
10447
+ return {
10448
+ ...target,
10449
+ env: { ...target.env, ...command.env },
10450
+ unset: [
10451
+ ...(target.unset ?? []).filter((key) => !Object.hasOwn(command.env ?? {}, key)),
10452
+ ...command.unset ?? []
10453
+ ]
10454
+ };
10455
+ }
10164
10456
  function childHandle(handle) {
10165
10457
  return {
10166
10458
  pid: handle.pid,
@@ -10185,6 +10477,7 @@ var Launcher = class {
10185
10477
  modulesDir;
10186
10478
  env;
10187
10479
  catalog;
10480
+ binfmts;
10188
10481
  compiled = /* @__PURE__ */ new Map();
10189
10482
  processes = /* @__PURE__ */ new Map();
10190
10483
  zombies = /* @__PURE__ */ new Set();
@@ -10219,6 +10512,7 @@ var Launcher = class {
10219
10512
  deciding = /* @__PURE__ */ new Map();
10220
10513
  identities = { asked: 0, timedOut: 0 };
10221
10514
  nodes;
10515
+ unlinked;
10222
10516
  held = new HeldSet((path) => this.released(path));
10223
10517
  processMounts;
10224
10518
  booting = new AbortController();
@@ -10263,9 +10557,18 @@ var Launcher = class {
10263
10557
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10264
10558
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
10265
10559
  this.openFiles.add(this.nodes);
10266
- this.fs = keepingOpen(fs, this.nodes);
10560
+ this.unlinked = new UnlinkedKeeper(
10561
+ fs,
10562
+ this.held,
10563
+ (path) => this.mountRoot(path),
10564
+ webLocks(),
10565
+ (path) => [...this.openFiles].some((nodes) => nodes.holds(path))
10566
+ );
10567
+ this.fs = keepingOpen(this.unlinked.wrap(), this.nodes);
10568
+ void this.unlinked.sweep("/");
10267
10569
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
10268
10570
  this.catalog = void 0;
10571
+ this.binfmts = void 0;
10269
10572
  });
10270
10573
  }
10271
10574
  setRoutes(table) {
@@ -10275,6 +10578,10 @@ var Launcher = class {
10275
10578
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
10276
10579
  return this.catalog;
10277
10580
  }
10581
+ interpreters() {
10582
+ this.binfmts ??= this.roots().then((roots) => scanBinfmts(this.base, roots));
10583
+ return this.binfmts;
10584
+ }
10278
10585
  async roots() {
10279
10586
  return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
10280
10587
  }
@@ -10320,30 +10627,28 @@ var Launcher = class {
10320
10627
  const command = (await this.commands()).get(name2);
10321
10628
  return command?.script ? command : void 0;
10322
10629
  }
10323
- async interpreted(file, argv, cwd) {
10630
+ async interpreted(file, argv, cwd, depth = 0) {
10324
10631
  const command = await this.scriptCommand(file);
10325
10632
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
10326
- const words = shebang(await this.head(script) ?? new Uint8Array());
10633
+ const head = await this.head(script);
10634
+ if (!head) return void 0;
10635
+ const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
10636
+ const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
10327
10637
  if (!words) return void 0;
10328
10638
  if (baseName(words[0]) === "env") words.shift();
10329
10639
  const [interp, ...rest] = words;
10330
- const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10331
- if (!found) return void 0;
10332
- const target = command ? {
10333
- ...found,
10334
- env: { ...found.env, ...command.env },
10335
- unset: [...found.unset ?? [], ...command.unset ?? []]
10336
- } : found;
10337
10640
  const arg = rest.join(" ");
10338
- return {
10339
- target,
10340
- args: [
10341
- ...target.prefix ?? [],
10342
- ...arg ? [arg] : [],
10343
- command ? script : file,
10344
- ...argv.slice(1)
10345
- ]
10346
- };
10641
+ const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
10642
+ const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10643
+ if (!found) {
10644
+ if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
10645
+ return void 0;
10646
+ }
10647
+ const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
10648
+ return inner && { ...inner, target: withScriptEnv(inner.target, command) };
10649
+ }
10650
+ const target = withScriptEnv(found, command);
10651
+ return { target, args: [...target.prefix ?? [], ...passed] };
10347
10652
  }
10348
10653
  async plan(file, argv, cwd) {
10349
10654
  const direct = await this.resolve(file, argv[0] ?? file, cwd);
@@ -10351,8 +10656,10 @@ var Launcher = class {
10351
10656
  return this.interpreted(file, argv, cwd);
10352
10657
  }
10353
10658
  async unrunnable(file, cwd) {
10354
- const head = await this.base.readFileBuffer(this.fs.resolvePath(cwd, file)).catch(() => null);
10355
- return head && !(head[0] === 35 && head[1] === 33) ? "ENOEXEC" : "ENOENT";
10659
+ const path = this.fs.resolvePath(cwd, file);
10660
+ const head = await this.base.readFileBuffer(path).catch(() => null);
10661
+ if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
10662
+ return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
10356
10663
  }
10357
10664
  module(path) {
10358
10665
  return this.base.stat(path).then((st) => {
@@ -10468,6 +10775,7 @@ var Launcher = class {
10468
10775
  openFiles: this.openFiles,
10469
10776
  nodes: this.nodes,
10470
10777
  held: this.held,
10778
+ unlinkGate: this.unlinked.gate,
10471
10779
  statfs: (path) => this.mounts.statfs(path),
10472
10780
  mounts: () => this.mounts.list(),
10473
10781
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -10625,6 +10933,7 @@ var Launcher = class {
10625
10933
  }
10626
10934
  released(path) {
10627
10935
  const at = normalizePath(path);
10936
+ this.unlinked.closed(at);
10628
10937
  const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10629
10938
  if (!open) void this.mounts.commit(at).catch(() => void 0);
10630
10939
  }
@@ -10720,10 +11029,23 @@ var Launcher = class {
10720
11029
  }
10721
11030
  };
10722
11031
  }
10723
- mount(spec) {
10724
- if (spec.type !== "fsa" || spec.source.startsWith("fsa:"))
10725
- return this.mounts.mount(spec, this.fs);
10726
- return this.mounts.mount({ ...spec, source: `fsa:${crypto.randomUUID()}` }, this.fs);
11032
+ async mount(spec) {
11033
+ const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
11034
+ const entry = await this.mounts.mount(
11035
+ fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
11036
+ this.fs
11037
+ );
11038
+ void this.unlinked.sweep(entry.target);
11039
+ return entry;
11040
+ }
11041
+ mountRoot(path) {
11042
+ let root = "/";
11043
+ for (const { target } of this.mounts.list()) {
11044
+ if (target.length > root.length && (path === target || path.startsWith(`${target}/`))) {
11045
+ root = target;
11046
+ }
11047
+ }
11048
+ return root;
10727
11049
  }
10728
11050
  async insert(target, handle, source) {
10729
11051
  const at = normalizePath(target);
@@ -10737,6 +11059,7 @@ var Launcher = class {
10737
11059
  await this.media.put(slot.id, handle);
10738
11060
  slot.medium.insert(handle);
10739
11061
  slot.served.invalidate(true);
11062
+ void this.unlinked.sweep(at);
10740
11063
  }
10741
11064
  umount(target, detach = false) {
10742
11065
  const at = normalizePath(target);
@@ -10812,6 +11135,7 @@ var Launcher = class {
10812
11135
  }
10813
11136
  async starting(argv, dir) {
10814
11137
  this.catalog = void 0;
11138
+ this.binfmts = void 0;
10815
11139
  const cwd = this.fs.resolvePath("/", dir ?? "/");
10816
11140
  const [file = ""] = argv;
10817
11141
  await this.base.mkdir(cwd, { recursive: true });