@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.
@@ -600,11 +600,54 @@ function toMs(v) {
600
600
  if (v === void 0 || v === null) return void 0;
601
601
  return v instanceof Date ? v.getTime() : v;
602
602
  }
603
+ function linkedStates(bridgeOf) {
604
+ const opens = /* @__PURE__ */ new Map();
605
+ const byId = /* @__PURE__ */ new Map();
606
+ const idOf2 = (st) => st?.ino === void 0 || (st.nlink ?? 1) < 2 ? void 0 : `${st.dev}:${st.ino}`;
607
+ function know(s, id) {
608
+ s.id = id;
609
+ if (!byId.has(id)) byId.set(id, s);
610
+ }
611
+ function opening(node, on) {
612
+ const s = node.live;
613
+ if (!on) {
614
+ opens.delete(s);
615
+ if (s.id !== void 0 && byId.get(s.id) === s) byId.delete(s.id);
616
+ s.id = void 0;
617
+ return;
618
+ }
619
+ opens.set(s, node);
620
+ const id = idOf2(s.stat);
621
+ if (id !== void 0) know(s, id);
622
+ }
623
+ function sharedWith(node) {
624
+ const st = node.live.stat;
625
+ if (st?.ino === void 0) return void 0;
626
+ const id = `${st.dev}:${st.ino}`;
627
+ if (!byId.has(id) && idOf2(st) !== void 0) {
628
+ for (const [s2, other] of opens) {
629
+ if (s2.id !== void 0 || s2.orphan) continue;
630
+ let st2;
631
+ try {
632
+ st2 = bridgeOf(other).lstat(liveNodePath(other));
633
+ } catch {
634
+ }
635
+ const known = idOf2(st2);
636
+ if (known !== void 0) know(s2, known);
637
+ }
638
+ }
639
+ const s = byId.get(id);
640
+ return s && s !== node.live && !stale(s) ? s : void 0;
641
+ }
642
+ const holder = (node) => opens.get(node.live) ?? node;
643
+ return { opening, sharedWith, know, idOf: idOf2, holder };
644
+ }
603
645
  function createHelpers(Fs, ops) {
604
646
  const bridgeOf = (node) => node.mount.opts.bridge;
605
647
  const fileIo = (node) => pinnedIo(bridgeOf(node), node.live.handle);
606
648
  const dirty = /* @__PURE__ */ new Set();
607
649
  const handled = /* @__PURE__ */ new Set();
650
+ const linked = linkedStates(bridgeOf);
608
651
  function markDirty(node) {
609
652
  node.live.dirty = true;
610
653
  dirty.add(node);
@@ -612,7 +655,7 @@ function createHelpers(Fs, ops) {
612
655
  function flushDirty() {
613
656
  for (const node of [...dirty]) {
614
657
  const s = node.live;
615
- if (!s.dirty || s.orphan) {
658
+ if (!s.dirty || stale(s)) {
616
659
  dirty.delete(node);
617
660
  continue;
618
661
  }
@@ -654,7 +697,7 @@ function createHelpers(Fs, ops) {
654
697
  return { ...node.live.stat, size };
655
698
  }
656
699
  if (!node.live.stat) {
657
- const st = call2(() => bridgeOf(node).lstat(liveNodePath(node)));
700
+ const st = call2(() => bridgeOf(node).lstat(liveNodePath(linked.holder(node))));
658
701
  node.live.stat = st;
659
702
  node.mode = modeFromStat(st);
660
703
  }
@@ -667,7 +710,7 @@ function createHelpers(Fs, ops) {
667
710
  function ensureLoaded(node) {
668
711
  const s = node.live;
669
712
  if (s.loaded) return;
670
- const bytes = call2(() => fileIo(node).readFile(liveNodePath(node)));
713
+ const bytes = call2(() => fileIo(node).readFile(liveNodePath(linked.holder(node))));
671
714
  s.data = bytes;
672
715
  s.len = bytes.length;
673
716
  s.loaded = true;
@@ -676,7 +719,7 @@ function createHelpers(Fs, ops) {
676
719
  const s = node.live;
677
720
  if (s.ranged || !s.ranges) return s.ranged;
678
721
  const bridge = bridgeOf(node);
679
- const path = () => liveNodePath(node);
722
+ const path = () => liveNodePath(linked.holder(node));
680
723
  const file = () => fileIo(node);
681
724
  const io = {
682
725
  pread: (_, at, n, version) => file().pread(path(), at, n, version),
@@ -698,11 +741,11 @@ function createHelpers(Fs, ops) {
698
741
  }
699
742
  function flushNode(node) {
700
743
  const s = node.live;
701
- if (!s.dirty || s.orphan || !(s.data || s.ranged)) return;
744
+ if (!s.dirty || stale(s) || !(s.data || s.ranged)) return;
702
745
  if (s.ranged) call2(() => s.ranged?.flush());
703
746
  else {
704
747
  const bytes = s.data.slice(0, s.len);
705
- call2(() => fileIo(node).writeFile(liveNodePath(node), bytes, true));
748
+ call2(() => fileIo(node).writeFile(liveNodePath(linked.holder(node)), bytes, true));
706
749
  }
707
750
  s.dirty = false;
708
751
  s.stat = void 0;
@@ -750,6 +793,7 @@ function createHelpers(Fs, ops) {
750
793
  Fs,
751
794
  bridgeOf,
752
795
  handled,
796
+ linked,
753
797
  call: call2,
754
798
  metadataCall,
755
799
  makeNode,
@@ -764,6 +808,33 @@ function createHelpers(Fs, ops) {
764
808
  truncate
765
809
  };
766
810
  }
811
+ function freshStat(h, node) {
812
+ const { statOf } = h;
813
+ const st = statOf(node);
814
+ if (!st.linkable) return st;
815
+ node.live.stat = void 0;
816
+ return statOf(node);
817
+ }
818
+ function orphaned(h, node, st) {
819
+ const { bridgeOf } = h;
820
+ const s = node.live;
821
+ const nlink = st.nlink ?? 1;
822
+ let kept = false;
823
+ try {
824
+ kept = nlink > 1 && s.handle !== void 0 && bridgeOf(node).kept?.(s.handle) === true;
825
+ } catch {
826
+ }
827
+ if (nlink > 1 && !kept && h.linked.holder(node) !== node) {
828
+ s.stat = void 0;
829
+ return;
830
+ }
831
+ s.orphan = true;
832
+ if (!kept) return;
833
+ s.kept = true;
834
+ s.stat = { ...st, nlink: nlink - 1 };
835
+ const id = h.linked.idOf(st);
836
+ if (id !== void 0) h.linked.know(s, id);
837
+ }
767
838
  function createNodeOps(h) {
768
839
  const { Fs, bridgeOf, call: call2, metadataCall, makeNode, statOf, childPath, flushNode, truncate } = h;
769
840
  const { ensureLoaded } = h;
@@ -794,7 +865,7 @@ function createNodeOps(h) {
794
865
  dev: st.dev ?? 1,
795
866
  ino: st.ino ?? inodeOf(liveNodePath(node)),
796
867
  mode: node.mode,
797
- nlink: 1,
868
+ nlink: st.nlink ?? 1,
798
869
  uid: owned ? st.uid ?? 0 : 0,
799
870
  gid: owned ? st.gid ?? 0 : 0,
800
871
  rdev: 0,
@@ -889,14 +960,14 @@ function createNodeOps(h) {
889
960
  if (node && node.live?.openCount > 0 && Fs.isFile(node.mode)) open = node;
890
961
  } catch {
891
962
  }
963
+ const st = open && freshStat(h, open);
892
964
  if (open) {
893
- statOf(open);
894
965
  const ranged = h.rangedOf(open);
895
966
  if (ranged) ranged.pin();
896
967
  else ensureLoaded(open);
897
968
  }
898
969
  call2(() => bridgeOf(parent).unlink(childPath(parent, name2)));
899
- if (open) open.live.orphan = true;
970
+ if (open && st) orphaned(h, open, st);
900
971
  },
901
972
  rmdir(parent, name2) {
902
973
  parent.live.listed = void 0;
@@ -944,6 +1015,7 @@ function createStreamOps(h) {
944
1015
  try {
945
1016
  const handle = bridgeOf(node).hold?.(liveNodePath(node), on, true, s.handle);
946
1017
  s.handle = on ? handle : void 0;
1018
+ h.linked.opening(node, on);
947
1019
  if (s.handle === void 0) h.handled.delete(node);
948
1020
  else h.handled.add(node);
949
1021
  } catch {
@@ -956,7 +1028,9 @@ function createStreamOps(h) {
956
1028
  if (s.openCount === 0 && !s.orphan) {
957
1029
  const bridge = bridgeOf(stream.node);
958
1030
  s.ranges = !!(bridge.pread && bridge.pwrite && bridge.truncate && statOf(stream.node).ranged);
959
- rangedOf(stream.node);
1031
+ const shared = h.linked.sharedWith(stream.node);
1032
+ if (shared) stream.node.live = shared;
1033
+ else rangedOf(stream.node);
960
1034
  }
961
1035
  if ((stream.flags & 3) !== 0) {
962
1036
  try {
@@ -1278,7 +1352,7 @@ function useLiveChown(Fs) {
1278
1352
  Fs.chown(target, uid, gid);
1279
1353
  };
1280
1354
  }
1281
- var S_IFDIR, S_IFREG, S_IFLNK, PERM_MASK, DEFAULT_DIR_PERM, UMASK, SEEK_CUR, SEEK_END, EARLY_WHOLE_FILE;
1355
+ var S_IFDIR, S_IFREG, S_IFLNK, PERM_MASK, DEFAULT_DIR_PERM, UMASK, SEEK_CUR, SEEK_END, EARLY_WHOLE_FILE, stale;
1282
1356
  var init_live_vfs_fs = __esm({
1283
1357
  "src/realm/live-vfs-fs.ts"() {
1284
1358
  "use strict";
@@ -1294,6 +1368,7 @@ var init_live_vfs_fs = __esm({
1294
1368
  SEEK_CUR = 1;
1295
1369
  SEEK_END = 2;
1296
1370
  EARLY_WHOLE_FILE = 64 * 1024;
1371
+ stale = (s) => !!s.orphan && !s.kept;
1297
1372
  }
1298
1373
  });
1299
1374
 
@@ -1369,10 +1444,12 @@ function parseSyncFsStat(json) {
1369
1444
  ...typeof s.atimeMs === "number" ? { atimeMs: s.atimeMs } : {},
1370
1445
  ...typeof s.ctimeMs === "number" ? { ctimeMs: s.ctimeMs } : {},
1371
1446
  ...typeof s.ino === "number" ? { ino: s.ino } : {},
1447
+ ...typeof s.nlink === "number" ? { nlink: s.nlink } : {},
1372
1448
  ...typeof s.dev === "number" ? { dev: s.dev } : {},
1373
1449
  ...s.readonly === true ? { readonly: true } : {},
1374
1450
  ...typeof s.maxFile === "number" ? { maxFile: s.maxFile } : {},
1375
1451
  ...s.ranged === true ? { ranged: true } : {},
1452
+ ...s.linkable === true ? { linkable: true } : {},
1376
1453
  ...typeof s.version === "string" ? { version: s.version } : {},
1377
1454
  ...typeof s.uid === "number" ? { uid: s.uid } : {},
1378
1455
  ...typeof s.gid === "number" ? { gid: s.gid } : {}
@@ -1393,6 +1470,7 @@ var init_sync_fs_wire = __esm({
1393
1470
  "mkdir",
1394
1471
  "rm",
1395
1472
  "rename",
1473
+ "link",
1396
1474
  "unlink",
1397
1475
  "rmdir",
1398
1476
  "symlink",
@@ -1639,6 +1717,9 @@ function createSyncFsSabBridge(transport, opts = {}) {
1639
1717
  rename: (from, to) => {
1640
1718
  run({ op: "rename", path: from, arg2: to }, from);
1641
1719
  },
1720
+ link: (from, to, follow2) => {
1721
+ run({ op: "link", path: from, arg2: to, ...follow2 ? { mode: 1 } : {} }, from);
1722
+ },
1642
1723
  unlink: (path) => {
1643
1724
  run({ op: "unlink", path }, path);
1644
1725
  },
@@ -4555,10 +4636,26 @@ function pathInode(path) {
4555
4636
  }
4556
4637
  return h;
4557
4638
  }
4558
- function cachingBridge(bridge, now = () => performance.now()) {
4559
- const stats = /* @__PURE__ */ new Map();
4560
- const lstats = /* @__PURE__ */ new Map();
4639
+ function inoKey(st) {
4640
+ return "ino" in st && typeof st.ino === "number" ? st.ino % 4294967296 | 0 : 0;
4641
+ }
4642
+ function pathHash(path) {
4643
+ let h = 2166136261;
4644
+ for (let i = 0; i < path.length; i++) h = Math.imul(h ^ path.charCodeAt(i), 16777619);
4645
+ return h === 0 ? 1 : h;
4646
+ }
4647
+ function index(map, key, path) {
4648
+ const paths = map.get(key) ?? /* @__PURE__ */ new Set();
4649
+ paths.add(path);
4650
+ map.set(key, paths);
4651
+ }
4652
+ function cachingBridge(bridge, now = () => performance.now(), shared) {
4653
+ const blind = shared !== void 0 && shared.length * 4 < SHARED_IDS_BYTES;
4654
+ const cache = new StatCache(blind ? void 0 : shared);
4655
+ const { stats, lstats } = cache;
4561
4656
  const cached = (map, path, get) => {
4657
+ if (blind) return get();
4658
+ cache.fresh();
4562
4659
  let hit = map.get(path);
4563
4660
  if (hit === void 0 || "until" in hit && hit.until <= now()) {
4564
4661
  try {
@@ -4566,22 +4663,29 @@ function cachingBridge(bridge, now = () => performance.now()) {
4566
4663
  } catch (e) {
4567
4664
  hit = { error: e, until: now() + MISS_TTL_MS };
4568
4665
  }
4569
- map.set(path, hit);
4666
+ cache.put(map, path, hit);
4570
4667
  }
4571
4668
  if ("until" in hit) throw hit.error;
4572
4669
  return hit;
4573
4670
  };
4574
- const invalidate = () => {
4575
- stats.clear();
4576
- lstats.clear();
4671
+ const invalidate = () => cache.mutated([]);
4672
+ const touched = (path, ino) => cache.changed(path, ino);
4673
+ const mutating = (op, ...paths) => {
4674
+ try {
4675
+ return op();
4676
+ } finally {
4677
+ cache.mutated(paths);
4678
+ }
4577
4679
  };
4578
- const mutating = (op) => {
4680
+ const rewriting = (p, op, ...also) => {
4681
+ const ino = cache.inoOf(p, () => bridge.stat(p));
4579
4682
  try {
4580
4683
  return op();
4581
4684
  } finally {
4582
- invalidate();
4685
+ cache.mutated([p, ...also], ino);
4583
4686
  }
4584
4687
  };
4688
+ const entry = (p) => [p, p.slice(0, p.lastIndexOf("/")) || "/"];
4585
4689
  return {
4586
4690
  readFile: (p) => bridge.readFile(p),
4587
4691
  readdir: (p) => bridge.readdir(p),
@@ -4589,9 +4693,9 @@ function cachingBridge(bridge, now = () => performance.now()) {
4589
4693
  const list = bridge.readdirStat(p);
4590
4694
  const base = p === "/" ? "" : p;
4591
4695
  for (const [name2, st] of list) {
4592
- if (!st) continue;
4593
- lstats.set(`${base}/${name2}`, st);
4594
- if (!st.isSymbolicLink) stats.set(`${base}/${name2}`, st);
4696
+ if (!st || blind) continue;
4697
+ cache.put(lstats, `${base}/${name2}`, st);
4698
+ if (!st.isSymbolicLink) cache.put(stats, `${base}/${name2}`, st);
4595
4699
  }
4596
4700
  return list;
4597
4701
  },
@@ -4606,40 +4710,42 @@ function cachingBridge(bridge, now = () => performance.now()) {
4606
4710
  return false;
4607
4711
  }
4608
4712
  },
4609
- writeFile: (p, bytes, open) => mutating(() => bridge.writeFile(p, bytes, open)),
4610
- mkdir: (p) => mutating(() => bridge.mkdir(p)),
4713
+ writeFile: (p, bytes, open) => rewriting(p, () => bridge.writeFile(p, bytes, open), ...entry(p).slice(1)),
4714
+ mkdir: (p) => mutating(() => bridge.mkdir(p), ...entry(p)),
4611
4715
  rm: (p) => mutating(() => bridge.rm(p)),
4612
4716
  rename: (from, to) => mutating(() => bridge.rename(from, to)),
4717
+ link: (from, to, follow2) => mutating(() => bridge.link(from, to, follow2)),
4613
4718
  unlink: (p) => mutating(() => bridge.unlink(p)),
4614
4719
  rmdir: (p) => mutating(() => bridge.rmdir(p)),
4615
- symlink: (target, link) => mutating(() => bridge.symlink(target, link)),
4616
- chmod: (p, mode) => mutating(() => bridge.chmod(p, mode)),
4617
- utimes: (p, a, m) => mutating(() => bridge.utimes(p, a, m)),
4618
- lutimes: (p, a, m) => mutating(() => bridge.lutimes(p, a, m)),
4720
+ symlink: (target, link) => mutating(() => bridge.symlink(target, link), ...entry(link)),
4721
+ chmod: (p, mode) => rewriting(p, () => bridge.chmod(p, mode)),
4722
+ utimes: (p, a, m) => rewriting(p, () => bridge.utimes(p, a, m)),
4723
+ lutimes: (p, a, m) => mutating(() => bridge.lutimes(p, a, m), p),
4619
4724
  ...bridge.hold ? { hold: bridge.hold.bind(bridge) } : {},
4620
4725
  ...bridge.kept ? { kept: bridge.kept.bind(bridge) } : {},
4621
- ...bridge.pinned ? { pinned: (handle) => pinnedIo2(bridge, handle, mutating) } : {},
4622
- ...bridge.create ? { create: (p) => mutating(() => bridge.create?.(p)) } : {},
4726
+ ...bridge.pinned ? { pinned: (handle) => pinnedIo2(bridge, handle, rewriting) } : {},
4727
+ ...bridge.create ? { create: (p) => mutating(() => bridge.create?.(p), ...entry(p)) } : {},
4623
4728
  ...bridge.pread && bridge.pwrite && bridge.truncate ? {
4624
4729
  pread: bridge.pread.bind(bridge),
4625
- pwrite: (p, at, bytes, transfer) => mutating(() => bridge.pwrite?.(p, at, bytes, transfer)),
4626
- truncate: (p, size) => mutating(() => bridge.truncate?.(p, size))
4730
+ pwrite: (p, at, bytes, transfer) => rewriting(p, () => bridge.pwrite?.(p, at, bytes, transfer)),
4731
+ truncate: (p, size) => rewriting(p, () => bridge.truncate?.(p, size))
4627
4732
  } : {},
4628
- invalidate
4733
+ invalidate,
4734
+ touched
4629
4735
  };
4630
4736
  }
4631
- function pinnedIo2(bridge, handle, mutating) {
4737
+ function pinnedIo2(bridge, handle, rewriting) {
4632
4738
  const io = bridge.pinned(handle);
4633
4739
  const { pread, pwrite, truncate } = io;
4634
4740
  return {
4635
4741
  readFile: (p) => io.readFile(p),
4636
- writeFile: (p, bytes, open) => mutating(() => io.writeFile(p, bytes, open)),
4742
+ writeFile: (p, bytes, open) => rewriting(p, () => io.writeFile(p, bytes, open)),
4637
4743
  pread: (p, at, n, version) => pread(p, at, n, version),
4638
- pwrite: (p, at, bytes, transfer) => mutating(() => pwrite(p, at, bytes, transfer)),
4639
- truncate: (p, size) => mutating(() => truncate(p, size))
4744
+ pwrite: (p, at, bytes, transfer) => rewriting(p, () => pwrite(p, at, bytes, transfer)),
4745
+ truncate: (p, size) => rewriting(p, () => truncate(p, size))
4640
4746
  };
4641
4747
  }
4642
- var WasiError, FileBuffer, LocalFile, MAX_LINKS, MISS_TTL_MS;
4748
+ var WasiError, FileBuffer, LocalFile, MAX_LINKS, MISS_TTL_MS, FS_GEN, RING, RING_SIZE, SLOT, SHARED_IDS_BYTES, StatCache;
4643
4749
  var init_wasi_files = __esm({
4644
4750
  "src/process/wasi/wasi-files.ts"() {
4645
4751
  "use strict";
@@ -4829,6 +4935,110 @@ var init_wasi_files = __esm({
4829
4935
  };
4830
4936
  MAX_LINKS = 40;
4831
4937
  MISS_TTL_MS = 20;
4938
+ FS_GEN = 4;
4939
+ RING = 8;
4940
+ RING_SIZE = 64;
4941
+ SLOT = 3;
4942
+ SHARED_IDS_BYTES = (RING + RING_SIZE * SLOT) * 4;
4943
+ StatCache = class {
4944
+ stats = /* @__PURE__ */ new Map();
4945
+ lstats = /* @__PURE__ */ new Map();
4946
+ byHash = /* @__PURE__ */ new Map();
4947
+ byIno = /* @__PURE__ */ new Map();
4948
+ shared;
4949
+ seen;
4950
+ constructor(shared) {
4951
+ this.shared = shared;
4952
+ this.seen = shared ? Atomics.load(shared, FS_GEN) : 0;
4953
+ }
4954
+ put(map, path, entry) {
4955
+ map.set(path, entry);
4956
+ if (!this.shared) return;
4957
+ index(this.byHash, pathHash(path), path);
4958
+ const ino = inoKey(entry);
4959
+ if (ino !== 0) index(this.byIno, ino, path);
4960
+ }
4961
+ fresh() {
4962
+ if (!this.shared) return;
4963
+ const now = Atomics.load(this.shared, FS_GEN);
4964
+ if (now === this.seen) return;
4965
+ this.replay(now);
4966
+ this.seen = now;
4967
+ }
4968
+ changed(path, ino = 0) {
4969
+ const key = ino % 4294967296 | 0;
4970
+ if (this.shared) {
4971
+ const g = this.publish(this.shared, path === void 0 ? 0 : pathHash(path), key);
4972
+ this.replay(g - 1);
4973
+ this.seen = g;
4974
+ if (path === void 0) this.clear();
4975
+ else this.drop(pathHash(path), key);
4976
+ }
4977
+ if (path === void 0) return;
4978
+ this.stats.delete(path);
4979
+ this.lstats.delete(path);
4980
+ }
4981
+ mutated(paths, ino = 0) {
4982
+ this.clear();
4983
+ if (!this.shared) return;
4984
+ const key = ino % 4294967296 | 0;
4985
+ for (const [i, hash] of (paths.length > 0 ? paths.map(pathHash) : [0]).entries()) {
4986
+ this.seen = this.publish(this.shared, hash, i === 0 ? key : 0);
4987
+ }
4988
+ }
4989
+ inoOf(path, look) {
4990
+ if (!this.shared) return 0;
4991
+ this.fresh();
4992
+ const hit = this.stats.get(path);
4993
+ if (hit && "ino" in hit && typeof hit.ino === "number") return hit.ino;
4994
+ try {
4995
+ return look().ino ?? 0;
4996
+ } catch {
4997
+ return 0;
4998
+ }
4999
+ }
5000
+ publish(shared, hash, ino) {
5001
+ const g = Atomics.add(shared, FS_GEN, 1) + 1;
5002
+ const at = RING + g % RING_SIZE * SLOT;
5003
+ Atomics.store(shared, at, 0);
5004
+ Atomics.store(shared, at + 1, hash);
5005
+ Atomics.store(shared, at + 2, ino);
5006
+ Atomics.store(shared, at, g);
5007
+ return g;
5008
+ }
5009
+ replay(upTo) {
5010
+ const shared = this.shared;
5011
+ const missed = [];
5012
+ for (let g = this.seen + 1; g <= upTo && upTo - this.seen <= RING_SIZE; g++) {
5013
+ const at = RING + g % RING_SIZE * SLOT;
5014
+ const tag = Atomics.load(shared, at);
5015
+ const hash = Atomics.load(shared, at + 1);
5016
+ const ino = Atomics.load(shared, at + 2);
5017
+ if (tag !== g || hash === 0 || Atomics.load(shared, at) !== g) break;
5018
+ missed.push([hash, ino]);
5019
+ }
5020
+ if (missed.length < upTo - this.seen) this.clear();
5021
+ else for (const [hash, ino] of missed) this.drop(hash, ino);
5022
+ }
5023
+ drop(hash, ino) {
5024
+ const paths = [
5025
+ ...this.byHash.get(hash) ?? [],
5026
+ ...ino === 0 ? [] : this.byIno.get(ino) ?? []
5027
+ ];
5028
+ for (const path of paths) {
5029
+ this.stats.delete(path);
5030
+ this.lstats.delete(path);
5031
+ }
5032
+ this.byHash.delete(hash);
5033
+ if (ino !== 0) this.byIno.delete(ino);
5034
+ }
5035
+ clear() {
5036
+ this.stats.clear();
5037
+ this.lstats.clear();
5038
+ this.byHash.clear();
5039
+ this.byIno.clear();
5040
+ }
5041
+ };
4832
5042
  }
4833
5043
  });
4834
5044
 
@@ -4923,6 +5133,8 @@ var init_wasi_fds = __esm({
4923
5133
  WasiFds = class {
4924
5134
  table = /* @__PURE__ */ new Map();
4925
5135
  buffers = /* @__PURE__ */ new Map();
5136
+ byId = /* @__PURE__ */ new Map();
5137
+ ids = /* @__PURE__ */ new Map();
4926
5138
  kernel;
4927
5139
  fs;
4928
5140
  constructor(kernel, fs) {
@@ -5161,11 +5373,18 @@ var init_wasi_fds = __esm({
5161
5373
  handed.add(e.file.buffer);
5162
5374
  promoted.set(e.file, fd);
5163
5375
  }
5164
- this.table.set(fd, { type: "kernel", kind: "file", nonblock: false, append: e.file.append });
5376
+ this.table.set(fd, {
5377
+ type: "kernel",
5378
+ kind: "file",
5379
+ nonblock: false,
5380
+ append: e.file.append,
5381
+ path: e.file.path,
5382
+ ino: this.inoOf(e.file.path)
5383
+ });
5165
5384
  }
5166
5385
  for (const file of promoted.keys()) {
5167
5386
  if (--file.buffer.opens <= 0) {
5168
- this.buffers.delete(file.path);
5387
+ this.forget(file.buffer);
5169
5388
  this.letGo(file.buffer);
5170
5389
  }
5171
5390
  }
@@ -5236,9 +5455,48 @@ var init_wasi_fds = __esm({
5236
5455
  const { buffer } = e.file;
5237
5456
  buffer.flush();
5238
5457
  if (--buffer.opens > 0) return;
5239
- if (this.buffers.get(buffer.path) === buffer) this.buffers.delete(buffer.path);
5458
+ this.forget(buffer);
5240
5459
  this.letGo(buffer);
5241
5460
  }
5461
+ forget(buffer) {
5462
+ for (const [p, b] of [...this.buffers]) if (b === buffer) this.buffers.delete(p);
5463
+ const id = this.ids.get(buffer);
5464
+ if (id !== void 0 && this.byId.get(id) === buffer) this.byId.delete(id);
5465
+ this.ids.delete(buffer);
5466
+ }
5467
+ know(buffer, id) {
5468
+ if (this.ids.has(buffer)) return;
5469
+ this.ids.set(buffer, id);
5470
+ if (!this.byId.has(id)) this.byId.set(id, buffer);
5471
+ }
5472
+ identity(st) {
5473
+ if (!st || st.ino === void 0 || (st.nlink ?? 1) < 2) return void 0;
5474
+ return `${st.dev ?? 0}:${st.ino}`;
5475
+ }
5476
+ sharing(path, st) {
5477
+ if (st?.ino === void 0) return void 0;
5478
+ const id = `${st.dev ?? 0}:${st.ino}`;
5479
+ if (!this.byId.has(id) && this.identity(st) !== void 0) this.learn();
5480
+ const buffer = this.byId.get(id);
5481
+ if (!buffer) return void 0;
5482
+ this.buffers.set(path, buffer);
5483
+ return buffer;
5484
+ }
5485
+ learn() {
5486
+ for (const [p, buffer] of this.buffers) {
5487
+ if (this.ids.has(buffer) || buffer.isOrphan()) continue;
5488
+ const known = this.identity(this.statOrMissing(p));
5489
+ if (known !== void 0) this.know(buffer, known);
5490
+ }
5491
+ }
5492
+ linked(from, to) {
5493
+ const buffer = this.buffers.get(from);
5494
+ if (!buffer || buffer.isOrphan()) return;
5495
+ const id = this.identity(this.statOrMissing(to));
5496
+ if (id === void 0) return;
5497
+ this.know(buffer, id);
5498
+ if (!this.buffers.has(to)) this.buffers.set(to, buffer);
5499
+ }
5242
5500
  letGo(buffer) {
5243
5501
  this.fs.hold?.(buffer.path, false, true, buffer.handle);
5244
5502
  }
@@ -5264,32 +5522,63 @@ var init_wasi_fds = __esm({
5264
5522
  for (const [p, buffer] of this.buffers) if (within(p, path)) buffer.flush();
5265
5523
  if (this.shared) this.kernel.call({ op: "fd-path-flush", path });
5266
5524
  }
5267
- unlinking(path) {
5268
- this.buffers.get(path)?.keep();
5525
+ holdsUnder(path) {
5526
+ for (const p of this.buffers.keys()) if (within(p, path)) return true;
5527
+ return false;
5528
+ }
5529
+ unlinking(path, linked) {
5530
+ const buffer = this.buffers.get(path);
5531
+ if (buffer && this.survives(buffer, linked)) buffer.flush();
5532
+ else buffer?.keep();
5269
5533
  if (this.shared) this.kernel.call({ op: "fd-path-unlinking", path });
5270
5534
  }
5271
- unlinked(path) {
5535
+ unlinked(path, linked) {
5272
5536
  if (this.shared) this.kernel.call({ op: "fd-path-unlinked", path });
5273
5537
  const buffer = this.buffers.get(path);
5274
5538
  if (!buffer) return;
5275
- buffer.orphan();
5539
+ this.detach(buffer, linked);
5276
5540
  this.buffers.delete(path);
5277
5541
  }
5278
- renamed(from, to) {
5542
+ inoOf(path) {
5543
+ try {
5544
+ return this.fs.stat(path).ino;
5545
+ } catch {
5546
+ return void 0;
5547
+ }
5548
+ }
5549
+ survives(buffer, linked) {
5550
+ return linked !== void 0 && buffer.handle !== void 0;
5551
+ }
5552
+ detach(buffer, linked) {
5553
+ if (this.survives(buffer, linked)) {
5554
+ const id = this.identity(linked);
5555
+ if (id !== void 0) this.know(buffer, id);
5556
+ return;
5557
+ }
5558
+ buffer.orphan();
5559
+ this.forget(buffer);
5560
+ }
5561
+ renamed(from, to, replacedLinked) {
5279
5562
  if (from === to) return;
5280
5563
  if (this.shared) this.kernel.call({ op: "fd-path-renamed", from, to });
5564
+ for (const e of this.table.values()) {
5565
+ if (e.type === "kernel" && e.path !== void 0 && within(e.path, from)) {
5566
+ e.path = to + e.path.slice(from.length);
5567
+ }
5568
+ }
5281
5569
  const moved = [];
5282
5570
  for (const [p, buffer] of this.buffers) {
5283
5571
  if (within(p, from)) moved.push([p, buffer]);
5284
5572
  else if (within(p, to)) {
5285
- buffer.orphan();
5573
+ this.detach(buffer, replacedLinked);
5286
5574
  this.buffers.delete(p);
5287
5575
  }
5288
5576
  }
5577
+ for (const [p] of moved) this.buffers.delete(p);
5289
5578
  for (const [p, buffer] of moved) {
5290
- this.buffers.delete(p);
5291
- buffer.path = to + p.slice(from.length);
5292
- this.buffers.set(buffer.path, buffer);
5579
+ const next2 = to + p.slice(from.length);
5580
+ if (buffer.path === p) buffer.path = next2;
5581
+ this.buffers.set(next2, buffer);
5293
5582
  }
5294
5583
  }
5295
5584
  resolve(dirfd, path) {
@@ -5332,6 +5621,7 @@ var init_wasi_fds = __esm({
5332
5621
  this.buffers.delete(path);
5333
5622
  buffer = void 0;
5334
5623
  }
5624
+ buffer ??= this.sharing(path, existing);
5335
5625
  if (buffer) {
5336
5626
  if (oflags & OFLAGS.TRUNC) buffer.truncate(0);
5337
5627
  } else {
@@ -5345,6 +5635,8 @@ var init_wasi_fds = __esm({
5345
5635
  buffer = new FileBuffer(io, path, empty, st.maxFile, ranged, st.version);
5346
5636
  buffer.handle = handle;
5347
5637
  this.buffers.set(path, buffer);
5638
+ const id = this.identity(st);
5639
+ if (id !== void 0) this.know(buffer, id);
5348
5640
  }
5349
5641
  buffer.opens++;
5350
5642
  return new LocalFile(buffer, readable2, writable2, (fdflags & FDFLAGS.APPEND) !== 0);
@@ -5356,7 +5648,10 @@ var init_wasi_fds = __esm({
5356
5648
  const made = !existing && this.createNew(path, oflags);
5357
5649
  const truncate = made || (oflags & OFLAGS.TRUNC) !== 0;
5358
5650
  const fd = this.kernel.sys.openVfs(path, flags, 0, truncate ? { truncate } : {});
5359
- this.table.set(fd, { type: "kernel", kind: "file", nonblock: false, append });
5651
+ const was = existing && this.inoOf(path);
5652
+ if (truncate) this.fs.touched?.(path, was);
5653
+ const ino = existing ? was : this.inoOf(path);
5654
+ this.table.set(fd, { type: "kernel", kind: "file", nonblock: false, append, path, ino });
5360
5655
  if (append) this.publishFlags(fd, { nonblock: false, append });
5361
5656
  this.bump();
5362
5657
  return fd;
@@ -5675,6 +5970,16 @@ var init_wasi_termios = __esm({
5675
5970
  });
5676
5971
 
5677
5972
  // src/process/wasi/wasi-host.ts
5973
+ function linkedOf(st) {
5974
+ return (st.nlink ?? 1) > 1 ? st : void 0;
5975
+ }
5976
+ function linkedElsewhere(fs, path) {
5977
+ try {
5978
+ return linkedOf(fs.lstat(path));
5979
+ } catch {
5980
+ return void 0;
5981
+ }
5982
+ }
5678
5983
  function filetypeOf(s) {
5679
5984
  if (!s) return FILETYPE.UNKNOWN;
5680
5985
  if (s.isSymbolicLink) return FILETYPE.SYMBOLIC_LINK;
@@ -5686,6 +5991,7 @@ function filestatOf(path, s) {
5686
5991
  filetype: filetypeOf(s),
5687
5992
  size: BigInt(s.size),
5688
5993
  ino: s.ino !== void 0 ? BigInt(s.ino) : pathInode(path),
5994
+ ...s.nlink !== void 0 ? { nlink: BigInt(s.nlink) } : {},
5689
5995
  mtimeNs: BigInt(Math.round(s.mtimeMs ?? 0)) * NS_PER_MS
5690
5996
  };
5691
5997
  }
@@ -5736,6 +6042,7 @@ var WasiExit, IDS, NS_PER_MS, AT_SYMLINK_NOFOLLOW2, S_IFIFO, DEFAULT_UMASK, MONO
5736
6042
  var init_wasi_host = __esm({
5737
6043
  "src/process/wasi/wasi-host.ts"() {
5738
6044
  "use strict";
6045
+ init_physical_path();
5739
6046
  init_cred();
5740
6047
  init_wasi_abi();
5741
6048
  init_wasi_fds();
@@ -6045,17 +6352,20 @@ var init_wasi_host = __esm({
6045
6352
  path_remove_directory: (dirfd, p, l) => void o.fs.rmdir(at(dirfd, p, l)),
6046
6353
  path_unlink_file: (dirfd, p, l) => {
6047
6354
  const path = at(dirfd, p, l);
6048
- if (o.fs.lstat(path).isDirectory) throw new WasiError("EISDIR");
6049
- fds.unlinking(path);
6355
+ const st = o.fs.lstat(path);
6356
+ if (st.isDirectory) throw new WasiError("EISDIR");
6357
+ const linked = linkedOf(st);
6358
+ fds.unlinking(path, linked);
6050
6359
  o.fs.unlink(path);
6051
- fds.unlinked(path);
6360
+ fds.unlinked(path, linked);
6052
6361
  },
6053
6362
  path_rename: (fd, p, l, fd2, p2, l2) => {
6054
6363
  const from = at(fd, p, l);
6055
6364
  const to = at(fd2, p2, l2);
6056
6365
  fds.flushPath(from);
6366
+ const replaced = fds.holdsUnder(to) ? linkedElsewhere(o.fs, to) : void 0;
6057
6367
  o.fs.rename(from, to);
6058
- fds.renamed(from, to);
6368
+ fds.renamed(from, to, replaced);
6059
6369
  },
6060
6370
  path_symlink: (tp, tl, dirfd, p, l) => void o.fs.symlink(mem.string(tp, tl), at(dirfd, p, l)),
6061
6371
  path_readlink: (dirfd, p, l, buf, len, used) => {
@@ -6064,7 +6374,17 @@ var init_wasi_host = __esm({
6064
6374
  mem.bytes(buf, n).set(target.subarray(0, n));
6065
6375
  mem.view().setUint32(used, n, true);
6066
6376
  },
6067
- path_link: () => E.NOTSUP
6377
+ path_link: (fd, lookup, p, l, fd2, p2, l2) => {
6378
+ const from = at(fd, p, l);
6379
+ const to = at(fd2, p2, l2);
6380
+ const follow2 = (lookup & LOOKUP_SYMLINK_FOLLOW) !== 0;
6381
+ const source = follow2 ? physicalPath(o.fs, from) : from;
6382
+ fds.flushPath(from);
6383
+ if (source !== from) fds.flushPath(source);
6384
+ o.fs.link(from, to, follow2);
6385
+ fds.linked(from, to);
6386
+ if (source !== from) fds.linked(source, to);
6387
+ }
6068
6388
  };
6069
6389
  }
6070
6390
  socket(fd) {
@@ -6150,11 +6470,29 @@ var init_wasi_host = __esm({
6150
6470
  if (!kernel.sys.pread) throw new WasiError("ESPIPE");
6151
6471
  return kernel.sys.pread(fd, Math.min(max, MAX_READ), at);
6152
6472
  },
6153
- pwrite: (body, at) => kernel.call({ op: "fd-pwrite", fd, offset: at, body }),
6473
+ pwrite: (body, at) => this.changed(fd, e, kernel.call({ op: "fd-pwrite", fd, offset: at, body })),
6154
6474
  size: () => this.kernelStat(fd).size,
6155
- truncate: (size) => void kernel.call({ op: "fd-resize", fd, size })
6475
+ truncate: (size) => this.changed(fd, e, void kernel.call({ op: "fd-resize", fd, size }))
6156
6476
  };
6157
6477
  }
6478
+ unnamed = /* @__PURE__ */ new WeakSet();
6479
+ changed(fd, e, result) {
6480
+ if (e.path === void 0 && !this.unnamed.has(e)) this.learnPath(fd, e);
6481
+ this.o.fs.touched?.(e.path, e.ino);
6482
+ return result;
6483
+ }
6484
+ learnPath(fd, e) {
6485
+ this.unnamed.add(e);
6486
+ let path;
6487
+ try {
6488
+ const st = this.kernelStat(fd);
6489
+ path = st.orphan ? void 0 : st.path;
6490
+ if (path !== void 0) e.ino = this.o.fs.stat(path).ino;
6491
+ } catch {
6492
+ return;
6493
+ }
6494
+ e.path = path;
6495
+ }
6158
6496
  kernelStat(fd) {
6159
6497
  return this.o.kernel.call({ op: "fd-vfs-stat", fd });
6160
6498
  }
@@ -6163,7 +6501,8 @@ var init_wasi_host = __esm({
6163
6501
  const e = this.fds.get(fd);
6164
6502
  if (e.type === "kernel") {
6165
6503
  try {
6166
- return this.o.kernel.sys.write(fd, data, { nonblock: e.nonblock });
6504
+ const n = this.o.kernel.sys.write(fd, data, { nonblock: e.nonblock });
6505
+ return this.fds.kind(fd, e) === "file" ? this.changed(fd, e, n) : n;
6167
6506
  } catch (err) {
6168
6507
  const broken = err.code === "EPIPE";
6169
6508
  if (broken && this.fds.kind(fd, e) !== "socket") throw new WasiExit(SIGPIPE_EXIT);
@@ -6249,7 +6588,7 @@ var init_wasi_host = __esm({
6249
6588
  v.setBigUint64(ptr, 1n, true);
6250
6589
  v.setBigUint64(ptr + 8, f.ino, true);
6251
6590
  v.setUint8(ptr + 16, f.filetype);
6252
- v.setBigUint64(ptr + 24, 1n, true);
6591
+ v.setBigUint64(ptr + 24, f.nlink ?? 1n, true);
6253
6592
  v.setBigUint64(ptr + 32, f.size, true);
6254
6593
  v.setBigUint64(ptr + 40, f.mtimeNs, true);
6255
6594
  v.setBigUint64(ptr + 48, f.mtimeNs, true);
@@ -6360,6 +6699,7 @@ var init_wasi_threads = __esm({
6360
6699
  "src/process/wasi/wasi-threads.ts"() {
6361
6700
  "use strict";
6362
6701
  init_protocol();
6702
+ init_wasi_files();
6363
6703
  LAST_TID = 0;
6364
6704
  RUNNING = 1;
6365
6705
  MAIN_TID = 1;
@@ -6382,7 +6722,7 @@ var init_wasi_threads = __esm({
6382
6722
  this.memory = memory;
6383
6723
  this.cap = cap;
6384
6724
  this.tid = tid;
6385
- this.ids = new Int32Array(ids ?? new SharedArrayBuffer(16));
6725
+ this.ids = new Int32Array(ids ?? new SharedArrayBuffer(SHARED_IDS_BYTES));
6386
6726
  if (!ids) Atomics.store(this.ids, LAST_TID, MAIN_TID);
6387
6727
  }
6388
6728
  spawn(arg) {
@@ -7052,6 +7392,13 @@ function procfsBridge(fs, tree) {
7052
7392
  () => fs.rename(from, to)
7053
7393
  ),
7054
7394
  symlink: (target, link) => readOnly([[link, false]], () => fs.symlink(target, link)),
7395
+ link: (from, to, follow2) => readOnly(
7396
+ [
7397
+ [from, follow2 === true],
7398
+ [to, false]
7399
+ ],
7400
+ () => fs.link(from, to, follow2)
7401
+ ),
7055
7402
  hold: (p, held, open, handle) => {
7056
7403
  if (handle !== void 0 && snapshots.delete(handle)) return void 0;
7057
7404
  if (!held) return fs.hold?.(p, held, open, handle);
@@ -7824,8 +8171,8 @@ function readExports(r, s) {
7824
8171
  for (let i = r.u32(); i > 0; i--) {
7825
8172
  const name2 = r.name();
7826
8173
  const kind = r.byte();
7827
- const index2 = r.u32();
7828
- if (kind === 0) s.info.exports[name2] = index2;
8174
+ const index3 = r.u32();
8175
+ if (kind === 0) s.info.exports[name2] = index3;
7829
8176
  }
7830
8177
  }
7831
8178
  function readElems(r, s) {
@@ -8128,15 +8475,15 @@ var init_wasix_linker = __esm({
8128
8475
  return out;
8129
8476
  }
8130
8477
  allocateClosure() {
8131
- const index2 = this.indices.allocate(this.table.length);
8132
- growTable(this.table, index2 + 1);
8133
- this.live.add(index2);
8134
- return index2;
8478
+ const index3 = this.indices.allocate(this.table.length);
8479
+ growTable(this.table, index3 + 1);
8480
+ this.live.add(index3);
8481
+ return index3;
8135
8482
  }
8136
- freeClosure(index2) {
8137
- this.indices?.free(index2);
8138
- this.live.delete(index2);
8139
- this.table.set(index2, null);
8483
+ freeClosure(index3) {
8484
+ this.indices?.free(index3);
8485
+ this.live.delete(index3);
8486
+ this.table.set(index3, null);
8140
8487
  }
8141
8488
  live = /* @__PURE__ */ new Set();
8142
8489
  prepareClosure(record) {
@@ -8369,12 +8716,12 @@ var init_wasix_linker = __esm({
8369
8716
  this.indexTable();
8370
8717
  const known = this.slots.get(fn);
8371
8718
  if (known !== void 0) return known;
8372
- const index2 = this.reserve(1);
8373
- growTable(this.table, index2 + 1);
8374
- this.table.set(index2, fn);
8375
- this.slots.set(fn, index2);
8376
- this.publisher?.({ kind: "slot", index: index2, handle, name: name2 });
8377
- return index2;
8719
+ const index3 = this.reserve(1);
8720
+ growTable(this.table, index3 + 1);
8721
+ this.table.set(index3, fn);
8722
+ this.slots.set(fn, index3);
8723
+ this.publisher?.({ kind: "slot", index: index3, handle, name: name2 });
8724
+ return index3;
8378
8725
  }
8379
8726
  allocate(size, alignment) {
8380
8727
  if (size === 0) return 0;
@@ -8659,11 +9006,11 @@ var init_wasix_sockets = __esm({
8659
9006
  });
8660
9007
 
8661
9008
  // src/process/wasi/wasix-host.ts
8662
- function futexWait(view, index2, expected, ms, interrupted) {
9009
+ function futexWait(view, index3, expected, ms, interrupted) {
8663
9010
  const deadline = performance.now() + ms;
8664
9011
  for (; ; ) {
8665
9012
  const slice = interrupted ? Math.min(FUTEX_SLICE_MS, ms) : ms;
8666
- if (Atomics.wait(view, index2, expected, slice) !== "timed-out") return true;
9013
+ if (Atomics.wait(view, index3, expected, slice) !== "timed-out") return true;
8667
9014
  if (interrupted?.()) return true;
8668
9015
  ms = deadline - performance.now();
8669
9016
  if (ms <= 0) return false;
@@ -8792,21 +9139,21 @@ var init_wasix_host = __esm({
8792
9139
  if (!this.linker) throw new WasiError("ENOTSUP");
8793
9140
  this.mem.view().setUint32(out, this.linker.allocateClosure(), true);
8794
9141
  },
8795
- closure_prepare: (backing, index2, argTypes, argCount, resultTypes, resultCount, userData) => {
9142
+ closure_prepare: (backing, index3, argTypes, argCount, resultTypes, resultCount, userData) => {
8796
9143
  if (!this.linker) throw new WasiError("ENOTSUP");
8797
9144
  const params = valueTypes(this.mem.bytes(argTypes, argCount));
8798
9145
  const results = valueTypes(this.mem.bytes(resultTypes, resultCount));
8799
9146
  if (!params || !results) throw new WasiError("EINVAL");
8800
9147
  try {
8801
- this.linker.prepareClosure({ index: index2, backing, userData, params, results });
9148
+ this.linker.prepareClosure({ index: index3, backing, userData, params, results });
8802
9149
  } catch (e) {
8803
9150
  if (e instanceof DlError) throw new WasiError("EINVAL");
8804
9151
  throw e;
8805
9152
  }
8806
9153
  },
8807
- closure_free: (index2) => {
9154
+ closure_free: (index3) => {
8808
9155
  if (!this.linker) throw new WasiError("ENOTSUP");
8809
- this.linker.freeClosure(index2);
9156
+ this.linker.freeClosure(index3);
8810
9157
  },
8811
9158
  call_dynamic: (...a) => void callDynamic(
8812
9159
  { mem: this.mem, table: () => this.table, types: this.types },
@@ -9111,10 +9458,10 @@ function parseFunctionNames(payload) {
9111
9458
  if (end > payload.length) throw new RangeError("truncated name section");
9112
9459
  if (id === 1) {
9113
9460
  for (let count = leb(); count > 0; count--) {
9114
- const index2 = leb();
9461
+ const index3 = leb();
9115
9462
  const length = leb();
9116
9463
  if (at + length > end) throw new RangeError("truncated name section");
9117
- names.set(index2, decoder3.decode(payload.subarray(at, at + length)));
9464
+ names.set(index3, decoder3.decode(payload.subarray(at, at + length)));
9118
9465
  at += length;
9119
9466
  }
9120
9467
  }
@@ -9138,7 +9485,7 @@ function nameFrame(line, module, name2) {
9138
9485
  }
9139
9486
  function sidecarNames(read2, path) {
9140
9487
  let names;
9141
- return (index2) => {
9488
+ return (index3) => {
9142
9489
  if (!names) {
9143
9490
  try {
9144
9491
  names = parseFunctionNames(read2(path));
@@ -9146,7 +9493,7 @@ function sidecarNames(read2, path) {
9146
9493
  names = /* @__PURE__ */ new Map();
9147
9494
  }
9148
9495
  }
9149
- return names.get(index2);
9496
+ return names.get(index3);
9150
9497
  };
9151
9498
  }
9152
9499
  var UNNAMED_FRAME, WASM_MODULE;
@@ -9507,7 +9854,7 @@ async function runWasiProcess(init, port2) {
9507
9854
  parent: () => identify(transport, init).ppid,
9508
9855
  kernel: { sys: traced(stats, "kernel", sys), call: call2 },
9509
9856
  fs: procfsBridge(
9510
- cachingBridge(traced(stats, "fs", createSyncFsSabBridge(transport))),
9857
+ cachingBridge(traced(stats, "fs", createSyncFsSabBridge(transport)), void 0, threads?.ids),
9511
9858
  procTree(call2, id.pid)
9512
9859
  ),
9513
9860
  ...descriptors(init, fork, threads)
@@ -9600,7 +9947,10 @@ async function runWasiThread(init, port2) {
9600
9947
  ppid: id.ppid,
9601
9948
  parent: () => identify(transport, init).ppid,
9602
9949
  kernel: { sys, call: call2 },
9603
- fs: procfsBridge(cachingBridge(createSyncFsSabBridge(transport)), procTree(call2, id.pid)),
9950
+ fs: procfsBridge(
9951
+ cachingBridge(createSyncFsSabBridge(transport), void 0, threads.ids),
9952
+ procTree(call2, id.pid)
9953
+ ),
9604
9954
  shared: threads.ids
9605
9955
  });
9606
9956
  const program = await programImports(init.program, host, transport, thread.tid);
@@ -9712,13 +10062,13 @@ var init_wasi_runtime = __esm({
9712
10062
  },
9713
10063
  unlock: () => this.call({ op: "dl-unlock", owner: this.owner }),
9714
10064
  allocate: (floor) => {
9715
- const index2 = this.call({ op: "dl-closure", floor });
10065
+ const index3 = this.call({ op: "dl-closure", floor });
9716
10066
  this.catchUp();
9717
10067
  this.bump();
9718
- return index2;
10068
+ return index3;
9719
10069
  },
9720
- free: (index2) => {
9721
- this.call({ op: "dl-closure-free", index: index2 });
10070
+ free: (index3) => {
10071
+ this.call({ op: "dl-closure-free", index: index3 });
9722
10072
  this.bump();
9723
10073
  }
9724
10074
  };
@@ -9771,8 +10121,8 @@ var init_wasi_runtime = __esm({
9771
10121
  });
9772
10122
 
9773
10123
  // src/process/js/async-sab.ts
9774
- async function changed(header, index2, seen, waitAsync = defaultWaitAsync) {
9775
- const waited = waitAsync(header, index2, seen);
10124
+ async function changed(header, index3, seen, waitAsync = defaultWaitAsync) {
10125
+ const waited = waitAsync(header, index3, seen);
9776
10126
  if (waited.async) await waited.value;
9777
10127
  }
9778
10128
  function createAsyncSabTransport(sab, port2, waitAsync = defaultWaitAsync) {
@@ -10534,7 +10884,7 @@ function openRequest(abs, o) {
10534
10884
  ...o.truncate ? { truncate: true } : {}
10535
10885
  };
10536
10886
  }
10537
- function index(n, what) {
10887
+ function index2(n, what) {
10538
10888
  if (!Number.isSafeInteger(n) || n < 0) throw new JsCallError("EINVAL", what);
10539
10889
  }
10540
10890
  var CHUNK2, MAX_IO, O_WRONLY6, O_RDWR6, O_APPEND3;
@@ -10707,7 +11057,7 @@ function syncOps(kernel, shared) {
10707
11057
  return got;
10708
11058
  };
10709
11059
  const read2 = (fd, max = CHUNK2) => {
10710
- index(max, "read");
11060
+ index2(max, "read");
10711
11061
  const emulated = readDevice(info(fd), max, shared.random);
10712
11062
  if (emulated) return emulated;
10713
11063
  const r = kernel.blocking({ op: "fd-read", fd, max: Math.min(max, MAX_IO) });
@@ -10785,8 +11135,8 @@ function syncFile(kernel, fd, path, append, forget) {
10785
11135
  return kernel.json({ op: "fd-vfs-stat", fd }).size;
10786
11136
  },
10787
11137
  read(position, length) {
10788
- index(position, "read");
10789
- index(length, "read");
11138
+ index2(position, "read");
11139
+ index2(length, "read");
10790
11140
  const parts = [];
10791
11141
  let got = 0;
10792
11142
  do {
@@ -10801,7 +11151,7 @@ function syncFile(kernel, fd, path, append, forget) {
10801
11151
  return joined(parts, got);
10802
11152
  },
10803
11153
  write(position, data) {
10804
- index(position, "write");
11154
+ index2(position, "write");
10805
11155
  let at = 0;
10806
11156
  do {
10807
11157
  live();
@@ -10815,7 +11165,7 @@ function syncFile(kernel, fd, path, append, forget) {
10815
11165
  },
10816
11166
  truncate(size) {
10817
11167
  live();
10818
- index(size, "truncate");
11168
+ index2(size, "truncate");
10819
11169
  kernel.call({ op: "fd-resize", fd, size });
10820
11170
  },
10821
11171
  sync() {
@@ -10846,6 +11196,7 @@ function syncPathOps(kernel, resolve, statOf, open) {
10846
11196
  rm: (path) => void kernel.call({ op: "rm", path: resolve(path) }),
10847
11197
  unlink: (path) => void kernel.call({ op: "unlink", path: resolve(path) }),
10848
11198
  rename: (from, to) => void kernel.call({ op: "rename", path: resolve(from), arg2: resolve(to) }),
11199
+ link: (from, to) => void kernel.call({ op: "link", path: resolve(from), arg2: resolve(to) }),
10849
11200
  symlink: (target, path) => void kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
10850
11201
  readlink: (path) => String(kernel.json({ op: "readlink", path: resolve(path) })),
10851
11202
  readFile(path) {
@@ -11211,7 +11562,7 @@ function createContext(o) {
11211
11562
  return got;
11212
11563
  };
11213
11564
  const read2 = async (fd, max = CHUNK2, signal) => {
11214
- index(max, "read");
11565
+ index2(max, "read");
11215
11566
  const emulated = readDevice(await info(fd), max, random);
11216
11567
  if (emulated) return emulated;
11217
11568
  return readPipe(kernel, fd, Math.min(max, MAX_IO), signal);
@@ -11358,8 +11709,8 @@ function fileHandle(kernel, fd, path, append, forget) {
11358
11709
  return s.size;
11359
11710
  },
11360
11711
  async read(position, length) {
11361
- index(position, "read");
11362
- index(length, "read");
11712
+ index2(position, "read");
11713
+ index2(length, "read");
11363
11714
  const parts = [];
11364
11715
  let got = 0;
11365
11716
  do {
@@ -11374,7 +11725,7 @@ function fileHandle(kernel, fd, path, append, forget) {
11374
11725
  return joined(parts, got);
11375
11726
  },
11376
11727
  async write(position, data) {
11377
- index(position, "write");
11728
+ index2(position, "write");
11378
11729
  let at = 0;
11379
11730
  do {
11380
11731
  live();
@@ -11388,7 +11739,7 @@ function fileHandle(kernel, fd, path, append, forget) {
11388
11739
  },
11389
11740
  async truncate(size) {
11390
11741
  live();
11391
- index(size, "truncate");
11742
+ index2(size, "truncate");
11392
11743
  await kernel.call({ op: "fd-resize", fd, size });
11393
11744
  },
11394
11745
  async sync() {
@@ -11442,6 +11793,7 @@ function pathOps(kernel, resolve, statOf, open) {
11442
11793
  rm: async (path) => void await kernel.call({ op: "rm", path: resolve(path) }),
11443
11794
  unlink: async (path) => void await kernel.call({ op: "unlink", path: resolve(path) }),
11444
11795
  rename: async (from, to) => void await kernel.call({ op: "rename", path: resolve(from), arg2: resolve(to) }),
11796
+ link: async (from, to) => void await kernel.call({ op: "link", path: resolve(from), arg2: resolve(to) }),
11445
11797
  symlink: async (target, path) => void await kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
11446
11798
  readlink: async (path) => String(await kernel.json({ op: "readlink", path: resolve(path) })),
11447
11799
  async readFile(path) {