@ai-ecoverse/slicc-kernel 1.57.1 → 1.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  },
@@ -3769,7 +3850,7 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
3769
3850
  const pidContent = (target, name2, own = false) => () => {
3770
3851
  const listing = procList();
3771
3852
  const info = find(listing.processes, target, own);
3772
- if (!info) throw error(ESRCH);
3853
+ if (!info) throw error();
3773
3854
  return pidFile(name2, info, listing.boot);
3774
3855
  };
3775
3856
  const pidNames = (names) => [.../* @__PURE__ */ new Set([...names, ...PID_FILES])];
@@ -3782,14 +3863,29 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
3782
3863
  dir.node_ops.readdir = () => [".", "..", ...PID_FILES];
3783
3864
  return dir;
3784
3865
  };
3866
+ const scans = /* @__PURE__ */ new Map();
3867
+ const listed = (target) => {
3868
+ let found = false;
3869
+ for (const [stream, pids] of scans) {
3870
+ if (Fs.streams && Fs.streams[stream.fd ?? -1] !== stream) scans.delete(stream);
3871
+ else found ||= pids.has(target);
3872
+ }
3873
+ return found;
3874
+ };
3785
3875
  const procOps = proc.node_ops;
3786
3876
  const lookup = procOps.lookup?.bind(procOps);
3787
3877
  const readdir = procOps.readdir?.bind(procOps);
3878
+ const listAll = (node) => {
3879
+ const names = new Set(readdir ? readdir(node) : [".", ".."]);
3880
+ for (const name2 of [...SYSTEM_FILES, "mounts"]) names.add(name2);
3881
+ for (const p of procList().processes) names.add(String(p.pid));
3882
+ return [...names];
3883
+ };
3788
3884
  proc.node_ops = {
3789
3885
  ...procOps,
3790
3886
  lookup: (parent, name2) => {
3791
3887
  if (/^[1-9]\d*$/.test(name2)) {
3792
- if (!infoOf(Number(name2))) throw error();
3888
+ if (!infoOf(Number(name2)) && !listed(Number(name2))) throw error();
3793
3889
  return pidDir(name2, Number(name2));
3794
3890
  }
3795
3891
  if (name2 === "mounts") return file(parent, name2, () => mountTable(sys.mountList?.() ?? []));
@@ -3803,11 +3899,36 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
3803
3899
  if (!lookup) throw error();
3804
3900
  return lookup(parent, name2);
3805
3901
  },
3806
- readdir: (node) => {
3807
- const names = new Set(readdir ? readdir(node) : [".", ".."]);
3808
- for (const name2 of [...SYSTEM_FILES, "mounts"]) names.add(name2);
3809
- for (const p of procList().processes) names.add(String(p.pid));
3810
- return [...names];
3902
+ readdir: (node) => listAll(node)
3903
+ };
3904
+ const track = (stream, names) => {
3905
+ let current = names;
3906
+ const remember = (list) => scans.set(stream, new Set(list.filter((n) => /^[1-9]\d*$/.test(n)).map(Number)));
3907
+ remember(current ?? []);
3908
+ Object.defineProperty(stream, "getdents", {
3909
+ configurable: true,
3910
+ enumerable: true,
3911
+ get: () => current,
3912
+ set: (next2) => {
3913
+ current = next2;
3914
+ if (next2) remember(next2);
3915
+ }
3916
+ });
3917
+ };
3918
+ const dirOps = proc.stream_ops ?? {};
3919
+ proc.stream_ops = {
3920
+ ...dirOps,
3921
+ open: (stream) => {
3922
+ dirOps.open?.(stream);
3923
+ track(stream, listAll(proc));
3924
+ },
3925
+ dup: (stream) => {
3926
+ dirOps.dup?.(stream);
3927
+ track(stream, stream.getdents);
3928
+ },
3929
+ close: (stream) => {
3930
+ scans.delete(stream);
3931
+ dirOps.close?.(stream);
3811
3932
  }
3812
3933
  };
3813
3934
  hashRemoveNode.call(Fs, self);
@@ -3831,7 +3952,7 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
3831
3952
  readdir: (node) => pidNames(selfReaddir ? selfReaddir(node) : [".", ".."])
3832
3953
  };
3833
3954
  }
