@ai-ecoverse/slicc-kernel 1.71.1 → 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.
package/dist/node.js CHANGED
@@ -371,6 +371,7 @@ var Dac = class {
371
371
  log;
372
372
  seen = /* @__PURE__ */ new Set();
373
373
  suppressed = false;
374
+ tagOf = () => void 0;
374
375
  constructor(mode, hook, log = (line) => console.warn(line)) {
375
376
  this.mode = mode;
376
377
  this.hook = hook;
@@ -379,7 +380,9 @@ var Dac = class {
379
380
  get enforcing() {
380
381
  return this.mode === "enforce";
381
382
  }
382
- denied(denial) {
383
+ denied(given) {
384
+ const tag = given.tag ?? (given.pid ? this.tagOf(given.pid) : void 0);
385
+ const denial = tag === void 0 ? given : { ...given, tag };
383
386
  try {
384
387
  this.hook?.({ ...denial });
385
388
  } catch {
@@ -397,7 +400,7 @@ var Dac = class {
397
400
  }
398
401
  this.seen.add(key);
399
402
  this.log(
400
- `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
403
+ `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid}${d.tag === void 0 ? "" : `, tag ${d.tag}`})`
401
404
  );
402
405
  }
403
406
  };
@@ -413,7 +416,8 @@ var Created = class {
413
416
  }
414
417
  };
415
418
  function deny(g, op, path, errno = "EACCES") {
416
- g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno });
419
+ const tag = g.tag === void 0 ? {} : { tag: g.tag };
420
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
417
421
  }
418
422
  var TTY_MODE = 400;
419
423
  function ttyOpenable(g, tty) {
@@ -2595,9 +2599,9 @@ function ownerFor(l, self) {
2595
2599
  if (!l.owners || !self) return void 0;
2596
2600
  return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
2597
2601
  }
2598
- function guardFor(l, self) {
2602
+ function guardFor(l, self, tag) {
2599
2603
  if (!l.dac || !self) return void 0;
2600
- return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
2604
+ return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac, tag };
2601
2605
  }
2602
2606
  async function accountOf(l, user) {
2603
2607
  const found = await l.users.lookup(user);
@@ -2701,6 +2705,7 @@ async function watchOp(l, req, watches, send, g) {
2701
2705
  function serveClient(port, host) {
2702
2706
  let state = "new";
2703
2707
  let self;
2708
+ const guard2 = (l) => guardFor(l, self, host.tag);
2704
2709
  const terminals = /* @__PURE__ */ new Map();
2705
2710
  const groups = /* @__PURE__ */ new Set();
2706
2711
  const sessions = /* @__PURE__ */ new Set();
@@ -2810,10 +2815,10 @@ function serveClient(port, host) {
2810
2815
  if (!FS_METHODS.includes(req.method)) {
2811
2816
  throw new Error(`unknown file system call ${req.method}`);
2812
2817
  }
2813
- return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
2818
+ return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guard2(l));
2814
2819
  case "watch":
2815
2820
  case "unwatch":
2816
- return watchOp(l, req, watches, send, guardFor(l, self));
2821
+ return watchOp(l, req, watches, send, guard2(l));
2817
2822
  case "mount":
2818
2823
  return { result: await l.mountAs(clientCred(self), req.spec) };
2819
2824
  case "umount":
@@ -3174,6 +3179,121 @@ var ProtectedFs = class {
3174
3179
  }
3175
3180
  };
3176
3181
 
3182
+ // src/fs/remote.ts
3183
+ var ANNOUNCE_WAIT_MS = 500;
3184
+ var PRUNE_EVERY_MS = 3e4;
3185
+ function lockManager() {
3186
+ return globalThis.navigator?.locks;
3187
+ }
3188
+ var RemoteWatch = class {
3189
+ channel;
3190
+ name;
3191
+ tab = crypto.randomUUID();
3192
+ peers = /* @__PURE__ */ new Set();
3193
+ others = /* @__PURE__ */ new Map();
3194
+ union = /* @__PURE__ */ new Set();
3195
+ listeners = /* @__PURE__ */ new Set();
3196
+ waiting = /* @__PURE__ */ new Map();
3197
+ locks;
3198
+ mine = [];
3199
+ seq = 0;
3200
+ pruned = 0;
3201
+ constructor(name2, options = {}) {
3202
+ this.name = name2;
3203
+ this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
3204
+ this.channel = new BroadcastChannel(name2);
3205
+ this.channel.onmessage = ({ data }) => this.receive(data);
3206
+ this.channel.unref?.();
3207
+ void this.locks?.request(this.lockOf(this.tab), () => new Promise(() => {
3208
+ }));
3209
+ this.channel.postMessage({ hello: this.tab });
3210
+ }
3211
+ lockOf(tab) {
3212
+ return `${this.name}:${tab}`;
3213
+ }
3214
+ receive(data) {
3215
+ if (data.hello !== void 0) this.greeted(data.hello);
3216
+ if (data.here !== void 0 && data.to === this.tab) this.peers.add(data.here);
3217
+ if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
3218
+ if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
3219
+ this.acked(data.ack, data.from);
3220
+ }
3221
+ if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [] });
3222
+ }
3223
+ greeted(peer) {
3224
+ this.peers.add(peer);
3225
+ this.channel.postMessage({ here: this.tab, to: peer });
3226
+ if (this.mine.length > 0) void this.announce(this.mine);
3227
+ }
3228
+ announced({ tab, paths, seq: seq2 }) {
3229
+ const from = tab;
3230
+ this.peers.add(from);
3231
+ if (paths.length > 0) this.others.set(from, paths);
3232
+ else this.others.delete(from);
3233
+ this.rebuild();
3234
+ this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
3235
+ }
3236
+ acked(seq2, from) {
3237
+ const wait2 = this.waiting.get(seq2);
3238
+ if (!wait2) return;
3239
+ wait2.left.delete(from);
3240
+ if (wait2.left.size === 0) wait2.done();
3241
+ }
3242
+ rebuild() {
3243
+ this.union.clear();
3244
+ for (const paths of this.others.values()) for (const path of paths) this.union.add(path);
3245
+ }
3246
+ async prune() {
3247
+ if (!this.locks) return;
3248
+ const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
3249
+ for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
3250
+ if (!held.has(this.lockOf(tab))) {
3251
+ this.others.delete(tab);
3252
+ this.peers.delete(tab);
3253
+ }
3254
+ }
3255
+ this.rebuild();
3256
+ }
3257
+ watched() {
3258
+ return this.union.size > 0;
3259
+ }
3260
+ wrote(path) {
3261
+ if (!this.union.has(path)) return;
3262
+ this.channel.postMessage({ changed: [path] });
3263
+ const now = Date.now();
3264
+ if (now - this.pruned < PRUNE_EVERY_MS) return;
3265
+ this.pruned = now;
3266
+ void this.prune();
3267
+ }
3268
+ announce(paths) {
3269
+ this.mine = [...paths];
3270
+ const seq2 = ++this.seq;
3271
+ this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
3272
+ if (this.peers.size === 0) return Promise.resolve();
3273
+ return new Promise((resolve) => {
3274
+ const done4 = () => {
3275
+ clearTimeout(timer);
3276
+ this.waiting.delete(seq2);
3277
+ resolve();
3278
+ };
3279
+ const timer = setTimeout(() => {
3280
+ done4();
3281
+ void this.prune();
3282
+ }, ANNOUNCE_WAIT_MS);
3283
+ this.waiting.set(seq2, { left: new Set(this.peers), done: done4 });
3284
+ });
3285
+ }
3286
+ emit(change) {
3287
+ for (const listener of this.listeners) listener(change);
3288
+ }
3289
+ listen(listener) {
3290
+ this.listeners.add(listener);
3291
+ return () => {
3292
+ this.listeners.delete(listener);
3293
+ };
3294
+ }
3295
+ };
3296
+
3177
3297
  // src/fs/opfs.ts
3178
3298
  var S_IFDIR = 16384;
3179
3299
  var S_IFREG = 32768;
@@ -3297,6 +3417,7 @@ var OpfsFs = class {
3297
3417
  dirs = new PathMap();
3298
3418
  checked = /* @__PURE__ */ new WeakMap();
3299
3419
  channel;
3420
+ remote;
3300
3421
  ranged;
3301
3422
  owners = false;
3302
3423
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
@@ -3304,9 +3425,15 @@ var OpfsFs = class {
3304
3425
  this.meta = sidecar(meta);
3305
3426
  this.ranged = options.ranged === true;
3306
3427
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
3428
+ this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
3307
3429
  if (this.channel) {
3430
+ const remote = this.remote;
3431
+ meta.listen?.(
3432
+ (c) => remote?.emit({ paths: c.paths ?? [], trees: c.trees ?? [] })
3433
+ );
3308
3434
  this.channel.onmessage = ({ data }) => {
3309
3435
  for (const path of data) this.forget(path);
3436
+ remote?.emit({ paths: data, trees: [] });
3310
3437
  };
3311
3438
  this.channel.unref?.();
3312
3439
  }
@@ -3315,6 +3442,16 @@ var OpfsFs = class {
3315
3442
  for (const path of paths) this.forget(path);
3316
3443
  this.channel?.postMessage(paths);
3317
3444
  }
3445
+ remoteChanges(listener) {
3446
+ return this.remote?.listen(listener) ?? (() => {
3447
+ });
3448
+ }
3449
+ announceWatch(paths) {
3450
+ return this.remote?.announce(paths) ?? Promise.resolve();
3451
+ }
3452
+ committed(target) {
3453
+ if (this.remote?.watched()) this.remote.wrote(target);
3454
+ }
3318
3455
  resolvePath(base, path) {
3319
3456
  return resolvePath(base, path);
3320
3457
  }
@@ -3666,15 +3803,18 @@ var OpfsFs = class {
3666
3803
  );
3667
3804
  }
3668
3805
  async pwrite(path, offset, bytes) {
3669
- const handle = (await this.fileFor(path))[0];
3806
+ const [handle, , target] = await this.fileFor(path);
3670
3807
  await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
3808
+ this.committed(target);
3671
3809
  }
3672
3810
  async truncate(path, size) {
3673
3811
  const handle = await this.existing(path, true);
3812
+ const target = this.remote?.watched() ? (await this.locate(path, true)).path : path;
3674
3813
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
3814
+ this.committed(target);
3675
3815
  }
3676
3816
  async writeSession(path, truncate) {
3677
- const handle = (await this.fileFor(path))[0];
3817
+ const [handle, , target] = await this.fileFor(path);
3678
3818
  const writable2 = await this.retrying(
3679
3819
  path,
3680
3820
  () => handle.createWritable({ keepExistingData: !truncate }),
@@ -3687,9 +3827,12 @@ var OpfsFs = class {
3687
3827
  truncate: (size) => writable2.truncate(size).catch((err) => {
3688
3828
  throw translate(err, path, "write", false);
3689
3829
  }),
3690
- close: () => writable2.close().catch((err) => {
3691
- throw translate(err, path, "commit", false);
3692
- })
3830
+ close: async () => {
3831
+ await writable2.close().catch((err) => {
3832
+ throw translate(err, path, "commit", false);
3833
+ });
3834
+ this.committed(target);
3835
+ }
3693
3836
  };
3694
3837
  }
3695
3838
  async readFile(path) {
@@ -3738,6 +3881,7 @@ var OpfsFs = class {
3738
3881
  await writable2?.abort().catch(() => void 0);
3739
3882
  throw translate(err, path, "commit", false);
3740
3883
  }
3884
+ this.committed(target);
3741
3885
  }
3742
3886
  async listing(path) {
3743
3887
  const found = await this.locate(path, true);
@@ -3759,12 +3903,12 @@ var OpfsFs = class {
3759
3903
  const base = dir === "/" ? "" : dir;
3760
3904
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
3761
3905
  const handed2 = this.owners ? await this.handedTo(dir) : void 0;
3762
- const owned3 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
3906
+ const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
3763
3907
  return Promise.all(
3764
3908
  children2.map(async ([name2, child], i) => {
3765
- if (!isHandle(child)) return [name2, owned3(this.linkStat(child), child)];
3909
+ if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
3766
3910
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
3767
- return [name2, stat && owned3(stat, metas[i])];
3911
+ return [name2, stat && owned4(stat, metas[i])];
3768
3912
  })
3769
3913
  );
3770
3914
  }
@@ -5273,6 +5417,136 @@ function binfmtOf(binfmts, path) {
5273
5417
  return best === void 0 ? void 0 : binfmts.get(best);
5274
5418
  }
5275
5419
 
5420
+ // src/fs/attest.ts
5421
+ var ATTEST_MAX = 1024 * 1024;
5422
+ var UNOWNED = 1e3;
5423
+ function refuse(code, path, why) {
5424
+ throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
5425
+ }
5426
+ function chainOf(path) {
5427
+ const parts2 = path.split("/").filter(Boolean);
5428
+ return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
5429
+ }
5430
+ function under2(path, target) {
5431
+ return path === target || path.startsWith(`${target}/`);
5432
+ }
5433
+ function owned(path, st) {
5434
+ return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
5435
+ }
5436
+ function unchanged(a, b, bytes) {
5437
+ return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
5438
+ }
5439
+ async function sha256(bytes) {
5440
+ const digest = new Uint8Array(
5441
+ await crypto.subtle.digest("SHA-256", bytes)
5442
+ );
5443
+ return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
5444
+ }
5445
+ function attested(path, st, bytes, digest, o, parents) {
5446
+ const { uid, gid, mode } = owned(path, st);
5447
+ return {
5448
+ path,
5449
+ bytes,
5450
+ uid,
5451
+ gid,
5452
+ mode,
5453
+ ino: st.ino,
5454
+ mtimeMs: st.mtime.getTime(),
5455
+ sha256: digest,
5456
+ users: o.users,
5457
+ onRootFs: true,
5458
+ parents
5459
+ };
5460
+ }
5461
+ async function trustedParents(fs, path, o) {
5462
+ const parents = [];
5463
+ for (const dir of chainOf(path).slice(0, -1)) {
5464
+ const st = await fs.lstat(dir);
5465
+ if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
5466
+ if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
5467
+ const perm = owned(dir, st);
5468
+ if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
5469
+ refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
5470
+ }
5471
+ parents.push(perm);
5472
+ }
5473
+ return parents;
5474
+ }
5475
+ async function attest(fs, asked, o) {
5476
+ if (typeof asked !== "string" || !asked.startsWith("/")) {
5477
+ refuse("EINVAL", String(asked), "not an absolute path");
5478
+ }
5479
+ const path = normalizePath(asked);
5480
+ const mount = o.mounts.find((target) => {
5481
+ const at = normalizePath(target);
5482
+ return at === "/" || under2(path, at);
5483
+ });
5484
+ if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
5485
+ const parents = await trustedParents(fs, path, o);
5486
+ for (let tries = 0; ; tries++) {
5487
+ const before = await fs.lstat(path);
5488
+ if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
5489
+ if (!before.isFile)
5490
+ refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
5491
+ if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
5492
+ const bytes = (await fs.readFileBuffer(path)).slice();
5493
+ const after = await fs.lstat(path);
5494
+ if (unchanged(before, after, bytes)) {
5495
+ return attested(path, after, bytes, await sha256(bytes), o, parents);
5496
+ }
5497
+ if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
5498
+ }
5499
+ }
5500
+ var WATCH_DEBOUNCE_MS = 50;
5501
+ var FileWatches = class {
5502
+ live = /* @__PURE__ */ new Set();
5503
+ local;
5504
+ remote;
5505
+ constructor(local, remote) {
5506
+ this.local = local;
5507
+ this.remote = remote;
5508
+ }
5509
+ announce() {
5510
+ return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
5511
+ }
5512
+ async watch(paths, notify) {
5513
+ const files = paths.map(normalizePath);
5514
+ const timers = /* @__PURE__ */ new Map();
5515
+ let active = true;
5516
+ const fire = (changed, trees) => {
5517
+ for (const file of files) {
5518
+ const hit = [...changed, ...trees].some((p) => under2(file, normalizePath(p)));
5519
+ if (!hit) continue;
5520
+ clearTimeout(timers.get(file));
5521
+ timers.set(
5522
+ file,
5523
+ setTimeout(() => {
5524
+ timers.delete(file);
5525
+ if (active) notify(file);
5526
+ }, WATCH_DEBOUNCE_MS)
5527
+ );
5528
+ }
5529
+ };
5530
+ const unwatch = this.local.watch(
5531
+ files,
5532
+ { recursive: false },
5533
+ (change) => fire("overflow" in change ? files : change.paths, [])
5534
+ );
5535
+ const unlisten = this.remote.remoteChanges?.((change) => fire(change.paths, change.trees));
5536
+ this.live.add(files);
5537
+ await this.announce();
5538
+ return () => {
5539
+ if (!active) return;
5540
+ active = false;
5541
+ for (const timer of timers.values()) clearTimeout(timer);
5542
+ unwatch();
5543
+ unlisten?.();
5544
+ this.live.delete(files);
5545
+ void this.announce();
5546
+ };
5547
+ }
5548
+ };
5549
+
5276
5550
  // src/fs/commands.ts
5277
5551
  var S_IFDIR2 = 16384;
5278
5552
  var S_IFREG2 = 32768;
@@ -5311,7 +5585,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5311
5585
  if (st || path === "/") return st ?? {};
5312
5586
  return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
5313
5587
  }
5314
- async function owned3(stat2, dir) {
5588
+ async function owned4(stat2, dir) {
5315
5589
  if (!owners) return stat2;
5316
5590
  const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
5317
5591
  return { ...stat2, uid, gid };
@@ -5325,17 +5599,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5325
5599
  }
5326
5600
  async function virtual(path) {
5327
5601
  if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
5328
- return owned3(synthetic(true, path));
5602
+ return owned4(synthetic(true, path));
5329
5603
  }
5330
5604
  const name2 = COMMAND.exec(path)?.[1];
5331
5605
  if (name2 !== void 0) {
5332
- return (await names()).has(name2) ? owned3(synthetic(false, path)) : void 0;
5606
+ return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
5333
5607
  }
5334
- if ((await userDir(path))?.size) return owned3(synthetic(true, path), path);
5608
+ if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
5335
5609
  const slash = path.lastIndexOf("/");
5336
5610
  const dir = path.slice(0, slash);
5337
5611
  const own = await userDir(dir);
5338
- return own?.has(path.slice(slash + 1)) ? owned3(synthetic(false, path), dir) : void 0;
5612
+ return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
5339
5613
  }
5340
5614
  async function linked(path) {
5341
5615
  return await virtual(path) ?? virtual(await followLinks(fs, path));
@@ -5462,11 +5736,11 @@ async function physicalPathAsync(fs, path) {
5462
5736
 
5463
5737
  // src/fs/watch.ts
5464
5738
  var WATCH_BATCH_LIMIT = 1e3;
5465
- var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
5739
+ var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
5466
5740
  var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5467
5741
  function touches(watcher, changed) {
5468
5742
  return watcher.paths.some(
5469
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf6(changed) === watched)
5743
+ (watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
5470
5744
  );
5471
5745
  }
5472
5746
  var FsWatchers = class {
@@ -7185,7 +7459,7 @@ async function keptStat(entry, req) {
7185
7459
  return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
7186
7460
  return json2(statJson(await entry.fs.stat(hidden)));
7187
7461
  }
7188
- async function owned(entry, own, req, path, target) {
7462
+ async function owned2(entry, own, req, path, target) {
7189
7463
  const source = target === path ? void 0 : path;
7190
7464
  if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
7191
7465
  return run(entry, path, req);
@@ -7240,7 +7514,7 @@ async function dispatchOne(entry, req) {
7240
7514
  }
7241
7515
  if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
7242
7516
  const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
7243
- return await owned(entry, entry.own, req, path, target);
7517
+ return await owned2(entry, entry.own, req, path, target);
7244
7518
  } catch (err) {
7245
7519
  return toErrno(err);
7246
7520
  }
@@ -11443,6 +11717,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11443
11717
  "proc-sudo",
11444
11718
  "proc-run",
11445
11719
  "proc-exit",
11720
+ "proc-kill-self",
11446
11721
  "proc-fork",
11447
11722
  "proc-kill",
11448
11723
  "thread-kill",
@@ -11570,6 +11845,20 @@ var WasmProcess = class {
11570
11845
  else this.adoptExec(pid);
11571
11846
  return pid;
11572
11847
  }
11848
+ async killSelf(sig, target, probe2) {
11849
+ if (!this.options.exitSignal) throw new KernelError("ENOSYS");
11850
+ if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
11851
+ const { caught, ignored } = this.signalMasks();
11852
+ const to = target === 0 ? -this.pgid() : target ?? this.pid;
11853
+ if (sig !== SIG.KILL && (caught | ignored) & sigbit(sig)) {
11854
+ if (!await this.options.kill?.(to, sig)) throw new KernelError("ESRCH");
11855
+ return { ok: true, kind: "void" };
11856
+ }
11857
+ if (probe2) return { ok: true, kind: "json", json: "terminate" };
11858
+ if (to !== this.pid) await this.options.kill?.(to, sig);
11859
+ this.options.exitSignal(sig);
11860
+ return DYING;
11861
+ }
11573
11862
  run(req) {
11574
11863
  return runChild(req, {
11575
11864
  children: this.children,
@@ -12167,6 +12456,8 @@ var WasmProcess = class {
12167
12456
  if (!this.options.exitNow) throw new KernelError("ENOSYS");
12168
12457
  this.options.exitNow(req.status & 255);
12169
12458
  return DYING;
12459
+ case "proc-kill-self":
12460
+ return this.killSelf(req.sig, req.target, req.probe);
12170
12461
  case "proc-identity":
12171
12462
  return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
12172
12463
  case "proc-exec": {
@@ -12601,6 +12892,7 @@ function processOptions(opts, header4, hooks, created3) {
12601
12892
  raise: hooks.raise,
12602
12893
  threadKill: hooks.threadKill,
12603
12894
  ...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
12895
+ ...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
12604
12896
  ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {}
12605
12897
  };
12606
12898
  }
@@ -12634,7 +12926,10 @@ function spawnWasmProcess(opts) {
12634
12926
  {
12635
12927
  raise: (sig) => signal(sig),
12636
12928
  threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig),
12637
- ...opts.program.abi === "js" ? { exitNow: (code) => exitNow(code) } : {}
12929
+ ...opts.program.abi === "js" ? {
12930
+ exitNow: (code) => exitNow(code),
12931
+ exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
12932
+ } : {}
12638
12933
  },
12639
12934
  created3
12640
12935
  )
@@ -14542,8 +14837,8 @@ var ChunkedBytes = class {
14542
14837
  }
14543
14838
  return out;
14544
14839
  }
14545
- write(offset, bytes, owned3 = false) {
14546
- const whole = owned3 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
14840
+ write(offset, bytes, owned4 = false) {
14841
+ const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
14547
14842
  for (let done4 = 0; done4 < bytes.length; ) {
14548
14843
  const at = offset + done4;
14549
14844
  const index = Math.floor(at / CHUNK3);
@@ -14593,7 +14888,7 @@ function created2(ino, kind, mode, from, owner) {
14593
14888
  gid: owner?.gid ?? from?.gid ?? 0
14594
14889
  };
14595
14890
  }
14596
- function owned2(node, change) {
14891
+ function owned3(node, change) {
14597
14892
  if (change.uid !== void 0) node.uid = change.uid;
14598
14893
  if (change.gid !== void 0) node.gid = change.gid;
14599
14894
  }
@@ -14764,7 +15059,7 @@ function tmpfs() {
14764
15059
  node.mtime = Date.now();
14765
15060
  }
14766
15061
  if (change.mtime !== void 0) node.mtime = change.mtime;
14767
- owned2(node, change);
15062
+ owned3(node, change);
14768
15063
  },
14769
15064
  async statfs() {
14770
15065
  return { bsize: 4096, blocks: 262144, bfree: 262144 };
@@ -15513,12 +15808,16 @@ var Launcher = class {
15513
15808
  usersMode;
15514
15809
  dac;
15515
15810
  sealed;
15811
+ rootFs;
15812
+ fileWatches;
15516
15813
  nextPid = 1e3;
15517
15814
  boot = Date.now();
15518
15815
  terminals = 0;
15519
15816
  constructor(options) {
15520
15817
  this.usersMode = usersMode(options.users);
15818
+ this.rootFs = options.fs;
15521
15819
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
15820
+ if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
15522
15821
  this.sealed = new SealedBroker(
15523
15822
  options.sealed,
15524
15823
  this.usersMode,
@@ -15768,7 +16067,7 @@ var Launcher = class {
15768
16067
  async mayExec(cred, req, planned) {
15769
16068
  const { dac } = this;
15770
16069
  if (!dac) return;
15771
- const g = { fs: this.fs, cred, pid: req.pid, dac };
16070
+ const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
15772
16071
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
15773
16072
  if (byPath(req.file)) {
15774
16073
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -16271,6 +16570,13 @@ var Launcher = class {
16271
16570
  kill(pid, sig, sender, from) {
16272
16571
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
16273
16572
  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;
16573
+ if (pid === -1) {
16574
+ const sender2 = from === void 0 ? void 0 : this.imageOf(from);
16575
+ return this.killAll(
16576
+ sig,
16577
+ (member) => this.imageOf(member) !== sender2 && may(this.imageOf(member))
16578
+ );
16579
+ }
16274
16580
  if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
16275
16581
  const handle = this.processes.get(pid);
16276
16582
  if (!handle) return false;
@@ -16278,6 +16584,15 @@ var Launcher = class {
16278
16584
  if (sig !== 0) handle.signal(sig);
16279
16585
  return true;
16280
16586
  }
16587
+ killAll(sig, may) {
16588
+ let any = false;
16589
+ for (const [pid, handle] of [...this.processes]) {
16590
+ if (pid === 1 || !may(pid)) continue;
16591
+ any = true;
16592
+ if (sig !== 0) handle.signal(sig);
16593
+ }
16594
+ return any;
16595
+ }
16281
16596
  async driver(type, spec) {
16282
16597
  if (type === "fsa") return this.fsaDriver(spec);
16283
16598
  if (type === "hostfs") {
@@ -16367,6 +16682,16 @@ var Launcher = class {
16367
16682
  }
16368
16683
  };
16369
16684
  }
16685
+ attest(path) {
16686
+ return attest(this.rootFs, path, {
16687
+ users: this.usersMode,
16688
+ mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
16689
+ });
16690
+ }
16691
+ watchFiles(paths, notify) {
16692
+ this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
16693
+ return this.fileWatches.watch(paths, notify);
16694
+ }
16370
16695
  async mount(spec) {
16371
16696
  this.guardMount(spec.target);
16372
16697
  const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
@@ -16547,7 +16872,7 @@ var Launcher = class {
16547
16872
  ...account.umask !== void 0 ? { umask: account.umask } : {}
16548
16873
  };
16549
16874
  }
16550
- async starting(argv, dir, account) {
16875
+ async starting(argv, dir, account, tag) {
16551
16876
  this.resetCatalogs();
16552
16877
  const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
16553
16878
  const start2 = dir ?? "/";
@@ -16555,13 +16880,13 @@ var Launcher = class {
16555
16880
  const [file = ""] = argv;
16556
16881
  if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16557
16882
  const planned = await this.plan(file, argv, cwd, scope);
16558
- await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
16883
+ await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
16559
16884
  return { cwd, file, planned };
16560
16885
  }
16561
16886
  async openTerminal(argv, options) {
16562
16887
  tagOption(options.tag);
16563
16888
  const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
16564
- const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
16889
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
16565
16890
  if (!planned) throw new Error(`${file}: command not found`);
16566
16891
  let leader = 0;
16567
16892
  const tty = new KernelTty(
@@ -16612,7 +16937,7 @@ var Launcher = class {
16612
16937
  async run(argv, options = {}) {
16613
16938
  tagOption(options.tag);
16614
16939
  const account = await this.account(options.user);
16615
- const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
16940
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
16616
16941
  const env = this.environment(cwd, options.env, account);
16617
16942
  const out = [];
16618
16943
  const err = [];