@ai-ecoverse/slicc-kernel 1.124.12 → 1.125.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.
Files changed (34) hide show
  1. package/README.md +46 -11
  2. package/dist/index.js.map +2 -2
  3. package/dist/kernel-worker.js +548 -94
  4. package/dist/kernel-worker.js.map +4 -4
  5. package/dist/node-process-worker.js +4496 -4350
  6. package/dist/node-process-worker.js.map +4 -4
  7. package/dist/node.js +553 -99
  8. package/dist/node.js.map +4 -4
  9. package/dist/process-worker.js +7137 -6991
  10. package/dist/process-worker.js.map +4 -4
  11. package/dist/types/fs/unlinked.d.ts +1 -0
  12. package/dist/types/fs/watch.d.ts +19 -0
  13. package/dist/types/index.d.ts +1 -1
  14. package/dist/types/kernel/fd-table.d.ts +1 -0
  15. package/dist/types/kernel/host.d.ts +1 -0
  16. package/dist/types/kernel/net/tls-ciphers.d.ts +6 -0
  17. package/dist/types/kernel/net/tls-client.d.ts +13 -1
  18. package/dist/types/kernel/net/tls-engine.d.ts +10 -0
  19. package/dist/types/kernel/net/tls-uplink.d.ts +14 -1
  20. package/dist/types/kernel/net/uplink-socket.d.ts +1 -1
  21. package/dist/types/kernel/process.d.ts +4 -1
  22. package/dist/types/kernel/select.d.ts +1 -0
  23. package/dist/types/kernel/socket-syscalls.d.ts +7 -0
  24. package/dist/types/kernel/vfs-file.d.ts +2 -0
  25. package/dist/types/kernel/watch-ops.d.ts +30 -0
  26. package/dist/types/launcher.d.ts +1 -0
  27. package/dist/types/process/js/js-context.d.ts +2 -0
  28. package/dist/types/process/js/js-dev.d.ts +1 -0
  29. package/dist/types/process/js/js-net.d.ts +16 -0
  30. package/dist/types/process/js/js-sync.d.ts +5 -1
  31. package/dist/types/process/js/js-watch.d.ts +17 -0
  32. package/dist/types/process/process-fds.d.ts +1 -1
  33. package/dist/types/process/wasi/wasi-host.d.ts +1 -0
  34. package/package.json +4 -4
package/dist/node.js CHANGED
@@ -930,7 +930,12 @@ function openPipe(capacity) {
930
930
  }
931
931
  },
932
932
  close: () => pipe.closeRead(),
933
- poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
933
+ poll: () => ({
934
+ readable: pipe.readReady,
935
+ writable: false,
936
+ hangup: pipe.writersGone,
937
+ nread: pipe.buffered
938
+ }),
934
939
  changed: (signal) => pipe.changed(signal),
935
940
  identity: pipe
936
941
  }),
@@ -1557,8 +1562,8 @@ function uplinkLink(conn, onReset) {
1557
1562
  open = false;
1558
1563
  conn.close();
1559
1564
  };
