@ai-ecoverse/slicc-kernel 1.56.2 → 1.57.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.
@@ -786,6 +786,7 @@ function createNodeOps(h) {
786
786
  return {
787
787
  getattr(node) {
788
788
  const st = statOf(node);
789
+ const owned = bridgeOf(node).owners === true;
789
790
  const s = node.live;
790
791
  const size = Fs.isDir(node.mode) ? 4096 : s.ranged?.size() ?? (s.loaded ? s.len : st.size);
791
792
  const mtime = new Date(st.mtimeMs ?? 0);
@@ -794,8 +795,8 @@ function createNodeOps(h) {
794
795
  ino: st.ino ?? inodeOf(liveNodePath(node)),
795
796
  mode: node.mode,
796
797
  nlink: 1,
797
- uid: 0,
798
- gid: 0,
798
+ uid: owned ? st.uid ?? 0 : 0,
799
+ gid: owned ? st.gid ?? 0 : 0,
799
800
  rdev: 0,
800
801
  size,
801
802
  atime: new Date(st.atimeMs ?? mtime.getTime()),
@@ -838,14 +839,17 @@ function createNodeOps(h) {
838
839
  mknod(parent, name2, mode) {
839
840
  parent.live.listed = void 0;
840
841
  const path = childPath(parent, name2);
841
- if (Fs.isDir(mode)) {
842
- call2(() => bridgeOf(parent).mkdir(path));
843
- const perm = mode & PERM_MASK & ~UMASK;
844
- if (perm !== DEFAULT_DIR_PERM) metadataCall(() => bridgeOf(parent).chmod(path, perm));
842
+ const bridge = bridgeOf(parent);
843
+ const perm = bridge.owners ? mode & PERM_MASK : void 0;
844
+ if (Fs.isDir(mode) && perm !== void 0) {
845
+ call2(() => bridge.mkdir(path, perm));
846
+ } else if (Fs.isDir(mode)) {
847
+ call2(() => bridge.mkdir(path));
848
+ const masked = mode & PERM_MASK & ~UMASK;
849
+ if (masked !== DEFAULT_DIR_PERM) metadataCall(() => bridge.chmod(path, masked));
845
850
  } else if (Fs.isFile(mode)) {
846
- const bridge = bridgeOf(parent);
847
851
  call2(
848
- () => bridge.create ? bridge.create(path) : bridge.writeFile(path, new Uint8Array(0))
852
+ () => bridge.create ? bridge.create(path, perm) : bridge.writeFile(path, new Uint8Array(0))
849
853
  );
850
854
  } else {
851
855
  throw new Fs.ErrnoError(wasiErrno("EPERM"));
@@ -1248,6 +1252,32 @@ function guardRenames(Fs) {
1248
1252
  rename(from, to);
1249
1253
  };
1250
1254
  }
1255
+ function idOf(value2) {
1256
+ return value2 === -1 || value2 === 4294967295 ? void 0 : value2 >>> 0;
1257
+ }
1258
+ function useLiveChown(Fs) {
1259
+ const chown = Fs.chown?.bind(Fs);
1260
+ if (!chown) return;
1261
+ Fs.chown = (path, uid, gid, dontFollow) => {
1262
+ const node = typeof path === "string" ? Fs.lookupPath(path, { follow: !dontFollow }).node : path;
1263
+ if (!node.live || node.live.orphan) return chown(path, uid, gid, dontFollow);
1264
+ try {
1265
+ node.mount.opts.bridge.chown?.(liveNodePath(node), idOf(uid), idOf(gid));
1266
+ } catch (err) {
1267
+ throw toErrno(Fs, err);
1268
+ }
1269
+ node.live.stat = void 0;
1270
+ if (node.parent?.live) node.parent.live.listed = void 0;
1271
+ };
1272
+ const { getStreamChecked } = Fs;
1273
+ if (!Fs.fchown || !getStreamChecked) return;
1274
+ Fs.fchown = (fd, uid, gid) => {
1275
+ const stream = getStreamChecked.call(Fs, fd);
1276
+ const live = stream.node.live !== void 0;
1277
+ const target = live || stream.path === void 0 ? stream.node : stream.path;
1278
+ Fs.chown(target, uid, gid);
1279
+ };
1280
+ }
1251
1281
  var S_IFDIR, S_IFREG, S_IFLNK, PERM_MASK, DEFAULT_DIR_PERM, UMASK, SEEK_CUR, SEEK_END, EARLY_WHOLE_FILE;
1252
1282
  var init_live_vfs_fs = __esm({
1253
1283
  "src/realm/live-vfs-fs.ts"() {
@@ -1292,6 +1322,7 @@ function mountVfsIntoEmscripten(Fs, deps) {
1292
1322
  const dirs = topLevelDirs(bridge, warn);
1293
1323
  const { plugin, mounted } = mountLiveVfsDirs(Fs, bridge, dirs ?? [], warn);
1294
1324
  if (dirs) liveRoot(Fs, bridge);
1325
+ if (bridge.owners && Fs.lookupPath) useLiveChown(Fs);
1295
1326
  try {
1296
1327
  Fs.chdir(deps.cwd);
1297
1328
  } catch (err) {
@@ -1342,7 +1373,9 @@ function parseSyncFsStat(json) {
1342
1373
  ...s.readonly === true ? { readonly: true } : {},
1343
1374
  ...typeof s.maxFile === "number" ? { maxFile: s.maxFile } : {},
1344
1375
  ...s.ranged === true ? { ranged: true } : {},
1345
- ...typeof s.version === "string" ? { version: s.version } : {}
1376
+ ...typeof s.version === "string" ? { version: s.version } : {},
1377
+ ...typeof s.uid === "number" ? { uid: s.uid } : {},
1378
+ ...typeof s.gid === "number" ? { gid: s.gid } : {}
1346
1379
  };
1347
1380
  }
1348
1381
  var SYNC_FS_OPS, SYNC_FS_REQUEST_TIMEOUT_MS;
@@ -1373,7 +1406,9 @@ var init_sync_fs_wire = __esm({
1373
1406
  "kept",
1374
1407
  "pread",
1375
1408
  "pwrite",
1376
- "truncate"
1409
+ "truncate",
1410
+ "chown",
1411
+ "lchown"
1377
1412
  ];
1378
1413
  SYNC_FS_REQUEST_TIMEOUT_MS = 25e3;
1379
1414
  }
@@ -1595,8 +1630,8 @@ function createSyncFsSabBridge(transport, opts = {}) {
1595
1630
  if (typeof v !== "boolean") throw errnoError("EIO", path);
1596
1631
  return v;
1597
1632
  },
1598
- mkdir: (path) => {
1599
- run({ op: "mkdir", path }, path);
1633
+ mkdir: (path, mode) => {
1634
+ run({ op: "mkdir", path, ...mode !== void 0 ? { mode } : {} }, path);
1600
1635
  },
1601
1636
  rm: (path) => {
1602
1637
  run({ op: "rm", path }, path);
@@ -1627,9 +1662,14 @@ function createSyncFsSabBridge(transport, opts = {}) {
1627
1662
  lutimes: (path, atimeMs, mtimeMs) => {
1628
1663
  run({ op: "lutimes", path, atimeMs, mtimeMs }, path);
1629
1664
  },
1630
- create: (path) => {
1631
- run({ op: "create", path }, path);
1665
+ create: (path, mode) => {
1666
+ run({ op: "create", path, ...mode !== void 0 ? { mode } : {} }, path);
1667
+ },
1668
+ chown: (path, uid, gid) => {
1669
+ const ids = { ...uid !== void 0 ? { uid } : {}, ...gid !== void 0 ? { gid } : {} };
1670
+ run({ op: "lchown", path, ...ids }, path);
1632
1671
  },
1672
+ ...opts.owners ? { owners: true } : {},
1633
1673
  hold: (path, held, open, handle) => {
1634
1674
  const mode = (held ? 1 : 0) | (open ? 2 : 0);
1635
1675
  if (mode === 3) return json({ op: "hold", path, mode }, path);
@@ -4049,6 +4089,9 @@ function reportGrowth(imports, publish) {
4049
4089
  return grown;
4050
4090
  };
4051
4091
  }
4092
+ function callerOwner(Fs, init) {
4093
+ return init.owners ? {} : { owner: ownByCaller(Fs, init.cred, credAbi(init.program.glue)) };
4094
+ }
4052
4095
  async function runWasmProcess(init, port2, deps = {}) {
4053
4096
  const signals = new SignalGate(
4054
4097
  createSyncSabTransport(init.sab, port2, {
@@ -4159,7 +4202,7 @@ async function runWasmProcess(init, port2, deps = {}) {
4159
4202
  const running = module;
4160
4203
  if (!hasStreams(running)) return runStdioOnly(running, init);
4161
4204
  const vfs = mountVfsIntoEmscripten(running.FS, {
4162
- bridge: createSyncFsSabBridge(transport),
4205
+ bridge: createSyncFsSabBridge(transport, init.owners ? { owners: true } : {}),
4163
4206
  cwd: init.cwd,
4164
4207
  warn: deps.warn ?? say(2)
4165
4208
  });
@@ -4201,7 +4244,7 @@ async function runWasmProcess(init, port2, deps = {}) {
4201
4244
  describeFork: () => describeForFork(running.FS, sys, streams, livePath),
4202
4245
  inherit: (actions) => describeInherited(running.FS, sys, streams, livePath, actions),
4203
4246
  stdioPromoter: () => vfsPromoter(running.FS, sys, streams, livePath),
4204
- owner: ownByCaller(running.FS, init.cred, credAbi(init.program.glue))
4247
+ ...callerOwner(running.FS, init)
4205
4248
  });
4206
4249
  running.sliccKernel.http = createHttpKernel(transport);
4207
4250
  running.sliccKernel.hostname = hostnameOf(init.env);
@@ -4512,10 +4555,26 @@ function pathInode(path) {
4512
4555
  }
4513
4556
  return h;
4514
4557
  }
4515
- function cachingBridge(bridge, now = () => performance.now()) {
4516
- const stats = /* @__PURE__ */ new Map();
4517
- const lstats = /* @__PURE__ */ new Map();
4558
+ function inoKey(st) {
4559
+ return "ino" in st && typeof st.ino === "number" ? st.ino % 4294967296 | 0 : 0;
4560
+ }
4561
+ function pathHash(path) {
4562
+ let h = 2166136261;
4563
+ for (let i = 0; i < path.length; i++) h = Math.imul(h ^ path.charCodeAt(i), 16777619);
4564
+ return h === 0 ? 1 : h;
4565
+ }
4566
+ function index(map, key, path) {
4567
+ const paths = map.get(key) ?? /* @__PURE__ */ new Set();
4568
+ paths.add(path);
4569
+ map.set(key, paths);
4570
+ }
4571
+ function cachingBridge(bridge, now = () => performance.now(), shared) {
4572
+ const blind = shared !== void 0 && shared.length * 4 < SHARED_IDS_BYTES;
4573
+ const cache = new StatCache(blind ? void 0 : shared);
4574
+ const { stats, lstats } = cache;
4518
4575
  const cached = (map, path, get) => {
4576
+ if (blind) return get();
4577
+ cache.fresh();
4519
4578
  let hit = map.get(path);
4520
4579
  if (hit === void 0 || "until" in hit && hit.until <= now()) {
4521
4580
  try {
@@ -4523,22 +4582,29 @@ function cachingBridge(bridge, now = () => performance.now()) {
4523
4582
  } catch (e) {
4524
4583
  hit = { error: e, until: now() + MISS_TTL_MS };
4525
4584
  }
4526
- map.set(path, hit);
4585
+ cache.put(map, path, hit);
4527
4586
  }
4528
4587
  if ("until" in hit) throw hit.error;
4529
4588
  return hit;
4530
4589
  };
4531
- const invalidate = () => {
4532
- stats.clear();
4533
- lstats.clear();
4590
+ const invalidate = () => cache.mutated([]);
4591
+ const touched = (path, ino) => cache.changed(path, ino);
4592
+ const mutating = (op, ...paths) => {
4593
+ try {
4594
+ return op();
4595
+ } finally {
4596
+ cache.mutated(paths);
4597
+ }
4534
4598
  };
4535
- const mutating = (op) => {
4599
+ const rewriting = (p, op, ...also) => {
4600
+ const ino = cache.inoOf(p, () => bridge.stat(p));
4536
4601
  try {
4537
4602
  return op();
4538
4603
  } finally {
4539
- invalidate();
4604
+ cache.mutated([p, ...also], ino);
4540
4605
  }
4541
4606
  };
4607
+ const entry = (p) => [p, p.slice(0, p.lastIndexOf("/")) || "/"];
4542
4608
  return {
4543
4609
  readFile: (p) => bridge.readFile(p),
4544
4610
  readdir: (p) => bridge.readdir(p),
@@ -4546,9 +4612,9 @@ function cachingBridge(bridge, now = () => performance.now()) {
4546
4612
  const list = bridge.readdirStat(p);
4547
4613
  const base = p === "/" ? "" : p;
4548
4614
  for (const [name2, st] of list) {
4549
- if (!st) continue;
4550
- lstats.set(`${base}/${name2}`, st);
4551
- if (!st.isSymbolicLink) stats.set(`${base}/${name2}`, st);
4615
+ if (!st || blind) continue;
4616
+ cache.put(lstats, `${base}/${name2}`, st);
4617
+ if (!st.isSymbolicLink) cache.put(stats, `${base}/${name2}`, st);
4552
4618
  }
4553
4619
  return list;
4554
4620
  },
@@ -4563,40 +4629,41 @@ function cachingBridge(bridge, now = () => performance.now()) {
4563
4629
  return false;
4564
4630
  }
4565
4631
  },
4566
- writeFile: (p, bytes, open) => mutating(() => bridge.writeFile(p, bytes, open)),
4567
- mkdir: (p) => mutating(() => bridge.mkdir(p)),
4632
+ writeFile: (p, bytes, open) => rewriting(p, () => bridge.writeFile(p, bytes, open), ...entry(p).slice(1)),
4633
+ mkdir: (p) => mutating(() => bridge.mkdir(p), ...entry(p)),
4568
4634
  rm: (p) => mutating(() => bridge.rm(p)),
4569
4635
  rename: (from, to) => mutating(() => bridge.rename(from, to)),
4570
4636
  unlink: (p) => mutating(() => bridge.unlink(p)),
4571
4637
  rmdir: (p) => mutating(() => bridge.rmdir(p)),
4572
- symlink: (target, link) => mutating(() => bridge.symlink(target, link)),
4573
- chmod: (p, mode) => mutating(() => bridge.chmod(p, mode)),
4574
- utimes: (p, a, m) => mutating(() => bridge.utimes(p, a, m)),
4575
- lutimes: (p, a, m) => mutating(() => bridge.lutimes(p, a, m)),
4638
+ symlink: (target, link) => mutating(() => bridge.symlink(target, link), ...entry(link)),
4639
+ chmod: (p, mode) => rewriting(p, () => bridge.chmod(p, mode)),
4640
+ utimes: (p, a, m) => rewriting(p, () => bridge.utimes(p, a, m)),
4641
+ lutimes: (p, a, m) => mutating(() => bridge.lutimes(p, a, m), p),
4576
4642
  ...bridge.hold ? { hold: bridge.hold.bind(bridge) } : {},
4577
4643
  ...bridge.kept ? { kept: bridge.kept.bind(bridge) } : {},
4578
- ...bridge.pinned ? { pinned: (handle) => pinnedIo2(bridge, handle, mutating) } : {},
4579
- ...bridge.create ? { create: (p) => mutating(() => bridge.create?.(p)) } : {},
4644
+ ...bridge.pinned ? { pinned: (handle) => pinnedIo2(bridge, handle, rewriting) } : {},
4645
+ ...bridge.create ? { create: (p) => mutating(() => bridge.create?.(p), ...entry(p)) } : {},
4580
4646
  ...bridge.pread && bridge.pwrite && bridge.truncate ? {
4581
4647
  pread: bridge.pread.bind(bridge),
4582
- pwrite: (p, at, bytes, transfer) => mutating(() => bridge.pwrite?.(p, at, bytes, transfer)),
4583
- truncate: (p, size) => mutating(() => bridge.truncate?.(p, size))
4648
+ pwrite: (p, at, bytes, transfer) => rewriting(p, () => bridge.pwrite?.(p, at, bytes, transfer)),
4649
+ truncate: (p, size) => rewriting(p, () => bridge.truncate?.(p, size))
4584
4650
  } : {},
4585
- invalidate
4651
+ invalidate,
4652
+ touched
4586
4653
  };
4587
4654
  }
4588
- function pinnedIo2(bridge, handle, mutating) {
4655
+ function pinnedIo2(bridge, handle, rewriting) {
4589
4656
  const io = bridge.pinned(handle);
4590
4657
  const { pread, pwrite, truncate } = io;
4591
4658
  return {
4592
4659
  readFile: (p) => io.readFile(p),
4593
- writeFile: (p, bytes, open) => mutating(() => io.writeFile(p, bytes, open)),
4660
+ writeFile: (p, bytes, open) => rewriting(p, () => io.writeFile(p, bytes, open)),
4594
4661
  pread: (p, at, n, version) => pread(p, at, n, version),
4595
- pwrite: (p, at, bytes, transfer) => mutating(() => pwrite(p, at, bytes, transfer)),
4596
- truncate: (p, size) => mutating(() => truncate(p, size))
4662
+ pwrite: (p, at, bytes, transfer) => rewriting(p, () => pwrite(p, at, bytes, transfer)),
4663
+ truncate: (p, size) => rewriting(p, () => truncate(p, size))
4597
4664
  };
4598
4665
  }
4599
- var WasiError, FileBuffer, LocalFile, MAX_LINKS, MISS_TTL_MS;
4666
+ var WasiError, FileBuffer, LocalFile, MAX_LINKS, MISS_TTL_MS, FS_GEN, RING, RING_SIZE, SLOT, SHARED_IDS_BYTES, StatCache;
4600
4667
  var init_wasi_files = __esm({
4601
4668
  "src/process/wasi/wasi-files.ts"() {
4602
4669
  "use strict";
@@ -4786,6 +4853,110 @@ var init_wasi_files = __esm({
4786
4853
  };
4787
4854
  MAX_LINKS = 40;
4788
4855
  MISS_TTL_MS = 20;
4856
+ FS_GEN = 4;
4857
+ RING = 8;
4858
+ RING_SIZE = 64;
4859
+ SLOT = 3;
4860
+ SHARED_IDS_BYTES = (RING + RING_SIZE * SLOT) * 4;
4861
+ StatCache = class {
4862
+ stats = /* @__PURE__ */ new Map();
4863
+ lstats = /* @__PURE__ */ new Map();
4864
+ byHash = /* @__PURE__ */ new Map();
4865
+ byIno = /* @__PURE__ */ new Map();
4866
+ shared;
4867
+ seen;
4868
+ constructor(shared) {
4869
+ this.shared = shared;
4870
+ this.seen = shared ? Atomics.load(shared, FS_GEN) : 0;
4871
+ }
4872
+ put(map, path, entry) {
4873
+ map.set(path, entry);
4874
+ if (!this.shared) return;
4875
+ index(this.byHash, pathHash(path), path);
4876
+ const ino = inoKey(entry);
4877
+ if (ino !== 0) index(this.byIno, ino, path);
4878
+ }
4879
+ fresh() {
4880
+ if (!this.shared) return;
4881
+ const now = Atomics.load(this.shared, FS_GEN);
4882
+ if (now === this.seen) return;
4883
+ this.replay(now);
4884
+ this.seen = now;
4885
+ }
4886
+ changed(path, ino = 0) {
4887
+ const key = ino % 4294967296 | 0;
4888
+ if (this.shared) {
4889
+ const g = this.publish(this.shared, path === void 0 ? 0 : pathHash(path), key);
4890
+ this.replay(g - 1);
4891
+ this.seen = g;
4892
+ if (path === void 0) this.clear();
4893
+ else this.drop(pathHash(path), key);
4894
+ }
4895
+ if (path === void 0) return;
4896
+ this.stats.delete(path);
4897
+ this.lstats.delete(path);
4898
+ }
4899
+ mutated(paths, ino = 0) {
4900
+ this.clear();
4901
+ if (!this.shared) return;
4902
+ const key = ino % 4294967296 | 0;
4903
+ for (const [i, hash] of (paths.length > 0 ? paths.map(pathHash) : [0]).entries()) {
4904
+ this.seen = this.publish(this.shared, hash, i === 0 ? key : 0);
4905
+ }
4906
+ }
4907
+ inoOf(path, look) {
4908
+ if (!this.shared) return 0;
4909
+ this.fresh();
4910
+ const hit = this.stats.get(path);
4911
+ if (hit && "ino" in hit && typeof hit.ino === "number") return hit.ino;
4912
+ try {
4913
+ return look().ino ?? 0;
4914
+ } catch {
4915
+ return 0;
4916
+ }
4917
+ }
4918
+ publish(shared, hash, ino) {
4919
+ const g = Atomics.add(shared, FS_GEN, 1) + 1;
4920
+ const at = RING + g % RING_SIZE * SLOT;
4921
+ Atomics.store(shared, at, 0);
4922
+ Atomics.store(shared, at + 1, hash);
4923
+ Atomics.store(shared, at + 2, ino);
4924
+ Atomics.store(shared, at, g);
4925
+ return g;
4926
+ }
4927
+ replay(upTo) {
4928
+ const shared = this.shared;
4929
+ const missed = [];
4930
+ for (let g = this.seen + 1; g <= upTo && upTo - this.seen <= RING_SIZE; g++) {
4931
+ const at = RING + g % RING_SIZE * SLOT;
4932
+ const tag = Atomics.load(shared, at);
4933
+ const hash = Atomics.load(shared, at + 1);
4934
+ const ino = Atomics.load(shared, at + 2);
4935
+ if (tag !== g || hash === 0 || Atomics.load(shared, at) !== g) break;
4936
+ missed.push([hash, ino]);
4937
+ }
4938
+ if (missed.length < upTo - this.seen) this.clear();
4939
+ else for (const [hash, ino] of missed) this.drop(hash, ino);
4940
+ }
4941
+ drop(hash, ino) {
4942
+ const paths = [
4943
+ ...this.byHash.get(hash) ?? [],
4944
+ ...ino === 0 ? [] : this.byIno.get(ino) ?? []
4945
+ ];
4946
+ for (const path of paths) {
4947
+ this.stats.delete(path);
4948
+ this.lstats.delete(path);
4949
+ }
4950
+ this.byHash.delete(hash);
4951
+ if (ino !== 0) this.byIno.delete(ino);
4952
+ }
4953
+ clear() {
4954
+ this.stats.clear();
4955
+ this.lstats.clear();
4956
+ this.byHash.clear();
4957
+ this.byIno.clear();
4958
+ }
4959
+ };
4789
4960
  }
4790
4961
  });
4791
4962
 
@@ -5118,7 +5289,14 @@ var init_wasi_fds = __esm({
5118
5289
  handed.add(e.file.buffer);
5119
5290
  promoted.set(e.file, fd);
5120
5291
  }
5121
- this.table.set(fd, { type: "kernel", kind: "file", nonblock: false, append: e.file.append });
5292
+ this.table.set(fd, {
5293
+ type: "kernel",
5294
+ kind: "file",
5295
+ nonblock: false,
5296
+ append: e.file.append,
5297
+ path: e.file.path,
5298
+ ino: this.inoOf(e.file.path)
5299
+ });
5122
5300
  }
5123
5301
  for (const file of promoted.keys()) {
5124
5302
  if (--file.buffer.opens <= 0) {
@@ -5232,9 +5410,21 @@ var init_wasi_fds = __esm({
5232
5410
  buffer.orphan();
5233
5411
  this.buffers.delete(path);
5234
5412
  }
5413
+ inoOf(path) {
5414
+ try {
5415
+ return this.fs.stat(path).ino;
5416
+ } catch {
5417
+ return void 0;
5418
+ }
5419
+ }
5235
5420
  renamed(from, to) {
5236
5421
  if (from === to) return;
5237
5422
  if (this.shared) this.kernel.call({ op: "fd-path-renamed", from, to });
5423
+ for (const e of this.table.values()) {
5424
+ if (e.type === "kernel" && e.path !== void 0 && within(e.path, from)) {
5425
+ e.path = to + e.path.slice(from.length);
5426
+ }
5427
+ }
5238
5428
  const moved = [];
5239
5429
  for (const [p, buffer] of this.buffers) {
5240
5430
  if (within(p, from)) moved.push([p, buffer]);
@@ -5313,7 +5503,10 @@ var init_wasi_fds = __esm({
5313
5503
  const made = !existing && this.createNew(path, oflags);
5314
5504
  const truncate = made || (oflags & OFLAGS.TRUNC) !== 0;
5315
5505
  const fd = this.kernel.sys.openVfs(path, flags, 0, truncate ? { truncate } : {});
5316
- this.table.set(fd, { type: "kernel", kind: "file", nonblock: false, append });
5506
+ const was = existing && this.inoOf(path);
5507
+ if (truncate) this.fs.touched?.(path, was);
5508
+ const ino = existing ? was : this.inoOf(path);
5509
+ this.table.set(fd, { type: "kernel", kind: "file", nonblock: false, append, path, ino });
5317
5510
  if (append) this.publishFlags(fd, { nonblock: false, append });
5318
5511
  this.bump();
5319
5512
  return fd;
@@ -6107,11 +6300,29 @@ var init_wasi_host = __esm({
6107
6300
  if (!kernel.sys.pread) throw new WasiError("ESPIPE");
6108
6301
  return kernel.sys.pread(fd, Math.min(max, MAX_READ), at);
6109
6302
  },
6110
- pwrite: (body, at) => kernel.call({ op: "fd-pwrite", fd, offset: at, body }),
6303
+ pwrite: (body, at) => this.changed(fd, e, kernel.call({ op: "fd-pwrite", fd, offset: at, body })),
6111
6304
  size: () => this.kernelStat(fd).size,
6112
- truncate: (size) => void kernel.call({ op: "fd-resize", fd, size })
6305
+ truncate: (size) => this.changed(fd, e, void kernel.call({ op: "fd-resize", fd, size }))
6113
6306
  };
6114
6307
  }
6308
+ unnamed = /* @__PURE__ */ new WeakSet();
6309
+ changed(fd, e, result) {
6310
+ if (e.path === void 0 && !this.unnamed.has(e)) this.learnPath(fd, e);
6311
+ this.o.fs.touched?.(e.path, e.ino);
6312
+ return result;
6313
+ }
6314
+ learnPath(fd, e) {
6315
+ this.unnamed.add(e);
6316
+ let path;
6317
+ try {
6318
+ const st = this.kernelStat(fd);
6319
+ path = st.orphan ? void 0 : st.path;
6320
+ if (path !== void 0) e.ino = this.o.fs.stat(path).ino;
6321
+ } catch {
6322
+ return;
6323
+ }
6324
+ e.path = path;
6325
+ }
6115
6326
  kernelStat(fd) {
6116
6327
  return this.o.kernel.call({ op: "fd-vfs-stat", fd });
6117
6328
  }
@@ -6120,7 +6331,8 @@ var init_wasi_host = __esm({
6120
6331
  const e = this.fds.get(fd);
6121
6332
  if (e.type === "kernel") {
6122
6333
  try {
6123
- return this.o.kernel.sys.write(fd, data, { nonblock: e.nonblock });
6334
+ const n = this.o.kernel.sys.write(fd, data, { nonblock: e.nonblock });
6335
+ return this.fds.kind(fd, e) === "file" ? this.changed(fd, e, n) : n;
6124
6336
  } catch (err) {
6125
6337
  const broken = err.code === "EPIPE";
6126
6338
  if (broken && this.fds.kind(fd, e) !== "socket") throw new WasiExit(SIGPIPE_EXIT);
@@ -6317,6 +6529,7 @@ var init_wasi_threads = __esm({
6317
6529
  "src/process/wasi/wasi-threads.ts"() {
6318
6530
  "use strict";
6319
6531
  init_protocol();
6532
+ init_wasi_files();
6320
6533
  LAST_TID = 0;
6321
6534
  RUNNING = 1;
6322
6535
  MAIN_TID = 1;
@@ -6339,7 +6552,7 @@ var init_wasi_threads = __esm({
6339
6552
  this.memory = memory;
6340
6553
  this.cap = cap;
6341
6554
  this.tid = tid;
6342
- this.ids = new Int32Array(ids ?? new SharedArrayBuffer(16));
6555
+ this.ids = new Int32Array(ids ?? new SharedArrayBuffer(SHARED_IDS_BYTES));
6343
6556
  if (!ids) Atomics.store(this.ids, LAST_TID, MAIN_TID);
6344
6557
  }
6345
6558
  spawn(arg) {
@@ -7781,8 +7994,8 @@ function readExports(r, s) {
7781
7994
  for (let i = r.u32(); i > 0; i--) {
7782
7995
  const name2 = r.name();
7783
7996
  const kind = r.byte();
7784
- const index2 = r.u32();
7785
- if (kind === 0) s.info.exports[name2] = index2;
7997
+ const index3 = r.u32();
7998
+ if (kind === 0) s.info.exports[name2] = index3;
7786
7999
  }
7787
8000
  }
7788
8001
  function readElems(r, s) {
@@ -8085,15 +8298,15 @@ var init_wasix_linker = __esm({
8085
8298
  return out;
8086
8299
  }
8087
8300
  allocateClosure() {
8088
- const index2 = this.indices.allocate(this.table.length);
8089
- growTable(this.table, index2 + 1);
8090
- this.live.add(index2);
8091
- return index2;
8301
+ const index3 = this.indices.allocate(this.table.length);
8302
+ growTable(this.table, index3 + 1);
8303
+ this.live.add(index3);
8304
+ return index3;
8092
8305
  }
8093
- freeClosure(index2) {
8094
- this.indices?.free(index2);
8095
- this.live.delete(index2);
8096
- this.table.set(index2, null);
8306
+ freeClosure(index3) {
8307
+ this.indices?.free(index3);
8308
+ this.live.delete(index3);
8309
+ this.table.set(index3, null);
8097
8310
  }
8098
8311
  live = /* @__PURE__ */ new Set();
8099
8312
  prepareClosure(record) {
@@ -8326,12 +8539,12 @@ var init_wasix_linker = __esm({
8326
8539
  this.indexTable();
8327
8540
  const known = this.slots.get(fn);
8328
8541
  if (known !== void 0) return known;
8329
- const index2 = this.reserve(1);
8330
- growTable(this.table, index2 + 1);
8331
- this.table.set(index2, fn);
8332
- this.slots.set(fn, index2);
8333
- this.publisher?.({ kind: "slot", index: index2, handle, name: name2 });
8334
- return index2;
8542
+ const index3 = this.reserve(1);
8543
+ growTable(this.table, index3 + 1);
8544
+ this.table.set(index3, fn);
8545
+ this.slots.set(fn, index3);
8546
+ this.publisher?.({ kind: "slot", index: index3, handle, name: name2 });
8547
+ return index3;
8335
8548
  }
8336
8549
  allocate(size, alignment) {
8337
8550
  if (size === 0) return 0;
@@ -8616,11 +8829,11 @@ var init_wasix_sockets = __esm({
8616
8829
  });
8617
8830
 
8618
8831
  // src/process/wasi/wasix-host.ts
8619
- function futexWait(view, index2, expected, ms, interrupted) {
8832
+ function futexWait(view, index3, expected, ms, interrupted) {
8620
8833
  const deadline = performance.now() + ms;
8621
8834
  for (; ; ) {
8622
8835
  const slice = interrupted ? Math.min(FUTEX_SLICE_MS, ms) : ms;
8623
- if (Atomics.wait(view, index2, expected, slice) !== "timed-out") return true;
8836
+ if (Atomics.wait(view, index3, expected, slice) !== "timed-out") return true;
8624
8837
  if (interrupted?.()) return true;
8625
8838
  ms = deadline - performance.now();
8626
8839
  if (ms <= 0) return false;
@@ -8749,21 +8962,21 @@ var init_wasix_host = __esm({
8749
8962
  if (!this.linker) throw new WasiError("ENOTSUP");
8750
8963
  this.mem.view().setUint32(out, this.linker.allocateClosure(), true);
8751
8964
  },
8752
- closure_prepare: (backing, index2, argTypes, argCount, resultTypes, resultCount, userData) => {
8965
+ closure_prepare: (backing, index3, argTypes, argCount, resultTypes, resultCount, userData) => {
8753
8966
  if (!this.linker) throw new WasiError("ENOTSUP");
8754
8967
  const params = valueTypes(this.mem.bytes(argTypes, argCount));
8755
8968
  const results = valueTypes(this.mem.bytes(resultTypes, resultCount));
8756
8969
  if (!params || !results) throw new WasiError("EINVAL");
8757
8970
  try {
8758
- this.linker.prepareClosure({ index: index2, backing, userData, params, results });
8971
+ this.linker.prepareClosure({ index: index3, backing, userData, params, results });
8759
8972
  } catch (e) {
8760
8973
  if (e instanceof DlError) throw new WasiError("EINVAL");
8761
8974
  throw e;
8762
8975
  }
8763
8976
  },
8764
- closure_free: (index2) => {
8977
+ closure_free: (index3) => {
8765
8978
  if (!this.linker) throw new WasiError("ENOTSUP");
8766
- this.linker.freeClosure(index2);
8979
+ this.linker.freeClosure(index3);
8767
8980
  },
8768
8981
  call_dynamic: (...a) => void callDynamic(
8769
8982
  { mem: this.mem, table: () => this.table, types: this.types },
@@ -9068,10 +9281,10 @@ function parseFunctionNames(payload) {
9068
9281
  if (end > payload.length) throw new RangeError("truncated name section");
9069
9282
  if (id === 1) {
9070
9283
  for (let count = leb(); count > 0; count--) {
9071
- const index2 = leb();
9284
+ const index3 = leb();
9072
9285
  const length = leb();
9073
9286
  if (at + length > end) throw new RangeError("truncated name section");
9074
- names.set(index2, decoder3.decode(payload.subarray(at, at + length)));
9287
+ names.set(index3, decoder3.decode(payload.subarray(at, at + length)));
9075
9288
  at += length;
9076
9289
  }
9077
9290
  }
@@ -9095,7 +9308,7 @@ function nameFrame(line, module, name2) {
9095
9308
  }
9096
9309
  function sidecarNames(read2, path) {
9097
9310
  let names;
9098
- return (index2) => {
9311
+ return (index3) => {
9099
9312
  if (!names) {
9100
9313
  try {
9101
9314
  names = parseFunctionNames(read2(path));
@@ -9103,7 +9316,7 @@ function sidecarNames(read2, path) {
9103
9316
  names = /* @__PURE__ */ new Map();
9104
9317
  }
9105
9318
  }
9106
- return names.get(index2);
9319
+ return names.get(index3);
9107
9320
  };
9108
9321
  }
9109
9322
  var UNNAMED_FRAME, WASM_MODULE;
@@ -9464,7 +9677,7 @@ async function runWasiProcess(init, port2) {
9464
9677
  parent: () => identify(transport, init).ppid,
9465
9678
  kernel: { sys: traced(stats, "kernel", sys), call: call2 },
9466
9679
  fs: procfsBridge(
9467
- cachingBridge(traced(stats, "fs", createSyncFsSabBridge(transport))),
9680
+ cachingBridge(traced(stats, "fs", createSyncFsSabBridge(transport)), void 0, threads?.ids),
9468
9681
  procTree(call2, id.pid)
9469
9682
  ),
9470
9683
  ...descriptors(init, fork, threads)
@@ -9557,7 +9770,10 @@ async function runWasiThread(init, port2) {
9557
9770
  ppid: id.ppid,
9558
9771
  parent: () => identify(transport, init).ppid,
9559
9772
  kernel: { sys, call: call2 },
9560
- fs: procfsBridge(cachingBridge(createSyncFsSabBridge(transport)), procTree(call2, id.pid)),
9773
+ fs: procfsBridge(
9774
+ cachingBridge(createSyncFsSabBridge(transport), void 0, threads.ids),
9775
+ procTree(call2, id.pid)
9776
+ ),
9561
9777
  shared: threads.ids
9562
9778
  });
9563
9779
  const program = await programImports(init.program, host, transport, thread.tid);
@@ -9669,13 +9885,13 @@ var init_wasi_runtime = __esm({
9669
9885
  },
9670
9886
  unlock: () => this.call({ op: "dl-unlock", owner: this.owner }),
9671
9887
  allocate: (floor) => {
9672
- const index2 = this.call({ op: "dl-closure", floor });
9888
+ const index3 = this.call({ op: "dl-closure", floor });
9673
9889
  this.catchUp();
9674
9890
  this.bump();
9675
- return index2;
9891
+ return index3;
9676
9892
  },
9677
- free: (index2) => {
9678
- this.call({ op: "dl-closure-free", index: index2 });
9893
+ free: (index3) => {
9894
+ this.call({ op: "dl-closure-free", index: index3 });
9679
9895
  this.bump();
9680
9896
  }
9681
9897
  };
@@ -9728,8 +9944,8 @@ var init_wasi_runtime = __esm({
9728
9944
  });
9729
9945
 
9730
9946
  // src/process/js/async-sab.ts
9731
- async function changed(header, index2, seen, waitAsync = defaultWaitAsync) {
9732
- const waited = waitAsync(header, index2, seen);
9947
+ async function changed(header, index3, seen, waitAsync = defaultWaitAsync) {
9948
+ const waited = waitAsync(header, index3, seen);
9733
9949
  if (waited.async) await waited.value;
9734
9950
  }
9735
9951
  function createAsyncSabTransport(sab, port2, waitAsync = defaultWaitAsync) {
@@ -10491,7 +10707,7 @@ function openRequest(abs, o) {
10491
10707
  ...o.truncate ? { truncate: true } : {}
10492
10708
  };
10493
10709
  }
10494
- function index(n, what) {
10710
+ function index2(n, what) {
10495
10711
  if (!Number.isSafeInteger(n) || n < 0) throw new JsCallError("EINVAL", what);
10496
10712
  }
10497
10713
  var CHUNK2, MAX_IO, O_WRONLY6, O_RDWR6, O_APPEND3;
@@ -10664,7 +10880,7 @@ function syncOps(kernel, shared) {
10664
10880
  return got;
10665
10881
  };
10666
10882
  const read2 = (fd, max = CHUNK2) => {
10667
- index(max, "read");
10883
+ index2(max, "read");
10668
10884
  const emulated = readDevice(info(fd), max, shared.random);
10669
10885
  if (emulated) return emulated;
10670
10886
  const r = kernel.blocking({ op: "fd-read", fd, max: Math.min(max, MAX_IO) });
@@ -10742,8 +10958,8 @@ function syncFile(kernel, fd, path, append, forget) {
10742
10958
  return kernel.json({ op: "fd-vfs-stat", fd }).size;
10743
10959
  },
10744
10960
  read(position, length) {
10745
- index(position, "read");
10746
- index(length, "read");
10961
+ index2(position, "read");
10962
+ index2(length, "read");
10747
10963
  const parts = [];
10748
10964
  let got = 0;
10749
10965
  do {
@@ -10758,7 +10974,7 @@ function syncFile(kernel, fd, path, append, forget) {
10758
10974
  return joined(parts, got);
10759
10975
  },
10760
10976
  write(position, data) {
10761
- index(position, "write");
10977
+ index2(position, "write");
10762
10978
  let at = 0;
10763
10979
  do {
10764
10980
  live();
@@ -10772,7 +10988,7 @@ function syncFile(kernel, fd, path, append, forget) {
10772
10988
  },
10773
10989
  truncate(size) {
10774
10990
  live();
10775
- index(size, "truncate");
10991
+ index2(size, "truncate");
10776
10992
  kernel.call({ op: "fd-resize", fd, size });
10777
10993
  },
10778
10994
  sync() {
@@ -11168,7 +11384,7 @@ function createContext(o) {
11168
11384
  return got;
11169
11385
  };
11170
11386
  const read2 = async (fd, max = CHUNK2, signal) => {
11171
- index(max, "read");
11387
+ index2(max, "read");
11172
11388
  const emulated = readDevice(await info(fd), max, random);
11173
11389
  if (emulated) return emulated;
11174
11390
  return readPipe(kernel, fd, Math.min(max, MAX_IO), signal);
@@ -11315,8 +11531,8 @@ function fileHandle(kernel, fd, path, append, forget) {
11315
11531
  return s.size;
11316
11532
  },
11317
11533
  async read(position, length) {
11318
- index(position, "read");
11319
- index(length, "read");
11534
+ index2(position, "read");
11535
+ index2(length, "read");
11320
11536
  const parts = [];
11321
11537
  let got = 0;
11322
11538
  do {
@@ -11331,7 +11547,7 @@ function fileHandle(kernel, fd, path, append, forget) {
11331
11547
  return joined(parts, got);
11332
11548
  },
11333
11549
  async write(position, data) {
11334
- index(position, "write");
11550
+ index2(position, "write");
11335
11551
  let at = 0;
11336
11552
  do {
11337
11553
  live();
@@ -11345,7 +11561,7 @@ function fileHandle(kernel, fd, path, append, forget) {
11345
11561
  },
11346
11562
  async truncate(size) {
11347
11563
  live();
11348
- index(size, "truncate");
11564
+ index2(size, "truncate");
11349
11565
  await kernel.call({ op: "fd-resize", fd, size });
11350
11566
  },
11351
11567
  async sync() {