@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.
package/dist/node.js CHANGED
@@ -98,6 +98,9 @@ async function lutimesOf(fs, path, atime, mtime) {
98
98
  if ((await fs.lstat(path)).isSymbolicLink) throw fsError("EOPNOTSUPP", path);
99
99
  return fs.utimes(path, atime, mtime);
100
100
  }
101
+ function mayLink(fs, path) {
102
+ return fs.links?.(path) ?? true;
103
+ }
101
104
  function fsError(code, path) {
102
105
  return Object.assign(new Error(`${code}: ${path}`), { code });
103
106
  }
@@ -2922,12 +2925,12 @@ var OpfsFs = class {
2922
2925
  const base = dir === "/" ? "" : dir;
2923
2926
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
2924
2927
  const handed2 = this.owners ? await this.handedTo(dir) : void 0;
2925
- const owned = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
2928
+ const owned2 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
2926
2929
  return Promise.all(
2927
2930
  children2.map(async ([name2, child], i) => {
2928
- if (!isHandle(child)) return [name2, owned(this.linkStat(child), child)];
2931
+ if (!isHandle(child)) return [name2, owned2(this.linkStat(child), child)];
2929
2932
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
2930
- return [name2, stat && owned(stat, metas[i])];
2933
+ return [name2, stat && owned2(stat, metas[i])];
2931
2934
  })
2932
2935
  );
2933
2936
  }
@@ -2986,6 +2989,12 @@ var OpfsFs = class {
2986
2989
  await this.meta.remove([found.path]);
2987
2990
  this.touch(parentOf2(found.path));
2988
2991
  }