1560
- void inbound(conn, rx).catch(() => {
1561
- onReset();
1565
+ void inbound(conn, rx).catch((err) => {
1566
+ onReset(err);
1562
1567
  close();
1563
1568
  tx.closeRead();
1564
1569
  }).finally(() => rx.closeWrite());
@@ -1649,7 +1654,7 @@ var KernelSocket = class _KernelSocket {
1649
1654
  state = "open";
1650
1655
  dialing;
1651
1656
  failed;
1652
- reset = false;
1657
+ reset;
1653
1658
  local;
1654
1659
  peer;
1655
1660
  peerCred;
@@ -1688,19 +1693,20 @@ var KernelSocket = class _KernelSocket {
1688
1693
  const { rx } = this.connection();
1689
1694
  if (this.readShut || max <= 0) return new Uint8Array(0);
1690
1695
  const bytes = await pipeCall(() => rx.read(max, signal));
1691
- if (bytes.length === 0 && this.reset) throw new KernelError("ECONNRESET");
1696
+ if (bytes.length === 0 && this.reset) throw this.reset;
1692
1697
  return bytes;
1693
1698
  }
1694
1699
  async peek(max, signal) {
1695
1700
  const { rx } = this.connection();
1696
1701
  if (this.readShut || max <= 0) return new Uint8Array(0);
1697
1702
  const bytes = await pipeCall(() => rx.peek(max, signal));
1698
- if (bytes.length === 0 && this.reset) throw new KernelError("ECONNRESET");
1703
+ if (bytes.length === 0 && this.reset) throw this.reset;
1699
1704
  return bytes;
1700
1705
  }
1701
1706
  async write(bytes, signal) {
1702
1707
  const { tx } = this.connection();
1703
1708
  if (this.writeShut) throw new KernelError("EPIPE");
1709
+ if (this.reset?.code === "EPROTO") throw this.reset;
1704
1710
  return pipeCall(() => tx.write(bytes, signal));
1705
1711
  }
1706
1712
  poll() {
@@ -1717,7 +1723,8 @@ var KernelSocket = class _KernelSocket {
1717
1723
  readable: this.readShut || rx.readReady,
1718
1724
  writable: this.writeShut || tx.writeReady,
1719
1725
  hangup: rx.writersGone && tx.readersGone,
1720
- rdhup: rx.writersGone
1726
+ rdhup: rx.writersGone,
1727
+ nread: this.readShut ? 0 : rx.buffered
1721
1728
  };
1722
1729
  }
1723
1730
  changed(signal) {
@@ -1777,8 +1784,8 @@ var KernelSocket = class _KernelSocket {
1777
1784
  if (this.state !== "open") throw new KernelError("EINVAL");
1778
1785
  if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
1779
1786
  this.failed = void 0;
1780
- const route = viaUplink && addr.family === "inet" ? this.net.uplinkFor(addr.host) : void 0;
1781
- if (route) return this.dial(addr, route);
1787
+ const route2 = viaUplink && addr.family === "inet" ? this.net.uplinkFor(addr.host) : void 0;
1788
+ if (route2) return this.dial(addr, route2);
1782
1789
  const target = this.net.target(addr, name2);
1783
1790
  if (!this.local) {
1784
1791
  if (addr.family === "inet") {
@@ -1834,9 +1841,9 @@ var KernelSocket = class _KernelSocket {
1834
1841
  }
1835
1842
  }
1836
1843
  switch;
1837
- connectSecure(addr, route, secure2) {
1844
+ connectSecure(addr, route2, secure2) {
1838
1845
  if (this.state !== "open") throw new KernelError("EISCONN");
1839
- return this.dial(addr, route, secure2);
1846
+ return this.dial(addr, route2, secure2);
1840
1847
  }
1841
1848
  upgradeSecure(make) {
1842
1849
  if (this.state !== "connected") throw new KernelError("ENOTCONN");
@@ -1844,7 +1851,7 @@ var KernelSocket = class _KernelSocket {
1844
1851
  if (this.switch.upgraded || this.writeShut || this.readShut) throw new KernelError("EINVAL");
1845
1852
  return this.switch.upgrade(make);
1846
1853
  }
1847
- dial(addr, route, secure2) {
1854
+ dial(addr, route2, secure2) {
1848
1855
  this.state = "connecting";
1849
1856
  const abort = new AbortController();
1850
1857
  this.dialing = abort;
@@ -1852,13 +1859,13 @@ var KernelSocket = class _KernelSocket {
1852
1859
  const timer = setTimeout(() => {
1853
1860
  timedOut = true;
1854
1861
  abort.abort(new KernelError("ETIMEDOUT"));
1855
- }, route.timeoutMs ?? DIAL_TIMEOUT_MS);
1862
+ }, route2.timeoutMs ?? DIAL_TIMEOUT_MS);
1856
1863
  const abandoned = new Promise((_, reject) => {
1857
1864
  abort.signal.addEventListener("abort", () => reject(abort.signal.reason), { once: true });
1858
1865
  });
1859
1866
  const { host, port } = addr;
1860
1867
  const dialled = new Promise(
1861
- (resolve2) => resolve2(route.uplink.dial({ network: "tcp", host, port, signal: abort.signal }))
1868
+ (resolve2) => resolve2(route2.uplink.dial({ network: "tcp", host, port, signal: abort.signal }))
1862
1869
  );
1863
1870
  dialled.then(
1864
1871
  (conn) => abort.signal.aborted && conn.close(),
@@ -1885,8 +1892,8 @@ var KernelSocket = class _KernelSocket {
1885
1892
  this.dialing = void 0;
1886
1893
  this.switch = conn;
1887
1894
  this.attach(
1888
- uplinkLink(conn, () => {
1889
- this.reset = true;
1895
+ uplinkLink(conn, (err) => {
1896
+ this.reset = err instanceof KernelError && err.code === "EPROTO" ? err : new KernelError("ECONNRESET");
1890
1897
  this.changes.wake();
1891
1898
  })
1892
1899
  );
@@ -2007,14 +2014,14 @@ var LoopbackNet = class {
2007
2014
  if (!this.origins) throw new KernelError("ENETUNREACH");
2008
2015
  void this.origins(conn, origin).catch(() => conn.close());
2009
2016
  }
2010
- useUplink(route) {
2011
- this.route = route;
2017
+ useUplink(route2) {
2018
+ this.route = route2;
2012
2019
  }
2013
2020
  tlsClient;
2014
2021
  uplinkFor(host) {
2015
- const route = this.route;
2016
- if (!route) return void 0;
2017
- return this.destination(host) === "uplink" ? route : void 0;
2022
+ const route2 = this.route;
2023
+ if (!route2) return void 0;
2024
+ return this.destination(host) === "uplink" ? route2 : void 0;
2018
2025
  }
2019
2026
  destination(host) {
2020
2027
  return (this.route?.routes ?? LOCAL_ONLY).classify(host);
@@ -5993,16 +6000,24 @@ var UnlinkedKeeper = class {
5993
6000
  gated();
5994
6001
  const held = /* @__PURE__ */ new Map();
5995
6002
  const hidden = /* @__PURE__ */ new Map();
5996
- for (; ; ) {
5997
- await take();
5998
- const now = this.heldWithin(target);
5999
- const next3 = paths.filter((path) => now.has(path) && !held.has(path));
6000
- if (next3.length === 0) break;
6001
- for (const path of next3) {
6002
- held.set(path, now.get(path));
6003
- const at = await this.hide(path);
6004
- if (at !== void 0) hidden.set(path, at);
6003
+ const moved2 = [];
6004
+ try {
6005
+ for (; ; ) {
6006
+ await take();
6007
+ const now = this.heldWithin(target);
6008
+ const next3 = paths.filter((path) => now.has(path) && !held.has(path));
6009
+ if (next3.length === 0) break;
6010
+ for (const path of next3) {
6011
+ const holders = now.get(path);
6012
+ held.set(path, holders);
6013
+ for (const holder of holders) moved2.push(holder.moving?.(path) ?? (() => void 0));
6014
+ const at = await this.hide(path);
6015
+ if (at !== void 0) hidden.set(path, at);
6016
+ }
6005
6017
  }
6018
+ } catch (err) {
6019
+ for (const settle of moved2) settle();
6020
+ throw err;
6006
6021
  }
6007
6022
  sealed();
6008
6023
  const done4 = () => {
@@ -6015,6 +6030,7 @@ var UnlinkedKeeper = class {
6015
6030
  files.set(path, file);
6016
6031
  for (const holder of holders) holder.keep(path, file);
6017
6032
  }
6033
+ for (const settle of moved2) settle();
6018
6034
  return {
6019
6035
  moved: new Set(hidden.keys()),
6020
6036
  commit: () => {
@@ -7070,7 +7086,7 @@ function satisfies(version, range2) {
7070
7086
  }
7071
7087
 
7072
7088
  // src/version.ts
7073
- var KERNEL_VERSION = true ? "1.124.12" : "0.0.0-dev";
7089
+ var KERNEL_VERSION = true ? "1.125.0" : "0.0.0-dev";
7074
7090
  var OS_RELEASE = "6.0.0-slicc";
7075
7091
  function procVersion() {
7076
7092
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9504,6 +9520,8 @@ async function physicalPathAsync(fs, path) {
9504
9520
  }
9505
9521
 
9506
9522
  // src/fs/watch.ts
9523
+ var WATCH_NOTES_LIMIT = 1e3;
9524
+ var baseOf2 = (path) => path.slice(path.lastIndexOf("/") + 1);
9507
9525
  var WATCH_BATCH_LIMIT = 1e3;
9508
9526
  var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
9509
9527
  var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
@@ -9513,8 +9531,39 @@ function touches(watcher, changed) {
9513
9531
  );
9514
9532
  }
9515
9533
  var REALS_KEPT = 4096;
9534
+ function noted(watch, note) {
9535
+ if (watch.ended) return;
9536
+ if ("ended" in note) {
9537
+ watch.notes.push(note);
9538
+ watch.ended = true;
9539
+ watch.wake?.();
9540
+ return;
9541
+ }
9542
+ if (watch.overflowed) return;
9543
+ if (watch.notes.length >= WATCH_NOTES_LIMIT) {
9544
+ watch.overflowed = true;
9545
+ watch.notes.push({ overflow: true });
9546
+ } else watch.notes.push(note);
9547
+ watch.wake?.();
9548
+ }
9549
+ function route(watch, type, path) {
9550
+ for (const root of watch.roots) if (routed(watch, root, type, path)) return;
9551
+ }
9552
+ function routed(watch, root, type, path) {
9553
+ if (path === root || type === "rename" && root !== "/" && under3(root, path)) {
9554
+ noted(watch, { type, filename: baseOf2(root) });
9555
+ if (type === "rename") noted(watch, { ended: true });
9556
+ return true;
9557
+ }
9558
+ if (parentOf6(path) === root) noted(watch, { type, filename: baseOf2(path) });
9559
+ else if (watch.recursive && under3(path, root)) {
9560
+ noted(watch, { type, filename: path.slice(root === "/" ? 1 : root.length + 1) });
9561
+ } else return false;
9562
+ return true;
9563
+ }
9516
9564
  var FsWatchers = class {
9517
9565
  watchers = /* @__PURE__ */ new Set();
9566
+ typed = /* @__PURE__ */ new Set();
9518
9567
  taps = [];
9519
9568
  reals = /* @__PURE__ */ new Map();
9520
9569
  reshapes = [];
@@ -9541,7 +9590,65 @@ var FsWatchers = class {
9541
9590
  this.watchers.delete(watcher);
9542
9591
  };
9543
9592
  }
9593
+ typedWatch(root, recursive, alias) {
9594
+ const main = normalizePath(root);
9595
+ const other = alias === void 0 ? main : normalizePath(alias);
9596
+ const watch = {
9597
+ roots: other === main ? [main] : [main, other],
9598
+ recursive,
9599
+ notes: [],
9600
+ overflowed: false,
9601
+ ended: false,
9602
+ wake: void 0
9603
+ };
9604
+ this.typed.add(watch);
9605
+ const close = () => {
9606
+ this.typed.delete(watch);
9607
+ watch.ended = true;
9608
+ watch.wake?.();
9609
+ };
9610
+ return {
9611
+ close,
9612
+ next: async (signal) => {
9613
+ while (watch.notes.length === 0 && !watch.ended && !signal.aborted) {
9614
+ await new Promise((resolve2) => {
9615
+ const done4 = () => {
9616
+ watch.wake = void 0;
9617
+ signal.removeEventListener("abort", done4);
9618
+ resolve2();
9619
+ };
9620
+ watch.wake = done4;
9621
+ signal.addEventListener("abort", done4, { once: true });
9622
+ });
9623
+ }
9624
+ const notes = watch.notes.splice(0);
9625
+ watch.overflowed = false;
9626
+ if (watch.ended) this.typed.delete(watch);
9627
+ return notes.length === 0 && watch.ended ? [{ ended: true }] : notes;
9628
+ }
9629
+ };
9630
+ }
9631
+ covers(path) {
9632
+ for (const watch of this.typed) {
9633
+ for (const root of watch.roots) {
9634
+ if (path === root || parentOf6(path) === root || watch.recursive && under3(path, root))
9635
+ return true;
9636
+ }
9637
+ }
9638
+ return false;
9639
+ }
9640
+ emit(type, paths) {
9641
+ if (this.typed.size === 0) return;
9642
+ for (const path of paths) {
9643
+ const at = normalizePath(path);
9644
+ for (const watch of [...this.typed]) route(watch, type, at);
9645
+ }
9646
+ }
9544
9647
  changed(...paths) {
9648
+ this.report(paths);
9649
+ this.emit("change", paths);
9650
+ }
9651
+ report(paths) {
9545
9652
  for (const tap of this.taps) tap(paths);
9546
9653
  for (const watcher of this.watchers) {
9547
9654
  for (const path of paths) {
@@ -9561,11 +9668,15 @@ var FsWatchers = class {
9561
9668
  watcher.notify(paths.length > WATCH_BATCH_LIMIT ? { overflow: true } : { paths });
9562
9669
  }
9563
9670
  wrap(fs) {
9564
- const after = (run2, changed) => async (...args) => {
9671
+ const after = (run2, changed, type) => async (...args) => {
9672
+ let done4 = false;
9565
9673
  try {
9566
9674
  await run2(...args);
9675
+ done4 = true;
9567
9676
  } finally {
9568
- this.changed(...changed(...args));
9677
+ const paths = changed(...args);
9678
+ this.report(paths);
9679
+ if (done4) this.emit(type, paths);
9569
9680
  }
9570
9681
  };
9571
9682
  const reals = this.reals;
@@ -9584,15 +9695,21 @@ var FsWatchers = class {
9584
9695
  reals.set(at, real);
9585
9696
  return real;
9586
9697
  };
9587
- const content = (run2) => async (...args) => {
9698
+ const content = (run2, types) => async (...args) => {
9699
+ const at = normalizePath(args[0]);
9700
+ const fresh2 = this.covers(at) && !await fs.exists(at).catch(() => true);
9701
+ let done4 = false;
9588
9702
  try {
9589
9703
  await run2(...args);
9704
+ done4 = true;
9590
9705
  } finally {
9591
- const at = normalizePath(args[0]);
9592
9706
  const real = await realOf(at);
9593
- this.changed(...real === at ? [at] : [at, real]);
9707
+ const paths = real === at ? [at] : [at, real];
9708
+ this.report(paths);
9709
+ if (done4) for (const type of types(fresh2)) this.emit(type, paths);
9594
9710
  }
9595
9711
  };
9712
+ const written2 = (fresh2) => fresh2 ? ["rename", "change"] : ["change"];
9596
9713
  const reshaping = (run2) => async (...args) => {
9597
9714
  this.reshaped();
9598
9715
  try {
@@ -9602,8 +9719,8 @@ var FsWatchers = class {
9602
9719
  }
9603
9720
  };
9604
9721
  const overrides = {
9605
- writeFile: content(fs.writeFile.bind(fs)),
9606
- ...fs.createExclusive ? { createExclusive: content(fs.createExclusive.bind(fs)) } : {},
9722
+ writeFile: content(fs.writeFile.bind(fs), written2),
9723
+ ...fs.createExclusive ? { createExclusive: content(fs.createExclusive.bind(fs), () => ["rename"]) } : {},
9607
9724
  mkdir: async (path, options) => {
9608
9725
  const created3 = [];
9609
9726
  if (options?.recursive) {
@@ -9613,33 +9730,39 @@ var FsWatchers = class {
9613
9730
  if (created3.length > 0 || !await fs.exists(ancestor)) created3.push(ancestor);
9614
9731
  }
9615
9732
  }
9733
+ const fresh2 = !options?.recursive || !(this.covers(path) && await fs.exists(path));
9734
+ let done4 = false;
9616
9735
  try {
9617
9736
  await fs.mkdir(path, options);
9737
+ done4 = true;
9618
9738
  } finally {
9619
- this.changed(...created3, path);
9739
+ this.report([...created3, path]);
9740
+ if (done4) this.emit("rename", fresh2 ? [...created3, path] : created3);
9620
9741
  }
9621
9742
  },
9622
- rm: reshaping(after(fs.rm.bind(fs), (path) => [path])),
9623
- rename: reshaping(after(fs.rename.bind(fs), (from, to) => [from, to])),
9743
+ rm: reshaping(after(fs.rm.bind(fs), (path) => [path], "rename")),
9744
+ rename: reshaping(after(fs.rename.bind(fs), (from, to) => [from, to], "rename")),
9624
9745
  link: reshaping(
9625
9746
  after(
9626
9747
  (from, to, follow2) => fs.link(from, to, follow2),
9627
- (_from, to) => [to]
9748
+ (_from, to) => [to],
9749
+ "rename"
9628
9750
  )
9629
9751
  ),
9630
- symlink: reshaping(after(fs.symlink.bind(fs), (_target, path) => [path])),
9631
- chmod: after(fs.chmod.bind(fs), (path) => [path]),
9632
- ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
9633
- utimes: after(fs.utimes.bind(fs), (path) => [path]),
9752
+ symlink: reshaping(after(fs.symlink.bind(fs), (_target, path) => [path], "rename")),
9753
+ chmod: after(fs.chmod.bind(fs), (path) => [path], "change"),
9754
+ ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path], "change") } : {},
9755
+ utimes: after(fs.utimes.bind(fs), (path) => [path], "change"),
9634
9756
  lutimes: after(
9635
9757
  (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
9636
- (path) => [path]
9758
+ (path) => [path],
9759
+ "change"
9637
9760
  )
9638
9761
  };
9639
9762
  const ranged2 = rangedOps(fs);
9640
9763
  if (ranged2) {
9641
- overrides.pwrite = content(ranged2.pwrite);
9642
- overrides.truncate = content(ranged2.truncate);
9764
+ overrides.pwrite = content(ranged2.pwrite, () => ["change"]);
9765
+ overrides.truncate = content(ranged2.truncate, () => ["change"]);
9643
9766
  }
9644
9767
  return new Proxy(fs, {
9645
9768
  get(target, key, receiver) {
@@ -10247,7 +10370,8 @@ var FifoTable = class {
10247
10370
  poll: () => ({
10248
10371
  readable: reads && pipe.readReady,
10249
10372
  writable: writes && pipe.writeReady,
10250
- hangup: reads ? pipe.writersGone : pipe.readersGone
10373
+ hangup: reads ? pipe.writersGone : pipe.readersGone,
10374
+ ...reads ? { nread: pipe.buffered } : {}
10251
10375
  }),
10252
10376
  changed: (signal) => pipe.changed(signal)
10253
10377
  });
@@ -10646,8 +10770,8 @@ var MountTable = class {
10646
10770
  ...error ? { error } : {}
10647
10771
  })
10648
10772
  );
10649
- const noted = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
10650
- return [...mounted, ...noted.map((note) => ({ ...note, options: { ...note.options } }))];
10773
+ const noted2 = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
10774
+ return [...mounted, ...noted2.map((note) => ({ ...note, options: { ...note.options } }))];
10651
10775
  }
10652
10776
  listed() {
10653
10777
  return this.list().filter((entry) => !this.system.has(entry.target));
@@ -14824,6 +14948,8 @@ function realmCa(owner, store) {
14824
14948
 
14825
14949
  // src/kernel/net/tls-engine.ts
14826
14950
  var TLS_CLIENT_INSECURE_SKIP_VERIFY = 1;
14951
+ var TLS_CLIENT_NO_SNI = 2;
14952
+ var TLS_CLIENT_VERIFY_CHAIN_ONLY = 4;
14827
14953
  var SCRATCH = 64 * 1024;
14828
14954
  var EOF = -1;
14829
14955
  var TlsError = class extends Error {
@@ -14880,10 +15006,22 @@ var TlsEngine = class {
14880
15006
  this.m._free(ptr);
14881
15007
  }
14882
15008
  }
15009
+ withSecret(bytes, fn) {
15010
+ const ptr = this.put(bytes);
15011
+ try {
15012
+ return fn(ptr);
15013
+ } finally {
15014
+ this.m.HEAPU8.fill(0, ptr, ptr + bytes.length + 1);
15015
+ this.m._free(ptr);
15016
+ }
15017
+ }
14883
15018
  intoScratch(fn) {
14884
15019
  const n = fn(this.scratch, SCRATCH);
14885
15020
  return n > 0 ? this.taken(n) : n;
14886
15021
  }
15022
+ get sessionControls() {
15023
+ return typeof this.m._tls_client_cert === "function";
15024
+ }
14887
15025
  get client() {
14888
15026
  return typeof this.m._tls_client_new === "function" ? this.m : void 0;
14889
15027
  }
@@ -15036,6 +15174,27 @@ var TlsSession = class {
15036
15174
  const out = this.engine.intoScratch((p, max) => this.c._tls_alpn(this.ptr, p, max));
15037
15175
  return out instanceof Uint8Array ? new TextDecoder().decode(out) : void 0;
15038
15176
  }
15177
+ ownCert(chain2, key, pass2) {
15178
+ const set3 = this.m._tls_client_cert;
15179
+ const keyLen = key[0] === 45 ? key.length + 1 : key.length;
15180
+ const ret = this.engine.withBytes(
15181
+ chain2,
15182
+ (c) => this.engine.withSecret(
15183
+ key,
15184
+ (k) => this.engine.withSecret(
15185
+ pass2,
15186
+ (p) => set3(this.ptr, c, chain2.length, k, keyLen, pass2.length > 0 ? p : 0, pass2.length)
15187
+ )
15188
+ )
15189
+ );
15190
+ if (ret !== 0) throw this.engine.error(ret, "client certificate");
15191
+ }
15192
+ get ciphersuite() {
15193
+ return this.m._tls_ciphersuite(this.ptr);
15194
+ }
15195
+ get nameMismatch() {
15196
+ return this.m._tls_name_mismatch(this.ptr) === 1;
15197
+ }
15039
15198
  peerChain() {
15040
15199
  const chain2 = [];
15041
15200
  const count2 = this.c._tls_peer_count(this.ptr);
@@ -15923,7 +16082,8 @@ var KernelTty = class {
15923
16082
  poll: () => ({
15924
16083
  readable: this.readable.length > 0 || this.hungUp,
15925
16084
  writable: true,
15926
- hangup: this.hungUp
16085
+ hangup: this.hungUp,
16086
+ nread: this.readable.reduce((n, chunk2) => n + (chunk2?.length ?? 0), 0)
15927
16087
  }),
15928
16088
  changed: (signal) => this.changed(signal),
15929
16089
  close: () => {
@@ -16119,7 +16279,8 @@ var PtyPair = class {
16119
16279
  poll: () => ({
16120
16280
  readable: this.queue.length > 0 || this.slaveGone,
16121
16281
  writable: true,
16122
- hangup: this.slaveGone
16282
+ hangup: this.slaveGone,
16283
+ nread: this.queue.reduce((n, chunk2) => n + chunk2.length, 0)
16123
16284
  }),
16124
16285
  changed: (signal) => this.changed(signal),
16125
16286
  close: () => this.closeMaster(),
@@ -16340,11 +16501,11 @@ async function runChild(req, deps) {
16340
16501
  if (pid !== 0) kill();
16341
16502
  decided.resolve();
16342
16503
  };
16343
- const controls = [];
16504
+ const controls2 = [];
16344
16505
  const capture = () => {
16345
16506
  const { promise, resolve: resolve2 } = Promise.withResolvers();
16346
16507
  const control = { shut: false, ended: resolve2, eof: promise };
16347
- controls.push(control);
16508
+ controls2.push(control);
16348
16509
  return { capture: true, max, overflow: () => stop("ENOBUFS"), control };
16349
16510
  };
16350
16511
  const decided = Promise.withResolvers();
@@ -16378,11 +16539,11 @@ async function runChild(req, deps) {
16378
16539
  let status;
16379
16540
  try {
16380
16541
  status = await reaped(deps, pid);
16381
- const open = controls.filter((c) => c.opened);
16542
+ const open = controls2.filter((c) => c.opened);
16382
16543
  await Promise.race([Promise.all(open.map((c) => c.eof)), decided.promise]);
16383
16544
  } finally {
16384
16545
  cancel?.();
16385
- for (const control of controls) control.shut = true;
16546
+ for (const control of controls2) control.shut = true;
16386
16547
  }
16387
16548
  const out = deps.children.captured(pid, 1);
16388
16549
  const err = deps.children.captured(pid, 2);
@@ -16401,13 +16562,17 @@ async function runChild(req, deps) {
16401
16562
  // src/kernel/select.ts
16402
16563
  function ready(fds, read, write) {
16403
16564
  const hangup = /* @__PURE__ */ new Set();
16565
+ const nread = {};
16404
16566
  const pick = (list, dir) => list.filter((fd) => {
16405
16567
  const state = pollFile(fds.get(fd).file);
16406
16568
  if (state.hangup) hangup.add(fd);
16407
- return state[dir] || state.hangup;
16569
+ const picked = state[dir] || state.hangup;
16570
+ if (picked && dir === "readable" && state.nread !== void 0) nread[fd] = state.nread;
16571
+ return picked;
16408
16572
  });
16409
16573
  const out = { read: pick(read, "readable"), write: pick(write, "writable") };
16410
16574
  if (hangup.size > 0) out.hangup = [...hangup];
16575
+ if (Object.keys(nread).length > 0) out.nread = nread;
16411
16576
  return out;
16412
16577
  }
16413
16578
  function interruption(signal) {
@@ -16964,6 +17129,47 @@ function encodeReceived(got) {
16964
17129
  return out;
16965
17130
  }
16966
17131
 
17132
+ // src/kernel/net/tls-ciphers.ts
17133
+ var SUITES = [
17134
+ [4865, "TLS_AES_128_GCM_SHA256", "TLS_AES_128_GCM_SHA256", "TLSv1.3"],
17135
+ [4866, "TLS_AES_256_GCM_SHA384", "TLS_AES_256_GCM_SHA384", "TLSv1.3"],
17136
+ [4867, "TLS_CHACHA20_POLY1305_SHA256", "TLS_CHACHA20_POLY1305_SHA256", "TLSv1.3"],
17137
+ [4868, "TLS_AES_128_CCM_SHA256", "TLS_AES_128_CCM_SHA256", "TLSv1.3"],
17138
+ [4869, "TLS_AES_128_CCM_8_SHA256", "TLS_AES_128_CCM_8_SHA256", "TLSv1.3"],
17139
+ [49195, "ECDHE-ECDSA-AES128-GCM-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17140
+ [49196, "ECDHE-ECDSA-AES256-GCM-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"],
17141
+ [49199, "ECDHE-RSA-AES128-GCM-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17142
+ [49200, "ECDHE-RSA-AES256-GCM-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"],
17143
+ [52392, "ECDHE-RSA-CHACHA20-POLY1305", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLSv1.2"],
17144
+ [
17145
+ 52393,
17146
+ "ECDHE-ECDSA-CHACHA20-POLY1305",
17147
+ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
17148
+ "TLSv1.2"
17149
+ ],
17150
+ [49187, "ECDHE-ECDSA-AES128-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLSv1.2"],
17151
+ [49188, "ECDHE-ECDSA-AES256-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLSv1.2"],
17152
+ [49191, "ECDHE-RSA-AES128-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLSv1.2"],
17153
+ [49192, "ECDHE-RSA-AES256-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLSv1.2"],
17154
+ [49161, "ECDHE-ECDSA-AES128-SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLSv1.0"],
17155
+ [49162, "ECDHE-ECDSA-AES256-SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLSv1.0"],
17156
+ [49171, "ECDHE-RSA-AES128-SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLSv1.0"],
17157
+ [49172, "ECDHE-RSA-AES256-SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLSv1.0"],
17158
+ [156, "AES128-GCM-SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17159
+ [157, "AES256-GCM-SHA384", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"],
17160
+ [158, "DHE-RSA-AES128-GCM-SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17161
+ [159, "DHE-RSA-AES256-GCM-SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"]
17162
+ ];
17163
+ function cipherOf(id2, protocol) {
17164
+ const known = SUITES.find(([n]) => n === id2);
17165
+ const hex3 = `0x${id2.toString(16).padStart(4, "0").toUpperCase()}`;
17166
+ return {
17167
+ name: known?.[1] ?? hex3,
17168
+ standardName: known?.[2] ?? hex3,
17169
+ version: known?.[3] ?? protocol
17170
+ };
17171
+ }
17172
+
16967
17173
  // src/kernel/net/tls-client.ts
16968
17174
  var WRITE_CHUNK = 16 * 1024;
16969
17175
  var BADCERT = [
@@ -16980,6 +17186,15 @@ function verifyCode(flags2) {
16980
17186
  if (flags2 === -1) return "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
16981
17187
  return BADCERT.find(([bit]) => (flags2 & bit) !== 0)?.[1] ?? "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
16982
17188
  }
17189
+ function isIP(name2) {
17190
+ return ipv4Bytes(name2) !== void 0 || name2.includes(":") && ipv6Groups(name2) !== void 0;
17191
+ }
17192
+ function clientFlags(o) {
17193
+ return (o.insecure ? TLS_CLIENT_INSECURE_SKIP_VERIFY : 0) | (o.sni === false ? TLS_CLIENT_NO_SNI : 0) | (o.checkHostname === false ? TLS_CLIENT_VERIFY_CHAIN_ONLY : 0);
17194
+ }
17195
+ function openSession(o) {
17196
+ return o.engine.clientSession(o.store, o.servername, o.alpn, clientFlags(o));
17197
+ }
16983
17198
  var Ciphertext = class {
16984
17199
  queue = [];
16985
17200
  ended = false;
@@ -17052,17 +17267,39 @@ async function handshake(conn, o, session, inbox) {
17052
17267
  }
17053
17268
  }
17054
17269
  }
17270
+ function controlsOf(o, session, protocol) {
17271
+ if (!o.engine.sessionControls) return {};
17272
+ const checked = o.checkHostname !== false && !o.insecure;
17273
+ return {
17274
+ cipher: cipherOf(session.ciphersuite, protocol),
17275
+ clientCertificate: o.clientCertificate === true,
17276
+ sni: o.sni !== false && !isIP(o.servername),
17277
+ hostnameChecked: checked,
17278
+ ...o.insecure ? {} : { hostnameMatches: checked || !session.nameMismatch }
17279
+ };
17280
+ }
17055
17281
  function infoOf(o, session) {
17056
17282
  const flags2 = session.verifyResult;
17057
17283
  const code = verifyCode(flags2);
17284
+ const protocol = session.version === 772 ? "TLSv1.3" : "TLSv1.2";
17058
17285
  return {
17059
- protocol: session.version === 772 ? "TLSv1.3" : "TLSv1.2",
17286
+ protocol,
17060
17287
  alpnProtocol: session.alpn ?? false,
17061
17288
  authorized: code === void 0,
17062
17289
  ...code ? { authorizationError: code, authorizationText: o.engine.verifyInfo(flags2).trim() } : {},
17063
- peerCertificates: session.peerChain()
17290
+ peerCertificates: session.peerChain(),
17291
+ ...controlsOf(o, session, protocol)
17064
17292
  };
17065
17293
  }
17294
+ var FATAL_ALERT = -30592;
17295
+ function alerted(err) {
17296
+ if (err.code === FATAL_ALERT)
17297
+ return new KernelError(
17298
+ "EPROTO",
17299
+ `ERR_SSL_FATAL_ALERT: the server ended the session: ${err.message}`
17300
+ );
17301
+ return new KernelError("EPROTO", `ERR_SSL_PROTOCOL_ERROR: ${err.message}`);
17302
+ }
17066
17303
  function plaintext(conn, session, inbox) {
17067
17304
  let freed = false;
17068
17305
  let writeClosed = false;
@@ -17077,7 +17314,12 @@ function plaintext(conn, session, inbox) {
17077
17314
  async read() {
17078
17315
  for (; ; ) {
17079
17316
  if (freed) return null;
17080
- const got = session.read();
17317
+ let got;
17318
+ try {
17319
+ got = session.read();
17320
+ } catch (err) {
17321
+ throw alerted(err);
17322
+ }
17081
17323
  await send(conn, session);
17082
17324
  if (got instanceof Uint8Array) return got;
17083
17325
  if (got === "eof") return null;
@@ -17105,8 +17347,7 @@ function plaintext(conn, session, inbox) {
17105
17347
  };
17106
17348
  }
17107
17349
  async function tlsClient(conn, o, taken = new Uint8Array(0)) {
17108
- const flags2 = o.insecure ? TLS_CLIENT_INSECURE_SKIP_VERIFY : 0;
17109
- const session = o.engine.clientSession(o.store, o.servername, o.alpn, flags2);
17350
+ const session = o.session ?? openSession(o);
17110
17351
  const inbox = new Ciphertext(conn, session);
17111
17352
  inbox.hand(taken);
17112
17353
  try {
@@ -17119,10 +17360,10 @@ async function tlsClient(conn, o, taken = new Uint8Array(0)) {
17119
17360
  }
17120
17361
 
17121
17362
  // src/kernel/net/tls-uplink.ts
17122
- var isIP = (text) => ipv4Bytes(text) !== void 0 || text.includes(":") && ipv6Groups(text) !== void 0;
17363
+ var isIP2 = (text) => ipv4Bytes(text) !== void 0 || text.includes(":") && ipv6Groups(text) !== void 0;
17123
17364
  var LABEL2 = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$/;
17124
17365
  function serverName(name2) {
17125
- if (isIP(name2.replace(/^\[|\]$/g, ""))) return name2.replace(/^\[|\]$/g, "");
17366
+ if (isIP2(name2.replace(/^\[|\]$/g, ""))) return name2.replace(/^\[|\]$/g, "");
17126
17367
  const bare = name2.endsWith(".") ? name2.slice(0, -1) : name2;
17127
17368
  if (bare.length === 0 || bare.length > 253 || !bare.split(".").every((l) => LABEL2.test(l))) {
17128
17369
  throw new KernelError(
@@ -17172,9 +17413,66 @@ function sessionOf(info, servername) {
17172
17413
  authorizationText: info.authorizationText
17173
17414
  } : {},
17174
17415
  servername,
17175
- peerCertificates: info.peerCertificates.map(encode)
17416
+ peerCertificates: info.peerCertificates.map(encode),
17417
+ ...controls(info)
17176
17418
  };
17177
17419
  }
17420
+ function controls(info) {
17421
+ if (!info.cipher) return {};
17422
+ return {
17423
+ cipher: info.cipher,
17424
+ clientCertificate: info.clientCertificate === true,
17425
+ sni: info.sni !== false,
17426
+ hostnameChecked: info.hostnameChecked === true,
17427
+ ...info.hostnameMatches === void 0 ? {} : { hostnameMatches: info.hostnameMatches }
17428
+ };
17429
+ }
17430
+ var KEY_ERRORS = [
17431
+ [-3, "ERR_OSSL_X509_KEY_VALUES_MISMATCH"],
17432
+ [-15232, "ERR_OSSL_BAD_DECRYPT"],
17433
+ [-15360, "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED"]
17434
+ ];
17435
+ function keyRefused(err) {
17436
+ const code = err instanceof TlsError ? err.code : 0;
17437
+ const name2 = KEY_ERRORS.find(([n]) => n === code)?.[1] ?? "ERR_OSSL_UNSUPPORTED";
17438
+ const text = code === -3 ? "the key does not match the certificate" : err.message;
17439
+ return new KernelError("EPROTO", `${name2}: ${text}`);
17440
+ }
17441
+ function takeCert(req) {
17442
+ const cert = req.clientCert;
17443
+ if (!cert) return void 0;
17444
+ delete req.clientCert;
17445
+ if (!Array.isArray(cert.chain) || cert.chain.length === 0 || typeof cert.key !== "string")
17446
+ throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: cert and key go together");
17447
+ let ders;
17448
+ let key;
17449
+ try {
17450
+ ders = cert.chain.map(base64Der);
17451
+ key = base64Der(cert.key);
17452
+ } catch {
17453
+ throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: cert or key is not base64");
17454
+ }
17455
+ const chain2 = new Uint8Array(ders.reduce((n, d) => n + d.length, 0));
17456
+ let at = 0;
17457
+ for (const der of ders) {
17458
+ chain2.set(der, at);
17459
+ at += der.length;
17460
+ }
17461
+ const pass2 = new TextEncoder().encode(cert.passphrase ?? "");
17462
+ return { chain: chain2, key, pass: pass2 };
17463
+ }
17464
+ function withCert(session, cert) {
17465
+ if (!cert) return;
17466
+ try {
17467
+ session.ownCert(cert.chain, cert.key, cert.pass);
17468
+ } catch (err) {
17469
+ throw keyRefused(err);
17470
+ }
17471
+ }
17472
+ function wipe(cert) {
17473
+ cert?.key.fill(0);
17474
+ cert?.pass.fill(0);
17475
+ }
17178
17476
  async function setUp(support, req) {
17179
17477
  const engine = await support.engine();
17180
17478
  if (!engine.client)
@@ -17192,21 +17490,66 @@ async function setUp(support, req) {
17192
17490
  }
17193
17491
  return { engine, store: own, own };
17194
17492
  }
17195
- function secure(support, req, servername) {
17196
- return async (raw) => {
17197
- const { engine, store, own } = await setUp(support, req);
17493
+ async function prepare(support, req, servername) {
17494
+ const cert = takeCert(req);
17495
+ try {
17496
+ return await prepareWith(support, req, servername, cert);
17497
+ } catch (err) {
17498
+ if (err instanceof KernelError) throw err;
17499
+ throw new KernelError("ENOTSUP", err instanceof Error ? err.message : String(err));
17500
+ } finally {
17501
+ wipe(cert);
17502
+ }
17503
+ }
17504
+ async function prepareWith(support, req, servername, cert) {
17505
+ if (req.checkHostname === false && req.insecure === true) {
17506
+ throw new KernelError(
17507
+ "EINVAL",
17508
+ "ERR_INVALID_ARG_VALUE: checkHostname: false needs rejectUnauthorized"
17509
+ );
17510
+ }
17511
+ const { engine, store, own } = await setUp(support, req);
17512
+ const options = {
17513
+ engine,
17514
+ store,
17515
+ servername,
17516
+ alpn: req.alpn ?? [],
17517
+ insecure: req.insecure === true,
17518
+ ...req.sni === false ? { sni: false } : {},
17519
+ ...req.checkHostname === false ? { checkHostname: false } : {},
17520
+ clientCertificate: cert !== void 0
17521
+ };
17522
+ let session;
17523
+ try {
17524
+ const controls2 = req.sni === false || req.checkHostname === false || cert !== void 0;
17525
+ if (controls2 && !engine.sessionControls) {
17526
+ throw new KernelError("ENOTSUP", "the installed tls engine has no client certificates");
17527
+ }
17528
+ session = openSession(options);
17529
+ withCert(session, cert);
17530
+ return { options: { ...options, session }, own };
17531
+ } catch (err) {
17532
+ session?.free();
17533
+ own?.release();
17534
+ throw err;
17535
+ }
17536
+ }
17537
+ function secure(prepared) {
17538
+ let ran = false;
17539
+ const make = async (raw) => {
17540
+ ran = true;
17198
17541
  try {
17199
- return await tlsClient(raw, {
17200
- engine,
17201
- store,
17202
- servername,
17203
- alpn: req.alpn ?? [],
17204
- insecure: req.insecure === true
17205
- });
17542
+ return await tlsClient(raw, prepared.options);
17206
17543
  } finally {
17207
- own?.release();
17544
+ prepared.own?.release();
17208
17545
  }
17209
17546
  };
17547
+ const abandon = () => {
17548
+ if (ran) return;
17549
+ prepared.options.session.free();
17550
+ prepared.own?.release();
17551
+ };
17552
+ return { make, abandon };
17210
17553
  }
17211
17554
  function supportOf(net) {
17212
17555
  const support = net.tlsClient;
@@ -17215,7 +17558,7 @@ function supportOf(net) {
17215
17558
  }
17216
17559
  async function uplinkAddress(proc, host) {
17217
17560
  const bare = host.replace(/^\[|\]$/g, "");
17218
- if (isIP(bare)) return bare;
17561
+ if (isIP2(bare)) return bare;
17219
17562
  const [first] = await proc.resolve(host, 0, () => proc.blocking());
17220
17563
  if (!first) throw new KernelError("ENOENT", `ENOTFOUND: ${host}`);
17221
17564
  return first;
@@ -17224,23 +17567,31 @@ async function connectSecure(proc, socket, host, port, req) {
17224
17567
  const servername = serverName(req.servername || host);
17225
17568
  if (isLoopbackHost(servername) || isLoopbackHost(host)) throw new KernelError("ENOTSUP");
17226
17569
  const address = await uplinkAddress(proc, host);
17227
- const route = proc.net.uplinkFor(address);
17228
- if (!route)
17570
+ const route2 = proc.net.uplinkFor(address);
17571
+ if (!route2)
17229
17572
  throw new KernelError("ENOTSUP", "TLS in the kernel needs a destination the uplink routes");
17230
17573
  const support = supportOf(proc.net);
17231
17574
  let info;
17232
- const make = secure(support, req, servername);
17233
- await socket.connectSecure({ family: "inet", host: address, port }, route, async (conn) => {
17234
- info = await conn.upgrade(make);
17235
- });
17575
+ const { make, abandon } = secure(await prepare(support, req, servername));
17576
+ try {
17577
+ await socket.connectSecure({ family: "inet", host: address, port }, route2, async (conn) => {
17578
+ info = await conn.upgrade(make);
17579
+ });
17580
+ } finally {
17581
+ abandon();
17582
+ }
17236
17583
  return sessionOf(info, servername);
17237
17584
  }
17238
17585
  async function upgradeSecure(proc, socket, req) {
17239
17586
  const peer = socket.peer;
17240
17587
  const host = req.servername || (peer?.family === "inet" ? peer.host : "");
17241
17588
  const servername = serverName(host);
17242
- const info = await socket.upgradeSecure(secure(supportOf(proc.net), req, servername));
17243
- return sessionOf(info, servername);
17589
+ const { make, abandon } = secure(await prepare(supportOf(proc.net), req, servername));
17590
+ try {
17591
+ return sessionOf(await socket.upgradeSecure(make), servername);
17592
+ } finally {
17593
+ abandon();
17594
+ }
17244
17595
  }
17245
17596
 
17246
17597
  // src/kernel/unix-dgram.ts
@@ -17602,17 +17953,23 @@ var NOT_HTTP_PORTS = /* @__PURE__ */ new Set([
17602
17953
  8883
17603
17954
  ]);
17604
17955
  var HOST_NAME = /^[A-Za-z0-9.-]+$|^\[?[0-9A-Fa-f:.]+\]?$/;
17956
+ function sessionControls(req) {
17957
+ return req.sni === false || req.checkHostname === false || req.clientCert !== void 0;
17958
+ }
17605
17959
  function wantsKernel(req) {
17606
17960
  if (req.path === "kernel") return true;
17607
17961
  if (req.path === "transport") return false;
17608
- return Array.isArray(req.ca) || req.alpn !== void 0 && req.alpn.length > 0 && !req.alpn.includes("http/1.1") || NOT_HTTP_PORTS.has(req.port);
17962
+ return Array.isArray(req.ca) || sessionControls(req) || req.alpn !== void 0 && req.alpn.length > 0 && !req.alpn.includes("http/1.1") || NOT_HTTP_PORTS.has(req.port);
17609
17963
  }
17610
17964
  async function secureConnect(req, proc) {
17611
17965
  if (!Number.isInteger(req.port) || req.port < 1 || req.port > 65535)
17612
17966
  throw new KernelError("EINVAL");
17613
17967
  const kernelPath = wantsKernel(req) && await uplinked(proc, req.host);
17614
17968
  if (!kernelPath) {
17615
- if (req.path === "kernel" || Array.isArray(req.ca)) throw new KernelError("ENOTSUP");
17969
+ if (req.path === "kernel" || Array.isArray(req.ca) || sessionControls(req)) {
17970
+ delete req.clientCert;
17971
+ throw new KernelError("ENOTSUP");
17972
+ }
17616
17973
  return { ...tlsOverTransport(req, proc), path: "transport", substitutes: true };
17617
17974
  }
17618
17975
  const socket = proc.net.socket("inet");
@@ -17746,13 +18103,17 @@ var VfsNode = class {
17746
18103
  const ranged2 = await this.prepare();
17747
18104
  await ranged2?.pin();
17748
18105
  }
18106
+ moving = /* @__PURE__ */ new Set();
17749
18107
  async load() {
17750
18108
  if (this.revoked) throw new KernelError("EIO");
17751
- if (!this.data) {
18109
+ while (!this.data) {
18110
+ const at = this.path;
17752
18111
  try {
17753
- this.data = await this.fs.readFileBuffer(this.path);
18112
+ this.data = await this.fs.readFileBuffer(at);
17754
18113
  } catch (err) {
17755
18114
  if (!isMissing(err)) throw err;
18115
+ while (this.moving.size > 0) await Promise.all(this.moving);
18116
+ if (this.path !== at) continue;
17756
18117
  this.data = new Uint8Array(0);
17757
18118
  this.missing = true;
17758
18119
  }
@@ -18106,6 +18467,16 @@ var VfsNodes = class {
18106
18467
  this.byPath.set(file.hidden, node);
18107
18468
  this.kept.set(node, file);
18108
18469
  }
18470
+ moving(path) {
18471
+ const node = this.byPath.get(path);
18472
+ if (!node) return () => void 0;
18473
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
18474
+ node.moving.add(promise);
18475
+ return () => {
18476
+ node.moving.delete(promise);
18477
+ resolve2();
18478
+ };
18479
+ }
18109
18480
  unkeep(path, file) {
18110
18481
  const node = this.byPath.get(file.hidden);
18111
18482
  if (!node) return;
@@ -18414,6 +18785,65 @@ function vfsFile(fs, opts, nodes) {
18414
18785
  });
18415
18786
  }
18416
18787
 
18788
+ // src/kernel/watch-ops.ts
18789
+ var WATCH_OPS = ["watch-open", "watch-next", "watch-close"];
18790
+ var WATCHING = new Set(WATCH_OPS);
18791
+ function isWatchSyscall(req) {
18792
+ return WATCHING.has(req.op);
18793
+ }
18794
+ var WATCHES_MAX = 8192;
18795
+ var parentOf8 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
18796
+ function visibleTo2(watch, root, visible) {
18797
+ return {
18798
+ close: () => watch.close(),
18799
+ next: async (signal) => {
18800
+ const notes = await watch.next(signal);
18801
+ const seen = /* @__PURE__ */ new Map();
18802
+ const shown = await Promise.all(
18803
+ notes.map(async (note) => {
18804
+ if (!("filename" in note) || !note.filename.includes("/")) return true;
18805
+ const dir = parentOf8(`${root === "/" ? "" : root}/${note.filename}`);
18806
+ if (!seen.has(dir)) seen.set(dir, visible(dir));
18807
+ return seen.get(dir);
18808
+ })
18809
+ );
18810
+ return notes.filter((_note, i) => shown[i]);
18811
+ }
18812
+ };
18813
+ }
18814
+ var ProcessWatches = class {
18815
+ open = /* @__PURE__ */ new Map();
18816
+ nextId = 1;
18817
+ source;
18818
+ constructor(source) {
18819
+ this.source = source;
18820
+ }
18821
+ async syscall(req, cancelled) {
18822
+ if (req.op === "watch-open") {
18823
+ if (!this.source) throw new KernelError("ENOSYS");
18824
+ if (this.open.size >= WATCHES_MAX) throw new KernelError("ENOSPC");
18825
+ const watch2 = await this.source.open(req.path, req.recursive === true);
18826
+ const id2 = this.nextId++;
18827
+ this.open.set(id2, watch2);
18828
+ return { ok: true, kind: "json", json: id2 };
18829
+ }
18830
+ const watch = this.open.get(req.id);
18831
+ if (req.op === "watch-close") {
18832
+ watch?.close();
18833
+ this.open.delete(req.id);
18834
+ return { ok: true, kind: "void" };
18835
+ }
18836
+ if (!watch) return { ok: true, kind: "json", json: [{ ended: true }] };
18837
+ const notes = await watch.next(cancelled);
18838
+ if (notes.some((note) => "ended" in note)) this.open.delete(req.id);
18839
+ return { ok: true, kind: "json", json: notes };
18840
+ }
18841
+ closeAll() {
18842
+ for (const watch of this.open.values()) watch.close();
18843
+ this.open.clear();
18844
+ }
18845
+ };
18846
+
18417
18847
  // src/kernel/process.ts
18418
18848
  function aborted(signal) {
18419
18849
  return new Promise((_, reject) => {
@@ -18540,7 +18970,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
18540
18970
  ...SEM_OPS,
18541
18971
  ...HTTP_OPS,
18542
18972
  ...PTY_OPS,
18543
- ...HOST_OPS
18973
+ ...HOST_OPS,
18974
+ ...WATCH_OPS
18544
18975
  ]);
18545
18976
  function isWasmSyscall(req) {
18546
18977
  const op = req.op;
@@ -18592,6 +19023,7 @@ function timeLeft(req, started) {
18592
19023
  }
18593
19024
  var WasmProcess = class {
18594
19025
  exited = false;
19026
+ watches;
18595
19027
  children;
18596
19028
  deathSignals = /* @__PURE__ */ new Map();
18597
19029
  caught = 0;
@@ -18622,6 +19054,7 @@ var WasmProcess = class {
18622
19054
  this.pid = pid;
18623
19055
  this.fds = fds;
18624
19056
  this.options = options;
19057
+ this.watches = new ProcessWatches(options.watches);
18625
19058
  this.inherited = options.ignored ?? 0;
18626
19059
  this.umask = options.umask ?? DEFAULT_UMASK2;
18627
19060
  this.cred = copyCred(options.cred ?? ROOT);
@@ -18879,6 +19312,7 @@ var WasmProcess = class {
18879
19312
  });
18880
19313
  }
18881
19314
  if (isHttpSyscall(req)) return await this.httpSyscall(req);
19315
+ if (isWatchSyscall(req)) return await this.watches.syscall(req, cancelled);
18882
19316
  if (isHostSyscall(req)) {
18883
19317
  return await hostSyscall(req, {
18884
19318
  pid: this.pid,
@@ -20019,6 +20453,7 @@ var WasmProcess = class {
20019
20453
  this.options.locks?.release(this.pid);
20020
20454
  for (const owner of this.lockOwners) this.options.locks?.release(owner);
20021
20455
  this.asyncOps.close();
20456
+ this.watches.closeAll();
20022
20457
  for (const [target, sig] of this.deathSignals) this.sendDeath(target, sig);
20023
20458
  this.deathSignals.clear();
20024
20459
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
@@ -20280,6 +20715,7 @@ function processOptions(opts, header4, hooks, created3) {
20280
20715
  locks: opts.locks,
20281
20716
  fifos: opts.fifos,
20282
20717
  ...opts.sems ? { sems: opts.sems } : {},
20718
+ watches: opts.watches,
20283
20719
  onReap: opts.onReap,
20284
20720
  resolver: opts.resolver,
20285
20721
  ...notifiers(header4),
@@ -21393,7 +21829,7 @@ function register(automaton, sources, ids, bytes, source) {
21393
21829
  }
21394
21830
  sources[id2].push(source);
21395
21831
  }
21396
- function prepare(secrets) {
21832
+ function prepare2(secrets) {
21397
21833
  const main = new Automaton();
21398
21834
  const wrap = new Automaton();
21399
21835
  const sources = [];
@@ -21544,7 +21980,7 @@ function merge(into, from) {
21544
21980
  }
21545
21981
  }
21546
21982
  function compile(secrets, options = {}) {
21547
- const c = prepare(secrets.map((secret) => ({ ...secret })));
21983
+ const c = prepare2(secrets.map((secret) => ({ ...secret })));
21548
21984
  const decoder4 = new TextDecoder();
21549
21985
  const idleMs = options.idleMs ?? 250;
21550
21986
  const scrubBytes = (input) => {
@@ -25565,6 +26001,7 @@ var Launcher = class {
25565
26001
  locks: this.locks,
25566
26002
  fifos: this.fifos,
25567
26003
  sems: this.sems,
26004
+ watches: this.watchSource(pid),
25568
26005
  resolver: this.resolver,
25569
26006
  onReap: (child) => this.reaped(child, pid),
25570
26007
  ...req.fork ? { fork: req.fork } : {},
@@ -26108,6 +26545,23 @@ var Launcher = class {
26108
26545
  mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
26109
26546
  });
26110
26547
  }
26548
+ watchSource(pid) {
26549
+ return {
26550
+ open: async (path, recursive) => {
26551
+ const at = normalizePath(path);
26552
+ const g = this.execGuard(this.credOf(pid), pid);
26553
+ if (g) await readable(g, "watch", at);
26554
+ const st = await this.fs.stat(at);
26555
+ const real = await this.fs.realpath?.(at, true).catch(() => at) ?? at;
26556
+ const watch = this.watchers.typedWatch(real, recursive && st.isDirectory, at);
26557
+ if (!g || g.cred.euid === 0) return watch;
26558
+ return visibleTo2(watch, real, async (dir) => {
26559
+ const walked = await access(g.fs, g.cred, dir, R_OK | X_OK, { directory: true });
26560
+ return walked.denied === void 0 && walked.missing !== true;
26561
+ });
26562
+ }
26563
+ };
26564
+ }
26111
26565
  watchFiles(paths, notify) {
26112
26566
  this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
26113
26567
  return this.fileWatches.watch(paths, notify);
@@ -26549,7 +27003,7 @@ function cronLocks() {
26549
27003
  import { AsyncLocalStorage } from "node:async_hooks";
26550
27004
  import { subscribe } from "node:diagnostics_channel";
26551
27005
  import { lookup as lookup2 } from "node:dns/promises";
26552
- import { isIP as isIP2 } from "node:net";
27006
+ import { isIP as isIP3 } from "node:net";
26553
27007
  var DEVTOOLS_PORTS = [9222, 9223, 9224, 9225, 9226, 9227, 9228, 9229];
26554
27008
  var METADATA_V6 = [64768, 3778, 0, 0, 0, 0, 0, 596].join(":");
26555
27009
  var resolveHost = async (host) => (await lookup2(host, { all: true, verbatim: true })).map((a) => a.address);
@@ -26561,7 +27015,7 @@ function ipv4Place(address) {
26561
27015
  }
26562
27016
  function placeOf(address) {
26563
27017
  const plain2 = address.replace(/^\[(.*)\]$/, "$1").toLowerCase();
26564
- const kind = isIP2(plain2);
27018
+ const kind = isIP3(plain2);
26565
27019
  if (kind === 4) return ipv4Place(plain2);
26566
27020
  if (kind !== 6) return void 0;
26567
27021
  const words2 = hextets2(plain2);
@@ -26614,7 +27068,7 @@ function connectRefusal(address, port, place) {
26614
27068
  }
26615
27069
  async function addressesOf(host, resolve2) {
26616
27070
  const plain2 = host.replace(/^\[(.*)\]$/, "$1");
26617
- if (isIP2(plain2)) return [plain2];
27071
+ if (isIP3(plain2)) return [plain2];
26618
27072
  if (loopbackName2(plain2)) return ["127.0.0.1"];
26619
27073
  return resolve2(plain2).catch(() => []);
26620
27074
  }
@@ -26949,7 +27403,7 @@ function isMemoryRoot(handle) {
26949
27403
 
26950
27404
  // src/node/ws-diagnose.ts
26951
27405
  import { lookup as lookup3 } from "node:dns/promises";
26952
- import { isIP as isIP3, connect as tcp } from "node:net";
27406
+ import { isIP as isIP4, connect as tcp } from "node:net";
26953
27407
  import { connect as tls } from "node:tls";
26954
27408
  var PROBE_MS = 3e3;
26955
27409
  var PASSED = /* @__PURE__ */ new Set(["ECONNREFUSED", "EHOSTUNREACH", "ENETUNREACH", "ETIMEDOUT", "ECONNRESET"]);
@@ -26971,7 +27425,7 @@ function abortable(work, signal) {
26971
27425
  }
26972
27426
  function probe(secure2, host, port, ms, signal) {
26973
27427
  return new Promise((resolve2) => {
26974
- const socket = secure2 ? tls({ host, port, ...isIP3(host) ? {} : { servername: host } }) : tcp({ host, port });
27428
+ const socket = secure2 ? tls({ host, port, ...isIP4(host) ? {} : { servername: host } }) : tcp({ host, port });
26975
27429
  const stop = () => done4("ABORTED");
26976
27430
  const done4 = (code) => {
26977
27431
  clearTimeout(timer);