@ai-ecoverse/slicc-kernel 1.57.1 → 1.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  },
@@ -4633,6 +4714,7 @@ function cachingBridge(bridge, now = () => performance.now(), shared) {
4633
4714
  mkdir: (p) => mutating(() => bridge.mkdir(p), ...entry(p)),
4634
4715
  rm: (p) => mutating(() => bridge.rm(p)),
4635
4716
  rename: (from, to) => mutating(() => bridge.rename(from, to)),
4717
+ link: (from, to, follow2) => mutating(() => bridge.link(from, to, follow2)),
4636
4718
  unlink: (p) => mutating(() => bridge.unlink(p)),
4637
4719
  rmdir: (p) => mutating(() => bridge.rmdir(p)),
4638
4720
  symlink: (target, link) => mutating(() => bridge.symlink(target, link), ...entry(link)),
@@ -5051,6 +5133,8 @@ var init_wasi_fds = __esm({
5051
5133
  WasiFds = class {
5052
5134
  table = /* @__PURE__ */ new Map();
5053
5135
  buffers = /* @__PURE__ */ new Map();
5136
+ byId = /* @__PURE__ */ new Map();
5137
+ ids = /* @__PURE__ */ new Map();
5054
5138
  kernel;
5055
5139
  fs;
5056
5140
  constructor(kernel, fs) {
@@ -5300,7 +5384,7 @@ var init_wasi_fds = __esm({
5300
5384
  }
5301
5385
  for (const file of promoted.keys()) {
5302
5386
  if (--file.buffer.opens <= 0) {
5303
- this.buffers.delete(file.path);
5387
+ this.forget(file.buffer);
5304
5388
  this.letGo(file.buffer);
5305
5389
  }
5306
5390
  }
@@ -5371,9 +5455,48 @@ var init_wasi_fds = __esm({
5371
5455
  const { buffer } = e.file;
5372
5456
  buffer.flush();
5373
5457
  if (--buffer.opens > 0) return;
5374
- if (this.buffers.get(buffer.path) === buffer) this.buffers.delete(buffer.path);
5458
+ this.forget(buffer);
5375
5459
  this.letGo(buffer);
5376
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
+ }
5377
5500
  letGo(buffer) {
5378
5501
  this.fs.hold?.(buffer.path, false, true, buffer.handle);
5379
5502
  }
@@ -5399,15 +5522,21 @@ var init_wasi_fds = __esm({
5399
5522
  for (const [p, buffer] of this.buffers) if (within(p, path)) buffer.flush();
5400
5523
  if (this.shared) this.kernel.call({ op: "fd-path-flush", path });
5401
5524
  }
5402
- unlinking(path) {
5403
- 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();
5404
5533
  if (this.shared) this.kernel.call({ op: "fd-path-unlinking", path });
5405
5534
  }
5406
- unlinked(path) {
5535
+ unlinked(path, linked) {
5407
5536
  if (this.shared) this.kernel.call({ op: "fd-path-unlinked", path });
5408
5537
  const buffer = this.buffers.get(path);
5409
5538
  if (!buffer) return;
5410
- buffer.orphan();
5539
+ this.detach(buffer, linked);
5411
5540
  this.buffers.delete(path);
5412
5541
  }
5413
5542
  inoOf(path) {
@@ -5417,7 +5546,19 @@ var init_wasi_fds = __esm({
5417
5546
  return void 0;
5418
5547
  }
5419
5548
  }
5420
- renamed(from, to) {
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) {
5421
5562
  if (from === to) return;
5422
5563
  if (this.shared) this.kernel.call({ op: "fd-path-renamed", from, to });
5423
5564
  for (const e of this.table.values()) {
@@ -5429,14 +5570,15 @@ var init_wasi_fds = __esm({
5429
5570
  for (const [p, buffer] of this.buffers) {
5430
5571
  if (within(p, from)) moved.push([p, buffer]);
5431
5572
  else if (within(p, to)) {
5432
- buffer.orphan();
5573
+ this.detach(buffer, replacedLinked);
5433
5574
  this.buffers.delete(p);
5434
5575
  }
5435
5576
  }
5577
+ for (const [p] of moved) this.buffers.delete(p);
5436
5578
  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);
5579
+ const next2 = to + p.slice(from.length);
5580
+ if (buffer.path === p) buffer.path = next2;
5581
+ this.buffers.set(next2, buffer);
5440
5582
  }
5441
5583
  }
5442
5584
  resolve(dirfd, path) {
@@ -5479,6 +5621,7 @@ var init_wasi_fds = __esm({
5479
5621
  this.buffers.delete(path);
5480
5622
  buffer = void 0;
5481
5623
  }
5624
+ buffer ??= this.sharing(path, existing);
5482
5625
  if (buffer) {
5483
5626
  if (oflags & OFLAGS.TRUNC) buffer.truncate(0);
5484
5627
  } else {
@@ -5492,6 +5635,8 @@ var init_wasi_fds = __esm({
5492
5635
  buffer = new FileBuffer(io, path, empty, st.maxFile, ranged, st.version);
5493
5636
  buffer.handle = handle;
5494
5637
  this.buffers.set(path, buffer);
5638
+ const id = this.identity(st);
5639
+ if (id !== void 0) this.know(buffer, id);
5495
5640
  }
5496
5641
  buffer.opens++;
5497
5642
  return new LocalFile(buffer, readable2, writable2, (fdflags & FDFLAGS.APPEND) !== 0);
@@ -5825,6 +5970,16 @@ var init_wasi_termios = __esm({
5825
5970
  });
5826
5971
 
5827
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
+ }
5828
5983
  function filetypeOf(s) {
5829
5984
  if (!s) return FILETYPE.UNKNOWN;
5830
5985
  if (s.isSymbolicLink) return FILETYPE.SYMBOLIC_LINK;
@@ -5836,6 +5991,7 @@ function filestatOf(path, s) {
5836
5991
  filetype: filetypeOf(s),
5837
5992
  size: BigInt(s.size),
5838
5993
  ino: s.ino !== void 0 ? BigInt(s.ino) : pathInode(path),
5994
+ ...s.nlink !== void 0 ? { nlink: BigInt(s.nlink) } : {},
5839
5995
  mtimeNs: BigInt(Math.round(s.mtimeMs ?? 0)) * NS_PER_MS
5840
5996
  };
5841
5997
  }
@@ -5886,6 +6042,7 @@ var WasiExit, IDS, NS_PER_MS, AT_SYMLINK_NOFOLLOW2, S_IFIFO, DEFAULT_UMASK, MONO
5886
6042
  var init_wasi_host = __esm({
5887
6043
  "src/process/wasi/wasi-host.ts"() {
5888
6044
  "use strict";
6045
+ init_physical_path();
5889
6046
  init_cred();
5890
6047
  init_wasi_abi();
5891
6048
  init_wasi_fds();
@@ -6195,17 +6352,20 @@ var init_wasi_host = __esm({
6195
6352
  path_remove_directory: (dirfd, p, l) => void o.fs.rmdir(at(dirfd, p, l)),
6196
6353
  path_unlink_file: (dirfd, p, l) => {
6197
6354
  const path = at(dirfd, p, l);
6198
- if (o.fs.lstat(path).isDirectory) throw new WasiError("EISDIR");
6199
- 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);
6200
6359
  o.fs.unlink(path);
6201
- fds.unlinked(path);
6360
+ fds.unlinked(path, linked);
6202
6361
  },
6203
6362
  path_rename: (fd, p, l, fd2, p2, l2) => {
6204
6363
  const from = at(fd, p, l);
6205
6364
  const to = at(fd2, p2, l2);
6206
6365
  fds.flushPath(from);
6366
+ const replaced = fds.holdsUnder(to) ? linkedElsewhere(o.fs, to) : void 0;
6207
6367
  o.fs.rename(from, to);
6208
- fds.renamed(from, to);
6368
+ fds.renamed(from, to, replaced);
6209
6369
  },
6210
6370
  path_symlink: (tp, tl, dirfd, p, l) => void o.fs.symlink(mem.string(tp, tl), at(dirfd, p, l)),
6211
6371
  path_readlink: (dirfd, p, l, buf, len, used) => {
@@ -6214,7 +6374,17 @@ var init_wasi_host = __esm({
6214
6374
  mem.bytes(buf, n).set(target.subarray(0, n));
6215
6375
  mem.view().setUint32(used, n, true);
6216
6376
  },
6217
- 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
+ }
6218
6388
  };
6219
6389
  }
6220
6390
  socket(fd) {
@@ -6418,7 +6588,7 @@ var init_wasi_host = __esm({
6418
6588
  v.setBigUint64(ptr, 1n, true);
6419
6589
  v.setBigUint64(ptr + 8, f.ino, true);
6420
6590
  v.setUint8(ptr + 16, f.filetype);
6421
- v.setBigUint64(ptr + 24, 1n, true);
6591
+ v.setBigUint64(ptr + 24, f.nlink ?? 1n, true);
6422
6592
  v.setBigUint64(ptr + 32, f.size, true);
6423
6593
  v.setBigUint64(ptr + 40, f.mtimeNs, true);
6424
6594
  v.setBigUint64(ptr + 48, f.mtimeNs, true);
@@ -7222,6 +7392,13 @@ function procfsBridge(fs, tree) {
7222
7392
  () => fs.rename(from, to)
7223
7393
  ),
7224
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
+ ),
7225
7402
  hold: (p, held, open, handle) => {
7226
7403
  if (handle !== void 0 && snapshots.delete(handle)) return void 0;
7227
7404
  if (!held) return fs.hold?.(p, held, open, handle);
@@ -11019,6 +11196,7 @@ function syncPathOps(kernel, resolve, statOf, open) {
11019
11196
  rm: (path) => void kernel.call({ op: "rm", path: resolve(path) }),
11020
11197
  unlink: (path) => void kernel.call({ op: "unlink", path: resolve(path) }),
11021
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) }),
11022
11200
  symlink: (target, path) => void kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
11023
11201
  readlink: (path) => String(kernel.json({ op: "readlink", path: resolve(path) })),
11024
11202
  readFile(path) {
@@ -11615,6 +11793,7 @@ function pathOps(kernel, resolve, statOf, open) {
11615
11793
  rm: async (path) => void await kernel.call({ op: "rm", path: resolve(path) }),
11616
11794
  unlink: async (path) => void await kernel.call({ op: "unlink", path: resolve(path) }),
11617
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) }),
11618
11797
  symlink: async (target, path) => void await kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
11619
11798
  readlink: async (path) => String(await kernel.json({ op: "readlink", path: resolve(path) })),
11620
11799
  async readFile(path) {