@ai-ecoverse/slicc-kernel 1.71.0 → 1.72.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.
@@ -141,11 +141,18 @@ var IndexedDbMeta = class {
141
141
  cache = new PathMap();
142
142
  channel;
143
143
  generation = 0;
144
+ listeners = /* @__PURE__ */ new Set();
145
+ listen(listener) {
146
+ this.listeners.add(listener);
147
+ }
144
148
  constructor(db) {
145
149
  this.db = db;
146
150
  db.onversionchange = () => db.close();
147
151
  this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);
148
- this.channel.onmessage = ({ data }) => this.drop(data);
152
+ this.channel.onmessage = ({ data }) => {
153
+ this.drop(data);
154
+ for (const listener of this.listeners) listener(data);
155
+ };
149
156
  this.channel.unref?.();
150
157
  }
151
158
  drop({ paths = [], trees = [] }) {
@@ -530,6 +537,121 @@ var ProtectedFs = class {
530
537
  }
531
538
  };
532
539
 
540
+ // src/fs/remote.ts
541
+ var ANNOUNCE_WAIT_MS = 500;
542
+ var PRUNE_EVERY_MS = 3e4;
543
+ function lockManager() {
544
+ return globalThis.navigator?.locks;
545
+ }
546
+ var RemoteWatch = class {
547
+ channel;
548
+ name;
549
+ tab = crypto.randomUUID();
550
+ peers = /* @__PURE__ */ new Set();
551
+ others = /* @__PURE__ */ new Map();
552
+ union = /* @__PURE__ */ new Set();
553
+ listeners = /* @__PURE__ */ new Set();
554
+ waiting = /* @__PURE__ */ new Map();
555
+ locks;
556
+ mine = [];
557
+ seq = 0;
558
+ pruned = 0;
559
+ constructor(name2, options = {}) {
560
+ this.name = name2;
561
+ this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
562
+ this.channel = new BroadcastChannel(name2);
563
+ this.channel.onmessage = ({ data }) => this.receive(data);
564
+ this.channel.unref?.();
565
+ void this.locks?.request(this.lockOf(this.tab), () => new Promise(() => {
566
+ }));
567
+ this.channel.postMessage({ hello: this.tab });
568
+ }
569
+ lockOf(tab) {
570
+ return `${this.name}:${tab}`;
571
+ }
572
+ receive(data) {
573
+ if (data.hello !== void 0) this.greeted(data.hello);
574
+ if (data.here !== void 0 && data.to === this.tab) this.peers.add(data.here);
575
+ if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
576
+ if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
577
+ this.acked(data.ack, data.from);
578
+ }
579
+ if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [] });
580
+ }
581
+ greeted(peer) {
582
+ this.peers.add(peer);
583
+ this.channel.postMessage({ here: this.tab, to: peer });
584
+ if (this.mine.length > 0) void this.announce(this.mine);
585
+ }
586
+ announced({ tab, paths, seq: seq2 }) {
587
+ const from = tab;
588
+ this.peers.add(from);
589
+ if (paths.length > 0) this.others.set(from, paths);
590
+ else this.others.delete(from);
591
+ this.rebuild();
592
+ this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
593
+ }
594
+ acked(seq2, from) {
595
+ const wait2 = this.waiting.get(seq2);
596
+ if (!wait2) return;
597
+ wait2.left.delete(from);
598
+ if (wait2.left.size === 0) wait2.done();
599
+ }
600
+ rebuild() {
601
+ this.union.clear();
602
+ for (const paths of this.others.values()) for (const path of paths) this.union.add(path);
603
+ }
604
+ async prune() {
605
+ if (!this.locks) return;
606
+ const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
607
+ for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
608
+ if (!held.has(this.lockOf(tab))) {
609
+ this.others.delete(tab);
610
+ this.peers.delete(tab);
611
+ }
612
+ }
613
+ this.rebuild();
614
+ }
615
+ watched() {
616
+ return this.union.size > 0;
617
+ }
618
+ wrote(path) {
619
+ if (!this.union.has(path)) return;
620
+ this.channel.postMessage({ changed: [path] });
621
+ const now = Date.now();
622
+ if (now - this.pruned < PRUNE_EVERY_MS) return;
623
+ this.pruned = now;
624
+ void this.prune();
625
+ }
626
+ announce(paths) {
627
+ this.mine = [...paths];
628
+ const seq2 = ++this.seq;
629
+ this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
630
+ if (this.peers.size === 0) return Promise.resolve();
631
+ return new Promise((resolve) => {
632
+ const done4 = () => {
633
+ clearTimeout(timer);
634
+ this.waiting.delete(seq2);
635
+ resolve();
636
+ };
637
+ const timer = setTimeout(() => {
638
+ done4();
639
+ void this.prune();
640
+ }, ANNOUNCE_WAIT_MS);
641
+ this.waiting.set(seq2, { left: new Set(this.peers), done: done4 });
642
+ });
643
+ }
644
+ emit(change) {
645
+ for (const listener of this.listeners) listener(change);
646
+ }
647
+ listen(listener) {
648
+ this.listeners.add(listener);
649
+ return () => {
650
+ this.listeners.delete(listener);
651
+ };
652
+ }
653
+ };
654
+
533
655
  // src/fs/opfs.ts