2992
+ links() {
2993
+ return false;
2994
+ }
2995
+ async link(from) {
2996
+ throw fsError("EPERM", `${from}: this file system has no hard links`);
2997
+ }
2989
2998
  async rename(from, to) {
2990
2999
  if (from === to) return;
2991
3000
  const source = await this.locate(from, false);
@@ -3227,6 +3236,8 @@ var UnlinkedKeeper = class {
3227
3236
  gate = new PathGate();
3228
3237
  queue = Promise.resolve();
3229
3238
  count = 0;
3239
+ links = /* @__PURE__ */ new Map();
3240
+ linked = /* @__PURE__ */ new Map();
3230
3241
  constructor(fs, holders, rootOf, options = {}) {
3231
3242
  const { locks, owner } = options;
3232
3243
  this.fs = fs;
@@ -3246,8 +3257,15 @@ var UnlinkedKeeper = class {
3246
3257
  wrap(fs = this.fs) {
3247
3258
  const keeper = this;
3248
3259
  const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
3260
+ const counted2 = (st) => {
3261
+ const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
3262
+ return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
3263
+ };
3249
3264
  return {
3250
3265
  ...fs,
3266
+ links: (path) => mayLink(fs, path),
3267
+ stat: async (path) => counted2(await fs.stat(path)),
3268
+ lstat: async (path) => counted2(await fs.lstat(path)),
3251
3269
  async rm(path, options) {
3252
3270
  const target = fs.resolvePath("/", path);
3253
3271
  const pinned = await keeper.pin(target);
@@ -3261,7 +3279,7 @@ var UnlinkedKeeper = class {
3261
3279
  await keeper.settle(pinned, () => fs.rename(from, to));
3262
3280
  },
3263
3281
  readdir: async (path) => visible(await fs.readdir(path)),
3264
- readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
3282
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
3265
3283
  };
3266
3284
  }
3267
3285
  async settle(pinned, op) {
@@ -3311,6 +3329,7 @@ var UnlinkedKeeper = class {
3311
3329
  for (const holder of held.get(path)) holder.unkeep(path, file);
3312
3330
  await this.serial(async () => {
3313
3331
  await Promise.allSettled([this.fs.rename(file.hidden, path)]);
3332
+ this.unlinkedName(file.hidden);
3314
3333
  await this.tidy(parentOf3(file.hidden));
3315
3334
  });
3316
3335
  }
@@ -3333,6 +3352,7 @@ var UnlinkedKeeper = class {
3333
3352
  try {
3334
3353
  await this.fs.mkdir(dir, { recursive: true });
3335
3354
  await this.fs.rename(path, hidden);
3355
+ await this.linkedName(hidden);
3336
3356
  return hidden;
3337
3357
  } catch {
3338
3358
  await this.tidy(dir);
@@ -3349,8 +3369,25 @@ var UnlinkedKeeper = class {
3349
3369
  return;
3350
3370
  }
3351
3371
  await this.fs.rm(hidden).catch(() => void 0);
3372
+ this.unlinkedName(hidden);
3352
3373
  await this.tidy(parentOf3(hidden));
3353
3374
  }
3375
+ async linkedName(hidden) {
3376
+ if (!mayLink(this.fs, hidden)) return;
3377
+ const st = await this.fs.lstat(hidden).catch(() => void 0);
3378
+ if (!st || (st.nlink ?? 1) < 2) return;
3379
+ const id2 = `${st.dev ?? 0}:${st.ino}`;
3380
+ this.linked.set(hidden, id2);
3381
+ this.links.set(id2, (this.links.get(id2) ?? 0) + 1);
3382
+ }
3383
+ unlinkedName(hidden) {
3384
+ const id2 = this.linked.get(hidden);
3385
+ if (id2 === void 0) return;
3386
+ this.linked.delete(hidden);
3387
+ const left = (this.links.get(id2) ?? 1) - 1;
3388
+ if (left > 0) this.links.set(id2, left);
3389
+ else this.links.delete(id2);
3390
+ }
3354
3391
  async tidy(dir) {
3355
3392
  const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
3356
3393
  if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
@@ -4412,6 +4449,8 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
4412
4449
  }
4413
4450
  },
4414
4451
  writeFile: (path, content, owner) => fs.writeFile(path, content, owner),
4452
+ link: (from, to, follow2) => fs.link(from, to, follow2),
4453
+ links: (path) => mayLink(fs, path),
4415
4454
  async exists(path) {
4416
4455
  return await fs.exists(path) || await linked(path) !== void 0;
4417
4456
  },
@@ -4576,6 +4615,10 @@ var FsWatchers = class {
4576
4615
  },
4577
4616
  rm: after(fs.rm.bind(fs), (path) => [path]),
4578
4617
  rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
4618
+ link: after(
4619
+ (from, to, follow2) => fs.link(from, to, follow2),
4620
+ (_from, to) => [to]
4621
+ ),
4579
4622
  symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
4580
4623
  chmod: after(fs.chmod.bind(fs), (path) => [path]),
4581
4624
  ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
@@ -4789,6 +4832,62 @@ var ChildTable = class {
4789
4832
  }
4790
4833
  };
4791
4834
 
4835
+ // src/fs/walk.ts
4836
+ var MAX_LINKS4 = 40;
4837
+ function parentOf5(path) {
4838
+ return path.slice(0, path.lastIndexOf("/")) || "/";
4839
+ }
4840
+ async function mustBeDir(fs, path) {
4841
+ if (!fs.lstat) return;
4842
+ const st = await fs.lstat(path);
4843
+ if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
4844
+ }
4845
+ async function linkOf(fs, path) {
4846
+ return fs.readlink?.(path).catch(() => void 0);
4847
+ }
4848
+ async function realDir(fs, dir, budget = { hops: 0 }) {
4849
+ const queue = dir.split("/").filter(Boolean);
4850
+ let at = "/";
4851
+ while (queue.length > 0) {
4852
+ const part = queue.shift();
4853
+ if (part === ".") continue;
4854
+ if (part === "..") {
4855
+ at = parentOf5(at);
4856
+ continue;
4857
+ }
4858
+ const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
4859
+ const link2 = await linkOf(fs, next2);
4860
+ if (link2 === void 0) {
4861
+ if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
4862
+ at = next2;
4863
+ continue;
4864
+ }
4865
+ if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", dir);
4866
+ budget.hops++;
4867
+ queue.unshift(...link2.split("/").filter(Boolean));
4868
+ if (link2.startsWith("/")) at = "/";
4869
+ }
4870
+ return at;
4871
+ }
4872
+ function joinName(dir, name2) {
4873
+ return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
4874
+ }
4875
+ async function realTarget(fs, path) {
4876
+ const budget = { hops: 0 };
4877
+ let dir = await realDir(fs, parentOf5(path), budget);
4878
+ let name2 = path.slice(path.lastIndexOf("/") + 1);
4879
+ for (; ; ) {
4880
+ const link2 = await linkOf(fs, joinName(dir, name2));
4881
+ if (link2 === void 0) break;
4882
+ if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", path);
4883
+ budget.hops++;
4884
+ const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
4885
+ dir = await realDir(fs, parentOf5(target), budget);
4886
+ name2 = target.slice(target.lastIndexOf("/") + 1);
4887
+ }
4888
+ return joinName(dir, name2);
4889
+ }
4890
+
4792
4891
  // src/mount/protocol.ts
4793
4892
  var DRIVER_PROTOCOL = [1, 1];
4794
4893
  function driverVersionError(theirs) {
@@ -4892,7 +4991,7 @@ var DriverConnection = class _DriverConnection {
4892
4991
  };
4893
4992
 
4894
4993
  // src/mount/mount-fs.ts
4895
- var MAX_LINKS4 = 40;
4994
+ var MAX_LINKS5 = 40;
4896
4995
  var STALE_RETRIES = 3;
4897
4996
  var SETTLE_ROUNDS = 4;
4898
4997
  var DEFAULT_IO = 8 * 1024 * 1024;
@@ -5056,8 +5155,13 @@ var HeldPaths = class extends Map {
5056
5155
  return this.revoked.has(path);
5057
5156
  }
5058
5157
  };
5059
- var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5158
+ var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5060
5159
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
5160
+ function inodeless(mount, attr) {
5161
+ if (attr.ino !== void 0) return false;
5162
+ if (mount.caps.links) mount.caps = { ...mount.caps, links: false };
5163
+ return true;
5164
+ }
5061
5165
  function statOf(attr, path, mount) {
5062
5166
  const mtime = new Date(attr.mtime);
5063
5167
  const fallback = attr.kind === "directory" ? 16877 : attr.kind === "symlink" ? 41471 : 33188;
@@ -5071,10 +5175,12 @@ function statOf(attr, path, mount) {
5071
5175
  atime: mtime,
5072
5176
  ctime: mtime,
5073
5177
  ino: attr.ino ?? inodeOf(path),
5178
+ ...!inodeless(mount, attr) && attr.nlink !== void 0 ? { nlink: attr.nlink } : {},
5074
5179
  dev: mount.dev,
5075
5180
  ...readonly(mount) ? { readonly: true } : {},
5076
5181
  ...mount.maxFile > 0 ? { maxFile: mount.maxFile } : {},
5077
5182
  ...mount.caps.ranges ? { ranged: true } : {},
5183
+ ...mount.caps.links ? { linkable: true } : {},
5078
5184
  ...attr.etag !== void 0 ? { version: attr.etag } : {}
5079
5185
  };
5080
5186
  }
@@ -5323,7 +5429,7 @@ var MountTable = class {
5323
5429
  mount.epoch++;
5324
5430
  for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
5325
5431
  mount.lists.delete(path);
5326
- mount.lists.delete(parentOf5(path));
5432
+ mount.lists.delete(parentOf6(path));
5327
5433
  for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
5328
5434
  }
5329
5435
  async getattr(mount, path, rel2, settled = false) {
@@ -5397,9 +5503,9 @@ var MountFs = class {
5397
5503
  throw err;
5398
5504
  }
5399
5505
  if (attr.kind !== "symlink") return real;
5400
- if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
5506
+ if (hops >= MAX_LINKS5) throw errnoError("ELOOP", path);
5401
5507
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
5402
- return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
5508
+ return this.follow(this.base.resolvePath(parentOf6(real), target), hops + 1, create);
5403
5509
  }
5404
5510
  async open(mount, rel2, write) {
5405
5511
  return await mount.conn.call({
@@ -5729,6 +5835,34 @@ var MountFs = class {
5729
5835
  this.table.forget(a.mount, normalizePath(to));
5730
5836
  }
5731
5837
  }
5838
+ links(path) {
5839
+ const found = this.at(path);
5840
+ return found ? found.mount.caps.links === true : mayLink(this.base, path);
5841
+ }
5842
+ async named(path) {
5843
+ const at = normalizePath(path);
5844
+ return joinName(await realDir(this, parentOf6(at)), at.slice(at.lastIndexOf("/") + 1));
5845
+ }
5846
+ async link(source, target, follow2 = false) {
5847
+ const from = follow2 ? await realTarget(this, normalizePath(source)) : await this.named(source);
5848
+ const to = await this.named(target);
5849
+ const a = this.at(from);
5850
+ const b = this.at(to);
5851
+ if (!a && !b) return this.base.link(from, to);
5852
+ if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
5853
+ this.writable(a.mount, normalizePath(to));
5854
+ if (a.mount.caps.links) await this.lstat(from);
5855
+ if (!a.mount.caps.links) {
5856
+ throw errnoError("EPERM", `${from}: this file system has no hard links`);
5857
+ }
5858
+ await this.table.commitUnder(from);
5859
+ try {
5860
+ await a.mount.conn.call({ op: "link", from: a.rel, to: b.rel });
5861
+ } finally {
5862
+ this.table.forget(a.mount, normalizePath(from));
5863
+ this.table.forget(a.mount, normalizePath(to));
5864
+ }
5865
+ }
5732
5866
  async symlink(target, path, owner) {
5733
5867
  const found = this.at(path);
5734
5868
  if (!found) return this.base.symlink(target, path, owner);
@@ -5828,10 +5962,12 @@ function statJson(s) {
5828
5962
  atimeMs: s.atime.getTime(),
5829
5963
  ctimeMs: s.ctime.getTime(),
5830
5964
  ino: s.ino,
5965
+ ...s.nlink !== void 0 ? { nlink: s.nlink } : {},
5831
5966
  ...s.dev !== void 0 ? { dev: s.dev } : {},
5832
5967
  ...s.readonly ? { readonly: true } : {},
5833
5968
  ...s.maxFile ? { maxFile: s.maxFile } : {},
5834
5969
  ...s.ranged ? { ranged: true } : {},
5970
+ ...s.linkable ? { linkable: true } : {},
5835
5971
  ...s.version !== void 0 ? { version: s.version } : {},
5836
5972
  ...s.uid !== void 0 ? { uid: s.uid } : {},
5837
5973
  ...s.gid !== void 0 ? { gid: s.gid } : {}
@@ -5900,6 +6036,9 @@ async function run(entry, path, req) {
5900
6036
  entry.renamed?.(path, to);
5901
6037
  return done;
5902
6038
  }
6039
+ case "link":
6040
+ await fs.link(path, fs.resolvePath(cwd, req.arg2 ?? ""), req.mode === 1);
6041
+ return done;
5903
6042
  case "unlink":
5904
6043
  if ((await fs.lstat(path)).isDirectory) throw syncError("EISDIR", path);
5905
6044
  await fs.rm(path);
@@ -5961,12 +6100,28 @@ async function ranged(fs, path, req) {
5961
6100
  return done;
5962
6101
  }
5963
6102
  var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
5964
- var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
6103
+ var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename", "link"]);
6104
+ var TWO_PATHS = /* @__PURE__ */ new Set(["rename", "link"]);
6105
+ var UNLINKING_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rename"]);
6106
+ async function linkedElsewhere(fs, path, from) {
6107
+ if (!mayLink(fs, path)) return false;
6108
+ const st = await fs.lstat(path).catch(() => void 0);
6109
+ if (!st || (st.nlink ?? 1) < 2) return false;
6110
+ const source = from === void 0 ? void 0 : await fs.lstat(from).catch(() => void 0);
6111
+ return !(source && source.ino === st.ino && source.dev === st.dev);
6112
+ }
5965
6113
  async function keptOr(entry, path, handle) {
5966
6114
  if (handle !== void 0) return entry.keptFor?.(handle)?.hidden ?? path;
5967
6115
  const kept = entry.unlinked?.(path);
5968
6116
  return kept && !await entry.fs.exists(path) ? kept.hidden : path;
5969
6117
  }
6118
+ async function owned(entry, own, req, path, target) {
6119
+ const source = target === path ? void 0 : path;
6120
+ if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
6121
+ return run(entry, path, req);
6122
+ }
6123
+ return own(target, () => run(entry, path, req));
6124
+ }
5970
6125
  async function dispatchSyncFs(req) {
5971
6126
  const entry = resolveSyncFsToken(req.token);
5972
6127
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -5990,8 +6145,8 @@ async function dispatchSyncFs(req) {
5990
6145
  return await (entry.gate ? entry.gate(path, serve) : serve());
5991
6146
  }
5992
6147
  if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
5993
- const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
5994
- return await entry.own(target, () => run(entry, path, req));
6148
+ const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
6149
+ return await owned(entry, entry.own, req, path, target);
5995
6150
  } catch (err) {
5996
6151
  return toErrno(err);
5997
6152
  }
@@ -6593,6 +6748,10 @@ function normalize(path) {
6593
6748
  }
6594
6749
  return `/${out.join("/")}`;
6595
6750
  }
6751
+ var RING = 8;
6752
+ var RING_SIZE = 64;
6753
+ var SLOT = 3;
6754
+ var SHARED_IDS_BYTES = (RING + RING_SIZE * SLOT) * 4;
6596
6755
 
6597
6756
  // src/process/wasi/wasi-procfs.ts
6598
6757
  var DIR_MODE = 16749;
@@ -9282,6 +9441,7 @@ var VfsNode = class {
9282
9441
  fs;
9283
9442
  path;
9284
9443
  orphaned;
9444
+ id;
9285
9445
  constructor(fs, path, contents2, orphaned = false, dirty = false, pin) {
9286
9446
  this.fs = fs;
9287
9447
  this.pin = pin;
@@ -9451,6 +9611,7 @@ var VfsNode = class {
9451
9611
  var VfsNodes = class {
9452
9612
  byPath = /* @__PURE__ */ new Map();
9453
9613
  kept = /* @__PURE__ */ new Map();
9614
+ byId = /* @__PURE__ */ new Map();
9454
9615
  keepsOnDisk = false;
9455
9616
  fs;
9456
9617
  entries = /* @__PURE__ */ new Map();
@@ -9470,7 +9631,7 @@ var VfsNodes = class {
9470
9631
  return run2;
9471
9632
  }
9472
9633
  async entryKey(path) {
9473
- const parent = parentOf6(path);
9634
+ const parent = parentOf5(path);
9474
9635
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
9475
9636
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
9476
9637
  }
@@ -9495,21 +9656,66 @@ var VfsNodes = class {
9495
9656
  await this.fs.writeFile(path, new Uint8Array(0), owner);
9496
9657
  });
9497
9658
  }
9498
- open(path, pin, writable = false) {
9499
- let node = this.byPath.get(path);
9500
- if (!node) {
9501
- node = new VfsNode(this.fs, path, void 0, false, false, pin);
9502
- this.byPath.set(path, node);
9503
- }
9659
+ async identity(path) {
9660
+ const st = await this.fs.stat?.(path).catch(() => void 0);
9661
+ if (!st || st.ino === void 0 || (st.nlink ?? 1) < 2) return void 0;
9662
+ return `${st.dev ?? 0}:${st.ino}`;
9663
+ }
9664
+ async identify(path) {
9665
+ return this.byPath.has(path) || !mayLink(this.fs, path) ? void 0 : this.identity(path);
9666
+ }
9667
+ async linked(from, to) {
9668
+ const node = this.byPath.get(from);
9669
+ if (!node || node.orphaned) return;
9670
+ const id2 = node.id ?? await this.identity(to);
9671
+ if (id2 === void 0 || node.orphaned) return;
9672
+ this.know(node, id2);
9673
+ if (!this.byPath.has(to)) this.byPath.set(to, node);
9674
+ }
9675
+ know(node, id2) {
9676
+ node.id = id2;
9677
+ if (!this.byId.has(id2)) this.byId.set(id2, node);
9678
+ }
9679
+ namesOf(node) {
9680
+ const out = [];
9681
+ for (const [p, n] of this.byPath) if (n === node) out.push(p);
9682
+ return out;
9683
+ }
9684
+ forget(node) {
9685
+ for (const p of this.namesOf(node)) this.byPath.delete(p);
9686
+ if (node.id !== void 0 && this.byId.get(node.id) === node) this.byId.delete(node.id);
9687
+ }
9688
+ orphan(node) {
9689
+ node.orphaned = true;
9690
+ this.forget(node);
9691
+ }
9692
+ dropName(path, node) {
9693
+ const others = this.namesOf(node).filter((p) => p !== path);
9694
+ if (others.length === 0) return false;
9695
+ this.byPath.delete(path);
9696
+ if (node.path === path) node.renamedTo(others[0]);
9697
+ return true;
9698
+ }
9699
+ named(path, id2) {
9700
+ const node = this.byPath.get(path) ?? (id2 === void 0 ? void 0 : this.byId.get(id2));
9701
+ if (node && !this.byPath.has(path)) this.byPath.set(path, node);
9702
+ return node;
9703
+ }
9704
+ create(path, id2, node) {
9705
+ this.byPath.set(path, node);
9706
+ if (id2 !== void 0) this.know(node, id2);
9707
+ return node;
9708
+ }
9709
+ open(path, pin, writable = false, id2) {
9710
+ const node = this.named(path, id2) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
9504
9711
  node.opens++;
9505
9712
  if (writable) node.writers++;
9506
9713
  return node;
9507
9714
  }
9508
- adopt(path, contents2, dirty, writable = false) {
9509
- let node = this.byPath.get(path);
9715
+ adopt(path, contents2, dirty, writable = false, id2) {
9716
+ let node = this.named(path, id2);
9510
9717
  if (!node) {
9511
- node = new VfsNode(this.fs, path, contents2, false, dirty);
9512
- this.byPath.set(path, node);
9718
+ node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
9513
9719
  } else if (dirty) {
9514
9720
  node.replace(contents2);
9515
9721
  }
@@ -9533,7 +9739,7 @@ var VfsNodes = class {
9533
9739
  if (writable && node.writers === 0) this.onClosed?.(node.path);
9534
9740
  return;
9535
9741
  }
9536
- if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
9742
+ this.forget(node);
9537
9743
  this.kept.get(node)?.release();
9538
9744
  this.kept.delete(node);
9539
9745
  this.onClosed?.(node.path);
@@ -9549,7 +9755,7 @@ var VfsNodes = class {
9549
9755
  }
9550
9756
  keep(path, file) {
9551
9757
  const node = this.byPath.get(path);
9552
- if (!node) {
9758
+ if (!node || this.dropName(path, node)) {
9553
9759
  file.release();
9554
9760
  return;
9555
9761
  }
@@ -9578,33 +9784,32 @@ var VfsNodes = class {
9578
9784
  }
9579
9785
  }
9580
9786
  revoke(prefix) {
9581
- for (const [path, node] of this.byPath) {
9787
+ for (const [path, node] of [...this.byPath]) {
9582
9788
  if (!within5(path, prefix)) continue;
9583
9789
  node.revoke();
9584
- this.byPath.delete(path);
9790
+ this.forget(node);
9585
9791
  }
9586
9792
  }
9587
9793
  unlinked(path) {
9588
9794
  for (const [held, node] of [...this.byPath]) {
9589
- if (!within5(held, path)) continue;
9590
- node.orphaned = true;
9591
- this.byPath.delete(held);
9795
+ if (!within5(held, path) || this.byPath.get(held) !== node) continue;
9796
+ if (!this.dropName(held, node)) this.orphan(node);
9592
9797
  }
9593
9798
  }
9594
9799
  renamed(from, to) {
9595
9800
  if (from === to) return;
9596
9801
  const moved2 = [];
9597
- for (const [p, node] of this.byPath) {
9598
- if (within5(p, from)) moved2.push(node);
9599
- else if (within5(p, to)) {
9600
- node.orphaned = true;
9601
- this.byPath.delete(p);
9802
+ for (const [p, node] of [...this.byPath]) {
9803
+ if (within5(p, from)) moved2.push([p, node]);
9804
+ else if (within5(p, to) && this.byPath.get(p) === node && !this.dropName(p, node)) {
9805
+ this.orphan(node);
9602
9806
  }
9603
9807
  }
9604
- for (const node of moved2) {
9605
- this.byPath.delete(node.path);
9606
- node.renamedTo(to + node.path.slice(from.length));
9607
- this.byPath.set(node.path, node);
9808
+ for (const [p] of moved2) this.byPath.delete(p);
9809
+ for (const [p, node] of moved2) {
9810
+ const next2 = to + p.slice(from.length);
9811
+ if (node.path === p) node.renamedTo(next2);
9812
+ this.byPath.set(next2, node);
9608
9813
  }
9609
9814
  }
9610
9815
  };
@@ -9618,65 +9823,11 @@ async function present(fs, path, follow2 = false) {
9618
9823
  throw err;
9619
9824
  }
9620
9825
  }
9621
- var MAX_LINKS5 = 40;
9622
- function parentOf6(path) {
9623
- return path.slice(0, path.lastIndexOf("/")) || "/";
9624
- }
9625
- async function mustBeDir(fs, path) {
9626
- if (!fs.lstat) return;
9627
- const st = await fs.lstat(path);
9628
- if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
9629
- }
9630
- async function linkOf(fs, path) {
9631
- return fs.readlink?.(path).catch(() => void 0);
9632
- }
9633
- async function realDir(fs, dir, budget = { hops: 0 }) {
9634
- const queue = dir.split("/").filter(Boolean);
9635
- let at = "/";
9636
- while (queue.length > 0) {
9637
- const part = queue.shift();
9638
- if (part === ".") continue;
9639
- if (part === "..") {
9640
- at = parentOf6(at);
9641
- continue;
9642
- }
9643
- const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
9644
- const link2 = await linkOf(fs, next2);
9645
- if (link2 === void 0) {
9646
- if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
9647
- at = next2;
9648
- continue;
9649
- }
9650
- if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", dir);
9651
- budget.hops++;
9652
- queue.unshift(...link2.split("/").filter(Boolean));
9653
- if (link2.startsWith("/")) at = "/";
9654
- }
9655
- return at;
9656
- }
9657
9826
  function errCode(err) {
9658
9827
  return err?.code;
9659
9828
  }
9660
- function joinName(dir, name2) {
9661
- return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
9662
- }
9663
- async function realTarget(fs, path) {
9664
- const budget = { hops: 0 };
9665
- let dir = await realDir(fs, parentOf6(path), budget);
9666
- let name2 = path.slice(path.lastIndexOf("/") + 1);
9667
- for (; ; ) {
9668
- const link2 = await linkOf(fs, joinName(dir, name2));
9669
- if (link2 === void 0) break;
9670
- if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
9671
- budget.hops++;
9672
- const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
9673
- dir = await realDir(fs, parentOf6(target), budget);
9674
- name2 = target.slice(target.lastIndexOf("/") + 1);
9675
- }
9676
- return joinName(dir, name2);
9677
- }
9678
9829
  async function targetDir(fs, path) {
9679
- return parentOf6(await realTarget(fs, path).catch(() => path));
9830
+ return parentOf5(await realTarget(fs, path).catch(() => path));
9680
9831
  }
9681
9832
  async function refuseReadonly(fs, path, flags, opts = {}) {
9682
9833
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -9712,6 +9863,15 @@ function keepingOpen(fs, nodes) {
9712
9863
  writeFile: (path, content, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content, owner)),
9713
9864
  createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
9714
9865
  symlink: (target, path, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path, owner)),
9866
+ links: (path) => mayLink(fs, path),
9867
+ link: async (from, to, follow2) => {
9868
+ const source = fs.resolvePath("/", from);
9869
+ const at = await (follow2 ? nodes.targetKey(source) : nodes.entryKey(source));
9870
+ await nodes.flush(at);
9871
+ const key = await nodes.entryKey(fs.resolvePath("/", to));
9872
+ await nodes.onEntry(key, () => fs.link(from, to, follow2));
9873
+ await nodes.linked(at, key);
9874
+ },
9715
9875
  mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
9716
9876
  };
9717
9877
  }
@@ -9719,7 +9879,7 @@ function vfsFile(fs, opts, nodes) {
9719
9879
  const access = opts.flags & O_ACCMODE;
9720
9880
  const readable = access !== O_WRONLY;
9721
9881
  const writable = access === O_WRONLY || access === O_RDWR;
9722
- 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);
9882
+ 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);
9723
9883
  let openError;
9724
9884
  const atOpen = (op) => {
9725
9885
  void node.serial(op).catch((err) => {
@@ -10154,12 +10314,12 @@ var WasmProcess = class {
10154
10314
  } else if (req.create) {
10155
10315
  await nodes.onEntry(path, async () => {
10156
10316
  const fresh = this.options.owners && !await present(this.options.fs, path, true);
10157
- this.fds.installAt(fd, this.openVfsFile(open));
10317
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10158
10318
  await this.fds.get(fd).file.stat?.();
10159
10319
  if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
10160
10320
  });
10161
10321
  } else {
10162
- this.fds.installAt(fd, this.openVfsFile(open));
10322
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10163
10323
  }
10164
10324
  if (req.existing && !await present(this.options.fs, path, true)) {
10165
10325
  throw new KernelError("ENOENT");
@@ -10190,7 +10350,7 @@ var WasmProcess = class {
10190
10350
  );
10191
10351
  return memoryFile(tree.read(path), path, req.position);
10192
10352
  }
10193
- openVfsFile(req) {
10353
+ openVfsFile(req, id2) {
10194
10354
  return vfsFile(
10195
10355
  this.options.fs,
10196
10356
  {
@@ -10201,7 +10361,8 @@ var WasmProcess = class {
10201
10361
  ...req.orphan ? { orphan: true } : {},
10202
10362
  ...req.truncate ? { truncate: true } : {},
10203
10363
  ...req.create ? { create: true } : {},
10204
- ...req.pin ? { pin: req.pin } : {}
10364
+ ...req.pin ? { pin: req.pin } : {},
10365
+ ...id2 !== void 0 && !req.orphan ? { id: id2 } : {}
10205
10366
  },
10206
10367
  this.nodes
10207
10368
  );
@@ -10349,6 +10510,7 @@ var WasmProcess = class {
10349
10510
  }
10350
10511
  if (req.path === void 0) throw new KernelError("EINVAL");
10351
10512
  const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path), req.handle);
10513
+ const id2 = req.orphan ? void 0 : await this.nodes.identify(path);
10352
10514
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
10353
10515
  const file = vfsFile(
10354
10516
  this.options.fs,
@@ -10359,7 +10521,8 @@ var WasmProcess = class {
10359
10521
  ...req.contents !== void 0 ? { contents: req.contents } : {},
10360
10522
  ...req.orphan ? { orphan: true } : {},
10361
10523
  ...req.dirty ? { dirty: true } : {},
10362
- ...req.pin ? { pin: req.pin } : {}
10524
+ ...req.pin ? { pin: req.pin } : {},
10525
+ ...id2 !== void 0 ? { id: id2 } : {}
10363
10526
  },
10364
10527
  this.nodes
10365
10528
  );
@@ -11431,6 +11594,9 @@ async function dispatch(h, req) {
11431
11594
  return h.unlink(req.path);
11432
11595
  case "rename":
11433
11596
  return h.rename(req.from, req.to);
11597
+ case "link":
11598
+ if (!h.link) throw fsError2("EPERM", "this file system has no hard links");
11599
+ return h.link(req.from, req.to);
11434
11600
  case "symlink":
11435
11601
  if (!h.symlink) throw fsError2("EPERM", "this file system has no symlinks");
11436
11602
  return h.symlink(req.target, req.path);
@@ -12616,8 +12782,8 @@ var ChunkedBytes = class {
12616
12782
  }
12617
12783
  return out;
12618
12784
  }
12619
- write(offset, bytes, owned = false) {
12620
- const whole = owned && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
12785
+ write(offset, bytes, owned2 = false) {
12786
+ const whole = owned2 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
12621
12787
  for (let done4 = 0; done4 < bytes.length; ) {
12622
12788
  const at = offset + done4;
12623
12789
  const index = Math.floor(at / CHUNK3);
@@ -12659,12 +12825,17 @@ function tmpfs() {
12659
12825
  mtime: Date.now(),
12660
12826
  data: new ChunkedBytes(),
12661
12827
  target: "",
12662
- children: /* @__PURE__ */ new Map()
12828
+ children: /* @__PURE__ */ new Map(),
12829
+ links: 1
12663
12830
  });
12664
12831
  const root = make("directory", 16877);
12665
12832
  const handles = /* @__PURE__ */ new Map();
12666
12833
  let nextFh = 0;
12667
12834
  const parts = (path) => path.split("/").filter(Boolean);
12835
+ const place = (dir, name2, node) => {
12836
+ dir.children.set(name2, node);
12837
+ dir.mtime = Date.now();
12838
+ };
12668
12839
  function find(path) {
12669
12840
  let node = root;
12670
12841
  for (const name2 of parts(path)) {
@@ -12688,7 +12859,8 @@ function tmpfs() {
12688
12859
  size: node.kind === "file" ? node.data.size : node.kind === "symlink" ? node.target.length : 4096,
12689
12860
  mtime: node.mtime,
12690
12861
  mode: node.mode,
12691
- ino: node.ino
12862
+ ino: node.ino,
12863
+ ...node.kind === "directory" ? {} : { nlink: node.links }
12692
12864
  });
12693
12865
  const handle = (fh) => {
12694
12866
  const found = handles.get(fh);
@@ -12703,8 +12875,7 @@ function tmpfs() {
12703
12875
  if (existing) return existing;
12704
12876
  if (!flags.create) throw fsError2("ENOENT", path);
12705
12877
  const node = make("file", 33188);
12706
- dir.children.set(name2, node);
12707
- dir.mtime = Date.now();
12878
+ place(dir, name2, node);
12708
12879
  return node;
12709
12880
  }
12710
12881
  return {
@@ -12743,8 +12914,7 @@ function tmpfs() {
12743
12914
  async mkdir(path) {
12744
12915
  const { dir, name: name2 } = parent(path);
12745
12916
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
12746
- dir.children.set(name2, make("directory", 16877));
12747
- dir.mtime = Date.now();
12917
+ place(dir, name2, make("directory", 16877));
12748
12918
  },
12749
12919
  async rmdir(path) {
12750
12920
  const { dir, name: name2 } = parent(path);
@@ -12761,6 +12931,7 @@ function tmpfs() {
12761
12931
  if (!node) throw fsError2("ENOENT", path);
12762
12932
  if (node.kind === "directory") throw fsError2("EISDIR", path);
12763
12933
  dir.children.delete(name2);
12934
+ node.links--;
12764
12935
  dir.mtime = Date.now();
12765
12936
  },
12766
12937
  async rename(from, to) {
@@ -12777,17 +12948,25 @@ function tmpfs() {
12777
12948
  throw fsError2(node.kind === "directory" ? "ENOTEMPTY" : "EISDIR", to);
12778
12949
  }
12779
12950
  source.dir.children.delete(source.name);
12951
+ if (replaced) replaced.links--;
12780
12952
  target.dir.children.set(target.name, node);
12781
12953
  source.dir.mtime = Date.now();
12782
12954
  target.dir.mtime = source.dir.mtime;
12783
12955
  },
12956
+ async link(from, to) {
12957
+ const node = find(from);
12958
+ if (node.kind === "directory") throw fsError2("EPERM", `${from} is a directory`);
12959
+ const { dir, name: name2 } = parent(to);
12960
+ if (dir.children.has(name2)) throw fsError2("EEXIST", to);
12961
+ place(dir, name2, node);
12962
+ node.links++;
12963
+ },
12784
12964
  async symlink(target, path) {
12785
12965
  const { dir, name: name2 } = parent(path);
12786
12966
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
12787
12967
  const node = make("symlink", 41471);
12788
12968
  node.target = target;
12789
- dir.children.set(name2, node);
12790
- dir.mtime = Date.now();
12969
+ place(dir, name2, node);
12791
12970
  },
12792
12971
  async readlink(path) {
12793
12972
  const node = find(path);
@@ -13687,7 +13866,7 @@ var Launcher = class {
13687
13866
  serveFilesystem(
13688
13867
  port2,
13689
13868
  tmpfs(),
13690
- { symlinks: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
13869
+ { symlinks: true, links: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
13691
13870
  { owned: true }
13692
13871
  );
13693
13872
  return {