@ai-ecoverse/slicc-kernel 1.57.0 → 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
  }
@@ -8159,6 +8314,10 @@ function normalize(path) {
8159
8314
  }
8160
8315
  return `/${out.join("/")}`;
8161
8316
  }
8317
+ var RING = 8;
8318
+ var RING_SIZE = 64;
8319
+ var SLOT = 3;
8320
+ var SHARED_IDS_BYTES = (RING + RING_SIZE * SLOT) * 4;
8162
8321
 
8163
8322
  // src/process/wasi/wasi-procfs.ts
8164
8323
  var DIR_MODE = 16749;
@@ -9527,6 +9686,7 @@ var VfsNode = class {
9527
9686
  fs;
9528
9687
  path;
9529
9688
  orphaned;
9689
+ id;
9530
9690
  constructor(fs, path, contents2, orphaned = false, dirty = false, pin) {
9531
9691
  this.fs = fs;
9532
9692
  this.pin = pin;
@@ -9696,6 +9856,7 @@ var VfsNode = class {
9696
9856
  var VfsNodes = class {
9697
9857
  byPath = /* @__PURE__ */ new Map();
9698
9858
  kept = /* @__PURE__ */ new Map();
9859
+ byId = /* @__PURE__ */ new Map();
9699
9860
  keepsOnDisk = false;
9700
9861
  fs;
9701
9862
  entries = /* @__PURE__ */ new Map();
@@ -9715,7 +9876,7 @@ var VfsNodes = class {
9715
9876
  return run2;
9716
9877
  }
9717
9878
  async entryKey(path) {
9718
- const parent = parentOf6(path);
9879
+ const parent = parentOf5(path);
9719
9880
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
9720
9881
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
9721
9882
  }
@@ -9740,21 +9901,66 @@ var VfsNodes = class {
9740
9901
  await this.fs.writeFile(path, new Uint8Array(0), owner);
9741
9902
  });
9742
9903
  }
9743
- open(path, pin, writable = false) {
9744
- let node = this.byPath.get(path);
9745
- if (!node) {
9746
- node = new VfsNode(this.fs, path, void 0, false, false, pin);
9747
- this.byPath.set(path, node);
9748
- }
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));
9749
9956
  node.opens++;
9750
9957
  if (writable) node.writers++;
9751
9958
  return node;
9752
9959
  }
9753
- adopt(path, contents2, dirty, writable = false) {
9754
- let node = this.byPath.get(path);
9960
+ adopt(path, contents2, dirty, writable = false, id2) {
9961
+ let node = this.named(path, id2);
9755
9962
  if (!node) {
9756
- node = new VfsNode(this.fs, path, contents2, false, dirty);
9757
- this.byPath.set(path, node);
9963
+ node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
9758
9964
  } else if (dirty) {
9759
9965
  node.replace(contents2);
9760
9966
  }
@@ -9778,7 +9984,7 @@ var VfsNodes = class {
9778
9984
  if (writable && node.writers === 0) this.onClosed?.(node.path);
9779
9985
  return;
9780
9986
  }
9781
- if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
9987
+ this.forget(node);
9782
9988
  this.kept.get(node)?.release();
9783
9989
  this.kept.delete(node);
9784
9990
  this.onClosed?.(node.path);
@@ -9794,7 +10000,7 @@ var VfsNodes = class {
9794
10000
  }
9795
10001
  keep(path, file) {
9796
10002
  const node = this.byPath.get(path);
9797
- if (!node) {
10003
+ if (!node || this.dropName(path, node)) {
9798
10004
  file.release();
9799
10005
  return;
9800
10006
  }
@@ -9823,33 +10029,32 @@ var VfsNodes = class {
9823
10029
  }
9824
10030
  }
9825
10031
  revoke(prefix) {
9826
- for (const [path, node] of this.byPath) {
10032
+ for (const [path, node] of [...this.byPath]) {
9827
10033
  if (!within5(path, prefix)) continue;
9828
10034
  node.revoke();
9829
- this.byPath.delete(path);
10035
+ this.forget(node);
9830
10036
  }
9831
10037
  }
9832
10038
  unlinked(path) {
9833
10039
  for (const [held, node] of [...this.byPath]) {
9834
- if (!within5(held, path)) continue;
9835
- node.orphaned = true;
9836
- this.byPath.delete(held);
10040
+ if (!within5(held, path) || this.byPath.get(held) !== node) continue;
10041
+ if (!this.dropName(held, node)) this.orphan(node);
9837
10042
  }
9838
10043
  }
9839
10044
  renamed(from, to) {
9840
10045
  if (from === to) return;
9841
10046
  const moved2 = [];
9842
- for (const [p, node] of this.byPath) {
9843
- if (within5(p, from)) moved2.push(node);
9844
- else if (within5(p, to)) {
9845
- node.orphaned = true;
9846
- 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);
9847
10051
  }
9848
10052
  }
9849
- for (const node of moved2) {
9850
- this.byPath.delete(node.path);
9851
- node.renamedTo(to + node.path.slice(from.length));
9852
- 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);
9853
10058
  }
9854
10059
  }
9855
10060
  };
@@ -9863,65 +10068,11 @@ async function present(fs, path, follow2 = false) {
9863
10068
  throw err;
9864
10069
  }
9865
10070
  }
