@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.
@@ -611,6 +611,7 @@ function cachingBridge(bridge, now = () => performance.now(), shared) {
611
611
  mkdir: (p) => mutating(() => bridge.mkdir(p), ...entry(p)),
612
612
  rm: (p) => mutating(() => bridge.rm(p)),
613
613
  rename: (from, to) => mutating(() => bridge.rename(from, to)),
614
+ link: (from, to, follow2) => mutating(() => bridge.link(from, to, follow2)),
614
615
  unlink: (p) => mutating(() => bridge.unlink(p)),
615
616
  rmdir: (p) => mutating(() => bridge.rmdir(p)),
616
617
  symlink: (target, link) => mutating(() => bridge.symlink(target, link), ...entry(link)),
@@ -1218,6 +1219,8 @@ var init_wasi_fds = __esm({
1218
1219
  WasiFds = class {
1219
1220
  table = /* @__PURE__ */ new Map();
1220
1221
  buffers = /* @__PURE__ */ new Map();
1222
+ byId = /* @__PURE__ */ new Map();
1223
+ ids = /* @__PURE__ */ new Map();
1221
1224
  kernel;
1222
1225
  fs;
1223
1226
  constructor(kernel, fs) {
@@ -1467,7 +1470,7 @@ var init_wasi_fds = __esm({
1467
1470
  }
1468
1471
  for (const file of promoted.keys()) {
1469
1472
  if (--file.buffer.opens <= 0) {
1470
- this.buffers.delete(file.path);
1473
+ this.forget(file.buffer);
1471
1474
  this.letGo(file.buffer);
1472
1475
  }
1473
1476
  }
@@ -1538,9 +1541,48 @@ var init_wasi_fds = __esm({
1538
1541
  const { buffer } = e.file;
1539
1542
  buffer.flush();
1540
1543
  if (--buffer.opens > 0) return;
1541
- if (this.buffers.get(buffer.path) === buffer) this.buffers.delete(buffer.path);
1544
+ this.forget(buffer);
1542
1545
  this.letGo(buffer);
1543
1546
  }
1547
+ forget(buffer) {
1548
+ for (const [p, b] of [...this.buffers]) if (b === buffer) this.buffers.delete(p);
1549
+ const id = this.ids.get(buffer);
1550
+ if (id !== void 0 && this.byId.get(id) === buffer) this.byId.delete(id);
1551
+ this.ids.delete(buffer);
1552
+ }
1553
+ know(buffer, id) {
1554
+ if (this.ids.has(buffer)) return;
1555
+ this.ids.set(buffer, id);
1556
+ if (!this.byId.has(id)) this.byId.set(id, buffer);
1557
+ }
1558
+ identity(st) {
1559
+ if (!st || st.ino === void 0 || (st.nlink ?? 1) < 2) return void 0;
1560
+ return `${st.dev ?? 0}:${st.ino}`;
1561
+ }
1562
+ sharing(path, st) {
1563
+ if (st?.ino === void 0) return void 0;
1564
+ const id = `${st.dev ?? 0}:${st.ino}`;
1565
+ if (!this.byId.has(id) && this.identity(st) !== void 0) this.learn();
1566
+ const buffer = this.byId.get(id);
1567
+ if (!buffer) return void 0;
1568
+ this.buffers.set(path, buffer);
1569
+ return buffer;
1570
+ }
1571
+ learn() {
1572
+ for (const [p, buffer] of this.buffers) {
1573
+ if (this.ids.has(buffer) || buffer.isOrphan()) continue;
1574
+ const known = this.identity(this.statOrMissing(p));
1575
+ if (known !== void 0) this.know(buffer, known);
1576
+ }
1577
+ }
1578
+ linked(from, to) {
1579
+ const buffer = this.buffers.get(from);
1580
+ if (!buffer || buffer.isOrphan()) return;
1581
+ const id = this.identity(this.statOrMissing(to));
1582
+ if (id === void 0) return;
1583
+ this.know(buffer, id);
1584
+ if (!this.buffers.has(to)) this.buffers.set(to, buffer);
1585
+ }
1544
1586
  letGo(buffer) {
1545
1587
  this.fs.hold?.(buffer.path, false, true, buffer.handle);
1546
1588
  }
@@ -1566,15 +1608,21 @@ var init_wasi_fds = __esm({
1566
1608
  for (const [p, buffer] of this.buffers) if (within(p, path)) buffer.flush();
1567
1609
  if (this.shared) this.kernel.call({ op: "fd-path-flush", path });
1568
1610
  }
1569
- unlinking(path) {
1570
- this.buffers.get(path)?.keep();
1611
+ holdsUnder(path) {
1612
+ for (const p of this.buffers.keys()) if (within(p, path)) return true;
1613
+ return false;
1614
+ }
1615
+ unlinking(path, linked) {
1616
+ const buffer = this.buffers.get(path);
1617
+ if (buffer && this.survives(buffer, linked)) buffer.flush();
1618
+ else buffer?.keep();
1571
1619
  if (this.shared) this.kernel.call({ op: "fd-path-unlinking", path });
1572
1620
  }
1573
- unlinked(path) {
1621
+ unlinked(path, linked) {
1574
1622
  if (this.shared) this.kernel.call({ op: "fd-path-unlinked", path });
1575
1623
  const buffer = this.buffers.get(path);
1576
1624
  if (!buffer) return;
1577
- buffer.orphan();
1625
+ this.detach(buffer, linked);
1578
1626
  this.buffers.delete(path);
1579
1627
  }
1580
1628
  inoOf(path) {
@@ -1584,7 +1632,19 @@ var init_wasi_fds = __esm({
1584
1632
  return void 0;
1585
1633
  }
1586
1634
  }
1587
- renamed(from, to) {
1635
+ survives(buffer, linked) {
1636
+ return linked !== void 0 && buffer.handle !== void 0;
1637
+ }
1638
+ detach(buffer, linked) {
1639
+ if (this.survives(buffer, linked)) {
1640
+ const id = this.identity(linked);
1641
+ if (id !== void 0) this.know(buffer, id);
1642
+ return;
1643
+ }
1644
+ buffer.orphan();
1645
+ this.forget(buffer);
1646
+ }
1647
+ renamed(from, to, replacedLinked) {
1588
1648
  if (from === to) return;
1589
1649
  if (this.shared) this.kernel.call({ op: "fd-path-renamed", from, to });
1590
1650
  for (const e of this.table.values()) {
@@ -1596,14 +1656,15 @@ var init_wasi_fds = __esm({
1596
1656
  for (const [p, buffer] of this.buffers) {
1597
1657
  if (within(p, from)) moved.push([p, buffer]);
1598
1658
  else if (within(p, to)) {
1599
- buffer.orphan();
1659
+ this.detach(buffer, replacedLinked);
1600
1660
  this.buffers.delete(p);
1601
1661
  }
1602
1662
  }
1663
+ for (const [p] of moved) this.buffers.delete(p);
1603
1664
  for (const [p, buffer] of moved) {
1604
- this.buffers.delete(p);
1605
- buffer.path = to + p.slice(from.length);
1606
- this.buffers.set(buffer.path, buffer);
1665
+ const next2 = to + p.slice(from.length);
1666
+ if (buffer.path === p) buffer.path = next2;
1667
+ this.buffers.set(next2, buffer);
1607
1668
  }
1608
1669
  }
1609
1670
  resolve(dirfd, path) {
@@ -1646,6 +1707,7 @@ var init_wasi_fds = __esm({
1646
1707
  this.buffers.delete(path);
1647
1708
  buffer = void 0;
1648
1709
  }
1710
+ buffer ??= this.sharing(path, existing);
1649
1711
  if (buffer) {
1650
1712
  if (oflags & OFLAGS.TRUNC) buffer.truncate(0);
1651
1713
  } else {
@@ -1659,6 +1721,8 @@ var init_wasi_fds = __esm({
1659
1721
  buffer = new FileBuffer(io, path, empty, st.maxFile, ranged, st.version);
1660
1722
  buffer.handle = handle;
1661
1723
  this.buffers.set(path, buffer);
1724
+ const id = this.identity(st);
1725
+ if (id !== void 0) this.know(buffer, id);
1662
1726
  }
1663
1727
  buffer.opens++;
1664
1728
  return new LocalFile(buffer, readable2, writable2, (fdflags & FDFLAGS.APPEND) !== 0);
@@ -1992,6 +2056,16 @@ var init_wasi_termios = __esm({
1992
2056
  });
1993
2057
 
1994
2058
  // src/process/wasi/wasi-host.ts
2059
+ function linkedOf(st) {
2060
+ return (st.nlink ?? 1) > 1 ? st : void 0;
2061
+ }
2062
+ function linkedElsewhere(fs, path) {
2063
+ try {
2064
+ return linkedOf(fs.lstat(path));
2065
+ } catch {
2066
+ return void 0;
2067
+ }
2068
+ }
1995
2069
  function filetypeOf(s) {
1996
2070
  if (!s) return FILETYPE.UNKNOWN;
1997
2071
  if (s.isSymbolicLink) return FILETYPE.SYMBOLIC_LINK;
@@ -2003,6 +2077,7 @@ function filestatOf(path, s) {
2003
2077
  filetype: filetypeOf(s),
2004
2078
  size: BigInt(s.size),
2005
2079
  ino: s.ino !== void 0 ? BigInt(s.ino) : pathInode(path),
2080
+ ...s.nlink !== void 0 ? { nlink: BigInt(s.nlink) } : {},
2006
2081
  mtimeNs: BigInt(Math.round(s.mtimeMs ?? 0)) * NS_PER_MS
2007
2082
  };
2008
2083
  }
@@ -2053,6 +2128,7 @@ var WasiExit, IDS, NS_PER_MS, AT_SYMLINK_NOFOLLOW, S_IFIFO, DEFAULT_UMASK, MONOT
2053
2128
  var init_wasi_host = __esm({
2054
2129
  "src/process/wasi/wasi-host.ts"() {
2055
2130
  "use strict";
2131
+ init_physical_path();
2056
2132
  init_cred();
2057
2133
  init_wasi_abi();
2058
2134
  init_wasi_fds();
@@ -2362,17 +2438,20 @@ var init_wasi_host = __esm({
2362
2438
  path_remove_directory: (dirfd, p, l) => void o.fs.rmdir(at(dirfd, p, l)),
2363
2439
  path_unlink_file: (dirfd, p, l) => {
2364
2440
  const path = at(dirfd, p, l);
2365
- if (o.fs.lstat(path).isDirectory) throw new WasiError("EISDIR");
2366
- fds.unlinking(path);
2441
+ const st = o.fs.lstat(path);
2442
+ if (st.isDirectory) throw new WasiError("EISDIR");
2443
+ const linked = linkedOf(st);
2444
+ fds.unlinking(path, linked);
2367
2445
  o.fs.unlink(path);
2368
- fds.unlinked(path);
2446
+ fds.unlinked(path, linked);
2369
2447
  },
2370
2448
  path_rename: (fd, p, l, fd2, p2, l2) => {
2371
2449
  const from = at(fd, p, l);
2372
2450
  const to = at(fd2, p2, l2);
2373
2451
  fds.flushPath(from);
2452
+ const replaced = fds.holdsUnder(to) ? linkedElsewhere(o.fs, to) : void 0;
2374
2453
  o.fs.rename(from, to);
2375
- fds.renamed(from, to);
2454
+ fds.renamed(from, to, replaced);
2376
2455
  },
2377
2456
  path_symlink: (tp, tl, dirfd, p, l) => void o.fs.symlink(mem.string(tp, tl), at(dirfd, p, l)),
2378
2457
  path_readlink: (dirfd, p, l, buf, len, used) => {
@@ -2381,7 +2460,17 @@ var init_wasi_host = __esm({
2381
2460
  mem.bytes(buf, n).set(target.subarray(0, n));
2382
2461
  mem.view().setUint32(used, n, true);
2383
2462
  },
2384
- path_link: () => E.NOTSUP
2463
+ path_link: (fd, lookup, p, l, fd2, p2, l2) => {
2464
+ const from = at(fd, p, l);
2465
+ const to = at(fd2, p2, l2);
2466
+ const follow2 = (lookup & LOOKUP_SYMLINK_FOLLOW) !== 0;
2467
+ const source = follow2 ? physicalPath(o.fs, from) : from;
2468
+ fds.flushPath(from);
2469
+ if (source !== from) fds.flushPath(source);
2470
+ o.fs.link(from, to, follow2);
2471
+ fds.linked(from, to);
2472
+ if (source !== from) fds.linked(source, to);
2473
+ }
2385
2474
  };
2386
2475
  }
2387
2476
  socket(fd) {
@@ -2585,7 +2674,7 @@ var init_wasi_host = __esm({
2585
2674
  v.setBigUint64(ptr, 1n, true);
2586
2675
  v.setBigUint64(ptr + 8, f.ino, true);
2587
2676
  v.setUint8(ptr + 16, f.filetype);
2588
- v.setBigUint64(ptr + 24, 1n, true);
2677
+ v.setBigUint64(ptr + 24, f.nlink ?? 1n, true);
2589
2678
  v.setBigUint64(ptr + 32, f.size, true);
2590
2679
  v.setBigUint64(ptr + 40, f.mtimeNs, true);
2591
2680
  v.setBigUint64(ptr + 48, f.mtimeNs, true);
@@ -3350,10 +3439,12 @@ function parseSyncFsStat(json) {
3350
3439
  ...typeof s.atimeMs === "number" ? { atimeMs: s.atimeMs } : {},
3351
3440
  ...typeof s.ctimeMs === "number" ? { ctimeMs: s.ctimeMs } : {},
3352
3441
  ...typeof s.ino === "number" ? { ino: s.ino } : {},
3442
+ ...typeof s.nlink === "number" ? { nlink: s.nlink } : {},
3353
3443
  ...typeof s.dev === "number" ? { dev: s.dev } : {},
3354
3444
  ...s.readonly === true ? { readonly: true } : {},
3355
3445
  ...typeof s.maxFile === "number" ? { maxFile: s.maxFile } : {},
3356
3446
  ...s.ranged === true ? { ranged: true } : {},
3447
+ ...s.linkable === true ? { linkable: true } : {},
3357
3448
  ...typeof s.version === "string" ? { version: s.version } : {},
3358
3449
  ...typeof s.uid === "number" ? { uid: s.uid } : {},
3359
3450
  ...typeof s.gid === "number" ? { gid: s.gid } : {}
@@ -3374,6 +3465,7 @@ var init_sync_fs_wire = __esm({
3374
3465
  "mkdir",
3375
3466
  "rm",
3376
3467
  "rename",
3468
+ "link",
3377
3469
  "unlink",
3378
3470
  "rmdir",
3379
3471
  "symlink",
@@ -4390,6 +4482,9 @@ function createSyncFsSabBridge(transport, opts = {}) {
4390
4482
  rename: (from, to) => {
4391
4483
  run({ op: "rename", path: from, arg2: to }, from);
4392
4484
  },
4485
+ link: (from, to, follow2) => {
4486
+ run({ op: "link", path: from, arg2: to, ...follow2 ? { mode: 1 } : {} }, from);
4487
+ },
4393
4488
  unlink: (path) => {
4394
4489
  run({ op: "unlink", path }, path);
4395
4490
  },
@@ -4593,6 +4688,7 @@ function syncPathOps(kernel, resolve, statOf, open) {
4593
4688
  rm: (path) => void kernel.call({ op: "rm", path: resolve(path) }),
4594
4689
  unlink: (path) => void kernel.call({ op: "unlink", path: resolve(path) }),
4595
4690
  rename: (from, to) => void kernel.call({ op: "rename", path: resolve(from), arg2: resolve(to) }),
4691
+ link: (from, to) => void kernel.call({ op: "link", path: resolve(from), arg2: resolve(to) }),
4596
4692
  symlink: (target, path) => void kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
4597
4693
  readlink: (path) => String(kernel.json({ op: "readlink", path: resolve(path) })),
4598
4694
  readFile(path) {
@@ -5189,6 +5285,7 @@ function pathOps(kernel, resolve, statOf, open) {
5189
5285
  rm: async (path) => void await kernel.call({ op: "rm", path: resolve(path) }),
5190
5286
  unlink: async (path) => void await kernel.call({ op: "unlink", path: resolve(path) }),
5191
5287
  rename: async (from, to) => void await kernel.call({ op: "rename", path: resolve(from), arg2: resolve(to) }),
5288
+ link: async (from, to) => void await kernel.call({ op: "link", path: resolve(from), arg2: resolve(to) }),
5192
5289
  symlink: async (target, path) => void await kernel.call({ op: "symlink", path: resolve(path), arg2: target }),
5193
5290
  readlink: async (path) => String(await kernel.json({ op: "readlink", path: resolve(path) })),
5194
5291
  async readFile(path) {
@@ -5440,11 +5537,54 @@ function toMs(v) {
5440
5537
  if (v === void 0 || v === null) return void 0;
5441
5538
  return v instanceof Date ? v.getTime() : v;
5442
5539
  }
5540
+ function linkedStates(bridgeOf) {
5541
+ const opens = /* @__PURE__ */ new Map();
5542
+ const byId = /* @__PURE__ */ new Map();
5543
+ const idOf2 = (st) => st?.ino === void 0 || (st.nlink ?? 1) < 2 ? void 0 : `${st.dev}:${st.ino}`;
5544
+ function know(s, id) {
5545
+ s.id = id;
5546
+ if (!byId.has(id)) byId.set(id, s);
5547
+ }
5548
+ function opening(node, on) {
5549
+ const s = node.live;
5550
+ if (!on) {
5551
+ opens.delete(s);
5552
+ if (s.id !== void 0 && byId.get(s.id) === s) byId.delete(s.id);
5553
+ s.id = void 0;
5554
+ return;
5555
+ }
5556
+ opens.set(s, node);
5557
+ const id = idOf2(s.stat);
5558
+ if (id !== void 0) know(s, id);
5559
+ }
5560
+ function sharedWith(node) {
5561
+ const st = node.live.stat;
5562
+ if (st?.ino === void 0) return void 0;
5563
+ const id = `${st.dev}:${st.ino}`;
5564
+ if (!byId.has(id) && idOf2(st) !== void 0) {
5565
+ for (const [s2, other] of opens) {
5566
+ if (s2.id !== void 0 || s2.orphan) continue;
5567
+ let st2;
5568
+ try {
5569
+ st2 = bridgeOf(other).lstat(liveNodePath(other));
5570
+ } catch {
5571
+ }
5572
+ const known = idOf2(st2);
5573
+ if (known !== void 0) know(s2, known);
5574
+ }
5575
+ }
5576
+ const s = byId.get(id);
5577
+ return s && s !== node.live && !stale(s) ? s : void 0;
5578
+ }
5579
+ const holder = (node) => opens.get(node.live) ?? node;
5580
+ return { opening, sharedWith, know, idOf: idOf2, holder };
5581
+ }
5443
5582
  function createHelpers(Fs, ops) {
5444
5583
  const bridgeOf = (node) => node.mount.opts.bridge;
5445
5584
  const fileIo = (node) => pinnedIo2(bridgeOf(node), node.live.handle);
5446
5585
  const dirty = /* @__PURE__ */ new Set();
5447
5586
  const handled = /* @__PURE__ */ new Set();
5587
+ const linked = linkedStates(bridgeOf);
5448
5588
  function markDirty(node) {
5449
5589
  node.live.dirty = true;
5450
5590
  dirty.add(node);
@@ -5452,7 +5592,7 @@ function createHelpers(Fs, ops) {
5452
5592
  function flushDirty() {
5453
5593
  for (const node of [...dirty]) {
5454
5594
  const s = node.live;
5455
- if (!s.dirty || s.orphan) {
5595
+ if (!s.dirty || stale(s)) {
5456
5596
  dirty.delete(node);
5457
5597
  continue;
5458
5598
  }
@@ -5494,7 +5634,7 @@ function createHelpers(Fs, ops) {
5494
5634
  return { ...node.live.stat, size };
5495
5635
  }
5496
5636
  if (!node.live.stat) {
5497
- const st = call2(() => bridgeOf(node).lstat(liveNodePath(node)));
5637
+ const st = call2(() => bridgeOf(node).lstat(liveNodePath(linked.holder(node))));
5498
5638
  node.live.stat = st;
5499
5639
  node.mode = modeFromStat(st);
5500
5640
  }
@@ -5507,7 +5647,7 @@ function createHelpers(Fs, ops) {
5507
5647
  function ensureLoaded(node) {
5508
5648
  const s = node.live;
5509
5649
  if (s.loaded) return;
5510
- const bytes = call2(() => fileIo(node).readFile(liveNodePath(node)));
5650
+ const bytes = call2(() => fileIo(node).readFile(liveNodePath(linked.holder(node))));
5511
5651
  s.data = bytes;
5512
5652
  s.len = bytes.length;
5513
5653
  s.loaded = true;
@@ -5516,7 +5656,7 @@ function createHelpers(Fs, ops) {
5516
5656
  const s = node.live;
5517
5657
  if (s.ranged || !s.ranges) return s.ranged;
5518
5658
  const bridge = bridgeOf(node);
5519
- const path = () => liveNodePath(node);
5659
+ const path = () => liveNodePath(linked.holder(node));
5520
5660
  const file = () => fileIo(node);
5521
5661
  const io = {
5522
5662
  pread: (_, at, n, version) => file().pread(path(), at, n, version),
@@ -5538,11 +5678,11 @@ function createHelpers(Fs, ops) {
5538
5678
  }
5539
5679
  function flushNode(node) {
5540
5680
  const s = node.live;
5541
- if (!s.dirty || s.orphan || !(s.data || s.ranged)) return;
5681
+ if (!s.dirty || stale(s) || !(s.data || s.ranged)) return;
5542
5682
  if (s.ranged) call2(() => s.ranged?.flush());
5543
5683
  else {
5544
5684
  const bytes = s.data.slice(0, s.len);
5545
- call2(() => fileIo(node).writeFile(liveNodePath(node), bytes, true));
5685
+ call2(() => fileIo(node).writeFile(liveNodePath(linked.holder(node)), bytes, true));
5546
5686
  }
5547
5687
  s.dirty = false;
5548
5688
  s.stat = void 0;
@@ -5590,6 +5730,7 @@ function createHelpers(Fs, ops) {
5590
5730
  Fs,
5591
5731
  bridgeOf,
5592
5732
  handled,
5733
+ linked,
5593
5734
  call: call2,
5594
5735
  metadataCall,
5595
5736
  makeNode,
@@ -5604,6 +5745,33 @@ function createHelpers(Fs, ops) {
5604
5745
  truncate
5605
5746
  };
5606
5747
  }
5748
+ function freshStat(h, node) {
5749
+ const { statOf } = h;
5750
+ const st = statOf(node);
5751
+ if (!st.linkable) return st;
5752
+ node.live.stat = void 0;
5753
+ return statOf(node);
5754
+ }
5755
+ function orphaned(h, node, st) {
5756
+ const { bridgeOf } = h;
5757
+ const s = node.live;
5758
+ const nlink = st.nlink ?? 1;
5759
+ let kept = false;
5760
+ try {
5761
+ kept = nlink > 1 && s.handle !== void 0 && bridgeOf(node).kept?.(s.handle) === true;
5762
+ } catch {
5763
+ }
5764
+ if (nlink > 1 && !kept && h.linked.holder(node) !== node) {
5765
+ s.stat = void 0;
5766
+ return;
5767
+ }
5768
+ s.orphan = true;
5769
+ if (!kept) return;
5770
+ s.kept = true;
5771
+ s.stat = { ...st, nlink: nlink - 1 };
5772
+ const id = h.linked.idOf(st);
5773
+ if (id !== void 0) h.linked.know(s, id);
5774
+ }
5607
5775
  function createNodeOps(h) {
5608
5776
  const { Fs, bridgeOf, call: call2, metadataCall, makeNode, statOf, childPath, flushNode, truncate } = h;
5609
5777
  const { ensureLoaded } = h;
@@ -5634,7 +5802,7 @@ function createNodeOps(h) {
5634
5802
  dev: st.dev ?? 1,
5635
5803
  ino: st.ino ?? inodeOf(liveNodePath(node)),
5636
5804
  mode: node.mode,
5637
- nlink: 1,
5805
+ nlink: st.nlink ?? 1,
5638
5806
  uid: owned ? st.uid ?? 0 : 0,
5639
5807
  gid: owned ? st.gid ?? 0 : 0,
5640
5808
  rdev: 0,
@@ -5729,14 +5897,14 @@ function createNodeOps(h) {
5729
5897
  if (node && node.live?.openCount > 0 && Fs.isFile(node.mode)) open = node;
5730
5898
  } catch {
5731
5899
  }
5900
+ const st = open && freshStat(h, open);
5732
5901
  if (open) {
5733
- statOf(open);
5734
5902
  const ranged = h.rangedOf(open);
5735
5903
  if (ranged) ranged.pin();
5736
5904
  else ensureLoaded(open);
5737
5905
  }
5738
5906
  call2(() => bridgeOf(parent).unlink(childPath(parent, name2)));
5739
- if (open) open.live.orphan = true;
5907
+ if (open && st) orphaned(h, open, st);
5740
5908
  },
5741
5909
  rmdir(parent, name2) {
5742
5910
  parent.live.listed = void 0;
@@ -5784,6 +5952,7 @@ function createStreamOps(h) {
5784
5952
  try {
5785
5953
  const handle = bridgeOf(node).hold?.(liveNodePath(node), on, true, s.handle);
5786
5954
  s.handle = on ? handle : void 0;
5955
+ h.linked.opening(node, on);
5787
5956
  if (s.handle === void 0) h.handled.delete(node);
5788
5957
  else h.handled.add(node);
5789
5958
  } catch {
@@ -5796,7 +5965,9 @@ function createStreamOps(h) {
5796
5965
  if (s.openCount === 0 && !s.orphan) {
5797
5966
  const bridge = bridgeOf(stream.node);
5798
5967
  s.ranges = !!(bridge.pread && bridge.pwrite && bridge.truncate && statOf(stream.node).ranged);
5799
- rangedOf(stream.node);
5968
+ const shared = h.linked.sharedWith(stream.node);
5969
+ if (shared) stream.node.live = shared;
5970
+ else rangedOf(stream.node);
5800
5971
  }
5801
5972
  if ((stream.flags & 3) !== 0) {
5802
5973
  try {
@@ -6118,7 +6289,7 @@ function useLiveChown(Fs) {
6118
6289
  Fs.chown(target, uid, gid);
6119
6290
  };
6120
6291
  }
6121
- var S_IFDIR, S_IFREG, S_IFLNK, PERM_MASK, DEFAULT_DIR_PERM, UMASK, SEEK_CUR, SEEK_END, EARLY_WHOLE_FILE;
6292
+ var S_IFDIR, S_IFREG, S_IFLNK, PERM_MASK, DEFAULT_DIR_PERM, UMASK, SEEK_CUR, SEEK_END, EARLY_WHOLE_FILE, stale;
6122
6293
  var init_live_vfs_fs = __esm({
6123
6294
  "src/realm/live-vfs-fs.ts"() {
6124
6295
  "use strict";
@@ -6134,6 +6305,7 @@ var init_live_vfs_fs = __esm({
6134
6305
  SEEK_CUR = 1;
6135
6306
  SEEK_END = 2;
6136
6307
  EARLY_WHOLE_FILE = 64 * 1024;
6308
+ stale = (s) => !!s.orphan && !s.kept;
6137
6309
  }
6138
6310
  });
6139
6311
 
@@ -8248,7 +8420,7 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
8248
8420
  const pidContent = (target, name2, own = false) => () => {
8249
8421
  const listing = procList();
8250
8422
  const info = find(listing.processes, target, own);
8251
- if (!info) throw error(ESRCH);
8423
+ if (!info) throw error();
8252
8424
  return pidFile(name2, info, listing.boot);
8253
8425
  };
8254
8426
  const pidNames = (names) => [.../* @__PURE__ */ new Set([...names, ...PID_FILES])];
@@ -8261,14 +8433,29 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
8261
8433
  dir.node_ops.readdir = () => [".", "..", ...PID_FILES];
8262
8434
  return dir;
8263
8435
  };
8436
+ const scans = /* @__PURE__ */ new Map();
8437
+ const listed = (target) => {
8438
+ let found = false;
8439
+ for (const [stream, pids] of scans) {
8440
+ if (Fs.streams && Fs.streams[stream.fd ?? -1] !== stream) scans.delete(stream);
8441
+ else found ||= pids.has(target);
8442
+ }
8443
+ return found;
8444
+ };
8264
8445
  const procOps = proc.node_ops;
8265
8446
  const lookup = procOps.lookup?.bind(procOps);
8266
8447
  const readdir = procOps.readdir?.bind(procOps);
8448
+ const listAll = (node) => {
8449
+ const names = new Set(readdir ? readdir(node) : [".", ".."]);
8450
+ for (const name2 of [...SYSTEM_FILES, "mounts"]) names.add(name2);
8451
+ for (const p of procList().processes) names.add(String(p.pid));
8452
+ return [...names];
8453
+ };
8267
8454
  proc.node_ops = {
8268
8455
  ...procOps,
8269
8456
  lookup: (parent, name2) => {
8270
8457
  if (/^[1-9]\d*$/.test(name2)) {
8271
- if (!infoOf(Number(name2))) throw error();
8458
+ if (!infoOf(Number(name2)) && !listed(Number(name2))) throw error();
8272
8459
  return pidDir(name2, Number(name2));
8273
8460
  }
8274
8461
  if (name2 === "mounts") return file(parent, name2, () => mountTable(sys.mountList?.() ?? []));
@@ -8282,11 +8469,36 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
8282
8469
  if (!lookup) throw error();
8283
8470
  return lookup(parent, name2);
8284
8471
  },
8285
- readdir: (node) => {
8286
- const names = new Set(readdir ? readdir(node) : [".", ".."]);
8287
- for (const name2 of [...SYSTEM_FILES, "mounts"]) names.add(name2);
8288
- for (const p of procList().processes) names.add(String(p.pid));
8289
- return [...names];
8472
+ readdir: (node) => listAll(node)
8473
+ };
8474
+ const track = (stream, names) => {
8475
+ let current = names;
8476
+ const remember = (list) => scans.set(stream, new Set(list.filter((n) => /^[1-9]\d*$/.test(n)).map(Number)));
8477
+ remember(current ?? []);
8478
+ Object.defineProperty(stream, "getdents", {
8479
+ configurable: true,
8480
+ enumerable: true,
8481
+ get: () => current,
8482
+ set: (next2) => {
8483
+ current = next2;
8484
+ if (next2) remember(next2);
8485
+ }
8486
+ });
8487
+ };
8488
+ const dirOps = proc.stream_ops ?? {};
8489
+ proc.stream_ops = {
8490
+ ...dirOps,
8491
+ open: (stream) => {
8492
+ dirOps.open?.(stream);
8493
+ track(stream, listAll(proc));
8494
+ },
8495
+ dup: (stream) => {
8496
+ dirOps.dup?.(stream);
8497
+ track(stream, stream.getdents);
8498
+ },
8499
+ close: (stream) => {
8500
+ scans.delete(stream);
8501
+ dirOps.close?.(stream);
8290
8502
  }
8291
8503
  };
8292
8504
  hashRemoveNode.call(Fs, self);
@@ -8310,7 +8522,7 @@ function useProcfs(Fs, sys, pid, memory = memorySource()) {
8310
8522
  readdir: (node) => pidNames(selfReaddir ? selfReaddir(node) : [".", ".."])
8311
8523
  };
8312
8524
  }
8313
- var HZ, DIR_MODE2, FILE_MODE, LINK_MODE2, ENOENT, ESRCH, EINVAL, UID2, KIB, FALLBACK_MEMORY, PID_FILES, SYSTEM_FILES, sigMask;
8525
+ var HZ, DIR_MODE2, FILE_MODE, LINK_MODE2, ENOENT, EINVAL, UID2, KIB, FALLBACK_MEMORY, PID_FILES, SYSTEM_FILES, sigMask;
8314
8526
  var init_procfs = __esm({
8315
8527
  "src/process/procfs.ts"() {
8316
8528
  "use strict";
@@ -8321,7 +8533,6 @@ var init_procfs = __esm({
8321
8533
  FILE_MODE = 33060;
8322
8534
  LINK_MODE2 = 41471;
8323
8535
  ENOENT = 44;
8324
- ESRCH = 71;
8325
8536
  EINVAL = 28;
8326
8537
  UID2 = 1e3;
8327
8538
  KIB = 1024;
@@ -9135,6 +9346,13 @@ function procfsBridge(fs, tree) {
9135
9346
  () => fs.rename(from, to)
9136
9347
  ),
9137
9348
  symlink: (target, link) => readOnly([[link, false]], () => fs.symlink(target, link)),
9349
+ link: (from, to, follow2) => readOnly(
9350
+ [
9351
+ [from, follow2 === true],
9352
+ [to, false]
9353
+ ],
9354
+ () => fs.link(from, to, follow2)
9355
+ ),
9138
9356
  hold: (p, held, open, handle) => {
9139
9357
  if (handle !== void 0 && snapshots.delete(handle)) return void 0;
9140
9358
  if (!held) return fs.hold?.(p, held, open, handle);