3834
- var HZ, DIR_MODE2, FILE_MODE, LINK_MODE2, ENOENT, ESRCH, EINVAL, UID, KIB, FALLBACK_MEMORY, PID_FILES, SYSTEM_FILES, sigMask;
3955
+ var HZ, DIR_MODE2, FILE_MODE, LINK_MODE2, ENOENT, EINVAL, UID, KIB, FALLBACK_MEMORY, PID_FILES, SYSTEM_FILES, sigMask;
3835
3956
  var init_procfs = __esm({
3836
3957
  "src/process/procfs.ts"() {
3837
3958
  "use strict";
@@ -3842,7 +3963,6 @@ var init_procfs = __esm({
3842
3963
  FILE_MODE = 33060;
3843
3964
  LINK_MODE2 = 41471;
3844
3965
  ENOENT = 44;
3845
- ESRCH = 71;
3846
3966
  EINVAL = 28;
3847
3967
  UID = 1e3;
3848
3968
  KIB = 1024;
@@ -4633,6 +4753,7 @@ function cachingBridge(bridge, now = () => performance.now(), shared) {
4633
4753
  mkdir: (p) => mutating(() => bridge.mkdir(p), ...entry(p)),
4634
4754
  rm: (p) => mutating(() => bridge.rm(p)),
4635
4755
  rename: (from, to) => mutating(() => bridge.rename(from, to)),
4756
+ link: (from, to, follow2) => mutating(() => bridge.link(from, to, follow2)),
4636
4757
  unlink: (p) => mutating(() => bridge.unlink(p)),
4637
4758
  rmdir: (p) => mutating(() => bridge.rmdir(p)),
4638
4759
  symlink: (target, link) => mutating(() => bridge.symlink(target, link), ...entry(link)),
@@ -5051,6 +5172,8 @@ var init_wasi_fds = __esm({
5051
5172
  WasiFds = class {
5052
5173
  table = /* @__PURE__ */ new Map();
5053
5174
  buffers = /* @__PURE__ */ new Map();
5175
+ byId = /* @__PURE__ */ new Map();
5176
+ ids = /* @__PURE__ */ new Map();
5054
5177
  kernel;
5055
5178
  fs;
5056
5179
  constructor(kernel, fs) {
@@ -5300,7 +5423,7 @@ var init_wasi_fds = __esm({
5300
5423
  }
5301
5424
  for (const file of promoted.keys()) {
5302
5425
  if (--file.buffer.opens <= 0) {
5303
- this.buffers.delete(file.path);
5426
+ this.forget(file.buffer);
5304
5427
  this.letGo(file.buffer);
5305
5428
  }
5306
5429
  }
@@ -5371,9 +5494,48 @@ var init_wasi_fds = __esm({
5371
5494
  const { buffer } = e.file;
5372
5495
  buffer.flush();
5373
5496
  if (--buffer.opens > 0) return;
5374
- if (this.buffers.get(buffer.path) === buffer) this.buffers.delete(buffer.path);
5497
+ this.forget(buffer);
5375
5498
  this.letGo(buffer);
5376
5499
  }
5500
+ forget(buffer) {
5501
+ for (const [p, b] of [...this.buffers]) if (b === buffer) this.buffers.delete(p);
5502
+ const id = this.ids.get(buffer);
5503
+ if (id !== void 0 && this.byId.get(id) === buffer) this.byId.delete(id);
5504
+ this.ids.delete(buffer);
5505
+ }
5506
+ know(buffer, id) {
5507
+ if (this.ids.has(buffer)) return;
5508
+ this.ids.set(buffer, id);
5509
+ if (!this.byId.has(id)) this.byId.set(id, buffer);
5510
+ }
5511
+ identity(st) {
5512
+ if (!st || st.ino === void 0 || (st.nlink ?? 1) < 2) return void 0;
5513
+ return `${st.dev ?? 0}:${st.ino}`;
5514
+ }
5515
+ sharing(path, st) {
5516
+ if (st?.ino === void 0) return void 0;
5517
+ const id = `${st.dev ?? 0}:${st.ino}`;
5518
+ if (!this.byId.has(id) && this.identity(st) !== void 0) this.learn();
5519
+ const buffer = this.byId.get(id);
5520
+ if (!buffer) return void 0;
5521
+ this.buffers.set(path, buffer);
5522
+ return buffer;
5523
+ }
5524
+ learn() {
5525
+ for (const [p, buffer] of this.buffers) {
5526
+ if (this.ids.has(buffer) || buffer.isOrphan()) continue;
5527
+ const known = this.identity(this.statOrMissing(p));
5528
+ if (known !== void 0) this.know(buffer, known);
5529
+ }
5530
+ }
5531
+ linked(from, to) {
5532
+ const buffer = this.buffers.get(from);
5533
+ if (!buffer || buffer.isOrphan()) return;
5534
+ const id = this.identity(this.statOrMissing(to));
5535
+ if (id === void 0) return;
5536
+ this.know(buffer, id);
5537
+ if (!this.buffers.has(to)) this.buffers.set(to, buffer);
5538
+ }
5377
5539
  letGo(buffer) {
5378
5540
  this.fs.hold?.(buffer.path, false, true, buffer.handle);
5379
5541
  }
@@ -5399,15 +5561,21 @@ var init_wasi_fds = __esm({
5399
5561
  for (const [p, buffer] of this.buffers) if (within(p, path)) buffer.flush();
5400
5562
  if (this.shared) this.kernel.call({ op: "fd-path-flush", path });
5401
5563
  }
5402
- unlinking(path) {
5403
- this.buffers.get(path)?.keep();
5564
+ holdsUnder(path) {
5565
+ for (const p of this.buffers.keys()) if (within(p, path)) return true;
5566
+ return false;
5567
+ }
5568
+ unlinking(path, linked) {
5569
+ const buffer = this.buffers.get(path);
5570
+ if (buffer && this.survives(buffer, linked)) buffer.flush();
5571
+ else buffer?.keep();
5404
5572
  if (this.shared) this.kernel.call({ op: "fd-path-unlinking", path });
5405
5573
  }
5406
- unlinked(path) {
5574
+ unlinked(path, linked) {
5407
5575
  if (this.shared) this.kernel.call({ op: "fd-path-unlinked", path });
5408
5576
  const buffer = this.buffers.get(path);
5409
5577
  if (!buffer) return;
5410
- buffer.orphan();
5578
+ this.detach(buffer, linked);
5411
5579
  this.buffers.delete(path);
5412
5580
  }
5413
5581
  inoOf(path) {
@@ -5417,7 +5585,19 @@ var init_wasi_fds = __esm({
5417
5585
  return void 0;
5418
5586
  }
5419
5587
  }
5420
- renamed(from, to) {
5588
+ survives(buffer, linked) {
5589
+ return linked !== void 0 && buffer.handle !== void 0;
5590
+ }
5591
+ detach(buffer, linked) {
5592
+ if (this.survives(buffer, linked)) {
5593
+ const id = this.identity(linked);
5594
+ if (id !== void 0) this.know(buffer, id);
5595
+ return;
5596
+ }
5597
+ buffer.orphan();
5598
+ this.forget(buffer);
5599
+ }
5600
+ renamed(from, to, replacedLinked) {
5421
5601
  if (from === to) return;
5422
5602
  if (this.shared) this.kernel.call({ op: "fd-path-renamed", from, to });
5423
5603
  for (const e of this.table.values()) {
@@ -5429,14 +5609,15 @@ var init_wasi_fds = __esm({
5429
5609
  for (const [p, buffer] of this.buffers) {
5430
5610
  if (within(p, from)) moved.push([p, buffer]);
5431
5611
  else if (within(p, to)) {
5432
- buffer.orphan();
5612
+ this.detach(buffer, replacedLinked);
5433
5613
  this.buffers.delete(p);
5434
5614
  }
5435
5615
  }
5616
+ for (const [p] of moved) this.buffers.delete(p);
5436
5617
  for (const [p, buffer] of moved) {
5437
- this.buffers.delete(p);
5438
- buffer.path = to + p.slice(from.length);
5439
- this.buffers.set(buffer.path, buffer);
5618
+ const next2 = to + p.slice(from.length);
5619
+ if (buffer.path === p) buffer.path = next2;
5620
+ this.buffers.set(next2, buffer);
5440
5621
  }
5441
5622
  }
5442
5623
  resolve(dirfd, path) {
@@ -5479,6 +5660,7 @@ var init_wasi_fds = __esm({
5479
5660
  this.buffers.delete(path);
5480
5661
  buffer = void 0;
5481
5662
  }
5663
+ buffer ??= this.sharing(path, existing);
5482
5664
  if (buffer) {
5483
5665
  if (oflags & OFLAGS.TRUNC) buffer.truncate(0);
5484
5666
  } else {
@@ -5492,6 +5674,8 @@ var init_wasi_fds = __esm({
5492
5674
  buffer = new FileBuffer(io, path, empty, st.maxFile, ranged, st.version);
5493
5675
  buffer.handle = handle;
5494
5676
  this.buffers.set(path, buffer);
5677
+ const id = this.identity(st);
5678
+ if (id !== void 0) this.know(buffer, id);
5495
5679
  }
5496
5680
  buffer.opens++;
5497
5681
  return new LocalFile(buffer, readable2, writable2, (fdflags & FDFLAGS.APPEND) !== 0);
@@ -5825,6 +6009,16 @@ var init_wasi_termios = __esm({
5825
6009
  });
5826
6010
 
5827
6011
  // src/process/wasi/wasi-host.ts
6012
+ function linkedOf(st) {
6013
+ return (st.nlink ?? 1) > 1 ? st : void 0;
6014
+ }
6015
+ function linkedElsewhere(fs, path) {
6016
+ try {
6017
+ return linkedOf(fs.lstat(path));
6018
+ } catch {
6019
+ return void 0;
6020
+ }
6021
+ }
5828
6022
  function filetypeOf(s) {
5829
6023
  if (!s) return FILETYPE.UNKNOWN;
5830
6024
  if (s.isSymbolicLink) return FILETYPE.SYMBOLIC_LINK;
@@ -5836,6 +6030,7 @@ function filestatOf(path, s) {
5836
6030
  filetype: filetypeOf(s),
5837
6031
  size: BigInt(s.size),
5838
6032
  ino: s.ino !== void 0 ? BigInt(s.ino) : pathInode(path),
6033
+ ...s.nlink !== void 0 ? { nlink: BigInt(s.nlink) } : {},
5839
6034
  mtimeNs: BigInt(Math.round(s.mtimeMs ?? 0)) * NS_PER_MS
5840
6035
  };
5841
6036
  }
@@ -5886,6 +6081,7 @@ var WasiExit, IDS, NS_PER_MS, AT_SYMLINK_NOFOLLOW2, S_IFIFO, DEFAULT_UMASK, MONO
5886
6081
  var init_wasi_host = __esm({
5887
6082
  "src/process/wasi/wasi-host.ts"() {
5888
6083
  "use strict";
6084
+ init_physical_path();
5889
6085
  init_cred();
5890
6086
  init_wasi_abi();
5891
6087
  init_wasi_fds();
@@ -6195,17 +6391,20 @@ var init_wasi_host = __esm({
6195
6391
  path_remove_directory: (dirfd, p, l) => void o.fs.rmdir(at(dirfd, p, l)),
6196
6392
  path_unlink_file: (dirfd, p, l) => {
6197
6393
  const path = at(dirfd, p, l);
6198
- if (o.fs.lstat(path).isDirectory) throw new WasiError("EISDIR");
6199
- fds.unlinking(path);
6394
+ const st = o.fs.lstat(path);
6395
+ if (st.isDirectory) throw new WasiError("EISDIR");
6396
+ const linked = linkedOf(st);
6397
+ fds.unlinking(path, linked);
6200
6398
  o.fs.unlink(path);
6201
- fds.unlinked(path);
6399
+ fds.unlinked(path, linked);
6202
6400
  },
6203
6401
  path_rename: (fd, p, l, fd2, p2, l2) => {
6204
6402
  const from = at(fd, p, l);
6205
6403
  const to = at(fd2, p2, l2);
6206
6404
  fds.flushPath(from);
6405
+ const replaced = fds.holdsUnder(to) ? linkedElsewhere(o.fs, to) : void 0;
6207
6406
  o.fs.rename(from, to);
6208
- fds.renamed(from, to);
6407
+ fds.renamed(from, to, replaced);
6209
6408
  },
6210
6409
  path_symlink: (tp, tl, dirfd, p, l) => void o.fs.symlink(mem.string(tp, tl), at(dirfd, p, l)),
6211
6410
  path_readlink: (dirfd, p, l, buf, len, used) => {
@@ -6214,7 +6413,17 @@ var init_wasi_host = __esm({
6214
6413
  mem.bytes(buf, n).set(target.subarray(0, n));
6215
6414
  mem.view().setUint32(used, n, true);
6216
6415
  },
6217
- path_link: () => E.NOTSUP
6416
+ path_link: (fd, lookup, p, l, fd2, p2, l2) => {
6417
+ const from = at(fd, p, l);
6418
+ const to = at(fd2, p2, l2);
6419
+ const follow2 = (lookup & LOOKUP_SYMLINK_FOLLOW) !== 0;
6420
+ const source = follow2 ? physicalPath(o.fs, from) : from;
6421
+ fds.flushPath(from);
6422
+ if (source !== from) fds.flushPath(source);
6423
+ o.fs.link(from, to, follow2);
6424
+ fds.linked(from, to);
6425
+ if (source !== from) fds.linked(source, to);
6426
+ }
6218
6427
  };
6219
6428
  }
6220
6429
  socket(fd) {
@@ -6418,7 +6627,7 @@ var init_wasi_host = __esm({
6418
6627
  v.setBigUint64(ptr, 1n, true);
6419
6628
  v.setBigUint64(ptr + 8, f.ino, true);
6420
6629
  v.setUint8(ptr + 16, f.filetype);
6421
- v.setBigUint64(ptr + 24, 1n, true);
6630
+ v.setBigUint64(ptr + 24, f.nlink ?? 1n, true);
6422
6631
  v.setBigUint64(ptr + 32, f.size, true);
6423
6632
  v.setBigUint64(ptr + 40, f.mtimeNs, true);
6424
6633
  v.setBigUint64(ptr + 48, f.mtimeNs, true);
@@ -7222,6 +7431,13 @@ function procfsBridge(fs, tree) {
7222
7431
  () => fs.rename(from, to)
7223
7432
  ),
7224
7433
  symlink: (target, link) => readOnly([[link, false]], () => fs.symlink(target, link)),
7434
+ link: (from, to, follow2) => readOnly(
7435
+ [
7436
+ [from, follow2 === true],
7437
+ [to, false]
7438
+ ],
7439
+ () => fs.link(from, to, follow2)
7440
+ ),
7225
7441
  hold: (p, held, open, handle) => {
7226
7442
  if (handle !== void 0 && snapshots.delete(handle)) return void 0;
7227
7443
  if (!held) return fs.hold?.(p, held, open, handle);
@@ -11019,6 +11235,7 @@ function syncPathOps(kernel, resolve, statOf, open) {
11019
11235
  rm: (path) => void kernel.call({ op: "rm", path: resolve(path) }),
11020
11236
  unlink: (path) => void kernel.call({ op: "unlink", path: resolve(path) }),
11021
11237
  rename: (from, to) => void kernel.call({ op: "rename", path: resolve(from), arg2: resolve(to) }),
11238
+ link: (from, to) => void kernel.call({ op: "link", path: resolve(from), arg2: resolve(to) }),
11022
11239
  symlink: (target, path) => void kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
11023
11240
  readlink: (path) => String(kernel.json({ op: "readlink", path: resolve(path) })),
11024
11241
  readFile(path) {
@@ -11615,6 +11832,7 @@ function pathOps(kernel, resolve, statOf, open) {
11615
11832
  rm: async (path) => void await kernel.call({ op: "rm", path: resolve(path) }),
11616
11833
  unlink: async (path) => void await kernel.call({ op: "unlink", path: resolve(path) }),
11617
11834
  rename: async (from, to) => void await kernel.call({ op: "rename", path: resolve(from), arg2: resolve(to) }),
11835
+ link: async (from, to) => void await kernel.call({ op: "link", path: resolve(from), arg2: resolve(to) }),
11618
11836
  symlink: async (target, path) => void await kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
11619
11837
  readlink: async (path) => String(await kernel.json({ op: "readlink", path: resolve(path) })),
11620
11838
  async readFile(path) {