@ai-ecoverse/slicc-kernel 1.87.0 → 1.88.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
@@ -3014,8 +3014,8 @@ var MemoryMeta = class {
3014
3014
  async get(paths) {
3015
3015
  return paths.map((path) => this.entries.get(path));
3016
3016
  }
3017
- async fresh(path) {
3018
- return this.entries.get(path);
3017
+ async fresh(paths) {
3018
+ return this.get(paths);
3019
3019
  }
3020
3020
  async update(path, change) {
3021
3021
  this.set(path, change(this.entries.get(path)));
@@ -3615,8 +3615,25 @@ var OpfsFs = class {
3615
3615
  }
3616
3616
  counted = [];
3617
3617
  async stamp(path) {
3618
- const entry = this.meta.fresh ? await this.meta.fresh(path) : (await this.meta.get([path]))[0];
3619
- return String(entry?.writes ?? 0);
3618
+ const paths = [path, "/"];
3619
+ const [entry, root] = this.meta.fresh ? await this.meta.fresh(paths) : await this.meta.get(paths);
3620
+ return `${entry?.writes ?? 0}:${root?.epoch ?? 0}`;
3621
+ }
3622
+ async epoch() {
3623
+ await this.meta.update("/", (entry) => ({
3624
+ ...entry,
3625
+ path: "/",
3626
+ epoch: (entry?.epoch ?? 0) + 1
3627
+ }));
3628
+ }
3629
+ async entering(path, change) {
3630
+ if (!isProtected(this.counted, path) && !holdsProtected(this.counted, path)) return change();
3631
+ await this.epoch();
3632
+ try {
3633
+ await change();
3634
+ } finally {
3635
+ await this.epoch();
3636
+ }
3620
3637
  }
3621
3638
  async counting(target) {
3622
3639
  if (!isProtected(this.counted, target)) return;
@@ -3687,9 +3704,9 @@ var OpfsFs = class {
3687
3704
  if (fields) await this.meta.update(path, (entry) => ({ ...entry, path, ...fields }));
3688
3705
  }
3689
3706
  guarded = [];
3690
- protect(prefixes2) {
3707
+ protect(prefixes2, versions = false) {
3691
3708
  this.guarded = prefixes2;
3692
- this.counted = prefixes2;
3709
+ this.counted = versions ? prefixes2 : [];
3693
3710
  }
3694
3711
  unguarded() {
3695
3712
  return Object.create(this, { guarded: { value: [] } });
@@ -4269,6 +4286,9 @@ var OpfsFs = class {
4269
4286
  const parent = await this.locate(parentOf4(to), true);
4270
4287
  const destination = resolvePath(parent.path, baseOf(to));
4271
4288
  this.refuse(destination, true);
4289
+ await this.entering(destination, () => this.moveTo(source, parent, destination, to));
4290
+ }
4291
+ async moveTo(source, parent, destination, to) {
4272
4292
  const sourceStat = await this.describe(source);
4273
4293
  if (sourceStat.isDirectory && within2(destination, source.path)) throw fsError("EINVAL", to);
4274
4294
  const target = await this.locate(destination, false).catch((err) => {
@@ -4376,15 +4396,18 @@ var OpfsFs = class {
4376
4396
  }
4377
4397
  const now = Date.now();
4378
4398
  const fields = await this.newFields(at, owner && { uid: owner.uid, gid: owner.gid }, false);
4379
- await this.meta.update(at, () => ({
4380
- path: at,
4381
- link: target,
4382
- dir: parent.path,
4383
- ino: inodeOf(at),
4384
- mtimeMs: now,
4385
- ctimeMs: now,
4386
- ...fields
4387
- }));
4399
+ await this.entering(
4400
+ at,
4401
+ () => this.meta.update(at, () => ({
4402
+ path: at,
4403
+ link: target,
4404
+ dir: parent.path,
4405
+ ino: inodeOf(at),
4406
+ mtimeMs: now,
4407
+ ctimeMs: now,
4408
+ ...fields
4409
+ }))
4410
+ );
4388
4411
  this.touch(parent.path);
4389
4412
  }
4390
4413
  async realpath(path, follow2) {
@@ -5480,8 +5503,12 @@ function closeAll(entry) {
5480
5503
 
5481
5504
  // src/commands.ts
5482
5505
  var NAME2 = /^[A-Za-z0-9._+-]+$/;
5506
+ var COMMAND_NAME = /^[^/\s\p{Cc}]+$/u;
5483
5507
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
5484
5508
  var PACKAGE = /\$\{package\}/g;
5509
+ function commandName(name2) {
5510
+ return COMMAND_NAME.test(name2) && name2.isWellFormed() && name2 !== "." && name2 !== "..";
5511
+ }
5485
5512
  function inside(pkg, rel2) {
5486
5513
  if (typeof rel2 !== "string" || rel2 === "") return void 0;
5487
5514
  const clean = rel2.replace(/^\.\//, "");
@@ -5571,7 +5598,7 @@ function commandsOf(pkg, manifest) {
5571
5598
  const shared = envOf(pkg, slicc.env);
5572
5599
  const out = [];
5573
5600
  for (const [name2, raw] of Object.entries(entries)) {
5574
- if (!NAME2.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
5601
+ if (!commandName(name2) || !raw || typeof raw !== "object") {
5575
5602
  continue;
5576
5603
  }
5577
5604
  const command = commandOf(pkg, name2, raw, packageAbi, shared);
@@ -5623,18 +5650,86 @@ async function packagesIn(fs, roots) {
5623
5650
  dirs.push(...await packages(fs, root));
5624
5651
  return dirs;
5625
5652
  }
5626
- async function scanCommands(fs, modules) {
5627
- const found = /* @__PURE__ */ new Map();
5628
- for (const pkg of await packagesIn(fs, modules)) {
5629
- let manifest;
5630
- try {
5631
- manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
5632
- } catch {
5633
- continue;
5653
+ function binEntries(manifest) {
5654
+ const { bin, name: name2 } = manifest;
5655
+ if (typeof bin === "string") {
5656
+ return typeof name2 === "string" ? [[name2.slice(name2.lastIndexOf("/") + 1), bin]] : [];
5657
+ }
5658
+ return bin && typeof bin === "object" ? Object.entries(bin) : [];
5659
+ }
5660
+ var MAX_HOPS = 40;
5661
+ function physicalResolver(fs) {
5662
+ const known = /* @__PURE__ */ new Map();
5663
+ const pending = /* @__PURE__ */ new Set();
5664
+ let hops = 0;
5665
+ const step = async (path) => {
5666
+ if (path === "/") return { path, file: false };
5667
+ const slash = path.lastIndexOf("/");
5668
+ const parent = await resolve2(path.slice(0, slash) || "/");
5669
+ if (parent === void 0) return void 0;
5670
+ const at = resolvePath(parent.path, path.slice(slash + 1));
5671
+ const st = await fs.lstat(at).catch(() => void 0);
5672
+ if (!st?.isSymbolicLink) return st ? { path: at, file: st.isFile === true } : void 0;
5673
+ const target = await fs.readlink(at).catch(() => void 0);
5674
+ if (target === void 0 || ++hops > MAX_HOPS) return void 0;
5675
+ return resolve2(resolvePath(parent.path, target));
5676
+ };
5677
+ const resolve2 = (path) => {
5678
+ if (pending.has(path)) return Promise.resolve(void 0);
5679
+ let found = known.get(path);
5680
+ if (!found) {
5681
+ pending.add(path);
5682
+ found = step(path).finally(() => pending.delete(path));
5683
+ known.set(path, found);
5634
5684
  }
5635
- for (const command of commandsOf(pkg, manifest)) {
5636
- if (!found.has(command.name))
5637
- found.set(command.name, await withPackagePaths(fs, pkg, command));
5685
+ return found;
5686
+ };
5687
+ return (path) => {
5688
+ hops = 0;
5689
+ return resolve2(path);
5690
+ };
5691
+ }
5692
+ async function binCommandsOf(physical, pkg, manifest, taken) {
5693
+ const entries = binEntries(manifest);
5694
+ if (entries.length === 0) return [];
5695
+ const home = (await physical(pkg))?.path;
5696
+ const out = [];
5697
+ for (const [name2, rel2] of entries) {
5698
+ if (taken.has(name2) || !commandName(name2)) continue;
5699
+ const script = inside(pkg, rel2);
5700
+ if (!script) continue;
5701
+ const real = await physical(script);
5702
+ if (!real?.file || !real.path.startsWith(`${home}/`)) continue;
5703
+ out.push({
5704
+ name: name2,
5705
+ abi: "emscripten",
5706
+ glue: script,
5707
+ wasm: script,
5708
+ argv0: name2,
5709
+ script,
5710
+ bin: true
5711
+ });
5712
+ }
5713
+ return out;
5714
+ }
5715
+ async function scanCommands(fs, modules, binsFrom = Number.POSITIVE_INFINITY) {
5716
+ const found = /* @__PURE__ */ new Map();
5717
+ const physical = physicalResolver(fs);
5718
+ for (const [index, root] of (typeof modules === "string" ? [modules] : modules).entries()) {
5719
+ for (const pkg of await packages(fs, root)) {
5720
+ let manifest;
5721
+ try {
5722
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
5723
+ } catch {
5724
+ continue;
5725
+ }
5726
+ const own = commandsOf(pkg, manifest);
5727
+ const named = new Set(own.map((command) => command.name));
5728
+ const bins = index >= binsFrom ? await binCommandsOf(physical, pkg, manifest, named) : [];
5729
+ for (const command of [...own, ...bins]) {
5730
+ if (!found.has(command.name))
5731
+ found.set(command.name, await withPackagePaths(fs, pkg, command));
5732
+ }
5638
5733
  }
5639
5734
  }
5640
5735
  if (!found.has("sh") && found.has("bash")) {
@@ -6093,7 +6188,7 @@ var FreshModules = class {
6093
6188
  };
6094
6189
 
6095
6190
  // src/fs/physical-path.ts
6096
- var MAX_HOPS = 40;
6191
+ var MAX_HOPS2 = 40;
6097
6192
  function start(path) {
6098
6193
  return { done: [], todo: path.split("/").filter((p) => p !== ""), hops: 0 };
6099
6194
  }
@@ -6118,7 +6213,7 @@ function rest(walk2, part) {
6118
6213
  return lexical(`/${[...walk2.done, part, ...walk2.todo].join("/")}`);
6119
6214
  }
6120
6215
  function follow(walk2, target) {
6121
- if (++walk2.hops > MAX_HOPS) return false;
6216
+ if (++walk2.hops > MAX_HOPS2) return false;
6122
6217
  if (target.startsWith("/")) walk2.done.length = 0;
6123
6218
  walk2.todo = [...target.split("/").filter((p) => p !== ""), ...walk2.todo];
6124
6219
  return true;
@@ -8269,7 +8364,7 @@ async function dispatchOne(entry, req) {
8269
8364
  }
8270
8365
 
8271
8366
  // src/realm/sync-sab-wire.ts
8272
- var SAB_HEADER_I32 = 16;
8367
+ var SAB_HEADER_I32 = 18;
8273
8368
  var SAB_CLOCK_OFFSET = SAB_HEADER_I32 * 4;
8274
8369
  var SAB_CLOCK_I64 = 3;
8275
8370
  var SAB_HEADER_BYTES = SAB_CLOCK_OFFSET + SAB_CLOCK_I64 * 8;
@@ -8289,6 +8384,7 @@ var SAB_I_OFFSET_HI = 12;
8289
8384
  var SAB_I_KILLED = 13;
8290
8385
  var SAB_I_ASYNC = 14;
8291
8386
  var SAB_I_STOP = 15;
8387
+ var SAB_I_BLOCKED = 16;
8292
8388
  var U32 = 2 ** 32;
8293
8389
  function storeU53(header4, lo, hi, value) {
8294
8390
  Atomics.store(header4, hi, Math.floor(value / U32));
@@ -8841,12 +8937,32 @@ function mountTable(mounts = []) {
8841
8937
  `;
8842
8938
  }
8843
8939
 
8940
+ // src/version.ts
8941
+ var KERNEL_VERSION = true ? "1.88.0" : "0.0.0-dev";
8942
+ var OS_RELEASE = "6.0.0-slicc";
8943
+ function procVersion() {
8944
+ return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
8945
+ `;
8946
+ }
8947
+
8844
8948
  // src/process/procfs.ts
8845
8949
  var HZ = 100;
8846
8950
  var KIB = 1024;
8847
8951
  var FALLBACK_MEMORY = 4 * KIB * KIB * KIB;
8848
8952
  var PID_FILES = ["cmdline", "comm", "stat", "statm", "status"];
8849
- var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime"];
8953
+ var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime", "version"];
8954
+ var SYS_TREE = {
8955
+ kernel: { osrelease: `${OS_RELEASE}
8956
+ `, ostype: "Linux\n" }
8957
+ };
8958
+ function sysEntry(parts2) {
8959
+ let at = SYS_TREE;
8960
+ for (const part of parts2) {
8961
+ if (typeof at !== "object" || !Object.hasOwn(at, part)) return void 0;
8962
+ at = at[part];
8963
+ }
8964
+ return at;
8965
+ }
8850
8966
  function commOf(info) {
8851
8967
  const name2 = info.argv[0] ?? "";
8852
8968
  return name2.slice(name2.lastIndexOf("/") + 1).slice(0, 15);
@@ -8934,7 +9050,7 @@ function pidStatus(info) {
8934
9050
  "SigQ: 0/0",
8935
9051
  "SigPnd: 0000000000000000",
8936
9052
  "ShdPnd: 0000000000000000",
8937
- "SigBlk: 0000000000000000",
9053
+ `SigBlk: ${sigMask(info.sigBlk)}`,
8938
9054
  `SigIgn: ${sigMask(info.sigIgn)}`,
8939
9055
  `SigCgt: ${sigMask(info.sigCgt)}`,
8940
9056
  ""
@@ -8970,6 +9086,7 @@ function memoryInfo(source, used = 0) {
8970
9086
  ].map(([key, value]) => `${`${key}:`.padEnd(16)}${value.padStart(12)}`).concat("").join("\n");
8971
9087
  }
8972
9088
  function systemFile(name2, listing, now, memory) {
9089
+ if (name2 === "version") return procVersion();
8973
9090
  const up = Math.max(0, now - listing.boot) / 1e3;
8974
9091
  if (name2 === "uptime") return `${up.toFixed(2)} ${up.toFixed(2)}
8975
9092
  `;
@@ -9060,11 +9177,17 @@ var ProcTree = class {
9060
9177
  names: () => [
9061
9178
  ...SYSTEM_NAMES,
9062
9179
  "mounts",
9180
+ "sys",
9063
9181
  "self",
9064
9182
  ...this.listing().processes.map((p) => String(p.pid))
9065
9183
  ]
9066
9184
  };
9067
9185
  }
9186
+ if (top === "sys") {
9187
+ const entry = sysEntry(parts2.slice(1));
9188
+ if (typeof entry === "string") return { kind: "file", content: () => entry };
9189
+ return entry && { kind: "dir", names: () => Object.keys(entry) };
9190
+ }
9068
9191
  if (top === "self") {
9069
9192
  return leaf === void 0 ? { kind: "link", target: String(this.pid) } : void 0;
9070
9193
  }
@@ -9142,7 +9265,7 @@ var ProcTree = class {
9142
9265
  }
9143
9266
  }
9144
9267
  };
9145
- var MAX_HOPS2 = 40;
9268
+ var MAX_HOPS3 = 40;
9146
9269
  function* procWalk(path, follow2) {
9147
9270
  let done4 = [];
9148
9271
  let todo = path.split("/").filter((part) => part !== "");
@@ -9161,7 +9284,7 @@ function* procWalk(path, follow2) {
9161
9284
  if (!follow2 && todo.length === 0) return void 0;
9162
9285
  const target = yield { at, check: todo.find((next2) => next2 !== ".") === ".." };
9163
9286
  if (typeof target === "string") {
9164
- if (++hops > MAX_HOPS2) return void 0;
9287
+ if (++hops > MAX_HOPS3) return void 0;
9165
9288
  [done4, todo] = throughLink(target, done4, todo);
9166
9289
  } else {
9167
9290
  broken ??= refusalOf(target);
@@ -12041,11 +12164,12 @@ var VfsNodes = class {
12041
12164
  byId = /* @__PURE__ */ new Map();
12042
12165
  keepsOnDisk = false;
12043
12166
  fs;
12044
- entries = /* @__PURE__ */ new Map();
12167
+ entries;
12045
12168
  onClosed;
12046
- constructor(fs, onClosed) {
12169
+ constructor(fs, onClosed, entries = /* @__PURE__ */ new Map()) {
12047
12170
  this.fs = fs;
12048
12171
  this.onClosed = onClosed;
12172
+ this.entries = entries;
12049
12173
  }
12050
12174
  async onEntry(path, op) {
12051
12175
  const key = await this.entryKey(path);
@@ -12535,6 +12659,7 @@ var HOSTS_ONLY = new Resolver({ routes: new Routes() });
12535
12659
  var WasmProcess = class {
12536
12660
  exited = false;
12537
12661
  children;
12662
+ deathSignals = /* @__PURE__ */ new Map();
12538
12663
  caught = 0;
12539
12664
  ignored = 0;
12540
12665
  inherited = 0;
@@ -12569,7 +12694,10 @@ var WasmProcess = class {
12569
12694
  this.nodes = options.nodes ?? new VfsNodes(options.fs);
12570
12695
  if (!options.nodes) options.openFiles?.add(this.nodes);
12571
12696
  this.children.onChildState = () => this.signal(SIG.CHLD);
12572
- this.children.onReap = options.onReap;
12697
+ this.children.onReap = (pid2) => {
12698
+ this.deathSignals.delete(pid2);
12699
+ options.onReap?.(pid2);
12700
+ };
12573
12701
  }
12574
12702
  ignores(bit) {
12575
12703
  return (this.signalMasks().ignored & bit) !== 0;
@@ -12597,6 +12725,10 @@ var WasmProcess = class {
12597
12725
  if (sig === SIG.STOP) return this.stop(sig);
12598
12726
  const bit = sigbit(sig);
12599
12727
  if (this.ignores(bit)) return "ignore";
12728
+ if (((this.options.blockedBits?.() ?? 0) & bit) !== 0) {
12729
+ this.options.onPending?.(sig);
12730
+ return "deliver";
12731
+ }
12600
12732
  if (!(this.caught & bit)) {
12601
12733
  const action = defaultAction(sig);
12602
12734
  return action === "stop" ? this.stop(sig) : action;
@@ -12613,16 +12745,27 @@ var WasmProcess = class {
12613
12745
  return "deliver";
12614
12746
  }
12615
12747
  async spawn(req) {
12616
- const { file, argv, argv0, env, cwd, stdio, inherit: inherit2, exec, pgid, session } = req;
12748
+ const { file, argv, argv0, env, cwd, stdio, inherit: inherit2, exec, pgid, session, deathSignal } = req;
12749
+ if (deathSignal !== void 0 && (exec || !isSignal(deathSignal))) {
12750
+ throw new KernelError("EINVAL");
12751
+ }
12617
12752
  const group = { ...pgid !== void 0 ? { pgid } : {}, ...session ? { session } : {} };
12618
12753
  const named = argv0 !== void 0 ? { argv0 } : {};
12619
12754
  const spawned = { file, argv, ...named, env, cwd, ...group, ...exec ? { exec } : {} };
12620
12755
  const pid = await this.children.spawn(spawned, stdio, inherit2);
12756
+ if (deathSignal !== void 0) this.onDeath(pgid === 0 || session ? -pid : pid, deathSignal);
12621
12757
  if (!exec) return pid;
12622
12758
  if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
12623
12759
  else this.adoptExec(pid);
12624
12760
  return pid;
12625
12761
  }
12762
+ onDeath(target, sig) {
12763
+ if (this.exited) this.sendDeath(target, sig);
12764
+ else this.deathSignals.set(target, sig);
12765
+ }
12766
+ sendDeath(target, sig) {
12767
+ void Promise.resolve(this.options.kill?.(target, sig)).catch(() => void 0);
12768
+ }
12626
12769
  async killSelf(sig, target, probe2) {
12627
12770
  if (!this.options.exitSignal) throw new KernelError("ENOSYS");
12628
12771
  if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
@@ -13521,6 +13664,8 @@ var WasmProcess = class {
13521
13664
  this.interrupt.abort();
13522
13665
  this.options.locks?.release(this.pid);
13523
13666
  this.asyncOps.close();
13667
+ for (const [target, sig] of this.deathSignals) this.sendDeath(target, sig);
13668
+ this.deathSignals.clear();
13524
13669
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
13525
13670
  await this.stampClipboards();
13526
13671
  await this.fds.closeAll();
@@ -13600,19 +13745,25 @@ var Dying = class {
13600
13745
  }
13601
13746
  };
13602
13747
  function killThread(thread, caught, sig) {
13603
- if (!thread || (caught & sigbit(sig)) === 0) return false;
13748
+ if (!thread) return false;
13749
+ const held = (Atomics.load(thread.header, SAB_I_BLOCKED) & sigbit(sig)) !== 0;
13750
+ if (!held && (caught & sigbit(sig)) === 0) return false;
13604
13751
  Atomics.or(thread.header, SAB_I_SIGNALS, sigbit(sig));
13752
+ if (held) return true;
13605
13753
  Atomics.notify(thread.header, SAB_I_SIGNALS);
13606
13754
  const blocked = thread.interrupt;
13607
13755
  thread.interrupt = new AbortController();
13608
13756
  blocked.abort();
13609
13757
  return true;
13610
13758
  }
13759
+ function unblocked(header4) {
13760
+ return Atomics.load(header4, SAB_I_SIGNALS) & ~Atomics.load(header4, SAB_I_BLOCKED);
13761
+ }
13611
13762
  function threadSignalsOf(sab) {
13612
13763
  return { header: new Int32Array(sab, 0, SAB_HEADER_I32), interrupt: new AbortController() };
13613
13764
  }
13614
13765
  function threadCancel(thread) {
13615
- return Atomics.load(thread.header, SAB_I_SIGNALS) !== 0 ? AbortSignal.abort() : thread.interrupt.signal;
13766
+ return unblocked(thread.header) !== 0 ? AbortSignal.abort() : thread.interrupt.signal;
13616
13767
  }
13617
13768
  function notifiers(header4) {
13618
13769
  return {
@@ -13737,15 +13888,16 @@ function processOptions(opts, header4, hooks, created3) {
13737
13888
  resolver: opts.resolver,
13738
13889
  ...notifiers(header4),
13739
13890
  onTimer: (which) => void Atomics.or(header4, SAB_I_TIMERS, 1 << which),
13740
- hasPending: () => Atomics.load(header4, SAB_I_SIGNALS) !== 0 || Atomics.load(header4, SAB_I_TIMERS) !== 0,
13891
+ hasPending: () => unblocked(header4) !== 0 || Atomics.load(header4, SAB_I_TIMERS) !== 0,
13741
13892
  raise: hooks.raise,
13742
13893
  threadKill: hooks.threadKill,
13743
13894
  ...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
13744
13895
  ...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
13745
- ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {}
13896
+ ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {},
13897
+ ...opts.program.abi === "wasi" ? { blockedBits: () => Atomics.load(header4, SAB_I_BLOCKED) } : {}
13746
13898
  };
13747
13899
  }
13748
- function threadInit(opts, program, sab, cred, thread) {
13900
+ function threadInit(opts, program, sab, cred, thread, main) {
13749
13901
  return {
13750
13902
  type: WASM_THREAD_INIT,
13751
13903
  pid: opts.shownPid ?? opts.pid,
@@ -13758,12 +13910,15 @@ function threadInit(opts, program, sab, cred, thread) {
13758
13910
  cred: copyCred(cred),
13759
13911
  owners: opts.owners === true,
13760
13912
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
13761
- thread
13913
+ thread,
13914
+ signals: main
13762
13915
  };
13763
13916
  }
13764
13917
  function spawnWasmProcess(opts) {
13765
13918
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
13766
13919
  const header4 = new Int32Array(sab, 0, SAB_HEADER_I32);
13920
+ if (opts.blocked && opts.program.abi === "wasi")
13921
+ Atomics.store(header4, SAB_I_BLOCKED, opts.blocked);
13767
13922
  const threadSignals = /* @__PURE__ */ new Map();
13768
13923
  const created3 = new Created();
13769
13924
  const process2 = new WasmProcess(
@@ -13864,6 +14019,7 @@ function spawnWasmProcess(opts) {
13864
14019
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
13865
14020
  const tw = opts.createWorker();
13866
14021
  const tsignals = threadSignalsOf(tsab);
14022
+ if (thread.blocked) Atomics.store(tsignals.header, SAB_I_BLOCKED, thread.blocked);
13867
14023
  threadSignals.set(thread.tid, tsignals);
13868
14024
  const tdispatch = (req) => dying.serving(
13869
14025
  () => isWasmSyscall(req) ? process2.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
@@ -13885,7 +14041,7 @@ function spawnWasmProcess(opts) {
13885
14041
  tresponder.dispose();
13886
14042
  tw.terminate();
13887
14043
  });
13888
- tw.postMessage(threadInit(opts, program, tsab, process2.cred, thread));
14044
+ tw.postMessage(threadInit(opts, program, tsab, process2.cred, thread, sab));
13889
14045
  };
13890
14046
  worker.postMessage(
13891
14047
  processInit(opts, sab, process2.umask, copyCred(process2.cred), lane?.lane),
@@ -13907,6 +14063,7 @@ function spawnWasmProcess(opts) {
13907
14063
  onState: (listener) => process2.onState(listener),
13908
14064
  memory: () => Atomics.load(header4, SAB_I_MEMORY) * 65536,
13909
14065
  ignoredSignals: (fork) => process2.inheritable(fork),
14066
+ blockedSignals: () => opts.program.abi === "wasi" ? Atomics.load(header4, SAB_I_BLOCKED) : 0,
13910
14067
  signalMasks: () => process2.signalMasks(),
13911
14068
  umask: () => process2.umask,
13912
14069
  cred: () => copyCred(process2.cred),
@@ -17132,15 +17289,25 @@ function shebang(head) {
17132
17289
  if (head[0] !== 35 || head[1] !== 33) return void 0;
17133
17290
  return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
17134
17291
  }
17135
- async function rootOnly(fs, path) {
17292
+ async function rootOnly(fs, path, dir = false) {
17136
17293
  const names = chain(path);
17137
17294
  for (const [i, at] of names.entries()) {
17138
17295
  const st = await fs.lstat(at).catch(() => void 0);
17139
- const kind = i === names.length - 1 ? st?.isFile : st?.isDirectory;
17296
+ const kind = i === names.length - 1 && !dir ? st?.isFile : st?.isDirectory;
17140
17297
  if (!kind || st?.uid !== 0 || (st.mode & 18) !== 0) return false;
17141
17298
  }
17142
17299
  return true;
17143
17300
  }
17301
+ function appendedRoots(given) {
17302
+ const roots = given?.append ?? [];
17303
+ if (!Array.isArray(roots)) throw new TypeError("commands.append must be an array of paths");
17304
+ for (const root of roots) {
17305
+ if (typeof root !== "string" || !root.startsWith("/") || normalizePath(root) !== root) {
17306
+ throw new TypeError(`commands.append: not an absolute, normalised path: ${String(root)}`);
17307
+ }
17308
+ }
17309
+ return [...roots];
17310
+ }
17144
17311
  function plainEnv(words2) {
17145
17312
  const next2 = words2[1];
17146
17313
  if (baseName(words2[0] ?? "") !== "env" || next2 === void 0) return false;
@@ -17233,6 +17400,8 @@ var Launcher = class {
17233
17400
  base;
17234
17401
  createWorker;
17235
17402
  modulesDir;
17403
+ appended;
17404
+ reportedRoots = /* @__PURE__ */ new Set();
17236
17405
  env;
17237
17406
  catalogs = /* @__PURE__ */ new Map();
17238
17407
  ownCatalogs = /* @__PURE__ */ new Map();
@@ -17339,6 +17508,7 @@ var Launcher = class {
17339
17508
  this.processMounts = options.processMounts ?? true;
17340
17509
  this.pnpmHome = options.env?.PNPM_HOME ?? SYSTEM_PNPM_HOME;
17341
17510
  this.modulesDir = options.modules ?? "/node_modules";
17511
+ this.appended = appendedRoots(options.commands);
17342
17512
  this.cdp = new CdpHosts(options.cdp);
17343
17513
  const hostname = options.hostname ?? DEFAULT_HOSTNAME;
17344
17514
  if (!isHostname(hostname)) throw new Error(`not a host name: ${String(hostname)}`);
@@ -17388,9 +17558,9 @@ var Launcher = class {
17388
17558
  this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
17389
17559
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
17390
17560
  this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
17391
- options.fs.protect?.(this.prefixes);
17392
17561
  const integrity = options.protect?.integrity;
17393
17562
  integrityOptions(integrity);
17563
+ options.fs.protect?.(this.prefixes, Boolean(integrity));
17394
17564
  if (integrity && this.prefixes.length > 0) {
17395
17565
  this.integrity = new Integrity(
17396
17566
  this.prefixes,
@@ -17413,7 +17583,7 @@ var Launcher = class {
17413
17583
  });
17414
17584
  void this.unlinked.sweep("/");
17415
17585
  this.watchers.watch(
17416
- [this.modulesDir, this.pnpmHome],
17586
+ [this.modulesDir, this.pnpmHome, ...this.appended],
17417
17587
  { recursive: true },
17418
17588
  () => this.resetCatalogs()
17419
17589
  );
@@ -17444,7 +17614,10 @@ var Launcher = class {
17444
17614
  commands(scope = "") {
17445
17615
  let found = this.catalogs.get(scope);
17446
17616
  if (!found) {
17447
- found = this.roots(scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
17617
+ found = this.trustedAppended().then(async (appended) => {
17618
+ const roots = await this.roots(scope, appended);
17619
+ return scanCommands(this.scanned, roots, roots.length - appended.length);
17620
+ }).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
17448
17621
  this.catalogs.set(scope, found);
17449
17622
  }
17450
17623
  return found;
@@ -17471,10 +17644,37 @@ var Launcher = class {
17471
17644
  }
17472
17645
  return found;
17473
17646
  }
17474
- async roots(scope = "") {
17475
- const system = [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
17647
+ async roots(scope = "", appended) {
17648
+ const system = [
17649
+ this.modulesDir,
17650
+ ...await pnpmGlobalRoots(this.base, this.pnpmHome),
17651
+ ...appended ?? await this.trustedAppended()
17652
+ ];
17476
17653
  return scope ? [...await pnpmGlobalRoots(this.base, scope), ...system] : system;
17477
17654
  }
17655
+ async rootOnlyPath(root) {
17656
+ for (const at of chain(root)) {
17657
+ const real = await physicalPathAsync(this.base, at);
17658
+ if (!await rootOnly(this.base, real, true)) return false;
17659
+ }
17660
+ return true;
17661
+ }
17662
+ async trustedAppended() {
17663
+ if (this.appended.length === 0 || this.usersMode === "off") return this.appended;
17664
+ const trusted = [];
17665
+ for (const root of this.appended) {
17666
+ const real = await physicalPathAsync(this.base, root);
17667
+ if (await this.rootOnlyPath(root)) trusted.push(root);
17668
+ else if (this.usersMode === "report" && await this.base.exists(real)) {
17669
+ if (!this.reportedRoots.has(root)) {
17670
+ this.reportedRoots.add(root);
17671
+ this.dac?.denied({ pid: 0, uid: 0, op: "commands", path: root, errno: "EACCES" });
17672
+ }
17673
+ trusted.push(root);
17674
+ }
17675
+ }
17676
+ return trusted;
17677
+ }
17478
17678
  resetCatalogs() {
17479
17679
  this.catalogs.clear();
17480
17680
  this.ownCatalogs.clear();
@@ -17580,14 +17780,22 @@ var Launcher = class {
17580
17780
  const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
17581
17781
  const via = [interp, ...inner?.via ?? []];
17582
17782
  const sources = [script, ...inner?.sources ?? []];
17583
- return inner && { ...inner, target: withScriptEnv(inner.target, command), via, sources };
17783
+ const bins = [...command?.bin ? [script] : [], ...inner?.bins ?? []];
17784
+ return inner && {
17785
+ ...inner,
17786
+ target: withScriptEnv(inner.target, command),
17787
+ via,
17788
+ sources,
17789
+ ...bins.length > 0 ? { bins } : {}
17790
+ };
17584
17791
  }
17585
17792
  const target = withScriptEnv(found, command);
17586
17793
  return {
17587
17794
  target,
17588
17795
  args: [...target.prefix ?? [], ...passed],
17589
17796
  via: [interp],
17590
- sources: [script]
17797
+ sources: [script],
17798
+ ...command?.bin ? { bins: [script] } : {}
17591
17799
  };
17592
17800
  }
17593
17801
  async plan(file, argv, cwd, scope, argv0) {
@@ -17613,6 +17821,7 @@ var Launcher = class {
17613
17821
  const want = i < via.length - 1 ? R_OK | X_OK : X_OK;
17614
17822
  await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
17615
17823
  }
17824
+ for (const bin of planned?.bins ?? []) await need(g, "exec", bin, R_OK | X_OK);
17616
17825
  }
17617
17826
  async checkedPlan(req, { ppid, cred, scope }, fds) {
17618
17827
  const planned = await this.plan(req.file, req.argv, req.cwd, scope, req.argv0);
@@ -17911,6 +18120,7 @@ var Launcher = class {
17911
18120
  },
17912
18121
  settling: () => this.settling.isSettling(pid),
17913
18122
  ...req.ignored ? { ignored: req.ignored } : {},
18123
+ ...req.blocked ? { blocked: req.blocked } : {},
17914
18124
  ...req.umask !== void 0 ? { umask: req.umask } : {},
17915
18125
  cred: req.cred,
17916
18126
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
@@ -18185,6 +18395,7 @@ var Launcher = class {
18185
18395
  report,
18186
18396
  ppid,
18187
18397
  ignored: this.ignoredBy(ppid),
18398
+ blocked: this.blockedBy(ppid),
18188
18399
  umask: this.umaskOf(ppid),
18189
18400
  cred,
18190
18401
  tag: this.tagOf(ppid),
@@ -18246,6 +18457,7 @@ var Launcher = class {
18246
18457
  ppid,
18247
18458
  fork: state,
18248
18459
  ignored: this.ignoredBy(ppid, true),
18460
+ blocked: this.blockedBy(ppid),
18249
18461
  umask: this.umaskOf(ppid),
18250
18462
  cred: this.credOf(ppid),
18251
18463
  tag: this.tagOf(ppid)
@@ -18297,11 +18509,16 @@ var Launcher = class {
18297
18509
  }
18298
18510
  signalMasksOf(pid) {
18299
18511
  const masks = this.processes.get(pid)?.signalMasks?.();
18300
- return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught } : {};
18512
+ const blocked = this.blockedBy(pid);
18513
+ const sigBlk = blocked ? { sigBlk: blocked } : {};
18514
+ return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught, ...sigBlk } : sigBlk;
18301
18515
  }
18302
18516
  controlsJobs(pid) {
18303
18517
  return this.settling.isSettling(pid) || (this.ignoredBy(pid) & sigbit(SIG.TTOU)) !== 0;
18304
18518
  }
18519
+ blockedBy(pid) {
18520
+ return this.processes.get(pid)?.blockedSignals?.() ?? 0;
18521
+ }
18305
18522
  ignoredBy(pid, fork = false) {
18306
18523
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
18307
18524
  }
@@ -18666,7 +18883,7 @@ var Launcher = class {
18666
18883
  (dir) => this.userCommandDir(dir),
18667
18884
  this.owners
18668
18885
  );
18669
- const nodes = new VfsNodes(fs, (path) => this.released(path));
18886
+ const nodes = new VfsNodes(fs, (path) => this.released(path), this.nodes.entries);
18670
18887
  nodes.keepsOnDisk = true;
18671
18888
  this.openFiles.add(nodes);
18672
18889
  const unlinked = new UnlinkedKeeper(
@@ -20059,6 +20276,7 @@ async function createNodeKernel(options = {}) {
20059
20276
  terminated
20060
20277
  ),
20061
20278
  ...options.modules ? { modules: options.modules } : {},
20279
+ ...options.commands ? { commands: options.commands } : {},
20062
20280
  ...options.env ? { env: options.env } : {},
20063
20281
  ...transport ? { transport } : {},
20064
20282
  ...options.network?.uplink ? { uplink: options.network.uplink } : {},