@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.
@@ -97,8 +97,8 @@ var MemoryMeta = class {
97
97
  async get(paths) {
98
98
  return paths.map((path) => this.entries.get(path));
99
99
  }
100
- async fresh(path) {
101
- return this.entries.get(path);
100
+ async fresh(paths) {
101
+ return this.get(paths);
102
102
  }
103
103
  async update(path, change) {
104
104
  this.set(path, change(this.entries.get(path)));
@@ -196,15 +196,13 @@ var IndexedDbMeta = class {
196
196
  }
197
197
  return paths.map((path) => this.cache.get(path) ?? void 0);
198
198
  }
199
- async fresh(path) {
199
+ async fresh(paths) {
200
200
  const [transaction, store] = this.transaction("readonly");
201
- const request2 = store.get(path);
202
- let entry;
203
- request2.onsuccess = () => {
204
- entry = request2.result ?? void 0;
205
- };
206
- await settle(transaction, () => void 0);
207
- return entry;
201
+ const requests = paths.map((path) => store.get(path));
202
+ return settle(
203
+ transaction,
204
+ () => requests.map((request2) => request2.result ?? void 0)
205
+ );
208
206
  }
209
207
  async update(path, change) {
210
208
  this.settleRemoves();
@@ -1020,8 +1018,25 @@ var OpfsFs = class {
1020
1018
  }
1021
1019
  counted = [];
1022
1020
  async stamp(path) {
1023
- const entry = this.meta.fresh ? await this.meta.fresh(path) : (await this.meta.get([path]))[0];
1024
- return String(entry?.writes ?? 0);
1021
+ const paths = [path, "/"];
1022
+ const [entry, root] = this.meta.fresh ? await this.meta.fresh(paths) : await this.meta.get(paths);
1023
+ return `${entry?.writes ?? 0}:${root?.epoch ?? 0}`;
1024
+ }
1025
+ async epoch() {
1026
+ await this.meta.update("/", (entry) => ({
1027
+ ...entry,
1028
+ path: "/",
1029
+ epoch: (entry?.epoch ?? 0) + 1
1030
+ }));
1031
+ }
1032
+ async entering(path, change) {
1033
+ if (!isProtected(this.counted, path) && !holdsProtected(this.counted, path)) return change();
1034
+ await this.epoch();
1035
+ try {
1036
+ await change();
1037
+ } finally {
1038
+ await this.epoch();
1039
+ }
1025
1040
  }
1026
1041
  async counting(target) {
1027
1042
  if (!isProtected(this.counted, target)) return;
@@ -1092,9 +1107,9 @@ var OpfsFs = class {
1092
1107
  if (fields) await this.meta.update(path, (entry) => ({ ...entry, path, ...fields }));
1093
1108
  }
1094
1109
  guarded = [];
1095
- protect(prefixes2) {
1110
+ protect(prefixes2, versions = false) {
1096
1111
  this.guarded = prefixes2;
1097
- this.counted = prefixes2;
1112
+ this.counted = versions ? prefixes2 : [];
1098
1113
  }
1099
1114
  unguarded() {
1100
1115
  return Object.create(this, { guarded: { value: [] } });
@@ -1674,6 +1689,9 @@ var OpfsFs = class {
1674
1689
  const parent = await this.locate(parentOf3(to), true);
1675
1690
  const destination = resolvePath(parent.path, baseOf(to));
1676
1691
  this.refuse(destination, true);
1692
+ await this.entering(destination, () => this.moveTo(source, parent, destination, to));
1693
+ }
1694
+ async moveTo(source, parent, destination, to) {
1677
1695
  const sourceStat = await this.describe(source);
1678
1696
  if (sourceStat.isDirectory && within2(destination, source.path)) throw fsError("EINVAL", to);
1679
1697
  const target = await this.locate(destination, false).catch((err) => {
@@ -1781,15 +1799,18 @@ var OpfsFs = class {
1781
1799
  }
1782
1800
  const now = Date.now();
1783
1801
  const fields = await this.newFields(at, owner && { uid: owner.uid, gid: owner.gid }, false);
1784
- await this.meta.update(at, () => ({
1785
- path: at,
1786
- link: target,
1787
- dir: parent.path,
1788
- ino: inodeOf(at),
1789
- mtimeMs: now,
1790
- ctimeMs: now,
1791
- ...fields
1792
- }));
1802
+ await this.entering(
1803
+ at,
1804
+ () => this.meta.update(at, () => ({
1805
+ path: at,
1806
+ link: target,
1807
+ dir: parent.path,
1808
+ ino: inodeOf(at),
1809
+ mtimeMs: now,
1810
+ ctimeMs: now,
1811
+ ...fields
1812
+ }))
1813
+ );
1793
1814
  this.touch(parent.path);
1794
1815
  }
1795
1816
  async realpath(path, follow2) {
@@ -7131,8 +7152,12 @@ function closeAll(entry) {
7131
7152
 
7132
7153
  // src/commands.ts
7133
7154
  var NAME2 = /^[A-Za-z0-9._+-]+$/;
7155
+ var COMMAND_NAME = /^[^/\s\p{Cc}]+$/u;
7134
7156
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
7135
7157
  var PACKAGE = /\$\{package\}/g;
7158
+ function commandName(name2) {
7159
+ return COMMAND_NAME.test(name2) && name2.isWellFormed() && name2 !== "." && name2 !== "..";
7160
+ }
7136
7161
  function inside(pkg, rel2) {
7137
7162
  if (typeof rel2 !== "string" || rel2 === "") return void 0;
7138
7163
  const clean = rel2.replace(/^\.\//, "");
@@ -7222,7 +7247,7 @@ function commandsOf(pkg, manifest) {
7222
7247
  const shared = envOf(pkg, slicc.env);
7223
7248
  const out = [];
7224
7249
  for (const [name2, raw] of Object.entries(entries)) {
7225
- if (!NAME2.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
7250
+ if (!commandName(name2) || !raw || typeof raw !== "object") {
7226
7251
  continue;
7227
7252
  }
7228
7253
  const command = commandOf(pkg, name2, raw, packageAbi, shared);
@@ -7274,18 +7299,86 @@ async function packagesIn(fs, roots) {
7274
7299
  dirs.push(...await packages(fs, root));
7275
7300
  return dirs;
7276
7301
  }
7277
- async function scanCommands(fs, modules) {
7278
- const found = /* @__PURE__ */ new Map();
7279
- for (const pkg of await packagesIn(fs, modules)) {
7280
- let manifest;
7281
- try {
7282
- manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
7283
- } catch {
7284
- continue;
7302
+ function binEntries(manifest) {
7303
+ const { bin, name: name2 } = manifest;
7304
+ if (typeof bin === "string") {
7305
+ return typeof name2 === "string" ? [[name2.slice(name2.lastIndexOf("/") + 1), bin]] : [];
7306
+ }
7307
+ return bin && typeof bin === "object" ? Object.entries(bin) : [];
7308
+ }
7309
+ var MAX_HOPS = 40;
7310
+ function physicalResolver(fs) {
7311
+ const known = /* @__PURE__ */ new Map();
7312
+ const pending = /* @__PURE__ */ new Set();
7313
+ let hops = 0;
7314
+ const step = async (path) => {
7315
+ if (path === "/") return { path, file: false };
7316
+ const slash = path.lastIndexOf("/");
7317
+ const parent = await resolve2(path.slice(0, slash) || "/");
7318
+ if (parent === void 0) return void 0;
7319
+ const at = resolvePath(parent.path, path.slice(slash + 1));
7320
+ const st = await fs.lstat(at).catch(() => void 0);
7321
+ if (!st?.isSymbolicLink) return st ? { path: at, file: st.isFile === true } : void 0;
7322
+ const target = await fs.readlink(at).catch(() => void 0);
7323
+ if (target === void 0 || ++hops > MAX_HOPS) return void 0;
7324
+ return resolve2(resolvePath(parent.path, target));
7325
+ };
7326
+ const resolve2 = (path) => {
7327
+ if (pending.has(path)) return Promise.resolve(void 0);
7328
+ let found = known.get(path);
7329
+ if (!found) {
7330
+ pending.add(path);
7331
+ found = step(path).finally(() => pending.delete(path));
7332
+ known.set(path, found);
7285
7333
  }
7286
- for (const command of commandsOf(pkg, manifest)) {
7287
- if (!found.has(command.name))
7288
- found.set(command.name, await withPackagePaths(fs, pkg, command));
7334
+ return found;
7335
+ };
7336
+ return (path) => {
7337
+ hops = 0;
7338
+ return resolve2(path);
7339
+ };
7340
+ }
7341
+ async function binCommandsOf(physical, pkg, manifest, taken) {
7342
+ const entries = binEntries(manifest);
7343
+ if (entries.length === 0) return [];
7344
+ const home = (await physical(pkg))?.path;
7345
+ const out = [];
7346
+ for (const [name2, rel2] of entries) {
7347
+ if (taken.has(name2) || !commandName(name2)) continue;
7348
+ const script = inside(pkg, rel2);
7349
+ if (!script) continue;
7350
+ const real = await physical(script);
7351
+ if (!real?.file || !real.path.startsWith(`${home}/`)) continue;
7352
+ out.push({
7353
+ name: name2,
7354
+ abi: "emscripten",
7355
+ glue: script,
7356
+ wasm: script,
7357
+ argv0: name2,
7358
+ script,
7359
+ bin: true
7360
+ });
7361
+ }
7362
+ return out;
7363
+ }
7364
+ async function scanCommands(fs, modules, binsFrom = Number.POSITIVE_INFINITY) {
7365
+ const found = /* @__PURE__ */ new Map();
7366
+ const physical = physicalResolver(fs);
7367
+ for (const [index, root] of (typeof modules === "string" ? [modules] : modules).entries()) {
7368
+ for (const pkg of await packages(fs, root)) {
7369
+ let manifest;
7370
+ try {
7371
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
7372
+ } catch {
7373
+ continue;
7374
+ }
7375
+ const own = commandsOf(pkg, manifest);
7376
+ const named = new Set(own.map((command) => command.name));
7377
+ const bins = index >= binsFrom ? await binCommandsOf(physical, pkg, manifest, named) : [];
7378
+ for (const command of [...own, ...bins]) {
7379
+ if (!found.has(command.name))
7380
+ found.set(command.name, await withPackagePaths(fs, pkg, command));
7381
+ }
7289
7382
  }
7290
7383
  }
7291
7384
  if (!found.has("sh") && found.has("bash")) {
@@ -7744,7 +7837,7 @@ var FreshModules = class {
7744
7837
  };
7745
7838
 
7746
7839
  // src/fs/physical-path.ts
7747
- var MAX_HOPS = 40;
7840
+ var MAX_HOPS2 = 40;
7748
7841
  function start(path) {
7749
7842
  return { done: [], todo: path.split("/").filter((p) => p !== ""), hops: 0 };
7750
7843
  }
@@ -7769,7 +7862,7 @@ function rest(walk2, part) {
7769
7862
  return lexical(`/${[...walk2.done, part, ...walk2.todo].join("/")}`);
7770
7863
  }
7771
7864
  function follow(walk2, target) {
7772
- if (++walk2.hops > MAX_HOPS) return false;
7865
+ if (++walk2.hops > MAX_HOPS2) return false;
7773
7866
  if (target.startsWith("/")) walk2.done.length = 0;
7774
7867
  walk2.todo = [...target.split("/").filter((p) => p !== ""), ...walk2.todo];
7775
7868
  return true;
@@ -9975,7 +10068,7 @@ async function dispatchOne(entry, req) {
9975
10068
  }
9976
10069
 
9977
10070
  // src/realm/sync-sab-wire.ts
9978
- var SAB_HEADER_I32 = 16;
10071
+ var SAB_HEADER_I32 = 18;
9979
10072
  var SAB_CLOCK_OFFSET = SAB_HEADER_I32 * 4;
9980
10073
  var SAB_CLOCK_I64 = 3;
9981
10074
  var SAB_HEADER_BYTES = SAB_CLOCK_OFFSET + SAB_CLOCK_I64 * 8;
@@ -9995,6 +10088,7 @@ var SAB_I_OFFSET_HI = 12;
9995
10088
  var SAB_I_KILLED = 13;
9996
10089
  var SAB_I_ASYNC = 14;
9997
10090
  var SAB_I_STOP = 15;
10091
+ var SAB_I_BLOCKED = 16;
9998
10092
  var U32 = 2 ** 32;
9999
10093
  function storeU53(header3, lo, hi, value) {
10000
10094
  Atomics.store(header3, hi, Math.floor(value / U32));
@@ -10520,12 +10614,32 @@ function mountTable(mounts = []) {
10520
10614
  `;
10521
10615
  }
10522
10616
 
10617
+ // src/version.ts
10618
+ var KERNEL_VERSION = true ? "1.88.0" : "0.0.0-dev";
10619
+ var OS_RELEASE = "6.0.0-slicc";
10620
+ function procVersion() {
10621
+ return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
10622
+ `;
10623
+ }
10624
+
10523
10625
  // src/process/procfs.ts
10524
10626
  var HZ = 100;
10525
10627
  var KIB = 1024;
10526
10628
  var FALLBACK_MEMORY = 4 * KIB * KIB * KIB;
10527
10629
  var PID_FILES = ["cmdline", "comm", "stat", "statm", "status"];
10528
- var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime"];
10630
+ var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime", "version"];
10631
+ var SYS_TREE = {
10632
+ kernel: { osrelease: `${OS_RELEASE}
10633
+ `, ostype: "Linux\n" }
10634
+ };
10635
+ function sysEntry(parts2) {
10636
+ let at = SYS_TREE;
10637
+ for (const part of parts2) {
10638
+ if (typeof at !== "object" || !Object.hasOwn(at, part)) return void 0;
10639
+ at = at[part];
10640
+ }
10641
+ return at;
10642
+ }
10529
10643
  function commOf(info) {
10530
10644
  const name2 = info.argv[0] ?? "";
10531
10645
  return name2.slice(name2.lastIndexOf("/") + 1).slice(0, 15);
@@ -10613,7 +10727,7 @@ function pidStatus(info) {
10613
10727
  "SigQ: 0/0",
10614
10728
  "SigPnd: 0000000000000000",
10615
10729
  "ShdPnd: 0000000000000000",
10616
- "SigBlk: 0000000000000000",
10730
+ `SigBlk: ${sigMask(info.sigBlk)}`,
10617
10731
  `SigIgn: ${sigMask(info.sigIgn)}`,
10618
10732
  `SigCgt: ${sigMask(info.sigCgt)}`,
10619
10733
  ""
@@ -10649,6 +10763,7 @@ function memoryInfo(source, used = 0) {
10649
10763
  ].map(([key, value]) => `${`${key}:`.padEnd(16)}${value.padStart(12)}`).concat("").join("\n");
10650
10764
  }
10651
10765
  function systemFile(name2, listing, now, memory) {
10766
+ if (name2 === "version") return procVersion();
10652
10767
  const up = Math.max(0, now - listing.boot) / 1e3;
10653
10768
  if (name2 === "uptime") return `${up.toFixed(2)} ${up.toFixed(2)}
10654
10769
  `;
@@ -10739,11 +10854,17 @@ var ProcTree = class {
10739
10854
  names: () => [
10740
10855
  ...SYSTEM_NAMES,
10741
10856
  "mounts",
10857
+ "sys",
10742
10858
  "self",
10743
10859
  ...this.listing().processes.map((p) => String(p.pid))
10744
10860
  ]
10745
10861
  };
10746
10862
  }
10863
+ if (top === "sys") {
10864
+ const entry = sysEntry(parts2.slice(1));
10865
+ if (typeof entry === "string") return { kind: "file", content: () => entry };
10866
+ return entry && { kind: "dir", names: () => Object.keys(entry) };
10867
+ }
10747
10868
  if (top === "self") {
10748
10869
  return leaf === void 0 ? { kind: "link", target: String(this.pid) } : void 0;
10749
10870
  }
@@ -10821,7 +10942,7 @@ var ProcTree = class {
10821
10942
  }
10822
10943
  }
10823
10944
  };
10824
- var MAX_HOPS2 = 40;
10945
+ var MAX_HOPS3 = 40;
10825
10946
  function* procWalk(path, follow2) {
10826
10947
  let done4 = [];
10827
10948
  let todo = path.split("/").filter((part) => part !== "");
@@ -10840,7 +10961,7 @@ function* procWalk(path, follow2) {
10840
10961
  if (!follow2 && todo.length === 0) return void 0;
10841
10962
  const target = yield { at, check: todo.find((next2) => next2 !== ".") === ".." };
10842
10963
  if (typeof target === "string") {
10843
- if (++hops > MAX_HOPS2) return void 0;
10964
+ if (++hops > MAX_HOPS3) return void 0;
10844
10965
  [done4, todo] = throughLink(target, done4, todo);
10845
10966
  } else {
10846
10967
  broken ??= refusalOf(target);
@@ -12399,11 +12520,12 @@ var VfsNodes = class {
12399
12520
  byId = /* @__PURE__ */ new Map();
12400
12521
  keepsOnDisk = false;
12401
12522
  fs;
12402
- entries = /* @__PURE__ */ new Map();
12523
+ entries;
12403
12524
  onClosed;
12404
- constructor(fs, onClosed) {
12525
+ constructor(fs, onClosed, entries = /* @__PURE__ */ new Map()) {
12405
12526
  this.fs = fs;
12406
12527
  this.onClosed = onClosed;
12528
+ this.entries = entries;
12407
12529
  }
12408
12530
  async onEntry(path, op) {
12409
12531
  const key = await this.entryKey(path);
@@ -12893,6 +13015,7 @@ var HOSTS_ONLY = new Resolver({ routes: new Routes() });
12893
13015
  var WasmProcess = class {
12894
13016
  exited = false;
12895
13017
  children;
13018
+ deathSignals = /* @__PURE__ */ new Map();
12896
13019
  caught = 0;
12897
13020
  ignored = 0;
12898
13021
  inherited = 0;
@@ -12927,7 +13050,10 @@ var WasmProcess = class {
12927
13050
  this.nodes = options.nodes ?? new VfsNodes(options.fs);
12928
13051
  if (!options.nodes) options.openFiles?.add(this.nodes);
12929
13052
  this.children.onChildState = () => this.signal(SIG.CHLD);
12930
- this.children.onReap = options.onReap;
13053
+ this.children.onReap = (pid2) => {
13054
+ this.deathSignals.delete(pid2);
13055
+ options.onReap?.(pid2);
13056
+ };
12931
13057
  }
12932
13058
  ignores(bit) {
12933
13059
  return (this.signalMasks().ignored & bit) !== 0;
@@ -12955,6 +13081,10 @@ var WasmProcess = class {
12955
13081
  if (sig === SIG.STOP) return this.stop(sig);
12956
13082
  const bit = sigbit(sig);
12957
13083
  if (this.ignores(bit)) return "ignore";
13084
+ if (((this.options.blockedBits?.() ?? 0) & bit) !== 0) {
13085
+ this.options.onPending?.(sig);
13086
+ return "deliver";
13087
+ }
12958
13088
  if (!(this.caught & bit)) {
12959
13089
  const action = defaultAction(sig);
12960
13090
  return action === "stop" ? this.stop(sig) : action;
@@ -12971,16 +13101,27 @@ var WasmProcess = class {
12971
13101
  return "deliver";
12972
13102
  }
12973
13103
  async spawn(req) {
12974
- const { file, argv, argv0, env, cwd, stdio, inherit: inherit2, exec, pgid, session } = req;
13104
+ const { file, argv, argv0, env, cwd, stdio, inherit: inherit2, exec, pgid, session, deathSignal } = req;
13105
+ if (deathSignal !== void 0 && (exec || !isSignal(deathSignal))) {
13106
+ throw new KernelError("EINVAL");
13107
+ }
12975
13108
  const group = { ...pgid !== void 0 ? { pgid } : {}, ...session ? { session } : {} };
12976
13109
  const named = argv0 !== void 0 ? { argv0 } : {};
12977
13110
  const spawned = { file, argv, ...named, env, cwd, ...group, ...exec ? { exec } : {} };
12978
13111
  const pid = await this.children.spawn(spawned, stdio, inherit2);
13112
+ if (deathSignal !== void 0) this.onDeath(pgid === 0 || session ? -pid : pid, deathSignal);
12979
13113
  if (!exec) return pid;
12980
13114
  if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
12981
13115
  else this.adoptExec(pid);
12982
13116
  return pid;
12983
13117
  }
13118
+ onDeath(target, sig) {
13119
+ if (this.exited) this.sendDeath(target, sig);
13120
+ else this.deathSignals.set(target, sig);
13121
+ }
13122
+ sendDeath(target, sig) {
13123
+ void Promise.resolve(this.options.kill?.(target, sig)).catch(() => void 0);
13124
+ }
12984
13125
  async killSelf(sig, target, probe) {
12985
13126
  if (!this.options.exitSignal) throw new KernelError("ENOSYS");
12986
13127
  if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
@@ -13879,6 +14020,8 @@ var WasmProcess = class {
13879
14020
  this.interrupt.abort();
13880
14021
  this.options.locks?.release(this.pid);
13881
14022
  this.asyncOps.close();
14023
+ for (const [target, sig] of this.deathSignals) this.sendDeath(target, sig);
14024
+ this.deathSignals.clear();
13882
14025
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
13883
14026
  await this.stampClipboards();
13884
14027
  await this.fds.closeAll();
@@ -13958,19 +14101,25 @@ var Dying = class {
13958
14101
  }
13959
14102
  };
13960
14103
  function killThread(thread, caught, sig) {
13961
- if (!thread || (caught & sigbit(sig)) === 0) return false;
14104
+ if (!thread) return false;
14105
+ const held = (Atomics.load(thread.header, SAB_I_BLOCKED) & sigbit(sig)) !== 0;
14106
+ if (!held && (caught & sigbit(sig)) === 0) return false;
13962
14107
  Atomics.or(thread.header, SAB_I_SIGNALS, sigbit(sig));
14108
+ if (held) return true;
13963
14109
  Atomics.notify(thread.header, SAB_I_SIGNALS);
13964
14110
  const blocked = thread.interrupt;
13965
14111
  thread.interrupt = new AbortController();
13966
14112
  blocked.abort();
13967
14113
  return true;
13968
14114
  }
14115
+ function unblocked(header3) {
14116
+ return Atomics.load(header3, SAB_I_SIGNALS) & ~Atomics.load(header3, SAB_I_BLOCKED);
14117
+ }
13969
14118
  function threadSignalsOf(sab) {
13970
14119
  return { header: new Int32Array(sab, 0, SAB_HEADER_I32), interrupt: new AbortController() };
13971
14120
  }
13972
14121
  function threadCancel(thread) {
13973
- return Atomics.load(thread.header, SAB_I_SIGNALS) !== 0 ? AbortSignal.abort() : thread.interrupt.signal;
14122
+ return unblocked(thread.header) !== 0 ? AbortSignal.abort() : thread.interrupt.signal;
13974
14123
  }
13975
14124
  function notifiers(header3) {
13976
14125
  return {
@@ -14095,15 +14244,16 @@ function processOptions(opts, header3, hooks, created3) {
14095
14244
  resolver: opts.resolver,
14096
14245
  ...notifiers(header3),
14097
14246
  onTimer: (which) => void Atomics.or(header3, SAB_I_TIMERS, 1 << which),
14098
- hasPending: () => Atomics.load(header3, SAB_I_SIGNALS) !== 0 || Atomics.load(header3, SAB_I_TIMERS) !== 0,
14247
+ hasPending: () => unblocked(header3) !== 0 || Atomics.load(header3, SAB_I_TIMERS) !== 0,
14099
14248
  raise: hooks.raise,
14100
14249
  threadKill: hooks.threadKill,
14101
14250
  ...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
14102
14251
  ...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
14103
- ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
14252
+ ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {},
14253
+ ...opts.program.abi === "wasi" ? { blockedBits: () => Atomics.load(header3, SAB_I_BLOCKED) } : {}
14104
14254
  };
14105
14255
  }
14106
- function threadInit(opts, program, sab, cred, thread) {
14256
+ function threadInit(opts, program, sab, cred, thread, main) {
14107
14257
  return {
14108
14258
  type: WASM_THREAD_INIT,
14109
14259
  pid: opts.shownPid ?? opts.pid,
@@ -14116,12 +14266,15 @@ function threadInit(opts, program, sab, cred, thread) {
14116
14266
  cred: copyCred(cred),
14117
14267
  owners: opts.owners === true,
14118
14268
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
14119
- thread
14269
+ thread,
14270
+ signals: main
14120
14271
  };
14121
14272
  }
14122
14273
  function spawnWasmProcess(opts) {
14123
14274
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
14124
14275
  const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
14276
+ if (opts.blocked && opts.program.abi === "wasi")
14277
+ Atomics.store(header3, SAB_I_BLOCKED, opts.blocked);
14125
14278
  const threadSignals = /* @__PURE__ */ new Map();
14126
14279
  const created3 = new Created();
14127
14280
  const process = new WasmProcess(
@@ -14222,6 +14375,7 @@ function spawnWasmProcess(opts) {
14222
14375
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
14223
14376
  const tw = opts.createWorker();
14224
14377
  const tsignals = threadSignalsOf(tsab);
14378
+ if (thread.blocked) Atomics.store(tsignals.header, SAB_I_BLOCKED, thread.blocked);
14225
14379
  threadSignals.set(thread.tid, tsignals);
14226
14380
  const tdispatch = (req) => dying.serving(
14227
14381
  () => isWasmSyscall(req) ? process.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
@@ -14243,7 +14397,7 @@ function spawnWasmProcess(opts) {
14243
14397
  tresponder.dispose();
14244
14398
  tw.terminate();
14245
14399
  });
14246
- tw.postMessage(threadInit(opts, program, tsab, process.cred, thread));
14400
+ tw.postMessage(threadInit(opts, program, tsab, process.cred, thread, sab));
14247
14401
  };
14248
14402
  worker.postMessage(
14249
14403
  processInit(opts, sab, process.umask, copyCred(process.cred), lane?.lane),
@@ -14265,6 +14419,7 @@ function spawnWasmProcess(opts) {
14265
14419
  onState: (listener) => process.onState(listener),
14266
14420
  memory: () => Atomics.load(header3, SAB_I_MEMORY) * 65536,
14267
14421
  ignoredSignals: (fork) => process.inheritable(fork),
14422
+ blockedSignals: () => opts.program.abi === "wasi" ? Atomics.load(header3, SAB_I_BLOCKED) : 0,
14268
14423
  signalMasks: () => process.signalMasks(),
14269
14424
  umask: () => process.umask,
14270
14425
  cred: () => copyCred(process.cred),
@@ -17387,15 +17542,25 @@ function shebang(head) {
17387
17542
  if (head[0] !== 35 || head[1] !== 33) return void 0;
17388
17543
  return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
17389
17544
  }
17390
- async function rootOnly(fs, path) {
17545
+ async function rootOnly(fs, path, dir = false) {
17391
17546
  const names = chain(path);
17392
17547
  for (const [i, at] of names.entries()) {
17393
17548
  const st = await fs.lstat(at).catch(() => void 0);
17394
- const kind = i === names.length - 1 ? st?.isFile : st?.isDirectory;
17549
+ const kind = i === names.length - 1 && !dir ? st?.isFile : st?.isDirectory;
17395
17550
  if (!kind || st?.uid !== 0 || (st.mode & 18) !== 0) return false;
17396
17551
  }
17397
17552
  return true;
17398
17553
  }
17554
+ function appendedRoots(given) {
17555
+ const roots = given?.append ?? [];
17556
+ if (!Array.isArray(roots)) throw new TypeError("commands.append must be an array of paths");
17557
+ for (const root of roots) {
17558
+ if (typeof root !== "string" || !root.startsWith("/") || normalizePath(root) !== root) {
17559
+ throw new TypeError(`commands.append: not an absolute, normalised path: ${String(root)}`);
17560
+ }
17561
+ }
17562
+ return [...roots];
17563
+ }
17399
17564
  function plainEnv(words2) {
17400
17565
  const next2 = words2[1];
17401
17566
  if (baseName(words2[0] ?? "") !== "env" || next2 === void 0) return false;
@@ -17488,6 +17653,8 @@ var Launcher = class {
17488
17653
  base;
17489
17654
  createWorker;
17490
17655
  modulesDir;
17656
+ appended;
17657
+ reportedRoots = /* @__PURE__ */ new Set();
17491
17658
  env;
17492
17659
  catalogs = /* @__PURE__ */ new Map();
17493
17660
  ownCatalogs = /* @__PURE__ */ new Map();
@@ -17594,6 +17761,7 @@ var Launcher = class {
17594
17761
  this.processMounts = options.processMounts ?? true;
17595
17762
  this.pnpmHome = options.env?.PNPM_HOME ?? SYSTEM_PNPM_HOME;
17596
17763
  this.modulesDir = options.modules ?? "/node_modules";
17764
+ this.appended = appendedRoots(options.commands);
17597
17765
  this.cdp = new CdpHosts(options.cdp);
17598
17766
  const hostname = options.hostname ?? DEFAULT_HOSTNAME;
17599
17767
  if (!isHostname(hostname)) throw new Error(`not a host name: ${String(hostname)}`);
@@ -17643,9 +17811,9 @@ var Launcher = class {
17643
17811
  this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
17644
17812
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
17645
17813
  this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
17646
- options.fs.protect?.(this.prefixes);
17647
17814
  const integrity = options.protect?.integrity;
17648
17815
  integrityOptions(integrity);
17816
+ options.fs.protect?.(this.prefixes, Boolean(integrity));
17649
17817
  if (integrity && this.prefixes.length > 0) {
17650
17818
  this.integrity = new Integrity(
17651
17819
  this.prefixes,
@@ -17668,7 +17836,7 @@ var Launcher = class {
17668
17836
  });
17669
17837
  void this.unlinked.sweep("/");
17670
17838
  this.watchers.watch(
17671
- [this.modulesDir, this.pnpmHome],
17839
+ [this.modulesDir, this.pnpmHome, ...this.appended],
17672
17840
  { recursive: true },
17673
17841
  () => this.resetCatalogs()
17674
17842
  );
@@ -17699,7 +17867,10 @@ var Launcher = class {
17699
17867
  commands(scope = "") {
17700
17868
  let found = this.catalogs.get(scope);
17701
17869
  if (!found) {
17702
- found = this.roots(scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
17870
+ found = this.trustedAppended().then(async (appended) => {
17871
+ const roots = await this.roots(scope, appended);
17872
+ return scanCommands(this.scanned, roots, roots.length - appended.length);
17873
+ }).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
17703
17874
  this.catalogs.set(scope, found);
17704
17875
  }
17705
17876
  return found;
@@ -17726,10 +17897,37 @@ var Launcher = class {
17726
17897
  }
17727
17898
  return found;
17728
17899
  }
17729
- async roots(scope = "") {
17730
- const system = [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
17900
+ async roots(scope = "", appended) {
17901
+ const system = [
17902
+ this.modulesDir,
17903
+ ...await pnpmGlobalRoots(this.base, this.pnpmHome),
17904
+ ...appended ?? await this.trustedAppended()
17905
+ ];
17731
17906
  return scope ? [...await pnpmGlobalRoots(this.base, scope), ...system] : system;
17732
17907
  }
17908
+ async rootOnlyPath(root) {
17909
+ for (const at of chain(root)) {
17910
+ const real = await physicalPathAsync(this.base, at);
17911
+ if (!await rootOnly(this.base, real, true)) return false;
17912
+ }
17913
+ return true;
17914
+ }
17915
+ async trustedAppended() {
17916
+ if (this.appended.length === 0 || this.usersMode === "off") return this.appended;
17917
+ const trusted = [];
17918
+ for (const root of this.appended) {
17919
+ const real = await physicalPathAsync(this.base, root);
17920
+ if (await this.rootOnlyPath(root)) trusted.push(root);
17921
+ else if (this.usersMode === "report" && await this.base.exists(real)) {
17922
+ if (!this.reportedRoots.has(root)) {
17923
+ this.reportedRoots.add(root);
17924
+ this.dac?.denied({ pid: 0, uid: 0, op: "commands", path: root, errno: "EACCES" });
17925
+ }
17926
+ trusted.push(root);
17927
+ }
17928
+ }
17929
+ return trusted;
17930
+ }
17733
17931
  resetCatalogs() {
17734
17932
  this.catalogs.clear();
17735
17933
  this.ownCatalogs.clear();
@@ -17835,14 +18033,22 @@ var Launcher = class {
17835
18033
  const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
17836
18034
  const via = [interp, ...inner?.via ?? []];
17837
18035
  const sources = [script, ...inner?.sources ?? []];
17838
- return inner && { ...inner, target: withScriptEnv(inner.target, command), via, sources };
18036
+ const bins = [...command?.bin ? [script] : [], ...inner?.bins ?? []];
18037
+ return inner && {
18038
+ ...inner,
18039
+ target: withScriptEnv(inner.target, command),
18040
+ via,
18041
+ sources,
18042
+ ...bins.length > 0 ? { bins } : {}
18043
+ };
17839
18044
  }
17840
18045
  const target = withScriptEnv(found, command);
17841
18046
  return {
17842
18047
  target,
17843
18048
  args: [...target.prefix ?? [], ...passed],
17844
18049
  via: [interp],
17845
- sources: [script]
18050
+ sources: [script],
18051
+ ...command?.bin ? { bins: [script] } : {}
17846
18052
  };
17847
18053
  }
17848
18054
  async plan(file, argv, cwd, scope, argv0) {
@@ -17868,6 +18074,7 @@ var Launcher = class {
17868
18074
  const want = i < via.length - 1 ? R_OK | X_OK : X_OK;
17869
18075
  await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
17870
18076
  }
18077
+ for (const bin of planned?.bins ?? []) await need(g, "exec", bin, R_OK | X_OK);
17871
18078
  }
17872
18079
  async checkedPlan(req, { ppid, cred, scope }, fds) {
17873
18080
  const planned = await this.plan(req.file, req.argv, req.cwd, scope, req.argv0);
@@ -18166,6 +18373,7 @@ var Launcher = class {
18166
18373
  },
18167
18374
  settling: () => this.settling.isSettling(pid),
18168
18375
  ...req.ignored ? { ignored: req.ignored } : {},
18376
+ ...req.blocked ? { blocked: req.blocked } : {},
18169
18377
  ...req.umask !== void 0 ? { umask: req.umask } : {},
18170
18378
  cred: req.cred,
18171
18379
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
@@ -18440,6 +18648,7 @@ var Launcher = class {
18440
18648
  report,
18441
18649
  ppid,
18442
18650
  ignored: this.ignoredBy(ppid),
18651
+ blocked: this.blockedBy(ppid),
18443
18652
  umask: this.umaskOf(ppid),
18444
18653
  cred,
18445
18654
  tag: this.tagOf(ppid),
@@ -18501,6 +18710,7 @@ var Launcher = class {
18501
18710
  ppid,
18502
18711
  fork: state,
18503
18712
  ignored: this.ignoredBy(ppid, true),
18713
+ blocked: this.blockedBy(ppid),
18504
18714
  umask: this.umaskOf(ppid),
18505
18715
  cred: this.credOf(ppid),
18506
18716
  tag: this.tagOf(ppid)
@@ -18552,11 +18762,16 @@ var Launcher = class {
18552
18762
  }
18553
18763
  signalMasksOf(pid) {
18554
18764
  const masks = this.processes.get(pid)?.signalMasks?.();
18555
- return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught } : {};
18765
+ const blocked = this.blockedBy(pid);
18766
+ const sigBlk = blocked ? { sigBlk: blocked } : {};
18767
+ return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught, ...sigBlk } : sigBlk;
18556
18768
  }
18557
18769
  controlsJobs(pid) {
18558
18770
  return this.settling.isSettling(pid) || (this.ignoredBy(pid) & sigbit(SIG.TTOU)) !== 0;
18559
18771
  }
18772
+ blockedBy(pid) {
18773
+ return this.processes.get(pid)?.blockedSignals?.() ?? 0;
18774
+ }
18560
18775
  ignoredBy(pid, fork = false) {
18561
18776
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
18562
18777
  }
@@ -18921,7 +19136,7 @@ var Launcher = class {
18921
19136
  (dir) => this.userCommandDir(dir),
18922
19137
  this.owners
18923
19138
  );
18924
- const nodes = new VfsNodes(fs, (path) => this.released(path));
19139
+ const nodes = new VfsNodes(fs, (path) => this.released(path), this.nodes.entries);
18925
19140
  nodes.keepsOnDisk = true;
18926
19141
  this.openFiles.add(nodes);
18927
19142
  const unlinked = new UnlinkedKeeper(
@@ -19325,6 +19540,7 @@ function serveKernel(port, deps) {
19325
19540
  createWorker: deps.createWorker,
19326
19541
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
19327
19542
  ...req.modules ? { modules: req.modules } : {},
19543
+ ...req.commands ? { commands: req.commands } : {},
19328
19544
  ...req.env ? { env: req.env } : {},
19329
19545
  ...hostOptions(req, port, sealed.hook, clipboard.hook, integrity.lookup),
19330
19546
  ...req.approve ? { approve: approvals.ask } : {},