9866
- var MAX_LINKS5 = 40;
9867
- function parentOf6(path) {
9868
- return path.slice(0, path.lastIndexOf("/")) || "/";
9869
- }
9870
- async function mustBeDir(fs, path) {
9871
- if (!fs.lstat) return;
9872
- const st = await fs.lstat(path);
9873
- if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
9874
- }
9875
- async function linkOf(fs, path) {
9876
- return fs.readlink?.(path).catch(() => void 0);
9877
- }
9878
- async function realDir(fs, dir, budget = { hops: 0 }) {
9879
- const queue = dir.split("/").filter(Boolean);
9880
- let at = "/";
9881
- while (queue.length > 0) {
9882
- const part = queue.shift();
9883
- if (part === ".") continue;
9884
- if (part === "..") {
9885
- at = parentOf6(at);
9886
- continue;
9887
- }
9888
- const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
9889
- const link2 = await linkOf(fs, next2);
9890
- if (link2 === void 0) {
9891
- if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
9892
- at = next2;
9893
- continue;
9894
- }
9895
- if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", dir);
9896
- budget.hops++;
9897
- queue.unshift(...link2.split("/").filter(Boolean));
9898
- if (link2.startsWith("/")) at = "/";
9899
- }
9900
- return at;
9901
- }
9902
10071
  function errCode(err) {
9903
10072
  return err?.code;
9904
10073
  }
9905
- function joinName(dir, name2) {
9906
- return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
9907
- }
9908
- async function realTarget(fs, path) {
9909
- const budget = { hops: 0 };
9910
- let dir = await realDir(fs, parentOf6(path), budget);
9911
- let name2 = path.slice(path.lastIndexOf("/") + 1);
9912
- for (; ; ) {
9913
- const link2 = await linkOf(fs, joinName(dir, name2));
9914
- if (link2 === void 0) break;
9915
- if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
9916
- budget.hops++;
9917
- const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
9918
- dir = await realDir(fs, parentOf6(target), budget);
9919
- name2 = target.slice(target.lastIndexOf("/") + 1);
9920
- }
9921
- return joinName(dir, name2);
9922
- }
9923
10074
  async function targetDir(fs, path) {
9924
- return parentOf6(await realTarget(fs, path).catch(() => path));
10075
+ return parentOf5(await realTarget(fs, path).catch(() => path));
9925
10076
  }
