@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
@@ -4028,7 +4028,12 @@ function openPipe(capacity) {
4028
4028
  }
4029
4029
  },
4030
4030
  close: () => pipe.closeRead(),
4031
- poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
4031
+ poll: () => ({
4032
+ readable: pipe.readReady,
4033
+ writable: false,
4034
+ hangup: pipe.writersGone,
4035
+ nread: pipe.buffered
4036
+ }),
4032
4037
  changed: (signal) => pipe.changed(signal),
4033
4038
  identity: pipe
4034
4039
  }),
@@ -4655,8 +4660,8 @@ function uplinkLink(conn, onReset) {
4655
4660
  open = false;
4656
4661
  conn.close();
4657
4662
  };
4658
- void inbound(conn, rx).catch(() => {
4659
- onReset();
4663
+ void inbound(conn, rx).catch((err) => {
4664
+ onReset(err);
4660
4665
  close();
4661
4666
  tx.closeRead();
4662
4667
  }).finally(() => rx.closeWrite());
@@ -4747,7 +4752,7 @@ var KernelSocket = class _KernelSocket {
4747
4752
  state = "open";
4748
4753
  dialing;
4749
4754
  failed;
4750
- reset = false;
4755
+ reset;
4751
4756
  local;
4752
4757
  peer;
4753
4758
  peerCred;
@@ -4786,19 +4791,20 @@ var KernelSocket = class _KernelSocket {
4786
4791
  const { rx } = this.connection();
4787
4792
  if (this.readShut || max <= 0) return new Uint8Array(0);
4788
4793
  const bytes = await pipeCall(() => rx.read(max, signal));
4789
- if (bytes.length === 0 && this.reset) throw new KernelError("ECONNRESET");
4794
+ if (bytes.length === 0 && this.reset) throw this.reset;
4790
4795
  return bytes;
4791
4796
  }
4792
4797
  async peek(max, signal) {
4793
4798
  const { rx } = this.connection();
4794
4799
  if (this.readShut || max <= 0) return new Uint8Array(0);
4795
4800
  const bytes = await pipeCall(() => rx.peek(max, signal));
4796
- if (bytes.length === 0 && this.reset) throw new KernelError("ECONNRESET");
4801
+ if (bytes.length === 0 && this.reset) throw this.reset;
4797
4802
  return bytes;
4798
4803
  }
4799
4804
  async write(bytes, signal) {
4800
4805
  const { tx } = this.connection();
4801
4806
  if (this.writeShut) throw new KernelError("EPIPE");
4807
+ if (this.reset?.code === "EPROTO") throw this.reset;
4802
4808
  return pipeCall(() => tx.write(bytes, signal));
4803
4809
  }
4804
4810
  poll() {
@@ -4815,7 +4821,8 @@ var KernelSocket = class _KernelSocket {
4815
4821
  readable: this.readShut || rx.readReady,
4816
4822
  writable: this.writeShut || tx.writeReady,
4817
4823
  hangup: rx.writersGone && tx.readersGone,
4818
- rdhup: rx.writersGone
4824
+ rdhup: rx.writersGone,
4825
+ nread: this.readShut ? 0 : rx.buffered
4819
4826
  };
4820
4827
  }
4821
4828
  changed(signal) {
@@ -4875,8 +4882,8 @@ var KernelSocket = class _KernelSocket {
4875
4882
  if (this.state !== "open") throw new KernelError("EINVAL");
4876
4883
  if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
4877
4884
  this.failed = void 0;
4878
- const route = viaUplink && addr.family === "inet" ? this.net.uplinkFor(addr.host) : void 0;
4879
- if (route) return this.dial(addr, route);
4885
+ const route2 = viaUplink && addr.family === "inet" ? this.net.uplinkFor(addr.host) : void 0;
4886
+ if (route2) return this.dial(addr, route2);
4880
4887
  const target = this.net.target(addr, name2);
4881
4888
  if (!this.local) {
4882
4889
  if (addr.family === "inet") {
@@ -4932,9 +4939,9 @@ var KernelSocket = class _KernelSocket {
4932
4939
  }
4933
4940
  }
4934
4941
  switch;
4935
- connectSecure(addr, route, secure2) {
4942
+ connectSecure(addr, route2, secure2) {
4936
4943
  if (this.state !== "open") throw new KernelError("EISCONN");
4937
- return this.dial(addr, route, secure2);
4944
+ return this.dial(addr, route2, secure2);
4938
4945
  }
4939
4946
  upgradeSecure(make) {
4940
4947
  if (this.state !== "connected") throw new KernelError("ENOTCONN");
@@ -4942,7 +4949,7 @@ var KernelSocket = class _KernelSocket {
4942
4949
  if (this.switch.upgraded || this.writeShut || this.readShut) throw new KernelError("EINVAL");
4943
4950
  return this.switch.upgrade(make);
4944
4951
  }
4945
- dial(addr, route, secure2) {
4952
+ dial(addr, route2, secure2) {
4946
4953
  this.state = "connecting";
4947
4954
  const abort = new AbortController();
4948
4955
  this.dialing = abort;
@@ -4950,13 +4957,13 @@ var KernelSocket = class _KernelSocket {
4950
4957
  const timer = setTimeout(() => {
4951
4958
  timedOut = true;
4952
4959
  abort.abort(new KernelError("ETIMEDOUT"));
4953
- }, route.timeoutMs ?? DIAL_TIMEOUT_MS);
4960
+ }, route2.timeoutMs ?? DIAL_TIMEOUT_MS);
4954
4961
  const abandoned = new Promise((_, reject) => {
4955
4962
  abort.signal.addEventListener("abort", () => reject(abort.signal.reason), { once: true });
4956
4963
  });
4957
4964
  const { host, port } = addr;
4958
4965
  const dialled = new Promise(
4959
- (resolve2) => resolve2(route.uplink.dial({ network: "tcp", host, port, signal: abort.signal }))
4966
+ (resolve2) => resolve2(route2.uplink.dial({ network: "tcp", host, port, signal: abort.signal }))
4960
4967
  );
4961
4968
  dialled.then(
4962
4969
  (conn) => abort.signal.aborted && conn.close(),
@@ -4983,8 +4990,8 @@ var KernelSocket = class _KernelSocket {
4983
4990
  this.dialing = void 0;
4984
4991
  this.switch = conn;
4985
4992
  this.attach(
4986
- uplinkLink(conn, () => {
4987
- this.reset = true;
4993
+ uplinkLink(conn, (err) => {
4994
+ this.reset = err instanceof KernelError && err.code === "EPROTO" ? err : new KernelError("ECONNRESET");
4988
4995
  this.changes.wake();
4989
4996
  })
4990
4997
  );
@@ -5105,14 +5112,14 @@ var LoopbackNet = class {
5105
5112
  if (!this.origins) throw new KernelError("ENETUNREACH");
5106
5113
  void this.origins(conn, origin).catch(() => conn.close());
5107
5114
  }
5108
- useUplink(route) {
5109
- this.route = route;
5115
+ useUplink(route2) {
5116
+ this.route = route2;
5110
5117
  }
5111
5118
  tlsClient;
5112
5119
  uplinkFor(host) {
5113
- const route = this.route;
5114
- if (!route) return void 0;
5115
- return this.destination(host) === "uplink" ? route : void 0;
5120
+ const route2 = this.route;
5121
+ if (!route2) return void 0;
5122
+ return this.destination(host) === "uplink" ? route2 : void 0;
5116
5123
  }
5117
5124
  destination(host) {
5118
5125
  return (this.route?.routes ?? LOCAL_ONLY).classify(host);
@@ -6625,7 +6632,7 @@ function satisfies(version, range2) {
6625
6632
  }
6626
6633
 
6627
6634
  // src/version.ts
6628
- var KERNEL_VERSION = true ? "1.124.12" : "0.0.0-dev";
6635
+ var KERNEL_VERSION = true ? "1.125.0" : "0.0.0-dev";
6629
6636
  var OS_RELEASE = "6.0.0-slicc";
6630
6637
  function procVersion() {
6631
6638
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -7682,16 +7689,24 @@ var UnlinkedKeeper = class {
7682
7689
  gated();
7683
7690
  const held = /* @__PURE__ */ new Map();
7684
7691
  const hidden = /* @__PURE__ */ new Map();
7685
- for (; ; ) {
7686
- await take();
7687
- const now = this.heldWithin(target);
7688
- const next3 = paths.filter((path) => now.has(path) && !held.has(path));
7689
- if (next3.length === 0) break;
7690
- for (const path of next3) {
7691
- held.set(path, now.get(path));
7692
- const at = await this.hide(path);
7693
- if (at !== void 0) hidden.set(path, at);
7692
+ const moved2 = [];
7693
+ try {
7694
+ for (; ; ) {
7695
+ await take();
7696
+ const now = this.heldWithin(target);
7697
+ const next3 = paths.filter((path) => now.has(path) && !held.has(path));
7698
+ if (next3.length === 0) break;
7699
+ for (const path of next3) {
7700
+ const holders = now.get(path);
7701
+ held.set(path, holders);
7702
+ for (const holder of holders) moved2.push(holder.moving?.(path) ?? (() => void 0));
7703
+ const at = await this.hide(path);
7704
+ if (at !== void 0) hidden.set(path, at);
7705
+ }
7694
7706
  }
7707
+ } catch (err) {
7708
+ for (const settle3 of moved2) settle3();
7709
+ throw err;
7695
7710
  }
7696
7711
  sealed();
7697
7712
  const done4 = () => {
@@ -7704,6 +7719,7 @@ var UnlinkedKeeper = class {
7704
7719
  files.set(path, file);
7705
7720
  for (const holder of holders) holder.keep(path, file);
7706
7721
  }
7722
+ for (const settle3 of moved2) settle3();
7707
7723
  return {
7708
7724
  moved: new Set(hidden.keys()),
7709
7725
  commit: () => {
@@ -9409,6 +9425,8 @@ async function physicalPathAsync(fs, path) {
9409
9425
  }
9410
9426
 
9411
9427
  // src/fs/watch.ts
9428
+ var WATCH_NOTES_LIMIT = 1e3;
9429
+ var baseOf2 = (path) => path.slice(path.lastIndexOf("/") + 1);
9412
9430
  var WATCH_BATCH_LIMIT = 1e3;
9413
9431
  var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
9414
9432
  var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
@@ -9418,8 +9436,39 @@ function touches(watcher, changed) {
9418
9436
  );
9419
9437
  }
9420
9438
  var REALS_KEPT = 4096;
9439
+ function noted(watch, note) {
9440
+ if (watch.ended) return;
9441
+ if ("ended" in note) {
9442
+ watch.notes.push(note);
9443
+ watch.ended = true;
9444
+ watch.wake?.();
9445
+ return;
9446
+ }
9447
+ if (watch.overflowed) return;
9448
+ if (watch.notes.length >= WATCH_NOTES_LIMIT) {
9449
+ watch.overflowed = true;
9450
+ watch.notes.push({ overflow: true });
9451
+ } else watch.notes.push(note);
9452
+ watch.wake?.();
9453
+ }
9454
+ function route(watch, type, path) {
9455
+ for (const root of watch.roots) if (routed(watch, root, type, path)) return;
9456
+ }
9457
+ function routed(watch, root, type, path) {
9458
+ if (path === root || type === "rename" && root !== "/" && under3(root, path)) {
9459
+ noted(watch, { type, filename: baseOf2(root) });
9460
+ if (type === "rename") noted(watch, { ended: true });
9461
+ return true;
9462
+ }
9463
+ if (parentOf6(path) === root) noted(watch, { type, filename: baseOf2(path) });
9464
+ else if (watch.recursive && under3(path, root)) {
9465
+ noted(watch, { type, filename: path.slice(root === "/" ? 1 : root.length + 1) });
9466
+ } else return false;
9467
+ return true;
9468
+ }
9421
9469
  var FsWatchers = class {
9422
9470
  watchers = /* @__PURE__ */ new Set();
9471
+ typed = /* @__PURE__ */ new Set();
9423
9472
  taps = [];
9424
9473
  reals = /* @__PURE__ */ new Map();
9425
9474
  reshapes = [];
@@ -9446,7 +9495,65 @@ var FsWatchers = class {
9446
9495
  this.watchers.delete(watcher);
9447
9496
  };
9448
9497
  }
9498
+ typedWatch(root, recursive, alias) {
9499
+ const main = normalizePath(root);
9500
+ const other = alias === void 0 ? main : normalizePath(alias);
9501
+ const watch = {
9502
+ roots: other === main ? [main] : [main, other],
9503
+ recursive,
9504
+ notes: [],
9505
+ overflowed: false,
9506
+ ended: false,
9507
+ wake: void 0
9508
+ };
9509
+ this.typed.add(watch);
9510
+ const close = () => {
9511
+ this.typed.delete(watch);
9512
+ watch.ended = true;
9513
+ watch.wake?.();
9514
+ };
9515
+ return {
9516
+ close,
9517
+ next: async (signal) => {
9518
+ while (watch.notes.length === 0 && !watch.ended && !signal.aborted) {
9519
+ await new Promise((resolve2) => {
9520
+ const done4 = () => {
9521
+ watch.wake = void 0;
9522
+ signal.removeEventListener("abort", done4);
9523
+ resolve2();
9524
+ };
9525
+ watch.wake = done4;
9526
+ signal.addEventListener("abort", done4, { once: true });
9527
+ });
9528
+ }
9529
+ const notes = watch.notes.splice(0);
9530
+ watch.overflowed = false;
9531
+ if (watch.ended) this.typed.delete(watch);
9532
+ return notes.length === 0 && watch.ended ? [{ ended: true }] : notes;
9533
+ }
9534
+ };
9535
+ }
9536
+ covers(path) {
9537
+ for (const watch of this.typed) {
9538
+ for (const root of watch.roots) {
9539
+ if (path === root || parentOf6(path) === root || watch.recursive && under3(path, root))
9540
+ return true;
9541
+ }
9542
+ }
9543
+ return false;
9544
+ }
9545
+ emit(type, paths) {
9546
+ if (this.typed.size === 0) return;
9547
+ for (const path of paths) {
9548
+ const at = normalizePath(path);
9549
+ for (const watch of [...this.typed]) route(watch, type, at);
9550
+ }
9551
+ }
9449
9552
  changed(...paths) {
9553
+ this.report(paths);
9554
+ this.emit("change", paths);
9555
+ }
9556
+ report(paths) {
9450
9557
  for (const tap of this.taps) tap(paths);
9451
9558
  for (const watcher of this.watchers) {
9452
9559
  for (const path of paths) {
@@ -9466,11 +9573,15 @@ var FsWatchers = class {
9466
9573
  watcher.notify(paths.length > WATCH_BATCH_LIMIT ? { overflow: true } : { paths });
9467
9574
  }
9468
9575
  wrap(fs) {
9469
- const after = (run2, changed) => async (...args) => {
9576
+ const after = (run2, changed, type) => async (...args) => {
9577
+ let done4 = false;
9470
9578
  try {
9471
9579
  await run2(...args);
9580
+ done4 = true;
9472
9581
  } finally {
9473
- this.changed(...changed(...args));
9582
+ const paths = changed(...args);
9583
+ this.report(paths);
9584
+ if (done4) this.emit(type, paths);
9474
9585
  }
9475
9586
  };
9476
9587
  const reals = this.reals;
@@ -9489,15 +9600,21 @@ var FsWatchers = class {
9489
9600
  reals.set(at, real);
9490
9601
  return real;
9491
9602
  };
9492
- const content = (run2) => async (...args) => {
9603
+ const content = (run2, types) => async (...args) => {
9604
+ const at = normalizePath(args[0]);
9605
+ const fresh2 = this.covers(at) && !await fs.exists(at).catch(() => true);
9606
+ let done4 = false;
9493
9607
  try {
9494
9608
  await run2(...args);
9609
+ done4 = true;
9495
9610
  } finally {
9496
- const at = normalizePath(args[0]);
9497
9611
  const real = await realOf(at);
9498
- this.changed(...real === at ? [at] : [at, real]);
9612
+ const paths = real === at ? [at] : [at, real];
9613
+ this.report(paths);
9614
+ if (done4) for (const type of types(fresh2)) this.emit(type, paths);
9499
9615
  }
9500
9616
  };
9617
+ const written2 = (fresh2) => fresh2 ? ["rename", "change"] : ["change"];
9501
9618
  const reshaping = (run2) => async (...args) => {
9502
9619
  this.reshaped();
9503
9620
  try {
@@ -9507,8 +9624,8 @@ var FsWatchers = class {
9507
9624
  }
9508
9625
  };
9509
9626
  const overrides = {
9510
- writeFile: content(fs.writeFile.bind(fs)),
9511
- ...fs.createExclusive ? { createExclusive: content(fs.createExclusive.bind(fs)) } : {},
9627
+ writeFile: content(fs.writeFile.bind(fs), written2),
9628
+ ...fs.createExclusive ? { createExclusive: content(fs.createExclusive.bind(fs), () => ["rename"]) } : {},
9512
9629
  mkdir: async (path, options) => {
9513
9630
  const created3 = [];
9514
9631
  if (options?.recursive) {
@@ -9518,33 +9635,39 @@ var FsWatchers = class {
9518
9635
  if (created3.length > 0 || !await fs.exists(ancestor)) created3.push(ancestor);
9519
9636
  }
9520
9637
  }
9638
+ const fresh2 = !options?.recursive || !(this.covers(path) && await fs.exists(path));
9639
+ let done4 = false;
9521
9640
  try {
9522
9641
  await fs.mkdir(path, options);
9642
+ done4 = true;
9523
9643
  } finally {
9524
- this.changed(...created3, path);
9644
+ this.report([...created3, path]);
9645
+ if (done4) this.emit("rename", fresh2 ? [...created3, path] : created3);
9525
9646
  }
9526
9647
  },
9527
- rm: reshaping(after(fs.rm.bind(fs), (path) => [path])),
9528
- rename: reshaping(after(fs.rename.bind(fs), (from, to) => [from, to])),
9648
+ rm: reshaping(after(fs.rm.bind(fs), (path) => [path], "rename")),
9649
+ rename: reshaping(after(fs.rename.bind(fs), (from, to) => [from, to], "rename")),
9529
9650
  link: reshaping(
9530
9651
  after(
9531
9652
  (from, to, follow2) => fs.link(from, to, follow2),
9532
- (_from, to) => [to]
9653
+ (_from, to) => [to],
9654
+ "rename"
9533
9655
  )
9534
9656
  ),
9535
- symlink: reshaping(after(fs.symlink.bind(fs), (_target, path) => [path])),
9536
- chmod: after(fs.chmod.bind(fs), (path) => [path]),
9537
- ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
9538
- utimes: after(fs.utimes.bind(fs), (path) => [path]),
9657
+ symlink: reshaping(after(fs.symlink.bind(fs), (_target, path) => [path], "rename")),
9658
+ chmod: after(fs.chmod.bind(fs), (path) => [path], "change"),
9659
+ ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path], "change") } : {},
9660
+ utimes: after(fs.utimes.bind(fs), (path) => [path], "change"),
9539
9661
  lutimes: after(
9540
9662
  (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
9541
- (path) => [path]
9663
+ (path) => [path],
9664
+ "change"
9542
9665
  )
9543
9666
  };
9544
9667
  const ranged2 = rangedOps(fs);
9545
9668
  if (ranged2) {
9546
- overrides.pwrite = content(ranged2.pwrite);
9547
- overrides.truncate = content(ranged2.truncate);
9669
+ overrides.pwrite = content(ranged2.pwrite, () => ["change"]);
9670
+ overrides.truncate = content(ranged2.truncate, () => ["change"]);
9548
9671
  }
9549
9672
  return new Proxy(fs, {
9550
9673
  get(target, key, receiver) {
@@ -10152,7 +10275,8 @@ var FifoTable = class {
10152
10275
  poll: () => ({
10153
10276
  readable: reads && pipe.readReady,
10154
10277
  writable: writes && pipe.writeReady,
10155
- hangup: reads ? pipe.writersGone : pipe.readersGone
10278
+ hangup: reads ? pipe.writersGone : pipe.readersGone,
10279
+ ...reads ? { nread: pipe.buffered } : {}
10156
10280
  }),
10157
10281
  changed: (signal) => pipe.changed(signal)
10158
10282
  });
@@ -10551,8 +10675,8 @@ var MountTable = class {
10551
10675
  ...error ? { error } : {}
10552
10676
  })
10553
10677
  );
10554
- const noted = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
10555
- return [...mounted, ...noted.map((note) => ({ ...note, options: { ...note.options } }))];
10678
+ const noted2 = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
10679
+ return [...mounted, ...noted2.map((note) => ({ ...note, options: { ...note.options } }))];
10556
10680
  }
10557
10681
  listed() {
10558
10682
  return this.list().filter((entry) => !this.system.has(entry.target));
@@ -14773,6 +14897,8 @@ function realmCa(owner, store) {
14773
14897
 
14774
14898
  // src/kernel/net/tls-engine.ts
14775
14899
  var TLS_CLIENT_INSECURE_SKIP_VERIFY = 1;
14900
+ var TLS_CLIENT_NO_SNI = 2;
14901
+ var TLS_CLIENT_VERIFY_CHAIN_ONLY = 4;
14776
14902
  var SCRATCH = 64 * 1024;
14777
14903
  var EOF = -1;
14778
14904
  var TlsError = class extends Error {
@@ -14829,10 +14955,22 @@ var TlsEngine = class {
14829
14955
  this.m._free(ptr);
14830
14956
  }
14831
14957
  }
14958
+ withSecret(bytes, fn) {
14959
+ const ptr = this.put(bytes);
14960
+ try {
14961
+ return fn(ptr);
14962
+ } finally {
14963
+ this.m.HEAPU8.fill(0, ptr, ptr + bytes.length + 1);
14964
+ this.m._free(ptr);
14965
+ }
14966
+ }
14832
14967
  intoScratch(fn) {
14833
14968
  const n = fn(this.scratch, SCRATCH);
14834
14969
  return n > 0 ? this.taken(n) : n;
14835
14970
  }
14971
+ get sessionControls() {
14972
+ return typeof this.m._tls_client_cert === "function";
14973
+ }
14836
14974
  get client() {
14837
14975
  return typeof this.m._tls_client_new === "function" ? this.m : void 0;
14838
14976
  }
@@ -14985,6 +15123,27 @@ var TlsSession = class {
14985
15123
  const out = this.engine.intoScratch((p, max) => this.c._tls_alpn(this.ptr, p, max));
14986
15124
  return out instanceof Uint8Array ? new TextDecoder().decode(out) : void 0;
14987
15125
  }
15126
+ ownCert(chain2, key, pass2) {
15127
+ const set3 = this.m._tls_client_cert;
15128
+ const keyLen = key[0] === 45 ? key.length + 1 : key.length;
15129
+ const ret = this.engine.withBytes(
15130
+ chain2,
15131
+ (c) => this.engine.withSecret(
15132
+ key,
15133
+ (k) => this.engine.withSecret(
15134
+ pass2,
15135
+ (p) => set3(this.ptr, c, chain2.length, k, keyLen, pass2.length > 0 ? p : 0, pass2.length)
15136
+ )
15137
+ )
15138
+ );
15139
+ if (ret !== 0) throw this.engine.error(ret, "client certificate");
15140
+ }
15141
+ get ciphersuite() {
15142
+ return this.m._tls_ciphersuite(this.ptr);
15143
+ }
15144
+ get nameMismatch() {
15145
+ return this.m._tls_name_mismatch(this.ptr) === 1;
15146
+ }
14988
15147
  peerChain() {
14989
15148
  const chain2 = [];
14990
15149
  const count2 = this.c._tls_peer_count(this.ptr);
@@ -15986,7 +16145,8 @@ var KernelTty = class {
15986
16145
  poll: () => ({
15987
16146
  readable: this.readable.length > 0 || this.hungUp,
15988
16147
  writable: true,
15989
- hangup: this.hungUp
16148
+ hangup: this.hungUp,
16149
+ nread: this.readable.reduce((n, chunk2) => n + (chunk2?.length ?? 0), 0)
15990
16150
  }),
15991
16151
  changed: (signal) => this.changed(signal),
15992
16152
  close: () => {
@@ -16182,7 +16342,8 @@ var PtyPair = class {
16182
16342
  poll: () => ({
16183
16343
  readable: this.queue.length > 0 || this.slaveGone,
16184
16344
  writable: true,
16185
- hangup: this.slaveGone
16345
+ hangup: this.slaveGone,
16346
+ nread: this.queue.reduce((n, chunk2) => n + chunk2.length, 0)
16186
16347
  }),
16187
16348
  changed: (signal) => this.changed(signal),
16188
16349
  close: () => this.closeMaster(),
@@ -16403,11 +16564,11 @@ async function runChild(req, deps) {
16403
16564
  if (pid !== 0) kill();
16404
16565
  decided.resolve();
16405
16566
  };
16406
- const controls = [];
16567
+ const controls2 = [];
16407
16568
  const capture = () => {
16408
16569
  const { promise, resolve: resolve2 } = Promise.withResolvers();
16409
16570
  const control = { shut: false, ended: resolve2, eof: promise };
16410
- controls.push(control);
16571
+ controls2.push(control);
16411
16572
  return { capture: true, max, overflow: () => stop("ENOBUFS"), control };
16412
16573
  };
16413
16574
  const decided = Promise.withResolvers();
@@ -16441,11 +16602,11 @@ async function runChild(req, deps) {
16441
16602
  let status;
16442
16603
  try {
16443
16604
  status = await reaped(deps, pid);
16444
- const open = controls.filter((c) => c.opened);
16605
+ const open = controls2.filter((c) => c.opened);
16445
16606
  await Promise.race([Promise.all(open.map((c) => c.eof)), decided.promise]);
16446
16607
  } finally {
16447
16608
  cancel?.();
16448
- for (const control of controls) control.shut = true;
16609
+ for (const control of controls2) control.shut = true;
16449
16610
  }
16450
16611
  const out = deps.children.captured(pid, 1);
16451
16612
  const err = deps.children.captured(pid, 2);
@@ -16464,13 +16625,17 @@ async function runChild(req, deps) {
16464
16625
  // src/kernel/select.ts
16465
16626
  function ready(fds, read, write) {
16466
16627
  const hangup = /* @__PURE__ */ new Set();
16628
+ const nread = {};
16467
16629
  const pick = (list, dir) => list.filter((fd) => {
16468
16630
  const state = pollFile(fds.get(fd).file);
16469
16631
  if (state.hangup) hangup.add(fd);
16470
- return state[dir] || state.hangup;
16632
+ const picked = state[dir] || state.hangup;
16633
+ if (picked && dir === "readable" && state.nread !== void 0) nread[fd] = state.nread;
16634
+ return picked;
16471
16635
  });
16472
16636
  const out = { read: pick(read, "readable"), write: pick(write, "writable") };
16473
16637
  if (hangup.size > 0) out.hangup = [...hangup];
16638
+ if (Object.keys(nread).length > 0) out.nread = nread;
16474
16639
  return out;
16475
16640
  }
16476
16641
  function interruption(signal) {
@@ -17027,6 +17192,47 @@ function encodeReceived(got) {
17027
17192
  return out;
17028
17193
  }
17029
17194
 
17195
+ // src/kernel/net/tls-ciphers.ts
17196
+ var SUITES = [
17197
+ [4865, "TLS_AES_128_GCM_SHA256", "TLS_AES_128_GCM_SHA256", "TLSv1.3"],
17198
+ [4866, "TLS_AES_256_GCM_SHA384", "TLS_AES_256_GCM_SHA384", "TLSv1.3"],
17199
+ [4867, "TLS_CHACHA20_POLY1305_SHA256", "TLS_CHACHA20_POLY1305_SHA256", "TLSv1.3"],
17200
+ [4868, "TLS_AES_128_CCM_SHA256", "TLS_AES_128_CCM_SHA256", "TLSv1.3"],
17201
+ [4869, "TLS_AES_128_CCM_8_SHA256", "TLS_AES_128_CCM_8_SHA256", "TLSv1.3"],
17202
+ [49195, "ECDHE-ECDSA-AES128-GCM-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17203
+ [49196, "ECDHE-ECDSA-AES256-GCM-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"],
17204
+ [49199, "ECDHE-RSA-AES128-GCM-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17205
+ [49200, "ECDHE-RSA-AES256-GCM-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"],
17206
+ [52392, "ECDHE-RSA-CHACHA20-POLY1305", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLSv1.2"],
17207
+ [
17208
+ 52393,
17209
+ "ECDHE-ECDSA-CHACHA20-POLY1305",
17210
+ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
17211
+ "TLSv1.2"
17212
+ ],
17213
+ [49187, "ECDHE-ECDSA-AES128-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLSv1.2"],
17214
+ [49188, "ECDHE-ECDSA-AES256-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLSv1.2"],
17215
+ [49191, "ECDHE-RSA-AES128-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLSv1.2"],
17216
+ [49192, "ECDHE-RSA-AES256-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLSv1.2"],
17217
+ [49161, "ECDHE-ECDSA-AES128-SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLSv1.0"],
17218
+ [49162, "ECDHE-ECDSA-AES256-SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLSv1.0"],
17219
+ [49171, "ECDHE-RSA-AES128-SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLSv1.0"],
17220
+ [49172, "ECDHE-RSA-AES256-SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLSv1.0"],
17221
+ [156, "AES128-GCM-SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17222
+ [157, "AES256-GCM-SHA384", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"],
17223
+ [158, "DHE-RSA-AES128-GCM-SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLSv1.2"],
17224
+ [159, "DHE-RSA-AES256-GCM-SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLSv1.2"]
17225
+ ];
17226
+ function cipherOf(id2, protocol) {
17227
+ const known = SUITES.find(([n]) => n === id2);
17228
+ const hex3 = `0x${id2.toString(16).padStart(4, "0").toUpperCase()}`;
17229
+ return {
17230
+ name: known?.[1] ?? hex3,
17231
+ standardName: known?.[2] ?? hex3,
17232
+ version: known?.[3] ?? protocol
17233
+ };
17234
+ }
17235
+
17030
17236
  // src/kernel/net/tls-client.ts
17031
17237
  var WRITE_CHUNK = 16 * 1024;
17032
17238
  var BADCERT = [
@@ -17043,6 +17249,15 @@ function verifyCode(flags2) {
17043
17249
  if (flags2 === -1) return "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
17044
17250
  return BADCERT.find(([bit]) => (flags2 & bit) !== 0)?.[1] ?? "UNABLE_TO_VERIFY_LEAF_SIGNATURE";
17045
17251
  }
17252
+ function isIP(name2) {
17253
+ return ipv4Bytes(name2) !== void 0 || name2.includes(":") && ipv6Groups(name2) !== void 0;
17254
+ }
17255
+ function clientFlags(o) {
17256
+ 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);
17257
+ }
17258
+ function openSession(o) {
17259
+ return o.engine.clientSession(o.store, o.servername, o.alpn, clientFlags(o));
17260
+ }
17046
17261
  var Ciphertext = class {
17047
17262
  queue = [];
17048
17263
  ended = false;
@@ -17115,17 +17330,39 @@ async function handshake(conn, o, session, inbox) {
17115
17330
  }
17116
17331
  }
17117
17332
  }
17333
+ function controlsOf(o, session, protocol) {
17334
+ if (!o.engine.sessionControls) return {};
17335
+ const checked = o.checkHostname !== false && !o.insecure;
17336
+ return {
17337
+ cipher: cipherOf(session.ciphersuite, protocol),
17338
+ clientCertificate: o.clientCertificate === true,
17339
+ sni: o.sni !== false && !isIP(o.servername),
17340
+ hostnameChecked: checked,
17341
+ ...o.insecure ? {} : { hostnameMatches: checked || !session.nameMismatch }
17342
+ };
17343
+ }
17118
17344
  function infoOf(o, session) {
17119
17345
  const flags2 = session.verifyResult;
17120
17346
  const code = verifyCode(flags2);
17347
+ const protocol = session.version === 772 ? "TLSv1.3" : "TLSv1.2";
17121
17348
  return {
17122
- protocol: session.version === 772 ? "TLSv1.3" : "TLSv1.2",
17349
+ protocol,
17123
17350
  alpnProtocol: session.alpn ?? false,
17124
17351
  authorized: code === void 0,
17125
17352
  ...code ? { authorizationError: code, authorizationText: o.engine.verifyInfo(flags2).trim() } : {},
17126
- peerCertificates: session.peerChain()
17353
+ peerCertificates: session.peerChain(),
17354
+ ...controlsOf(o, session, protocol)
17127
17355
  };
17128
17356
  }
17357
+ var FATAL_ALERT = -30592;
17358
+ function alerted(err) {
17359
+ if (err.code === FATAL_ALERT)
17360
+ return new KernelError(
17361
+ "EPROTO",
17362
+ `ERR_SSL_FATAL_ALERT: the server ended the session: ${err.message}`
17363
+ );
17364
+ return new KernelError("EPROTO", `ERR_SSL_PROTOCOL_ERROR: ${err.message}`);
17365
+ }
17129
17366
  function plaintext(conn, session, inbox) {
17130
17367
  let freed = false;
17131
17368
  let writeClosed = false;
@@ -17140,7 +17377,12 @@ function plaintext(conn, session, inbox) {
17140
17377
  async read() {
17141
17378
  for (; ; ) {
17142
17379
  if (freed) return null;
17143
- const got = session.read();
17380
+ let got;
17381
+ try {
17382
+ got = session.read();
17383
+ } catch (err) {
17384
+ throw alerted(err);
17385
+ }
17144
17386
  await send(conn, session);
17145
17387
  if (got instanceof Uint8Array) return got;
17146
17388
  if (got === "eof") return null;
@@ -17168,8 +17410,7 @@ function plaintext(conn, session, inbox) {
17168
17410
  };
17169
17411
  }
17170
17412
  async function tlsClient(conn, o, taken = new Uint8Array(0)) {
17171
- const flags2 = o.insecure ? TLS_CLIENT_INSECURE_SKIP_VERIFY : 0;
17172
- const session = o.engine.clientSession(o.store, o.servername, o.alpn, flags2);
17413
+ const session = o.session ?? openSession(o);
17173
17414
  const inbox = new Ciphertext(conn, session);
17174
17415
  inbox.hand(taken);
17175
17416
  try {
@@ -17182,10 +17423,10 @@ async function tlsClient(conn, o, taken = new Uint8Array(0)) {
17182
17423
  }
17183
17424
 
17184
17425
  // src/kernel/net/tls-uplink.ts
17185
- var isIP = (text) => ipv4Bytes(text) !== void 0 || text.includes(":") && ipv6Groups(text) !== void 0;
17426
+ var isIP2 = (text) => ipv4Bytes(text) !== void 0 || text.includes(":") && ipv6Groups(text) !== void 0;
17186
17427
  var LABEL2 = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$/;
17187
17428
  function serverName(name2) {
17188
- if (isIP(name2.replace(/^\[|\]$/g, ""))) return name2.replace(/^\[|\]$/g, "");
17429
+ if (isIP2(name2.replace(/^\[|\]$/g, ""))) return name2.replace(/^\[|\]$/g, "");
17189
17430
  const bare = name2.endsWith(".") ? name2.slice(0, -1) : name2;
17190
17431
  if (bare.length === 0 || bare.length > 253 || !bare.split(".").every((l) => LABEL2.test(l))) {
17191
17432
  throw new KernelError(
@@ -17235,9 +17476,66 @@ function sessionOf(info, servername) {
17235
17476
  authorizationText: info.authorizationText
17236
17477
  } : {},
17237
17478
  servername,
17238
- peerCertificates: info.peerCertificates.map(encode)
17479
+ peerCertificates: info.peerCertificates.map(encode),
17480
+ ...controls(info)
17239
17481
  };
17240
17482
  }
17483
+ function controls(info) {
17484
+ if (!info.cipher) return {};
17485
+ return {
17486
+ cipher: info.cipher,
17487
+ clientCertificate: info.clientCertificate === true,
17488
+ sni: info.sni !== false,
17489
+ hostnameChecked: info.hostnameChecked === true,
17490
+ ...info.hostnameMatches === void 0 ? {} : { hostnameMatches: info.hostnameMatches }
17491
+ };
17492
+ }
17493
+ var KEY_ERRORS = [
17494
+ [-3, "ERR_OSSL_X509_KEY_VALUES_MISMATCH"],
17495
+ [-15232, "ERR_OSSL_BAD_DECRYPT"],
17496
+ [-15360, "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED"]
17497
+ ];
17498
+ function keyRefused(err) {
17499
+ const code = err instanceof TlsError ? err.code : 0;
17500
+ const name2 = KEY_ERRORS.find(([n]) => n === code)?.[1] ?? "ERR_OSSL_UNSUPPORTED";
17501
+ const text = code === -3 ? "the key does not match the certificate" : err.message;
17502
+ return new KernelError("EPROTO", `${name2}: ${text}`);
17503
+ }
17504
+ function takeCert(req) {
17505
+ const cert = req.clientCert;
17506
+ if (!cert) return void 0;
17507
+ delete req.clientCert;
17508
+ if (!Array.isArray(cert.chain) || cert.chain.length === 0 || typeof cert.key !== "string")
17509
+ throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: cert and key go together");
17510
+ let ders;
17511
+ let key;
17512
+ try {
17513
+ ders = cert.chain.map(base64Der);
17514
+ key = base64Der(cert.key);
17515
+ } catch {
17516
+ throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: cert or key is not base64");
17517
+ }
17518
+ const chain2 = new Uint8Array(ders.reduce((n, d) => n + d.length, 0));
17519
+ let at = 0;
17520
+ for (const der of ders) {
17521
+ chain2.set(der, at);
17522
+ at += der.length;
17523
+ }
17524
+ const pass2 = new TextEncoder().encode(cert.passphrase ?? "");
17525
+ return { chain: chain2, key, pass: pass2 };
17526
+ }
17527
+ function withCert(session, cert) {
17528
+ if (!cert) return;
17529
+ try {
17530
+ session.ownCert(cert.chain, cert.key, cert.pass);
17531
+ } catch (err) {
17532
+ throw keyRefused(err);
17533
+ }
17534
+ }
17535
+ function wipe(cert) {
17536
+ cert?.key.fill(0);
17537
+ cert?.pass.fill(0);
17538
+ }
17241
17539
  async function setUp(support, req) {
17242
17540
  const engine = await support.engine();
17243
17541
  if (!engine.client)
@@ -17255,21 +17553,66 @@ async function setUp(support, req) {
17255
17553
  }
17256
17554
  return { engine, store: own, own };
17257
17555
  }
17258
- function secure(support, req, servername) {
17259
- return async (raw) => {
17260
- const { engine, store, own } = await setUp(support, req);
17556
+ async function prepare(support, req, servername) {
17557
+ const cert = takeCert(req);
17558
+ try {
17559
+ return await prepareWith(support, req, servername, cert);
17560
+ } catch (err) {
17561
+ if (err instanceof KernelError) throw err;
17562
+ throw new KernelError("ENOTSUP", err instanceof Error ? err.message : String(err));
17563
+ } finally {
17564
+ wipe(cert);
17565
+ }
17566
+ }
17567
+ async function prepareWith(support, req, servername, cert) {
17568
+ if (req.checkHostname === false && req.insecure === true) {
17569
+ throw new KernelError(
17570
+ "EINVAL",
17571
+ "ERR_INVALID_ARG_VALUE: checkHostname: false needs rejectUnauthorized"
17572
+ );
17573
+ }
17574
+ const { engine, store, own } = await setUp(support, req);
17575
+ const options = {
17576
+ engine,
17577
+ store,
17578
+ servername,
17579
+ alpn: req.alpn ?? [],
17580
+ insecure: req.insecure === true,
17581
+ ...req.sni === false ? { sni: false } : {},
17582
+ ...req.checkHostname === false ? { checkHostname: false } : {},
17583
+ clientCertificate: cert !== void 0
17584
+ };
17585
+ let session;
17586
+ try {
17587
+ const controls2 = req.sni === false || req.checkHostname === false || cert !== void 0;
17588
+ if (controls2 && !engine.sessionControls) {
17589
+ throw new KernelError("ENOTSUP", "the installed tls engine has no client certificates");
17590
+ }
17591
+ session = openSession(options);
17592
+ withCert(session, cert);
17593
+ return { options: { ...options, session }, own };
17594
+ } catch (err) {
17595
+ session?.free();
17596
+ own?.release();
17597
+ throw err;
17598
+ }
17599
+ }
17600
+ function secure(prepared) {
17601
+ let ran = false;
17602
+ const make = async (raw) => {
17603
+ ran = true;
17261
17604
  try {
17262
- return await tlsClient(raw, {
17263
- engine,
17264
- store,
17265
- servername,
17266
- alpn: req.alpn ?? [],
17267
- insecure: req.insecure === true
17268
- });
17605
+ return await tlsClient(raw, prepared.options);
17269
17606
  } finally {
17270
- own?.release();
17607
+ prepared.own?.release();
17271
17608
  }
17272
17609
  };
17610
+ const abandon = () => {
17611
+ if (ran) return;
17612
+ prepared.options.session.free();
17613
+ prepared.own?.release();
17614
+ };
17615
+ return { make, abandon };
17273
17616
  }
17274
17617
  function supportOf(net) {
17275
17618
  const support = net.tlsClient;
@@ -17278,7 +17621,7 @@ function supportOf(net) {
17278
17621
  }
17279
17622
  async function uplinkAddress(proc, host) {
17280
17623
  const bare = host.replace(/^\[|\]$/g, "");
17281
- if (isIP(bare)) return bare;
17624
+ if (isIP2(bare)) return bare;
17282
17625
  const [first] = await proc.resolve(host, 0, () => proc.blocking());
17283
17626
  if (!first) throw new KernelError("ENOENT", `ENOTFOUND: ${host}`);
17284
17627
  return first;
@@ -17287,23 +17630,31 @@ async function connectSecure(proc, socket, host, port, req) {
17287
17630
  const servername = serverName(req.servername || host);
17288
17631
  if (isLoopbackHost(servername) || isLoopbackHost(host)) throw new KernelError("ENOTSUP");
17289
17632
  const address = await uplinkAddress(proc, host);
17290
- const route = proc.net.uplinkFor(address);
17291
- if (!route)
17633
+ const route2 = proc.net.uplinkFor(address);
17634
+ if (!route2)
17292
17635
  throw new KernelError("ENOTSUP", "TLS in the kernel needs a destination the uplink routes");
17293
17636
  const support = supportOf(proc.net);
17294
17637
  let info;
17295
- const make = secure(support, req, servername);
17296
- await socket.connectSecure({ family: "inet", host: address, port }, route, async (conn) => {
17297
- info = await conn.upgrade(make);
17298
- });
17638
+ const { make, abandon } = secure(await prepare(support, req, servername));
17639
+ try {
17640
+ await socket.connectSecure({ family: "inet", host: address, port }, route2, async (conn) => {
17641
+ info = await conn.upgrade(make);
17642
+ });
17643
+ } finally {
17644
+ abandon();
17645
+ }
17299
17646
  return sessionOf(info, servername);
17300
17647
  }
17301
17648
  async function upgradeSecure(proc, socket, req) {
17302
17649
  const peer = socket.peer;
17303
17650
  const host = req.servername || (peer?.family === "inet" ? peer.host : "");
17304
17651
  const servername = serverName(host);
17305
- const info = await socket.upgradeSecure(secure(supportOf(proc.net), req, servername));
17306
- return sessionOf(info, servername);
17652
+ const { make, abandon } = secure(await prepare(supportOf(proc.net), req, servername));
17653
+ try {
17654
+ return sessionOf(await socket.upgradeSecure(make), servername);
17655
+ } finally {
17656
+ abandon();
17657
+ }
17307
17658
  }
17308
17659
 
17309
17660
  // src/kernel/unix-dgram.ts
@@ -17665,17 +18016,23 @@ var NOT_HTTP_PORTS = /* @__PURE__ */ new Set([
17665
18016
  8883
17666
18017
  ]);
17667
18018
  var HOST_NAME = /^[A-Za-z0-9.-]+$|^\[?[0-9A-Fa-f:.]+\]?$/;
18019
+ function sessionControls(req) {
18020
+ return req.sni === false || req.checkHostname === false || req.clientCert !== void 0;
18021
+ }
17668
18022
  function wantsKernel(req) {
17669
18023
  if (req.path === "kernel") return true;
17670
18024
  if (req.path === "transport") return false;
17671
- 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);
18025
+ 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);
17672
18026
  }
17673
18027
  async function secureConnect(req, proc) {
17674
18028
  if (!Number.isInteger(req.port) || req.port < 1 || req.port > 65535)
17675
18029
  throw new KernelError("EINVAL");
17676
18030
  const kernelPath = wantsKernel(req) && await uplinked(proc, req.host);
17677
18031
  if (!kernelPath) {
17678
- if (req.path === "kernel" || Array.isArray(req.ca)) throw new KernelError("ENOTSUP");
18032
+ if (req.path === "kernel" || Array.isArray(req.ca) || sessionControls(req)) {
18033
+ delete req.clientCert;
18034
+ throw new KernelError("ENOTSUP");
18035
+ }
17679
18036
  return { ...tlsOverTransport(req, proc), path: "transport", substitutes: true };
17680
18037
  }
17681
18038
  const socket = proc.net.socket("inet");
@@ -17809,13 +18166,17 @@ var VfsNode = class {
17809
18166
  const ranged2 = await this.prepare();
17810
18167
  await ranged2?.pin();
17811
18168
  }
18169
+ moving = /* @__PURE__ */ new Set();
17812
18170
  async load() {
17813
18171
  if (this.revoked) throw new KernelError("EIO");
17814
- if (!this.data) {
18172
+ while (!this.data) {
18173
+ const at = this.path;
17815
18174
  try {
17816
- this.data = await this.fs.readFileBuffer(this.path);
18175
+ this.data = await this.fs.readFileBuffer(at);
17817
18176
  } catch (err) {
17818
18177
  if (!isMissing(err)) throw err;
18178
+ while (this.moving.size > 0) await Promise.all(this.moving);
18179
+ if (this.path !== at) continue;
17819
18180
  this.data = new Uint8Array(0);
17820
18181
  this.missing = true;
17821
18182
  }
@@ -18169,6 +18530,16 @@ var VfsNodes = class {
18169
18530
  this.byPath.set(file.hidden, node);
18170
18531
  this.kept.set(node, file);
18171
18532
  }
18533
+ moving(path) {
18534
+ const node = this.byPath.get(path);
18535
+ if (!node) return () => void 0;
18536
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
18537
+ node.moving.add(promise);
18538
+ return () => {
18539
+ node.moving.delete(promise);
18540
+ resolve2();
18541
+ };
18542
+ }
18172
18543
  unkeep(path, file) {
18173
18544
  const node = this.byPath.get(file.hidden);
18174
18545
  if (!node) return;
@@ -18477,6 +18848,65 @@ function vfsFile(fs, opts, nodes) {
18477
18848
  });
18478
18849
  }
18479
18850
 
18851
+ // src/kernel/watch-ops.ts
18852
+ var WATCH_OPS = ["watch-open", "watch-next", "watch-close"];
18853
+ var WATCHING = new Set(WATCH_OPS);
18854
+ function isWatchSyscall(req) {
18855
+ return WATCHING.has(req.op);
18856
+ }
18857
+ var WATCHES_MAX = 8192;
18858
+ var parentOf8 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
18859
+ function visibleTo2(watch, root, visible) {
18860
+ return {
18861
+ close: () => watch.close(),
18862
+ next: async (signal) => {
18863
+ const notes = await watch.next(signal);
18864
+ const seen = /* @__PURE__ */ new Map();
18865
+ const shown = await Promise.all(
18866
+ notes.map(async (note) => {
18867
+ if (!("filename" in note) || !note.filename.includes("/")) return true;
18868
+ const dir = parentOf8(`${root === "/" ? "" : root}/${note.filename}`);
18869
+ if (!seen.has(dir)) seen.set(dir, visible(dir));
18870
+ return seen.get(dir);
18871
+ })
18872
+ );
18873
+ return notes.filter((_note, i) => shown[i]);
18874
+ }
18875
+ };
18876
+ }
18877
+ var ProcessWatches = class {
18878
+ open = /* @__PURE__ */ new Map();
18879
+ nextId = 1;
18880
+ source;
18881
+ constructor(source) {
18882
+ this.source = source;
18883
+ }
18884
+ async syscall(req, cancelled) {
18885
+ if (req.op === "watch-open") {
18886
+ if (!this.source) throw new KernelError("ENOSYS");
18887
+ if (this.open.size >= WATCHES_MAX) throw new KernelError("ENOSPC");
18888
+ const watch2 = await this.source.open(req.path, req.recursive === true);
18889
+ const id2 = this.nextId++;
18890
+ this.open.set(id2, watch2);
18891
+ return { ok: true, kind: "json", json: id2 };
18892
+ }
18893
+ const watch = this.open.get(req.id);
18894
+ if (req.op === "watch-close") {
18895
+ watch?.close();
18896
+ this.open.delete(req.id);
18897
+ return { ok: true, kind: "void" };
18898
+ }
18899
+ if (!watch) return { ok: true, kind: "json", json: [{ ended: true }] };
18900
+ const notes = await watch.next(cancelled);
18901
+ if (notes.some((note) => "ended" in note)) this.open.delete(req.id);
18902
+ return { ok: true, kind: "json", json: notes };
18903
+ }
18904
+ closeAll() {
18905
+ for (const watch of this.open.values()) watch.close();
18906
+ this.open.clear();
18907
+ }
18908
+ };
18909
+
18480
18910
  // src/kernel/process.ts
18481
18911
  function aborted(signal) {
18482
18912
  return new Promise((_, reject) => {
@@ -18603,7 +19033,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
18603
19033
  ...SEM_OPS,
18604
19034
  ...HTTP_OPS,
18605
19035
  ...PTY_OPS,
18606
- ...HOST_OPS
19036
+ ...HOST_OPS,
19037
+ ...WATCH_OPS
18607
19038
  ]);
18608
19039
  function isWasmSyscall(req) {
18609
19040
  const op = req.op;
@@ -18655,6 +19086,7 @@ function timeLeft(req, started) {
18655
19086
  }
18656
19087
  var WasmProcess = class {
18657
19088
  exited = false;
19089
+ watches;
18658
19090
  children;
18659
19091
  deathSignals = /* @__PURE__ */ new Map();
18660
19092
  caught = 0;
@@ -18685,6 +19117,7 @@ var WasmProcess = class {
18685
19117
  this.pid = pid;
18686
19118
  this.fds = fds;
18687
19119
  this.options = options;
19120
+ this.watches = new ProcessWatches(options.watches);
18688
19121
  this.inherited = options.ignored ?? 0;
18689
19122
  this.umask = options.umask ?? DEFAULT_UMASK2;
18690
19123
  this.cred = copyCred(options.cred ?? ROOT);
@@ -18942,6 +19375,7 @@ var WasmProcess = class {
18942
19375
  });
18943
19376
  }
18944
19377
  if (isHttpSyscall(req)) return await this.httpSyscall(req);
19378
+ if (isWatchSyscall(req)) return await this.watches.syscall(req, cancelled);
18945
19379
  if (isHostSyscall(req)) {
18946
19380
  return await hostSyscall(req, {
18947
19381
  pid: this.pid,
@@ -20082,6 +20516,7 @@ var WasmProcess = class {
20082
20516
  this.options.locks?.release(this.pid);
20083
20517
  for (const owner of this.lockOwners) this.options.locks?.release(owner);
20084
20518
  this.asyncOps.close();
20519
+ this.watches.closeAll();
20085
20520
  for (const [target, sig] of this.deathSignals) this.sendDeath(target, sig);
20086
20521
  this.deathSignals.clear();
20087
20522
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
@@ -20343,6 +20778,7 @@ function processOptions(opts, header3, hooks, created3) {
20343
20778
  locks: opts.locks,
20344
20779
  fifos: opts.fifos,
20345
20780
  ...opts.sems ? { sems: opts.sems } : {},
20781
+ watches: opts.watches,
20346
20782
  onReap: opts.onReap,
20347
20783
  resolver: opts.resolver,
20348
20784
  ...notifiers(header3),
@@ -21456,7 +21892,7 @@ function register(automaton, sources, ids, bytes, source) {
21456
21892
  }
21457
21893
  sources[id2].push(source);
21458
21894
  }
21459
- function prepare(secrets) {
21895
+ function prepare2(secrets) {
21460
21896
  const main = new Automaton();
21461
21897
  const wrap = new Automaton();
21462
21898
  const sources = [];
@@ -21607,7 +22043,7 @@ function merge(into, from) {
21607
22043
  }
21608
22044
  }
21609
22045
  function compile(secrets, options = {}) {
21610
- const c = prepare(secrets.map((secret) => ({ ...secret })));
22046
+ const c = prepare2(secrets.map((secret) => ({ ...secret })));
21611
22047
  const decoder4 = new TextDecoder();
21612
22048
  const idleMs = options.idleMs ?? 250;
21613
22049
  const scrubBytes = (input) => {
@@ -25560,6 +25996,7 @@ var Launcher = class {
25560
25996
  locks: this.locks,
25561
25997
  fifos: this.fifos,
25562
25998
  sems: this.sems,
25999
+ watches: this.watchSource(pid),
25563
26000
  resolver: this.resolver,
25564
26001
  onReap: (child) => this.reaped(child, pid),
25565
26002
  ...req.fork ? { fork: req.fork } : {},
@@ -26103,6 +26540,23 @@ var Launcher = class {
26103
26540
  mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
26104
26541
  });
26105
26542
  }
26543
+ watchSource(pid) {
26544
+ return {
26545
+ open: async (path, recursive) => {
26546
+ const at = normalizePath(path);
26547
+ const g = this.execGuard(this.credOf(pid), pid);
26548
+ if (g) await readable(g, "watch", at);
26549
+ const st = await this.fs.stat(at);
26550
+ const real = await this.fs.realpath?.(at, true).catch(() => at) ?? at;
26551
+ const watch = this.watchers.typedWatch(real, recursive && st.isDirectory, at);
26552
+ if (!g || g.cred.euid === 0) return watch;
26553
+ return visibleTo2(watch, real, async (dir) => {
26554
+ const walked = await access(g.fs, g.cred, dir, R_OK | X_OK, { directory: true });
26555
+ return walked.denied === void 0 && walked.missing !== true;
26556
+ });
26557
+ }
26558
+ };
26559
+ }
26106
26560
  watchFiles(paths, notify) {
26107
26561
  this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
26108
26562
  return this.fileWatches.watch(paths, notify);