@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.
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
  }
@@ -3828,7 +3972,14 @@ var OpfsFs = class {
3828
3972
  links() {
3829
3973
  return false;
3830
3974
  }
3831
- async link(from) {
3975
+ async link(from, to, follow2) {
3976
+ await (follow2 ? this.stat(from) : this.lstat(from));
3977
+ if (!(await this.stat(parentOf4(to))).isDirectory) throw fsError("ENOTDIR", to);
3978
+ if (await this.lstat(to).then(
3979
+ () => true,
3980
+ () => false
3981
+ ))
3982
+ throw fsError("EEXIST", to);
3832
3983
  throw fsError("EPERM", `${from}: this file system has no hard links`);
3833
3984
  }
3834
3985
  async rename(from, to) {
@@ -5266,6 +5417,136 @@ function binfmtOf(binfmts, path) {
5266
5417
  return best === void 0 ? void 0 : binfmts.get(best);
5267
5418
  }
5268
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
+
5269
5550
  // src/fs/commands.ts
5270
5551
  var S_IFDIR2 = 16384;
5271
5552
  var S_IFREG2 = 32768;
@@ -5304,7 +5585,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5304
5585
  if (st || path === "/") return st ?? {};
5305
5586
  return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
5306
5587
  }
5307
- async function owned3(stat2, dir) {
5588
+ async function owned4(stat2, dir) {
5308
5589
  if (!owners) return stat2;
5309
5590
  const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
5310
5591
  return { ...stat2, uid, gid };
@@ -5318,17 +5599,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5318
5599
  }
5319
5600
  async function virtual(path) {
5320
5601
  if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
5321
- return owned3(synthetic(true, path));
5602
+ return owned4(synthetic(true, path));
5322
5603
  }
5323
5604
  const name2 = COMMAND.exec(path)?.[1];
5324
5605
  if (name2 !== void 0) {
5325
- return (await names()).has(name2) ? owned3(synthetic(false, path)) : void 0;
5606
+ return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
5326
5607
  }
5327
- if ((await userDir(path))?.size) return owned3(synthetic(true, path), path);
5608
+ if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
5328
5609
  const slash = path.lastIndexOf("/");
5329
5610
  const dir = path.slice(0, slash);
5330
5611
  const own = await userDir(dir);
5331
- 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;
5332
5613
  }
5333
5614
  async function linked(path) {
5334
5615
  return await virtual(path) ?? virtual(await followLinks(fs, path));
@@ -5455,11 +5736,11 @@ async function physicalPathAsync(fs, path) {
5455
5736
 
5456
5737
  // src/fs/watch.ts
5457
5738
  var WATCH_BATCH_LIMIT = 1e3;
5458
- var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
5739
+ var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
5459
5740
  var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5460
5741
  function touches(watcher, changed) {
5461
5742
  return watcher.paths.some(
5462
- (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)
5463
5744
  );
5464
5745
  }
5465
5746
  var FsWatchers = class {
@@ -7178,7 +7459,7 @@ async function keptStat(entry, req) {
7178
7459
  return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
7179
7460
  return json2(statJson(await entry.fs.stat(hidden)));
7180
7461
  }
7181
- async function owned(entry, own, req, path, target) {
7462
+ async function owned2(entry, own, req, path, target) {
7182
7463
  const source = target === path ? void 0 : path;
7183
7464
  if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
7184
7465
  return run(entry, path, req);
@@ -7233,7 +7514,7 @@ async function dispatchOne(entry, req) {
7233
7514
  }
7234
7515
  if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
7235
7516
  const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
7236
- return await owned(entry, entry.own, req, path, target);
7517
+ return await owned2(entry, entry.own, req, path, target);
7237
7518
  } catch (err) {
7238
7519
  return toErrno(err);
7239
7520
  }
@@ -11436,6 +11717,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11436
11717
  "proc-sudo",
11437
11718
  "proc-run",
11438
11719
  "proc-exit",
11720
+ "proc-kill-self",
11439
11721
  "proc-fork",
11440
11722
  "proc-kill",
11441
11723
  "thread-kill",
@@ -11563,6 +11845,20 @@ var WasmProcess = class {
11563
11845
  else this.adoptExec(pid);
11564
11846
  return pid;
11565
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
+ }
11566
11862
  run(req) {
11567
11863
  return runChild(req, {
11568
11864
  children: this.children,
@@ -12160,6 +12456,8 @@ var WasmProcess = class {
12160
12456
  if (!this.options.exitNow) throw new KernelError("ENOSYS");
12161
12457
  this.options.exitNow(req.status & 255);
12162
12458
  return DYING;
12459
+ case "proc-kill-self":
12460
+ return this.killSelf(req.sig, req.target, req.probe);
12163
12461
  case "proc-identity":
12164
12462
  return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
12165
12463
  case "proc-exec": {
@@ -12594,6 +12892,7 @@ function processOptions(opts, header4, hooks, created3) {
12594
12892
  raise: hooks.raise,
12595
12893
  threadKill: hooks.threadKill,
12596
12894
  ...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
12895
+ ...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
12597
12896
  ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {}
12598
12897
  };
12599
12898
  }
@@ -12627,7 +12926,10 @@ function spawnWasmProcess(opts) {
12627
12926
  {
12628
12927
  raise: (sig) => signal(sig),
12629
12928
  threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig),
12630
- ...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
+ } : {}
12631
12933
  },
12632
12934
  created3
12633
12935
  )
@@ -14535,8 +14837,8 @@ var ChunkedBytes = class {
14535
14837
  }
14536
14838
  return out;
14537
14839
  }
14538
- write(offset, bytes, owned3 = false) {
14539
- 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;
14540
14842
  for (let done4 = 0; done4 < bytes.length; ) {
14541
14843
  const at = offset + done4;
14542
14844
  const index = Math.floor(at / CHUNK3);
@@ -14586,7 +14888,7 @@ function created2(ino, kind, mode, from, owner) {
14586
14888
  gid: owner?.gid ?? from?.gid ?? 0
14587
14889
  };
14588
14890
  }
14589
- function owned2(node, change) {
14891
+ function owned3(node, change) {
14590
14892
  if (change.uid !== void 0) node.uid = change.uid;
14591
14893
  if (change.gid !== void 0) node.gid = change.gid;
14592
14894
  }
@@ -14757,7 +15059,7 @@ function tmpfs() {
14757
15059
  node.mtime = Date.now();
14758
15060
  }
14759
15061
  if (change.mtime !== void 0) node.mtime = change.mtime;
14760
- owned2(node, change);
15062
+ owned3(node, change);
14761
15063
  },
14762
15064
  async statfs() {
14763
15065
  return { bsize: 4096, blocks: 262144, bfree: 262144 };
@@ -15506,12 +15808,16 @@ var Launcher = class {
15506
15808
  usersMode;
15507
15809
  dac;
15508
15810
  sealed;
15811
+ rootFs;
15812
+ fileWatches;
15509
15813
  nextPid = 1e3;
15510
15814
  boot = Date.now();
15511
15815
  terminals = 0;
15512
15816
  constructor(options) {
15513
15817
  this.usersMode = usersMode(options.users);
15818
+ this.rootFs = options.fs;
15514
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);
15515
15821
  this.sealed = new SealedBroker(
15516
15822
  options.sealed,
15517
15823
  this.usersMode,
@@ -15761,7 +16067,7 @@ var Launcher = class {
15761
16067
  async mayExec(cred, req, planned) {
15762
16068
  const { dac } = this;
15763
16069
  if (!dac) return;
15764
- const g = { fs: this.fs, cred, pid: req.pid, dac };
16070
+ const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
15765
16071
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
15766
16072
  if (byPath(req.file)) {
15767
16073
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -16264,6 +16570,13 @@ var Launcher = class {
16264
16570
  kill(pid, sig, sender, from) {
16265
16571
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
16266
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
+ }
16267
16580
  if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
16268
16581
  const handle = this.processes.get(pid);
16269
16582
  if (!handle) return false;
@@ -16271,6 +16584,15 @@ var Launcher = class {
16271
16584
  if (sig !== 0) handle.signal(sig);
16272
16585
  return true;
16273
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
+ }
16274
16596
  async driver(type, spec) {
16275
16597
  if (type === "fsa") return this.fsaDriver(spec);
16276
16598
  if (type === "hostfs") {
@@ -16360,6 +16682,16 @@ var Launcher = class {
16360
16682
  }
16361
16683
  };
16362
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
+ }
16363
16695
  async mount(spec) {
16364
16696
  this.guardMount(spec.target);
16365
16697
  const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
@@ -16540,7 +16872,7 @@ var Launcher = class {
16540
16872
  ...account.umask !== void 0 ? { umask: account.umask } : {}
16541
16873
  };
16542
16874
  }
16543
- async starting(argv, dir, account) {
16875
+ async starting(argv, dir, account, tag) {
16544
16876
  this.resetCatalogs();
16545
16877
  const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
16546
16878
  const start2 = dir ?? "/";
@@ -16548,13 +16880,13 @@ var Launcher = class {
16548
16880
  const [file = ""] = argv;
16549
16881
  if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16550
16882
  const planned = await this.plan(file, argv, cwd, scope);
16551
- await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
16883
+ await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
16552
16884
  return { cwd, file, planned };
16553
16885
  }
16554
16886
  async openTerminal(argv, options) {
16555
16887
  tagOption(options.tag);
16556
16888
  const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
16557
- 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);
16558
16890
  if (!planned) throw new Error(`${file}: command not found`);
16559
16891
  let leader = 0;
16560
16892
  const tty = new KernelTty(
@@ -16605,7 +16937,7 @@ var Launcher = class {
16605
16937
  async run(argv, options = {}) {
16606
16938
  tagOption(options.tag);
16607
16939
  const account = await this.account(options.user);
16608
- 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);
16609
16941
  const env = this.environment(cwd, options.env, account);
16610
16942
  const out = [];
16611
16943
  const err = [];