@ai-ecoverse/slicc-kernel 1.57.1 → 1.58.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.
@@ -188,6 +188,9 @@ async function lutimesOf(fs, path, atime, mtime) {
188
188
  if ((await fs.lstat(path)).isSymbolicLink) throw fsError("EOPNOTSUPP", path);
189
189
  return fs.utimes(path, atime, mtime);
190
190
  }
191
+ function mayLink(fs, path) {
192
+ return fs.links?.(path) ?? true;
193
+ }
191
194
  function fsError(code, path) {
192
195
  return Object.assign(new Error(`${code}: ${path}`), { code });
193
196
  }
@@ -791,12 +794,12 @@ var OpfsFs = class {
791
794
  const base = dir === "/" ? "" : dir;
792
795
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
793
796
  const handed2 = this.owners ? await this.handedTo(dir) : void 0;
794
- const owned = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
797
+ const owned2 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
795
798
  return Promise.all(
796
799
  children2.map(async ([name2, child], i) => {
797
- if (!isHandle(child)) return [name2, owned(this.linkStat(child), child)];
800
+ if (!isHandle(child)) return [name2, owned2(this.linkStat(child), child)];
798
801
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
799
- return [name2, stat && owned(stat, metas[i])];
802
+ return [name2, stat && owned2(stat, metas[i])];
800
803
  })
801
804
  );
802
805
  }
@@ -855,6 +858,12 @@ var OpfsFs = class {
855
858
  await this.meta.remove([found.path]);
856
859
  this.touch(parentOf2(found.path));
857
860
  }
861
+ links() {
862
+ return false;
863
+ }
864
+ async link(from) {
865
+ throw fsError("EPERM", `${from}: this file system has no hard links`);
866
+ }
858
867
  async rename(from, to) {
859
868
  if (from === to) return;
860
869
  const source = await this.locate(from, false);
@@ -5814,6 +5823,8 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
5814
5823
  }
5815
5824
  },
5816
5825
  writeFile: (path, content, owner) => fs.writeFile(path, content, owner),
5826
+ link: (from, to, follow2) => fs.link(from, to, follow2),
5827
+ links: (path) => mayLink(fs, path),
5817
5828
  async exists(path) {
5818
5829
  return await fs.exists(path) || await linked(path) !== void 0;
5819
5830
  },