9926
10077
  async function refuseReadonly(fs, path, flags, opts = {}) {
9927
10078
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -9957,6 +10108,15 @@ function keepingOpen(fs, nodes) {
9957
10108
  writeFile: (path, content, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content, owner)),
9958
10109
  createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
9959
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
+ },
9960
10120
  mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
9961
10121
  };
9962
10122
  }
@@ -9964,7 +10124,7 @@ function vfsFile(fs, opts, nodes) {
9964
10124
  const access = opts.flags & O_ACCMODE;
9965
10125
  const readable = access !== O_WRONLY;
9966
10126
  const writable = access === O_WRONLY || access === O_RDWR;
9967
- 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);
9968
10128
  let openError;
9969
10129
  const atOpen = (op) => {
9970
10130
  void node.serial(op).catch((err) => {
@@ -10399,12 +10559,12 @@ var WasmProcess = class {
10399
10559
  } else if (req.create) {
10400
10560
  await nodes.onEntry(path, async () => {
10401
10561
  const fresh = this.options.owners && !await present(this.options.fs, path, true);
10402
- this.fds.installAt(fd, this.openVfsFile(open));
10562
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10403
10563
  await this.fds.get(fd).file.stat?.();
10404
10564
  if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
10405
10565
  });
10406
10566
  } else {
10407
- this.fds.installAt(fd, this.openVfsFile(open));
10567
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10408
10568
  }
10409
10569
  if (req.existing && !await present(this.options.fs, path, true)) {
10410
10570
  throw new KernelError("ENOENT");
@@ -10435,7 +10595,7 @@ var WasmProcess = class {
10435
10595
  );
10436
10596
  return memoryFile(tree.read(path), path, req.position);
10437
10597
  }
10438
- openVfsFile(req) {
10598
+ openVfsFile(req, id2) {
10439
10599
  return vfsFile(
10440
10600
  this.options.fs,
10441
10601
  {
@@ -10446,7 +10606,8 @@ var WasmProcess = class {
10446
10606
  ...req.orphan ? { orphan: true } : {},
10447
10607
  ...req.truncate ? { truncate: true } : {},
10448
10608
  ...req.create ? { create: true } : {},
10449
- ...req.pin ? { pin: req.pin } : {}
10609
+ ...req.pin ? { pin: req.pin } : {},
10610
+ ...id2 !== void 0 && !req.orphan ? { id: id2 } : {}
10450
10611
  },
10451
10612
  this.nodes
10452
10613
  );
@@ -10594,6 +10755,7 @@ var WasmProcess = class {
10594
10755
  }
10595
10756
  if (req.path === void 0) throw new KernelError("EINVAL");
10596
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);
10597
10759
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
10598
10760
  const file = vfsFile(
10599
10761
  this.options.fs,
@@ -10604,7 +10766,8 @@ var WasmProcess = class {
10604
10766
  ...req.contents !== void 0 ? { contents: req.contents } : {},
10605
10767
  ...req.orphan ? { orphan: true } : {},
10606
10768
  ...req.dirty ? { dirty: true } : {},
10607
- ...req.pin ? { pin: req.pin } : {}
10769
+ ...req.pin ? { pin: req.pin } : {},
10770
+ ...id2 !== void 0 ? { id: id2 } : {}
10608
10771
  },
10609
10772
  this.nodes
10610
10773
  );
@@ -11676,6 +11839,9 @@ async function dispatch(h, req) {
11676
11839
  return h.unlink(req.path);
11677
11840
  case "rename":
11678
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);
11679
11845
  case "symlink":
11680
11846
  if (!h.symlink) throw fsError2("EPERM", "this file system has no symlinks");
11681
11847
  return h.symlink(req.target, req.path);
@@ -12758,8 +12924,8 @@ var ChunkedBytes = class {
12758
12924
  }
12759
12925
  return out;
12760
12926
  }
12761
- write(offset, bytes, owned = false) {
12762
- 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;
12763
12929
  for (let done4 = 0; done4 < bytes.length; ) {
12764
12930
  const at = offset + done4;
12765
12931
  const index = Math.floor(at / CHUNK3);
@@ -12801,12 +12967,17 @@ function tmpfs() {
12801
12967
  mtime: Date.now(),
12802
12968
  data: new ChunkedBytes(),
12803
12969
  target: "",
12804
- children: /* @__PURE__ */ new Map()
12970
+ children: /* @__PURE__ */ new Map(),
12971
+ links: 1
12805
12972
  });
12806
12973
  const root = make("directory", 16877);
12807
12974
  const handles = /* @__PURE__ */ new Map();
12808
12975
  let nextFh = 0;
12809
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
+ };
12810
12981
  function find(path) {
12811
12982
  let node = root;
12812
12983
  for (const name2 of parts(path)) {
@@ -12830,7 +13001,8 @@ function tmpfs() {
12830
13001
  size: node.kind === "file" ? node.data.size : node.kind === "symlink" ? node.target.length : 4096,
12831
13002
  mtime: node.mtime,
12832
13003
  mode: node.mode,
12833
- ino: node.ino
13004
+ ino: node.ino,
13005
+ ...node.kind === "directory" ? {} : { nlink: node.links }
12834
13006
  });
12835
13007
  const handle = (fh) => {
12836
13008
  const found = handles.get(fh);
@@ -12845,8 +13017,7 @@ function tmpfs() {
12845
13017
  if (existing) return existing;
12846
13018
  if (!flags.create) throw fsError2("ENOENT", path);
12847
13019
  const node = make("file", 33188);
12848
- dir.children.set(name2, node);
12849
- dir.mtime = Date.now();
13020
+ place(dir, name2, node);
12850
13021
  return node;
12851
13022
  }
12852
13023
  return {
@@ -12885,8 +13056,7 @@ function tmpfs() {
12885
13056
  async mkdir(path) {
12886
13057
  const { dir, name: name2 } = parent(path);
12887
13058
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
12888
- dir.children.set(name2, make("directory", 16877));
12889
- dir.mtime = Date.now();
13059
+ place(dir, name2, make("directory", 16877));
12890
13060
  },
12891
13061
  async rmdir(path) {
12892
13062
  const { dir, name: name2 } = parent(path);
@@ -12903,6 +13073,7 @@ function tmpfs() {
12903
13073
  if (!node) throw fsError2("ENOENT", path);
12904
13074
  if (node.kind === "directory") throw fsError2("EISDIR", path);
12905
13075
  dir.children.delete(name2);
13076
+ node.links--;
12906
13077
  dir.mtime = Date.now();
12907
13078
  },
12908
13079
  async rename(from, to) {
@@ -12919,17 +13090,25 @@ function tmpfs() {
12919
13090
  throw fsError2(node.kind === "directory" ? "ENOTEMPTY" : "EISDIR", to);
12920
13091
  }
12921
13092
  source.dir.children.delete(source.name);
13093
+ if (replaced) replaced.links--;
12922
13094
  target.dir.children.set(target.name, node);
12923
13095
  source.dir.mtime = Date.now();
12924
13096
  target.dir.mtime = source.dir.mtime;
12925
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
+ },
12926
13106
  async symlink(target, path) {
12927
13107
  const { dir, name: name2 } = parent(path);
12928
13108
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
12929
13109
  const node = make("symlink", 41471);
12930
13110
  node.target = target;
12931
- dir.children.set(name2, node);
12932
- dir.mtime = Date.now();
13111
+ place(dir, name2, node);
12933
13112
  },
12934
13113
  async readlink(path) {
12935
13114
  const node = find(path);
@@ -13829,7 +14008,7 @@ var Launcher = class {
13829
14008
  serveFilesystem(
13830
14009
  port2,
13831
14010
  tmpfs(),
13832
- { symlinks: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
14011
+ { symlinks: true, links: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
13833
14012
  { owned: true }
13834
14013
  );
13835
14014
  return {