534
656
  var S_IFDIR = 16384;
535
657
  var S_IFREG = 32768;
@@ -657,6 +779,7 @@ var OpfsFs = class {
657
779
  dirs = new PathMap();
658
780
  checked = /* @__PURE__ */ new WeakMap();
659
781
  channel;
782
+ remote;
660
783
  ranged;
661
784
  owners = false;
662
785
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
@@ -664,9 +787,15 @@ var OpfsFs = class {
664
787
  this.meta = sidecar(meta);
665
788
  this.ranged = options.ranged === true;
666
789
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
790
+ this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
667
791
  if (this.channel) {
792
+ const remote = this.remote;
793
+ meta.listen?.(
794
+ (c) => remote?.emit({ paths: c.paths ?? [], trees: c.trees ?? [] })
795
+ );
668
796
  this.channel.onmessage = ({ data }) => {
669
797
  for (const path of data) this.forget(path);
798
+ remote?.emit({ paths: data, trees: [] });
670
799
  };
671
800
  this.channel.unref?.();
672
801
  }
@@ -675,6 +804,16 @@ var OpfsFs = class {
675
804
  for (const path of paths) this.forget(path);
676
805
  this.channel?.postMessage(paths);
677
806
  }
807
+ remoteChanges(listener) {
808
+ return this.remote?.listen(listener) ?? (() => {
809
+ });
810
+ }
811
+ announceWatch(paths) {
812
+ return this.remote?.announce(paths) ?? Promise.resolve();
813
+ }
814
+ committed(target) {
815
+ if (this.remote?.watched()) this.remote.wrote(target);
816
+ }
678
817
  resolvePath(base, path) {
679
818
  return resolvePath(base, path);
680
819
  }
@@ -1026,15 +1165,18 @@ var OpfsFs = class {
1026
1165
  );
1027
1166
  }
1028
1167
  async pwrite(path, offset, bytes) {
1029
- const handle = (await this.fileFor(path))[0];
1168
+ const [handle, , target] = await this.fileFor(path);
1030
1169
  await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
1170
+ this.committed(target);
1031
1171
  }
1032
1172
  async truncate(path, size) {
1033
1173
  const handle = await this.existing(path, true);
1174
+ const target = this.remote?.watched() ? (await this.locate(path, true)).path : path;
1034
1175
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
1176
+ this.committed(target);
1035
1177
  }
1036
1178
  async writeSession(path, truncate) {
1037
- const handle = (await this.fileFor(path))[0];
1179
+ const [handle, , target] = await this.fileFor(path);
1038
1180
  const writable2 = await this.retrying(
1039
1181
  path,
1040
1182
  () => handle.createWritable({ keepExistingData: !truncate }),
@@ -1047,9 +1189,12 @@ var OpfsFs = class {
1047
1189
  truncate: (size) => writable2.truncate(size).catch((err) => {
1048
1190
  throw translate(err, path, "write", false);
1049
1191
  }),
1050
- close: () => writable2.close().catch((err) => {
1051
- throw translate(err, path, "commit", false);
1052
- })
1192
+ close: async () => {
1193
+ await writable2.close().catch((err) => {
1194
+ throw translate(err, path, "commit", false);
1195
+ });
1196
+ this.committed(target);
1197
+ }
1053
1198
  };
1054
1199
  }
1055
1200
  async readFile(path) {
@@ -1098,6 +1243,7 @@ var OpfsFs = class {
1098
1243
  await writable2?.abort().catch(() => void 0);
1099
1244
  throw translate(err, path, "commit", false);
1100
1245
  }
1246
+ this.committed(target);
1101
1247
  }
1102
1248
  async listing(path) {
1103
1249
  const found = await this.locate(path, true);
@@ -1119,12 +1265,12 @@ var OpfsFs = class {
1119
1265
  const base = dir === "/" ? "" : dir;
1120
1266
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
1121
1267
  const handed2 = this.owners ? await this.handedTo(dir) : void 0;
1122
- const owned3 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
1268
+ const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
1123
1269
  return Promise.all(
1124
1270
  children2.map(async ([name2, child], i) => {
1125
- if (!isHandle(child)) return [name2, owned3(this.linkStat(child), child)];
1271
+ if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
1126
1272
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
1127
- return [name2, stat && owned3(stat, metas[i])];
1273
+ return [name2, stat && owned4(stat, metas[i])];
1128
1274
  })
1129
1275
  );
1130
1276
  }
@@ -1188,7 +1334,14 @@ var OpfsFs = class {
1188
1334
  links() {
1189
1335
  return false;
1190
1336
  }
1191
- async link(from) {
1337
+ async link(from, to, follow2) {
1338
+ await (follow2 ? this.stat(from) : this.lstat(from));
1339
+ if (!(await this.stat(parentOf3(to))).isDirectory) throw fsError("ENOTDIR", to);
1340
+ if (await this.lstat(to).then(
1341
+ () => true,
1342
+ () => false
1343
+ ))
1344
+ throw fsError("EEXIST", to);
1192
1345
  throw fsError("EPERM", `${from}: this file system has no hard links`);
1193
1346
  }
1194
1347
  async rename(from, to) {
@@ -1726,6 +1879,7 @@ var Dac = class {
1726
1879
  log;
1727
1880
  seen = /* @__PURE__ */ new Set();
1728
1881
  suppressed = false;
1882
+ tagOf = () => void 0;
1729
1883
  constructor(mode, hook, log = (line) => console.warn(line)) {
1730
1884
  this.mode = mode;
1731
1885
  this.hook = hook;
@@ -1734,7 +1888,9 @@ var Dac = class {
1734
1888
  get enforcing() {
1735
1889
  return this.mode === "enforce";
1736
1890
  }
1737
- denied(denial) {
1891
+ denied(given) {
1892
+ const tag = given.tag ?? (given.pid ? this.tagOf(given.pid) : void 0);
1893
+ const denial = tag === void 0 ? given : { ...given, tag };
1738
1894
  try {
1739
1895
  this.hook?.({ ...denial });
1740
1896
  } catch {
@@ -1752,7 +1908,7 @@ var Dac = class {
1752
1908
  }
1753
1909
  this.seen.add(key);
1754
1910
  this.log(
1755
- `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
1911
+ `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid}${d.tag === void 0 ? "" : `, tag ${d.tag}`})`
1756
1912
  );
1757
1913
  }
1758
1914
  };
@@ -1768,7 +1924,8 @@ var Created = class {
1768
1924
  }
1769
1925
  };
1770
1926
  function deny(g, op, path, errno = "EACCES") {
1771
- g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno });
1927
+ const tag = g.tag === void 0 ? {} : { tag: g.tag };
1928
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
1772
1929
  }
1773
1930
  var TTY_MODE = 400;
1774
1931
  function ttyOpenable(g, tty) {
@@ -3738,9 +3895,9 @@ function ownerFor(l, self) {
3738
3895
  if (!l.owners || !self) return void 0;
3739
3896
  return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
3740
3897
  }
3741
- function guardFor(l, self) {
3898
+ function guardFor(l, self, tag) {
3742
3899
  if (!l.dac || !self) return void 0;
3743
- return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
3900
+ return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac, tag };
3744
3901
  }
3745
3902
  async function accountOf(l, user) {
3746
3903
  const found = await l.users.lookup(user);
@@ -3844,6 +4001,7 @@ async function watchOp(l, req, watches, send, g) {
3844
4001
  function serveClient(port, host) {
3845
4002
  let state = "new";
3846
4003
  let self;
4004
+ const guard2 = (l) => guardFor(l, self, host.tag);
3847
4005
  const terminals = /* @__PURE__ */ new Map();
3848
4006
  const groups = /* @__PURE__ */ new Set();
3849
4007
  const sessions = /* @__PURE__ */ new Set();
@@ -3953,10 +4111,10 @@ function serveClient(port, host) {
3953
4111
  if (!FS_METHODS.includes(req.method)) {
3954
4112
  throw new Error(`unknown file system call ${req.method}`);
3955
4113
  }
3956
- return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
4114
+ return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guard2(l));
3957
4115
  case "watch":
3958
4116
  case "unwatch":
3959
- return watchOp(l, req, watches, send, guardFor(l, self));
4117
+ return watchOp(l, req, watches, send, guard2(l));
3960
4118
  case "mount":
3961
4119
  return { result: await l.mountAs(clientCred(self), req.spec) };
3962
4120
  case "umount":
@@ -6617,6 +6775,136 @@ function binfmtOf(binfmts, path) {
6617
6775
  return best === void 0 ? void 0 : binfmts.get(best);
6618
6776
  }
6619
6777
 
6778
+ // src/fs/attest.ts
6779
+ var ATTEST_MAX = 1024 * 1024;
6780
+ var UNOWNED = 1e3;
6781
+ function refuse(code, path, why) {
6782
+ throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
6783
+ }
6784
+ function chainOf(path) {
6785
+ const parts2 = path.split("/").filter(Boolean);
6786
+ return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
6787
+ }
6788
+ function under2(path, target) {
6789
+ return path === target || path.startsWith(`${target}/`);
6790
+ }
6791
+ function owned(path, st) {
6792
+ return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
6793
+ }
6794
+ function unchanged(a, b, bytes) {
6795
+ return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
6796
+ }
6797
+ async function sha256(bytes) {
6798
+ const digest = new Uint8Array(
6799
+ await crypto.subtle.digest("SHA-256", bytes)
6800
+ );
6801
+ return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
6802
+ }
6803
+ function attested(path, st, bytes, digest, o, parents) {
6804
+ const { uid, gid, mode } = owned(path, st);
6805
+ return {
6806
+ path,
6807
+ bytes,
6808
+ uid,
6809
+ gid,
6810
+ mode,
6811
+ ino: st.ino,
6812
+ mtimeMs: st.mtime.getTime(),
6813
+ sha256: digest,
6814
+ users: o.users,
6815
+ onRootFs: true,
6816
+ parents
6817
+ };
6818
+ }
6819
+ async function trustedParents(fs, path, o) {
6820
+ const parents = [];
6821
+ for (const dir of chainOf(path).slice(0, -1)) {
6822
+ const st = await fs.lstat(dir);
6823
+ if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
6824
+ if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
6825
+ const perm = owned(dir, st);
6826
+ if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
6827
+ refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
6828
+ }
6829
+ parents.push(perm);
6830
+ }
6831
+ return parents;
6832
+ }
6833
+ async function attest(fs, asked, o) {
6834
+ if (typeof asked !== "string" || !asked.startsWith("/")) {
6835
+ refuse("EINVAL", String(asked), "not an absolute path");
6836
+ }
6837
+ const path = normalizePath(asked);
6838
+ const mount = o.mounts.find((target) => {
6839
+ const at = normalizePath(target);
6840
+ return at === "/" || under2(path, at);
6841
+ });
6842
+ if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
6843
+ const parents = await trustedParents(fs, path, o);
6844
+ for (let tries = 0; ; tries++) {
6845
+ const before = await fs.lstat(path);
6846
+ if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
6847
+ if (!before.isFile)
6848
+ refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
6849
+ if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
6850
+ const bytes = (await fs.readFileBuffer(path)).slice();
6851
+ const after = await fs.lstat(path);
6852
+ if (unchanged(before, after, bytes)) {
6853
+ return attested(path, after, bytes, await sha256(bytes), o, parents);
6854
+ }
6855
+ if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
6856
+ }
6857
+ }
6858
+ var WATCH_DEBOUNCE_MS = 50;
6859
+ var FileWatches = class {
6860
+ live = /* @__PURE__ */ new Set();
6861
+ local;
6862
+ remote;
6863
+ constructor(local, remote) {
6864
+ this.local = local;
6865
+ this.remote = remote;
6866
+ }
6867
+ announce() {
6868
+ return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
6869
+ }
6870
+ async watch(paths, notify) {
6871
+ const files = paths.map(normalizePath);
6872
+ const timers = /* @__PURE__ */ new Map();
6873
+ let active = true;
6874
+ const fire = (changed, trees) => {
6875
+ for (const file of files) {
6876
+ const hit = [...changed, ...trees].some((p) => under2(file, normalizePath(p)));
6877
+ if (!hit) continue;
6878
+ clearTimeout(timers.get(file));
6879
+ timers.set(
6880
+ file,
6881
+ setTimeout(() => {
6882
+ timers.delete(file);
6883
+ if (active) notify(file);
6884
+ }, WATCH_DEBOUNCE_MS)
6885
+ );
6886
+ }
6887
+ };
6888
+ const unwatch = this.local.watch(
6889
+ files,
6890
+ { recursive: false },
6891
+ (change) => fire("overflow" in change ? files : change.paths, [])
6892
+ );
6893
+ const unlisten = this.remote.remoteChanges?.((change) => fire(change.paths, change.trees));
6894
+ this.live.add(files);
6895
+ await this.announce();
6896
+ return () => {
6897
+ if (!active) return;
6898
+ active = false;
6899
+ for (const timer of timers.values()) clearTimeout(timer);
6900
+ unwatch();
6901
+ unlisten?.();
6902
+ this.live.delete(files);
6903
+ void this.announce();
6904
+ };
6905
+ }
6906
+ };
6907
+
6620
6908
  // src/fs/commands.ts
6621
6909
  var S_IFDIR2 = 16384;
6622
6910
  var S_IFREG2 = 32768;
@@ -6655,7 +6943,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6655
6943
  if (st || path === "/") return st ?? {};
6656
6944
  return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
6657
6945
  }
6658
- async function owned3(stat2, dir) {
6946
+ async function owned4(stat2, dir) {
6659
6947
  if (!owners) return stat2;
6660
6948
  const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
6661
6949
  return { ...stat2, uid, gid };
@@ -6669,17 +6957,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6669
6957
  }
6670
6958
  async function virtual(path) {
6671
6959
  if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
6672
- return owned3(synthetic(true, path));
6960
+ return owned4(synthetic(true, path));
6673
6961
  }
6674
6962
  const name2 = COMMAND.exec(path)?.[1];
6675
6963
  if (name2 !== void 0) {
6676
- return (await names()).has(name2) ? owned3(synthetic(false, path)) : void 0;
6964
+ return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
6677
6965
  }
6678
- if ((await userDir(path))?.size) return owned3(synthetic(true, path), path);
6966
+ if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
6679
6967
  const slash = path.lastIndexOf("/");
6680
6968
  const dir = path.slice(0, slash);
6681
6969
  const own = await userDir(dir);
6682
- return own?.has(path.slice(slash + 1)) ? owned3(synthetic(false, path), dir) : void 0;
6970
+ return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
6683
6971
  }
6684
6972
  async function linked(path) {
6685
6973
  return await virtual(path) ?? virtual(await followLinks(fs, path));
@@ -7028,11 +7316,11 @@ var UnlinkedKeeper = class {
7028
7316
 
7029
7317
  // src/fs/watch.ts
7030
7318
  var WATCH_BATCH_LIMIT = 1e3;
7031
- var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
7319
+ var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
7032
7320
  var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
7033
7321
  function touches(watcher, changed) {
7034
7322
  return watcher.paths.some(
7035
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf6(changed) === watched)
7323
+ (watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
7036
7324
  );
7037
7325
  }
7038
7326
  var FsWatchers = class {
@@ -8751,7 +9039,7 @@ async function keptStat(entry, req) {
8751
9039
  return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
8752
9040
  return json2(statJson(await entry.fs.stat(hidden)));
8753
9041
  }
8754
- async function owned(entry, own, req, path, target) {
9042
+ async function owned2(entry, own, req, path, target) {
8755
9043
  const source = target === path ? void 0 : path;
8756
9044
  if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
8757
9045
  return run(entry, path, req);
@@ -8806,7 +9094,7 @@ async function dispatchOne(entry, req) {
8806
9094
  }
8807
9095
  if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
8808
9096
  const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
8809
- return await owned(entry, entry.own, req, path, target);
9097
+ return await owned2(entry, entry.own, req, path, target);
8810
9098
  } catch (err) {
8811
9099
  return toErrno(err);
8812
9100
  }
@@ -11661,6 +11949,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11661
11949
  "proc-sudo",
11662
11950
  "proc-run",
11663
11951
  "proc-exit",
11952
+ "proc-kill-self",
11664
11953
  "proc-fork",
11665
11954
  "proc-kill",
11666
11955
  "thread-kill",
@@ -11788,6 +12077,20 @@ var WasmProcess = class {
11788
12077
  else this.adoptExec(pid);
11789
12078
  return pid;
11790
12079
  }
12080
+ async killSelf(sig, target, probe) {
12081
+ if (!this.options.exitSignal) throw new KernelError("ENOSYS");
12082
+ if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
12083
+ const { caught, ignored } = this.signalMasks();
12084
+ const to = target === 0 ? -this.pgid() : target ?? this.pid;
12085
+ if (sig !== SIG.KILL && (caught | ignored) & sigbit(sig)) {
12086
+ if (!await this.options.kill?.(to, sig)) throw new KernelError("ESRCH");
12087
+ return { ok: true, kind: "void" };
12088
+ }
12089
+ if (probe) return { ok: true, kind: "json", json: "terminate" };
12090
+ if (to !== this.pid) await this.options.kill?.(to, sig);
12091
+ this.options.exitSignal(sig);
12092
+ return DYING;
12093
+ }
11791
12094
  run(req) {
11792
12095
  return runChild(req, {
11793
12096
  children: this.children,
@@ -12385,6 +12688,8 @@ var WasmProcess = class {
12385
12688
  if (!this.options.exitNow) throw new KernelError("ENOSYS");
12386
12689
  this.options.exitNow(req.status & 255);
12387
12690
  return DYING;
12691
+ case "proc-kill-self":
12692
+ return this.killSelf(req.sig, req.target, req.probe);
12388
12693
  case "proc-identity":
12389
12694
  return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
12390
12695
  case "proc-exec": {
@@ -12819,6 +13124,7 @@ function processOptions(opts, header3, hooks, created3) {
12819
13124
  raise: hooks.raise,
12820
13125
  threadKill: hooks.threadKill,
12821
13126
  ...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
13127
+ ...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
12822
13128
  ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
12823
13129
  };
12824
13130
  }
@@ -12852,7 +13158,10 @@ function spawnWasmProcess(opts) {
12852
13158
  {
12853
13159
  raise: (sig) => signal(sig),
12854
13160
  threadKill: (tid, sig) => killThread(threadSignals.get(tid), process.signalMasks().caught, sig),
12855
- ...opts.program.abi === "js" ? { exitNow: (code) => exitNow(code) } : {}
13161
+ ...opts.program.abi === "js" ? {
13162
+ exitNow: (code) => exitNow(code),
13163
+ exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
13164
+ } : {}
12856
13165
  },
12857
13166
  created3
12858
13167
  )
@@ -14657,8 +14966,8 @@ var ChunkedBytes = class {
14657
14966
  }
14658
14967
  return out;
14659
14968
  }
14660
- write(offset, bytes, owned3 = false) {
14661
- const whole = owned3 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
14969
+ write(offset, bytes, owned4 = false) {
14970
+ const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
14662
14971
  for (let done4 = 0; done4 < bytes.length; ) {
14663
14972
  const at = offset + done4;
14664
14973
  const index = Math.floor(at / CHUNK3);
@@ -14708,7 +15017,7 @@ function created2(ino, kind, mode, from, owner) {
14708
15017
  gid: owner?.gid ?? from?.gid ?? 0
14709
15018
  };
14710
15019
  }
14711
- function owned2(node, change) {
15020
+ function owned3(node, change) {
14712
15021
  if (change.uid !== void 0) node.uid = change.uid;
14713
15022
  if (change.gid !== void 0) node.gid = change.gid;
14714
15023
  }
@@ -14879,7 +15188,7 @@ function tmpfs() {
14879
15188
  node.mtime = Date.now();
14880
15189
  }
14881
15190
  if (change.mtime !== void 0) node.mtime = change.mtime;
14882
- owned2(node, change);
15191
+ owned3(node, change);
14883
15192
  },
14884
15193
  async statfs() {
14885
15194
  return { bsize: 4096, blocks: 262144, bfree: 262144 };
@@ -15628,12 +15937,16 @@ var Launcher = class {
15628
15937
  usersMode;
15629
15938
  dac;
15630
15939
  sealed;
15940
+ rootFs;
15941
+ fileWatches;
15631
15942
  nextPid = 1e3;
15632
15943
  boot = Date.now();
15633
15944
  terminals = 0;
15634
15945
  constructor(options) {
15635
15946
  this.usersMode = usersMode(options.users);
15947
+ this.rootFs = options.fs;
15636
15948
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
15949
+ if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
15637
15950
  this.sealed = new SealedBroker(
15638
15951
  options.sealed,
15639
15952
  this.usersMode,
@@ -15883,7 +16196,7 @@ var Launcher = class {
15883
16196
  async mayExec(cred, req, planned) {
15884
16197
  const { dac } = this;
15885
16198
  if (!dac) return;
15886
- const g = { fs: this.fs, cred, pid: req.pid, dac };
16199
+ const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
15887
16200
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
15888
16201
  if (byPath(req.file)) {
15889
16202
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -16386,6 +16699,13 @@ var Launcher = class {
16386
16699
  kill(pid, sig, sender, from) {
16387
16700
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
16388
16701
  const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target) || from !== void 0 && this.granted.get(this.jobs.shown(target)) === from;
16702
+ if (pid === -1) {
16703
+ const sender2 = from === void 0 ? void 0 : this.imageOf(from);
16704
+ return this.killAll(
16705
+ sig,
16706
+ (member) => this.imageOf(member) !== sender2 && may(this.imageOf(member))
16707
+ );
16708
+ }
16389
16709
  if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
16390
16710
  const handle = this.processes.get(pid);
16391
16711
  if (!handle) return false;
@@ -16393,6 +16713,15 @@ var Launcher = class {
16393
16713
  if (sig !== 0) handle.signal(sig);
16394
16714
  return true;
16395
16715
  }
16716
+ killAll(sig, may) {
16717
+ let any = false;
16718
+ for (const [pid, handle] of [...this.processes]) {
16719
+ if (pid === 1 || !may(pid)) continue;
16720
+ any = true;
16721
+ if (sig !== 0) handle.signal(sig);
16722
+ }
16723
+ return any;
16724
+ }
16396
16725
  async driver(type, spec) {
16397
16726
  if (type === "fsa") return this.fsaDriver(spec);
16398
16727
  if (type === "hostfs") {
@@ -16482,6 +16811,16 @@ var Launcher = class {
16482
16811
  }
16483
16812
  };
16484
16813
  }
16814
+ attest(path) {
16815
+ return attest(this.rootFs, path, {
16816
+ users: this.usersMode,
16817
+ mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
16818
+ });
16819
+ }
16820
+ watchFiles(paths, notify) {
16821
+ this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
16822
+ return this.fileWatches.watch(paths, notify);
16823
+ }
16485
16824
  async mount(spec) {
16486
16825
  this.guardMount(spec.target);
16487
16826
  const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
@@ -16662,7 +17001,7 @@ var Launcher = class {
16662
17001
  ...account.umask !== void 0 ? { umask: account.umask } : {}
16663
17002
  };
16664
17003
  }
16665
- async starting(argv, dir, account) {
17004
+ async starting(argv, dir, account, tag) {
16666
17005
  this.resetCatalogs();
16667
17006
  const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
16668
17007
  const start2 = dir ?? "/";
@@ -16670,13 +17009,13 @@ var Launcher = class {
16670
17009
  const [file = ""] = argv;
16671
17010
  if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16672
17011
  const planned = await this.plan(file, argv, cwd, scope);
16673
- await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
17012
+ await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
16674
17013
  return { cwd, file, planned };
16675
17014
  }
16676
17015
  async openTerminal(argv, options) {
16677
17016
  tagOption(options.tag);
16678
17017
  const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
16679
- const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
17018
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
16680
17019
  if (!planned) throw new Error(`${file}: command not found`);
16681
17020
  let leader = 0;
16682
17021
  const tty = new KernelTty(
@@ -16727,7 +17066,7 @@ var Launcher = class {
16727
17066
  async run(argv, options = {}) {
16728
17067
  tagOption(options.tag);
16729
17068
  const account = await this.account(options.user);
16730
- const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
17069
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
16731
17070
  const env = this.environment(cwd, options.env, account);
16732
17071
  const out = [];
16733
17072
  const err = [];
@@ -16886,9 +17225,31 @@ var USERS_OPS = /* @__PURE__ */ new Set([
16886
17225
  "groups-add-member",
16887
17226
  "groups-remove-member"
16888
17227
  ]);
17228
+ var ATTEST_OPS = /* @__PURE__ */ new Set(["attest", "watch-files", "unwatch-files"]);
17229
+ function isAttestRequest(req) {
17230
+ return ATTEST_OPS.has(req.op);
17231
+ }
16889
17232
  function isUsersRequest(req) {
16890
17233
  return USERS_OPS.has(req.op);
16891
17234
  }
17235
+ function transferOf2(req, result) {
17236
+ if (result instanceof MessagePort) return [result];
17237
+ if (req.op === "attest") return [result.bytes.buffer];
17238
+ return void 0;
17239
+ }
17240
+ function attestOps(port) {
17241
+ const watches = /* @__PURE__ */ new Map();
17242
+ return async (l, req) => {
17243
+ if (req.op === "attest") return l.attest(req.path);
17244
+ if (req.op === "unwatch-files") {
17245
+ watches.get(req.watch)?.();
17246
+ return watches.delete(req.watch);
17247
+ }
17248
+ const notify = (path) => port.postMessage({ fileChanged: { watch: req.id, path } });
17249
+ watches.set(req.id, await l.watchFiles(req.paths, notify));
17250
+ return req.id;
17251
+ };
17252
+ }
16892
17253
  function failure(err) {
16893
17254
  const code = err?.code;
16894
17255
  return {
@@ -17027,8 +17388,10 @@ function serveKernel(port, deps) {
17027
17388
  if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
17028
17389
  return l.mounts.list();
17029
17390
  }
17391
+ const attestOp = attestOps(port);
17030
17392
  async function handle(req) {
17031
17393
  if (req.op === "init") return init(req);
17394
+ if (isAttestRequest(req)) return attestOp(await ready2(), req);
17032
17395
  if (req.op === "open-terminal") return terminal(req);
17033
17396
  if (req.op === "connect") return connect(req.user, req.tag);
17034
17397
  if (isUsersRequest(req)) return usersOp(await ready2(), req);
@@ -17077,7 +17440,7 @@ function serveKernel(port, deps) {
17077
17440
  if (answered(event.data, remote, uplink, approvals)) return;
17078
17441
  const req = event.data;
17079
17442
  handle(req).then(
17080
- (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
17443
+ (result) => reply(req.id, { result }, transferOf2(req, result)),
17081
17444
  (err) => reply(req.id, failure(err))
17082
17445
  );
17083
17446
  });