@@ -5952,6 +5963,8 @@ var UnlinkedKeeper = class {
5952
5963
  gate = new PathGate();
5953
5964
  queue = Promise.resolve();
5954
5965
  count = 0;
5966
+ links = /* @__PURE__ */ new Map();
5967
+ linked = /* @__PURE__ */ new Map();
5955
5968
  constructor(fs, holders, rootOf, options = {}) {
5956
5969
  const { locks, owner } = options;
5957
5970
  this.fs = fs;
@@ -5971,8 +5984,15 @@ var UnlinkedKeeper = class {
5971
5984
  wrap(fs = this.fs) {
5972
5985
  const keeper = this;
5973
5986
  const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
5987
+ const counted = (st) => {
5988
+ const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
5989
+ return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
5990
+ };
5974
5991
  return {
5975
5992
  ...fs,
5993
+ links: (path) => mayLink(fs, path),
5994
+ stat: async (path) => counted(await fs.stat(path)),
5995
+ lstat: async (path) => counted(await fs.lstat(path)),
5976
5996
  async rm(path, options) {
5977
5997
  const target = fs.resolvePath("/", path);
5978
5998
  const pinned = await keeper.pin(target);
@@ -5986,7 +6006,7 @@ var UnlinkedKeeper = class {
5986
6006
  await keeper.settle(pinned, () => fs.rename(from, to));
5987
6007
  },
5988
6008
  readdir: async (path) => visible(await fs.readdir(path)),
5989
- readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
6009
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted(st)])
5990
6010
  };
5991
6011
  }
5992
6012
  async settle(pinned, op) {
@@ -6036,6 +6056,7 @@ var UnlinkedKeeper = class {
6036
6056
  for (const holder of held.get(path)) holder.unkeep(path, file);
6037
6057
  await this.serial(async () => {
6038
6058
  await Promise.allSettled([this.fs.rename(file.hidden, path)]);
6059
+ this.unlinkedName(file.hidden);
6039
6060
  await this.tidy(parentOf3(file.hidden));
6040
6061
  });
6041
6062
  }
@@ -6058,6 +6079,7 @@ var UnlinkedKeeper = class {
6058
6079
  try {
6059
6080
  await this.fs.mkdir(dir, { recursive: true });
6060
6081
  await this.fs.rename(path, hidden);
6082
+ await this.linkedName(hidden);
6061
6083
  return hidden;
6062
6084
  } catch {
6063
6085
  await this.tidy(dir);
@@ -6074,8 +6096,25 @@ var UnlinkedKeeper = class {
6074
6096
  return;
6075
6097
  }
6076
6098
  await this.fs.rm(hidden).catch(() => void 0);
6099
+ this.unlinkedName(hidden);
6077
6100
  await this.tidy(parentOf3(hidden));
6078
6101
  }
6102
+ async linkedName(hidden) {
6103
+ if (!mayLink(this.fs, hidden)) return;
6104
+ const st = await this.fs.lstat(hidden).catch(() => void 0);
6105
+ if (!st || (st.nlink ?? 1) < 2) return;
6106
+ const id2 = `${st.dev ?? 0}:${st.ino}`;
6107
+ this.linked.set(hidden, id2);
6108
+ this.links.set(id2, (this.links.get(id2) ?? 0) + 1);
6109
+ }
6110
+ unlinkedName(hidden) {
6111
+ const id2 = this.linked.get(hidden);
6112
+ if (id2 === void 0) return;
6113
+ this.linked.delete(hidden);
6114
+ const left = (this.links.get(id2) ?? 1) - 1;
6115
+ if (left > 0) this.links.set(id2, left);
6116
+ else this.links.delete(id2);
6117
+ }
6079
6118
  async tidy(dir) {
6080
6119
  const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
6081
6120
  if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
@@ -6169,6 +6208,10 @@ var FsWatchers = class {
6169
6208
  },
6170
6209
  rm: after(fs.rm.bind(fs), (path) => [path]),
6171
6210
  rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
6211
+ link: after(
6212
+ (from, to, follow2) => fs.link(from, to, follow2),
6213
+ (_from, to) => [to]
6214
+ ),
6172
6215
  symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
6173
6216
  chmod: after(fs.chmod.bind(fs), (path) => [path]),
6174
6217
  ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
@@ -6382,6 +6425,62 @@ var ChildTable = class {
6382
6425
  }
6383
6426
  };
6384
6427
 
6428
+ // src/fs/walk.ts
6429
+ var MAX_LINKS4 = 40;
6430
+ function parentOf5(path) {
6431
+ return path.slice(0, path.lastIndexOf("/")) || "/";
6432
+ }
6433
+ async function mustBeDir(fs, path) {
6434
+ if (!fs.lstat) return;
6435
+ const st = await fs.lstat(path);
6436
+ if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
6437
+ }
6438
+ async function linkOf(fs, path) {
6439
+ return fs.readlink?.(path).catch(() => void 0);
6440
+ }
6441
+ async function realDir(fs, dir, budget = { hops: 0 }) {
6442
+ const queue = dir.split("/").filter(Boolean);
6443
+ let at = "/";
6444
+ while (queue.length > 0) {
6445
+ const part = queue.shift();
6446
+ if (part === ".") continue;
6447
+ if (part === "..") {
6448
+ at = parentOf5(at);
6449
+ continue;
6450
+ }
6451
+ const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
6452
+ const link2 = await linkOf(fs, next2);
6453
+ if (link2 === void 0) {
6454
+ if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
6455
+ at = next2;
6456
+ continue;
6457
+ }
6458
+ if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", dir);
6459
+ budget.hops++;
6460
+ queue.unshift(...link2.split("/").filter(Boolean));
6461
+ if (link2.startsWith("/")) at = "/";
6462
+ }
6463
+ return at;
6464
+ }
6465
+ function joinName(dir, name2) {
6466
+ return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
6467
+ }
6468
+ async function realTarget(fs, path) {
6469
+ const budget = { hops: 0 };
6470
+ let dir = await realDir(fs, parentOf5(path), budget);
6471
+ let name2 = path.slice(path.lastIndexOf("/") + 1);
6472
+ for (; ; ) {
6473
+ const link2 = await linkOf(fs, joinName(dir, name2));
6474
+ if (link2 === void 0) break;
6475
+ if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", path);
6476
+ budget.hops++;
6477
+ const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
6478
+ dir = await realDir(fs, parentOf5(target), budget);
6479
+ name2 = target.slice(target.lastIndexOf("/") + 1);
6480
+ }
6481
+ return joinName(dir, name2);
6482
+ }
6483
+
6385
6484
  // src/mount/protocol.ts
6386
6485
  var DRIVER_PROTOCOL = [1, 1];
6387
6486
  function driverVersionError(theirs) {
@@ -6485,7 +6584,7 @@ var DriverConnection = class _DriverConnection {
6485
6584
  };
6486
6585
 
6487
6586
  // src/mount/mount-fs.ts
6488
- var MAX_LINKS4 = 40;
6587
+ var MAX_LINKS5 = 40;
6489
6588
  var STALE_RETRIES = 3;
6490
6589
  var SETTLE_ROUNDS = 4;
6491
6590
  var DEFAULT_IO = 8 * 1024 * 1024;
@@ -6649,8 +6748,13 @@ var HeldPaths = class extends Map {
6649
6748
  return this.revoked.has(path);
6650
6749
  }
6651
6750
  };
6652
- var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6751
+ var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6653
6752
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
6753
+ function inodeless(mount, attr) {
6754
+ if (attr.ino !== void 0) return false;
6755
+ if (mount.caps.links) mount.caps = { ...mount.caps, links: false };
6756
+ return true;
6757
+ }
6654
6758
  function statOf(attr, path, mount) {
6655
6759
  const mtime = new Date(attr.mtime);
6656
6760
  const fallback = attr.kind === "directory" ? 16877 : attr.kind === "symlink" ? 41471 : 33188;
@@ -6664,10 +6768,12 @@ function statOf(attr, path, mount) {
6664
6768
  atime: mtime,
6665
6769
  ctime: mtime,
6666
6770
  ino: attr.ino ?? inodeOf(path),
6771
+ ...!inodeless(mount, attr) && attr.nlink !== void 0 ? { nlink: attr.nlink } : {},
6667
6772
  dev: mount.dev,
6668
6773
  ...readonly(mount) ? { readonly: true } : {},
6669
6774
  ...mount.maxFile > 0 ? { maxFile: mount.maxFile } : {},
6670
6775
  ...mount.caps.ranges ? { ranged: true } : {},
6776
+ ...mount.caps.links ? { linkable: true } : {},
6671
6777
  ...attr.etag !== void 0 ? { version: attr.etag } : {}
6672
6778
  };
6673
6779
  }
@@ -6916,7 +7022,7 @@ var MountTable = class {
6916
7022
  mount.epoch++;
6917
7023
  for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
6918
7024
  mount.lists.delete(path);
6919
- mount.lists.delete(parentOf5(path));
7025
+ mount.lists.delete(parentOf6(path));
6920
7026
  for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
6921
7027
  }
6922
7028
  async getattr(mount, path, rel2, settled = false) {
@@ -6990,9 +7096,9 @@ var MountFs = class {
6990
7096
  throw err;
6991
7097
  }
6992
7098
  if (attr.kind !== "symlink") return real;
6993
- if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
7099
+ if (hops >= MAX_LINKS5) throw errnoError("ELOOP", path);
6994
7100
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
6995
- return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
7101
+ return this.follow(this.base.resolvePath(parentOf6(real), target), hops + 1, create);
6996
7102
  }
6997
7103
  async open(mount, rel2, write) {
6998
7104
  return await mount.conn.call({
@@ -7322,6 +7428,34 @@ var MountFs = class {
7322
7428
  this.table.forget(a.mount, normalizePath(to));
7323
7429
  }
7324
7430
  }
7431
+ links(path) {
7432
+ const found = this.at(path);
7433
+ return found ? found.mount.caps.links === true : mayLink(this.base, path);
7434
+ }
7435
+ async named(path) {
7436
+ const at = normalizePath(path);
7437
+ return joinName(await realDir(this, parentOf6(at)), at.slice(at.lastIndexOf("/") + 1));
7438
+ }
7439
+ async link(source, target, follow2 = false) {
7440
+ const from = follow2 ? await realTarget(this, normalizePath(source)) : await this.named(source);
7441
+ const to = await this.named(target);
7442
+ const a = this.at(from);
7443
+ const b = this.at(to);
7444
+ if (!a && !b) return this.base.link(from, to);
7445
+ if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
7446
+ this.writable(a.mount, normalizePath(to));
7447
+ if (a.mount.caps.links) await this.lstat(from);
7448
+ if (!a.mount.caps.links) {
7449
+ throw errnoError("EPERM", `${from}: this file system has no hard links`);
7450
+ }
7451
+ await this.table.commitUnder(from);
7452
+ try {
7453
+ await a.mount.conn.call({ op: "link", from: a.rel, to: b.rel });
7454
+ } finally {
7455
+ this.table.forget(a.mount, normalizePath(from));
7456
+ this.table.forget(a.mount, normalizePath(to));
7457
+ }
7458
+ }
7325
7459
  async symlink(target, path, owner) {
7326
7460
  const found = this.at(path);
7327
7461
  if (!found) return this.base.symlink(target, path, owner);
@@ -7421,10 +7555,12 @@ function statJson(s) {
7421
7555
  atimeMs: s.atime.getTime(),
7422
7556
  ctimeMs: s.ctime.getTime(),
7423
7557
  ino: s.ino,
7558
+ ...s.nlink !== void 0 ? { nlink: s.nlink } : {},
7424
7559
  ...s.dev !== void 0 ? { dev: s.dev } : {},
7425
7560
  ...s.readonly ? { readonly: true } : {},
7426
7561
  ...s.maxFile ? { maxFile: s.maxFile } : {},
7427
7562
  ...s.ranged ? { ranged: true } : {},
7563
+ ...s.linkable ? { linkable: true } : {},
7428
7564
  ...s.version !== void 0 ? { version: s.version } : {},
7429
7565
  ...s.uid !== void 0 ? { uid: s.uid } : {},
7430
7566
  ...s.gid !== void 0 ? { gid: s.gid } : {}
@@ -7493,6 +7629,9 @@ async function run(entry, path, req) {
7493
7629
  entry.renamed?.(path, to);
7494
7630
  return done;
7495
7631
  }
7632
+ case "link":
7633
+ await fs.link(path, fs.resolvePath(cwd, req.arg2 ?? ""), req.mode === 1);
7634
+ return done;
7496
7635
  case "unlink":
7497
7636
  if ((await fs.lstat(path)).isDirectory) throw syncError("EISDIR", path);
7498
7637
  await fs.rm(path);
@@ -7554,12 +7693,28 @@ async function ranged(fs, path, req) {
7554
7693
  return done;
7555
7694
  }
7556
7695
  var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
7557
- var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
7696
+ var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename", "link"]);
7697
+ var TWO_PATHS = /* @__PURE__ */ new Set(["rename", "link"]);
7698
+ var UNLINKING_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rename"]);
7699
+ async function linkedElsewhere(fs, path, from) {
7700
+ if (!mayLink(fs, path)) return false;
7701
+ const st = await fs.lstat(path).catch(() => void 0);
7702
+ if (!st || (st.nlink ?? 1) < 2) return false;
7703
+ const source = from === void 0 ? void 0 : await fs.lstat(from).catch(() => void 0);
7704
+ return !(source && source.ino === st.ino && source.dev === st.dev);
7705
+ }
7558
7706
  async function keptOr(entry, path, handle) {
7559
7707
  if (handle !== void 0) return entry.keptFor?.(handle)?.hidden ?? path;
7560
7708
  const kept = entry.unlinked?.(path);
7561
7709
  return kept && !await entry.fs.exists(path) ? kept.hidden : path;
7562
7710
  }
7711
+ async function owned(entry, own, req, path, target) {
7712
+ const source = target === path ? void 0 : path;
7713
+ if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
7714
+ return run(entry, path, req);
7715
+ }
7716
+ return own(target, () => run(entry, path, req));
7717
+ }
7563
7718
  async function dispatchSyncFs(req) {
7564
7719
  const entry = resolveSyncFsToken(req.token);
7565
7720
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -7583,8 +7738,8 @@ async function dispatchSyncFs(req) {
7583
7738
  return await (entry.gate ? entry.gate(path, serve) : serve());
7584
7739
  }
7585
7740
  if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
7586
- const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
7587
- return await entry.own(target, () => run(entry, path, req));
7741
+ const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
7742
+ return await owned(entry, entry.own, req, path, target);
7588
7743
  } catch (err) {
7589
7744
  return toErrno(err);
7590
7745
  }
@@ -9531,6 +9686,7 @@ var VfsNode = class {
9531
9686
  fs;
9532
9687
  path;
9533
9688
  orphaned;
9689
+ id;
9534
9690
  constructor(fs, path, contents2, orphaned = false, dirty = false, pin) {
9535
9691
  this.fs = fs;
9536
9692
  this.pin = pin;
@@ -9700,6 +9856,7 @@ var VfsNode = class {
9700
9856
  var VfsNodes = class {
9701
9857
  byPath = /* @__PURE__ */ new Map();
9702
9858
  kept = /* @__PURE__ */ new Map();
9859
+ byId = /* @__PURE__ */ new Map();
9703
9860
  keepsOnDisk = false;
9704
9861
  fs;
9705
9862
  entries = /* @__PURE__ */ new Map();
@@ -9719,7 +9876,7 @@ var VfsNodes = class {
9719
9876
  return run2;
9720
9877
  }
9721
9878
  async entryKey(path) {
9722
- const parent = parentOf6(path);
9879
+ const parent = parentOf5(path);
9723
9880
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
9724
9881
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
9725
9882
  }
@@ -9744,21 +9901,66 @@ var VfsNodes = class {
9744
9901
  await this.fs.writeFile(path, new Uint8Array(0), owner);
9745
9902
  });
9746
9903
  }
9747
- open(path, pin, writable = false) {
9748
- let node = this.byPath.get(path);
9749
- if (!node) {
9750
- node = new VfsNode(this.fs, path, void 0, false, false, pin);
9751
- this.byPath.set(path, node);
9752
- }
9904
+ async identity(path) {
9905
+ const st = await this.fs.stat?.(path).catch(() => void 0);
9906
+ if (!st || st.ino === void 0 || (st.nlink ?? 1) < 2) return void 0;
9907
+ return `${st.dev ?? 0}:${st.ino}`;
9908
+ }
9909
+ async identify(path) {
9910
+ return this.byPath.has(path) || !mayLink(this.fs, path) ? void 0 : this.identity(path);
9911
+ }
9912
+ async linked(from, to) {
9913
+ const node = this.byPath.get(from);
9914
+ if (!node || node.orphaned) return;
9915
+ const id2 = node.id ?? await this.identity(to);
9916
+ if (id2 === void 0 || node.orphaned) return;
9917
+ this.know(node, id2);
9918
+ if (!this.byPath.has(to)) this.byPath.set(to, node);
9919
+ }
9920
+ know(node, id2) {
9921
+ node.id = id2;
9922
+ if (!this.byId.has(id2)) this.byId.set(id2, node);
9923
+ }
9924
+ namesOf(node) {
9925
+ const out = [];
9926
+ for (const [p, n] of this.byPath) if (n === node) out.push(p);
9927
+ return out;
9928
+ }
9929
+ forget(node) {
9930
+ for (const p of this.namesOf(node)) this.byPath.delete(p);
9931
+ if (node.id !== void 0 && this.byId.get(node.id) === node) this.byId.delete(node.id);
9932
+ }
9933
+ orphan(node) {
9934
+ node.orphaned = true;
9935
+ this.forget(node);
9936
+ }
9937
+ dropName(path, node) {
9938
+ const others = this.namesOf(node).filter((p) => p !== path);
9939
+ if (others.length === 0) return false;
9940
+ this.byPath.delete(path);
9941
+ if (node.path === path) node.renamedTo(others[0]);
9942
+ return true;
9943
+ }
9944
+ named(path, id2) {
9945
+ const node = this.byPath.get(path) ?? (id2 === void 0 ? void 0 : this.byId.get(id2));
9946
+ if (node && !this.byPath.has(path)) this.byPath.set(path, node);
9947
+ return node;
9948
+ }
9949
+ create(path, id2, node) {
9950
+ this.byPath.set(path, node);
9951
+ if (id2 !== void 0) this.know(node, id2);
9952
+ return node;
9953
+ }
9954
+ open(path, pin, writable = false, id2) {
9955
+ const node = this.named(path, id2) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
9753
9956
  node.opens++;
9754
9957
  if (writable) node.writers++;
9755
9958
  return node;
9756
9959
  }
9757
- adopt(path, contents2, dirty, writable = false) {
9758
- let node = this.byPath.get(path);
9960
+ adopt(path, contents2, dirty, writable = false, id2) {
9961
+ let node = this.named(path, id2);
9759
9962
  if (!node) {
9760
- node = new VfsNode(this.fs, path, contents2, false, dirty);
9761
- this.byPath.set(path, node);
9963
+ node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
9762
9964
  } else if (dirty) {
9763
9965
  node.replace(contents2);
9764
9966
  }
@@ -9782,7 +9984,7 @@ var VfsNodes = class {
9782
9984
  if (writable && node.writers === 0) this.onClosed?.(node.path);
9783
9985
  return;
9784
9986
  }
9785
- if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
9987
+ this.forget(node);
9786
9988
  this.kept.get(node)?.release();
9787
9989
  this.kept.delete(node);
9788
9990
  this.onClosed?.(node.path);
@@ -9798,7 +10000,7 @@ var VfsNodes = class {
9798
10000
  }
9799
10001
  keep(path, file) {
9800
10002
  const node = this.byPath.get(path);
9801
- if (!node) {
10003
+ if (!node || this.dropName(path, node)) {
9802
10004
  file.release();
9803
10005
  return;
9804
10006
  }
@@ -9827,33 +10029,32 @@ var VfsNodes = class {
9827
10029
  }
9828
10030
  }
9829
10031
  revoke(prefix) {
9830
- for (const [path, node] of this.byPath) {
10032
+ for (const [path, node] of [...this.byPath]) {
9831
10033
  if (!within5(path, prefix)) continue;
9832
10034
  node.revoke();
9833
- this.byPath.delete(path);
10035
+ this.forget(node);
9834
10036
  }
9835
10037
  }
9836
10038
  unlinked(path) {
9837
10039
  for (const [held, node] of [...this.byPath]) {
9838
- if (!within5(held, path)) continue;
9839
- node.orphaned = true;
9840
- this.byPath.delete(held);
10040
+ if (!within5(held, path) || this.byPath.get(held) !== node) continue;
10041
+ if (!this.dropName(held, node)) this.orphan(node);
9841
10042
  }
9842
10043
  }
9843
10044
  renamed(from, to) {
9844
10045
  if (from === to) return;
9845
10046
  const moved2 = [];
9846
- for (const [p, node] of this.byPath) {
9847
- if (within5(p, from)) moved2.push(node);
9848
- else if (within5(p, to)) {
9849
- node.orphaned = true;
9850
- this.byPath.delete(p);
10047
+ for (const [p, node] of [...this.byPath]) {
10048
+ if (within5(p, from)) moved2.push([p, node]);
10049
+ else if (within5(p, to) && this.byPath.get(p) === node && !this.dropName(p, node)) {
10050
+ this.orphan(node);
9851
10051
  }
9852
10052
  }
9853
- for (const node of moved2) {
9854
- this.byPath.delete(node.path);
9855
- node.renamedTo(to + node.path.slice(from.length));
9856
- this.byPath.set(node.path, node);
10053
+ for (const [p] of moved2) this.byPath.delete(p);
10054
+ for (const [p, node] of moved2) {
10055
+ const next2 = to + p.slice(from.length);
10056
+ if (node.path === p) node.renamedTo(next2);
10057
+ this.byPath.set(next2, node);
9857
10058
  }
9858
10059
  }
9859
10060
  };
@@ -9867,65 +10068,11 @@ async function present(fs, path, follow2 = false) {
9867
10068
  throw err;
9868
10069
  }
9869
10070
  }
9870
- var MAX_LINKS5 = 40;
9871
- function parentOf6(path) {
9872
- return path.slice(0, path.lastIndexOf("/")) || "/";
9873
- }
9874
- async function mustBeDir(fs, path) {
9875
- if (!fs.lstat) return;
9876
- const st = await fs.lstat(path);
9877
- if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
9878
- }
9879
- async function linkOf(fs, path) {
9880
- return fs.readlink?.(path).catch(() => void 0);
9881
- }
9882
- async function realDir(fs, dir, budget = { hops: 0 }) {
9883
- const queue = dir.split("/").filter(Boolean);
9884
- let at = "/";
9885
- while (queue.length > 0) {
9886
- const part = queue.shift();
9887
- if (part === ".") continue;
9888
- if (part === "..") {
9889
- at = parentOf6(at);
9890
- continue;
9891
- }
9892
- const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
9893
- const link2 = await linkOf(fs, next2);
9894
- if (link2 === void 0) {
9895
- if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
9896
- at = next2;
9897
- continue;
9898
- }
9899
- if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", dir);
9900
- budget.hops++;
9901
- queue.unshift(...link2.split("/").filter(Boolean));
9902
- if (link2.startsWith("/")) at = "/";
9903
- }
9904
- return at;
9905
- }
9906
10071
  function errCode(err) {
9907
10072
  return err?.code;
9908
10073
  }
9909
- function joinName(dir, name2) {
9910
- return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
9911
- }
9912
- async function realTarget(fs, path) {
9913
- const budget = { hops: 0 };
9914
- let dir = await realDir(fs, parentOf6(path), budget);
9915
- let name2 = path.slice(path.lastIndexOf("/") + 1);
9916
- for (; ; ) {
9917
- const link2 = await linkOf(fs, joinName(dir, name2));
9918
- if (link2 === void 0) break;
9919
- if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
9920
- budget.hops++;
9921
- const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
9922
- dir = await realDir(fs, parentOf6(target), budget);
9923
- name2 = target.slice(target.lastIndexOf("/") + 1);
9924
- }
9925
- return joinName(dir, name2);
9926
- }
9927
10074
  async function targetDir(fs, path) {
9928
- return parentOf6(await realTarget(fs, path).catch(() => path));
10075
+ return parentOf5(await realTarget(fs, path).catch(() => path));
9929
10076
  }
9930
10077
  async function refuseReadonly(fs, path, flags, opts = {}) {
9931
10078
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -9961,6 +10108,15 @@ function keepingOpen(fs, nodes) {
9961
10108
  writeFile: (path, content, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content, owner)),
9962
10109
  createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
9963
10110
  symlink: (target, path, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path, owner)),
10111
+ links: (path) => mayLink(fs, path),
10112
+ link: async (from, to, follow2) => {
10113
+ const source = fs.resolvePath("/", from);
10114
+ const at = await (follow2 ? nodes.targetKey(source) : nodes.entryKey(source));
10115
+ await nodes.flush(at);
10116
+ const key = await nodes.entryKey(fs.resolvePath("/", to));
10117
+ await nodes.onEntry(key, () => fs.link(from, to, follow2));
10118
+ await nodes.linked(at, key);
10119
+ },
9964
10120
  mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
9965
10121
  };
9966
10122
  }
@@ -9968,7 +10124,7 @@ function vfsFile(fs, opts, nodes) {
9968
10124
  const access = opts.flags & O_ACCMODE;
9969
10125
  const readable = access !== O_WRONLY;
9970
10126
  const writable = access === O_WRONLY || access === O_RDWR;
9971
- const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable) : nodes.open(opts.path, opts.pin, writable);
10127
+ const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable, opts.id) : nodes.open(opts.path, opts.pin, writable, opts.id);
9972
10128
  let openError;
9973
10129
  const atOpen = (op) => {
9974
10130
  void node.serial(op).catch((err) => {
@@ -10403,12 +10559,12 @@ var WasmProcess = class {
10403
10559
  } else if (req.create) {
10404
10560
  await nodes.onEntry(path, async () => {
10405
10561
  const fresh = this.options.owners && !await present(this.options.fs, path, true);
10406
- this.fds.installAt(fd, this.openVfsFile(open));
10562
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10407
10563
  await this.fds.get(fd).file.stat?.();
10408
10564
  if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
10409
10565
  });
10410
10566
  } else {
10411
- this.fds.installAt(fd, this.openVfsFile(open));
10567
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10412
10568
  }
10413
10569
  if (req.existing && !await present(this.options.fs, path, true)) {
10414
10570
  throw new KernelError("ENOENT");
@@ -10439,7 +10595,7 @@ var WasmProcess = class {
10439
10595
  );
10440
10596
  return memoryFile(tree.read(path), path, req.position);
10441
10597
  }
10442
- openVfsFile(req) {
10598
+ openVfsFile(req, id2) {
10443
10599
  return vfsFile(
10444
10600
  this.options.fs,
10445
10601
  {
@@ -10450,7 +10606,8 @@ var WasmProcess = class {
10450
10606
  ...req.orphan ? { orphan: true } : {},
10451
10607
  ...req.truncate ? { truncate: true } : {},
10452
10608
  ...req.create ? { create: true } : {},
10453
- ...req.pin ? { pin: req.pin } : {}
10609
+ ...req.pin ? { pin: req.pin } : {},
10610
+ ...id2 !== void 0 && !req.orphan ? { id: id2 } : {}
10454
10611
  },
10455
10612
  this.nodes
10456
10613
  );
@@ -10598,6 +10755,7 @@ var WasmProcess = class {
10598
10755
  }
10599
10756
  if (req.path === void 0) throw new KernelError("EINVAL");
10600
10757
  const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path), req.handle);
10758
+ const id2 = req.orphan ? void 0 : await this.nodes.identify(path);
10601
10759
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
10602
10760
  const file = vfsFile(
10603
10761
  this.options.fs,
@@ -10608,7 +10766,8 @@ var WasmProcess = class {
10608
10766
  ...req.contents !== void 0 ? { contents: req.contents } : {},
10609
10767
  ...req.orphan ? { orphan: true } : {},
10610
10768
  ...req.dirty ? { dirty: true } : {},
10611
- ...req.pin ? { pin: req.pin } : {}
10769
+ ...req.pin ? { pin: req.pin } : {},
10770
+ ...id2 !== void 0 ? { id: id2 } : {}
10612
10771
  },
10613
10772
  this.nodes
10614
10773
  );
@@ -11680,6 +11839,9 @@ async function dispatch(h, req) {
11680
11839
  return h.unlink(req.path);
11681
11840
  case "rename":
11682
11841
  return h.rename(req.from, req.to);
11842
+ case "link":
11843
+ if (!h.link) throw fsError2("EPERM", "this file system has no hard links");
11844
+ return h.link(req.from, req.to);
11683
11845
  case "symlink":
11684
11846
  if (!h.symlink) throw fsError2("EPERM", "this file system has no symlinks");
11685
11847
  return h.symlink(req.target, req.path);
@@ -12762,8 +12924,8 @@ var ChunkedBytes = class {
12762
12924
  }
12763
12925
  return out;
12764
12926
  }
12765
- write(offset, bytes, owned = false) {
12766
- const whole = owned && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
12927
+ write(offset, bytes, owned2 = false) {
12928
+ const whole = owned2 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
12767
12929
  for (let done4 = 0; done4 < bytes.length; ) {
12768
12930
  const at = offset + done4;
12769
12931
  const index = Math.floor(at / CHUNK3);
@@ -12805,12 +12967,17 @@ function tmpfs() {
12805
12967
  mtime: Date.now(),
12806
12968
  data: new ChunkedBytes(),
12807
12969
  target: "",
12808
- children: /* @__PURE__ */ new Map()
12970
+ children: /* @__PURE__ */ new Map(),
12971
+ links: 1
12809
12972
  });
12810
12973
  const root = make("directory", 16877);
12811
12974
  const handles = /* @__PURE__ */ new Map();
12812
12975
  let nextFh = 0;
12813
12976
  const parts = (path) => path.split("/").filter(Boolean);
12977
+ const place = (dir, name2, node) => {
12978
+ dir.children.set(name2, node);
12979
+ dir.mtime = Date.now();
12980
+ };
12814
12981
  function find(path) {
12815
12982
  let node = root;
12816
12983
  for (const name2 of parts(path)) {
@@ -12834,7 +13001,8 @@ function tmpfs() {
12834
13001
  size: node.kind === "file" ? node.data.size : node.kind === "symlink" ? node.target.length : 4096,
12835
13002
  mtime: node.mtime,
12836
13003
  mode: node.mode,
12837
- ino: node.ino
13004
+ ino: node.ino,
13005
+ ...node.kind === "directory" ? {} : { nlink: node.links }
12838
13006
  });
12839
13007
  const handle = (fh) => {
12840
13008
  const found = handles.get(fh);
@@ -12849,8 +13017,7 @@ function tmpfs() {
12849
13017
  if (existing) return existing;
12850
13018
  if (!flags.create) throw fsError2("ENOENT", path);
12851
13019
  const node = make("file", 33188);
12852
- dir.children.set(name2, node);
12853
- dir.mtime = Date.now();
13020
+ place(dir, name2, node);
12854
13021
  return node;
12855
13022
  }
12856
13023
  return {
@@ -12889,8 +13056,7 @@ function tmpfs() {
12889
13056
  async mkdir(path) {
12890
13057
  const { dir, name: name2 } = parent(path);
12891
13058
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
12892
- dir.children.set(name2, make("directory", 16877));
12893
- dir.mtime = Date.now();
13059
+ place(dir, name2, make("directory", 16877));
12894
13060
  },
12895
13061
  async rmdir(path) {
12896
13062
  const { dir, name: name2 } = parent(path);
@@ -12907,6 +13073,7 @@ function tmpfs() {
12907
13073
  if (!node) throw fsError2("ENOENT", path);
12908
13074
  if (node.kind === "directory") throw fsError2("EISDIR", path);
12909
13075
  dir.children.delete(name2);
13076
+ node.links--;
12910
13077
  dir.mtime = Date.now();
12911
13078
  },
12912
13079
  async rename(from, to) {
@@ -12923,17 +13090,25 @@ function tmpfs() {
12923
13090
  throw fsError2(node.kind === "directory" ? "ENOTEMPTY" : "EISDIR", to);
12924
13091
  }
12925
13092
  source.dir.children.delete(source.name);
13093
+ if (replaced) replaced.links--;
12926
13094
  target.dir.children.set(target.name, node);
12927
13095
  source.dir.mtime = Date.now();
12928
13096
  target.dir.mtime = source.dir.mtime;
12929
13097
  },
13098
+ async link(from, to) {
13099
+ const node = find(from);
13100
+ if (node.kind === "directory") throw fsError2("EPERM", `${from} is a directory`);
13101
+ const { dir, name: name2 } = parent(to);
13102
+ if (dir.children.has(name2)) throw fsError2("EEXIST", to);
13103
+ place(dir, name2, node);
13104
+ node.links++;
13105
+ },
12930
13106
  async symlink(target, path) {
12931
13107
  const { dir, name: name2 } = parent(path);
12932
13108
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
12933
13109
  const node = make("symlink", 41471);
12934
13110
  node.target = target;
12935
- dir.children.set(name2, node);
12936
- dir.mtime = Date.now();
13111
+ place(dir, name2, node);
12937
13112
  },
12938
13113
  async readlink(path) {
12939
13114
  const node = find(path);
@@ -13833,7 +14008,7 @@ var Launcher = class {
13833
14008
  serveFilesystem(
13834
14009
  port2,
13835
14010
  tmpfs(),
13836
- { symlinks: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
14011
+ { symlinks: true, links: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
13837
14012
  { owned: true }
13838
14013
  );
13839
14014
  return {