@ai-ecoverse/slicc-kernel 1.65.2 → 1.66.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.
@@ -1313,7 +1313,7 @@ function portCdpHook(ask, gone) {
1313
1313
  }
1314
1314
 
1315
1315
  // src/client/protocol.ts
1316
- var PROTOCOL = [1, 8];
1316
+ var PROTOCOL = [1, 9];
1317
1317
  var FS_METHODS = [
1318
1318
  "readFile",
1319
1319
  "writeFile",
@@ -1685,6 +1685,64 @@ async function openable(g, op, path, want, create) {
1685
1685
  await need(g, op, path, want, { directory: st?.isDirectory ?? false });
1686
1686
  }
1687
1687
 
1688
+ // src/kernel/cred.ts
1689
+ var ROOT = Object.freeze({
1690
+ ruid: 0,
1691
+ euid: 0,
1692
+ suid: 0,
1693
+ rgid: 0,
1694
+ egid: 0,
1695
+ sgid: 0,
1696
+ groups: [0]
1697
+ });
1698
+ var MAX_ID = 4294967294;
1699
+ var MAX_GROUPS = 65536;
1700
+ var UIDS = ["ruid", "euid", "suid"];
1701
+ var GIDS = ["rgid", "egid", "sgid"];
1702
+ function userCred(uid, gid, groups = [gid]) {
1703
+ return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
1704
+ }
1705
+ function copyCred(cred) {
1706
+ return { ...cred, groups: [...cred.groups] };
1707
+ }
1708
+ function id(value) {
1709
+ if (value === void 0 || value === -1 || value === 4294967295) return void 0;
1710
+ if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
1711
+ throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
1712
+ }
1713
+ return value;
1714
+ }
1715
+ function changeCred(cur, change) {
1716
+ const privileged = cur.euid === 0;
1717
+ const next2 = copyCred(cur);
1718
+ for (const keys of [UIDS, GIDS]) {
1719
+ const own = keys.map((key) => cur[key]);
1720
+ for (const key of keys) {
1721
+ const value = id(change[key]);
1722
+ if (value === void 0) continue;
1723
+ if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
1724
+ next2[key] = value;
1725
+ }
1726
+ }
1727
+ if (change.groups !== void 0) {
1728
+ if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
1729
+ throw fsError("EINVAL", "not a group list");
1730
+ }
1731
+ const groups = change.groups.map((g) => id(g) ?? Number.NaN);
1732
+ if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
1733
+ if (!privileged) throw fsError("EPERM", "setgroups");
1734
+ next2.groups = groups;
1735
+ }
1736
+ return next2;
1737
+ }
1738
+ function maySignal(sender, target) {
1739
+ if (sender.euid === 0) return true;
1740
+ return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
1741
+ }
1742
+ function maySee(viewer, target) {
1743
+ return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
1744
+ }
1745
+
1688
1746
  // src/kernel/pipe.ts
1689
1747
  var PIPE_CAPACITY = 64 * 1024;
1690
1748
  var PipeError = class extends Error {
@@ -1992,9 +2050,9 @@ var FdTable = class _FdTable {
1992
2050
  closesOnExec(fd) {
1993
2051
  return this.cloexec.has(fd);
1994
2052
  }
1995
- setStatusFlags(fd, flags) {
2053
+ setStatusFlags(fd, flags2) {
1996
2054
  this.get(fd);
1997
- this.status.set(fd, flags);
2055
+ this.status.set(fd, flags2);
1998
2056
  }
1999
2057
  statusFlags(fd) {
2000
2058
  return this.status.get(fd);
@@ -2039,7 +2097,7 @@ var FdTable = class _FdTable {
2039
2097
  const child = new _FdTable();
2040
2098
  for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
2041
2099
  for (const fd of this.cloexec) child.cloexec.add(fd);
2042
- for (const [fd, flags] of this.status) child.status.set(fd, flags);
2100
+ for (const [fd, flags2] of this.status) child.status.set(fd, flags2);
2043
2101
  return child;
2044
2102
  }
2045
2103
  async closeAll() {
@@ -2506,9 +2564,9 @@ var KernelSocket = class _KernelSocket {
2506
2564
  case SO_TYPE:
2507
2565
  return SOCK_STREAM;
2508
2566
  case SO_ERROR: {
2509
- const failed2 = this.failed;
2567
+ const failed3 = this.failed;
2510
2568
  this.failed = void 0;
2511
- return failed2 ? wasiErrno(failed2) : 0;
2569
+ return failed3 ? wasiErrno(failed3) : 0;
2512
2570
  }
2513
2571
  case SO_ACCEPTCONN:
2514
2572
  return this.state === "listening" ? 1 : 0;
@@ -3015,64 +3073,6 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
3015
3073
  };
3016
3074
  }
3017
3075
 
3018
- // src/kernel/cred.ts
3019
- var ROOT = Object.freeze({
3020
- ruid: 0,
3021
- euid: 0,
3022
- suid: 0,
3023
- rgid: 0,
3024
- egid: 0,
3025
- sgid: 0,
3026
- groups: [0]
3027
- });
3028
- var MAX_ID = 4294967294;
3029
- var MAX_GROUPS = 65536;
3030
- var UIDS = ["ruid", "euid", "suid"];
3031
- var GIDS = ["rgid", "egid", "sgid"];
3032
- function userCred(uid, gid, groups = [gid]) {
3033
- return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
3034
- }
3035
- function copyCred(cred) {
3036
- return { ...cred, groups: [...cred.groups] };
3037
- }
3038
- function id(value) {
3039
- if (value === void 0 || value === -1 || value === 4294967295) return void 0;
3040
- if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
3041
- throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
3042
- }
3043
- return value;
3044
- }
3045
- function changeCred(cur, change) {
3046
- const privileged = cur.euid === 0;
3047
- const next2 = copyCred(cur);
3048
- for (const keys of [UIDS, GIDS]) {
3049
- const own = keys.map((key) => cur[key]);
3050
- for (const key of keys) {
3051
- const value = id(change[key]);
3052
- if (value === void 0) continue;
3053
- if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
3054
- next2[key] = value;
3055
- }
3056
- }
3057
- if (change.groups !== void 0) {
3058
- if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
3059
- throw fsError("EINVAL", "not a group list");
3060
- }
3061
- const groups = change.groups.map((g) => id(g) ?? Number.NaN);
3062
- if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
3063
- if (!privileged) throw fsError("EPERM", "setgroups");
3064
- next2.groups = groups;
3065
- }
3066
- return next2;
3067
- }
3068
- function maySignal(sender, target) {
3069
- if (sender.euid === 0) return true;
3070
- return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
3071
- }
3072
- function maySee(viewer, target) {
3073
- return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
3074
- }
3075
-
3076
3076
  // src/users.ts
3077
3077
  var PASSWD = "/etc/passwd";
3078
3078
  var GROUP = "/etc/group";
@@ -3537,6 +3537,12 @@ async function runAs(l, self, requested) {
3537
3537
  }
3538
3538
  return target;
3539
3539
  }
3540
+ function clientCred(self) {
3541
+ return self ? credOf(self) : copyCred(ROOT);
3542
+ }
3543
+ async function runTagged(l, self, requested, tag) {
3544
+ return { user: await runAs(l, self, requested), tag };
3545
+ }
3540
3546
  function processes(l, self) {
3541
3547
  const viewer = credOf(self);
3542
3548
  return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
@@ -3671,7 +3677,7 @@ function serveClient(port, host) {
3671
3677
  async function terminal(req, l) {
3672
3678
  const session = await l.openTerminal(req.argv, {
3673
3679
  ...req.options,
3674
- user: await runAs(l, self, req.options.user),
3680
+ ...await runTagged(l, self, req.options.user, host.tag),
3675
3681
  onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
3676
3682
  });
3677
3683
  if (state === "closed") {
@@ -3693,7 +3699,7 @@ function serveClient(port, host) {
3693
3699
  let leader;
3694
3700
  const result = await l.run(req.argv, {
3695
3701
  ...req.options,
3696
- user: await runAs(l, self, req.options.user),
3702
+ ...await runTagged(l, self, req.options.user, host.tag),
3697
3703
  collect: false,
3698
3704
  onStdout: out(1),
3699
3705
  onStderr: out(2),
@@ -3734,9 +3740,9 @@ function serveClient(port, host) {
3734
3740
  case "unwatch":
3735
3741
  return watchOp(l, req, watches, send, guardFor(l, self));
3736
3742
  case "mount":
3737
- return { result: await l.mount(req.spec) };
3743
+ return { result: await l.mountAs(clientCred(self), req.spec) };
3738
3744
  case "umount":
3739
- l.umount(req.target);
3745
+ await l.umountAs(clientCred(self), req.target);
3740
3746
  return { result: true };
3741
3747
  case "mounts":
3742
3748
  return { result: l.mounts.list() };
@@ -4947,8 +4953,8 @@ function ipv6(host) {
4947
4953
  const head = groups(halves[0]);
4948
4954
  const back = halves.length === 2 ? groups(halves[1]) : [];
4949
4955
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
4950
- const words = [...head, ...back].map((g) => Number.parseInt(g, 16));
4951
- const missing3 = 8 - words.length - tail.length;
4956
+ const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
4957
+ const missing3 = 8 - words2.length - tail.length;
4952
4958
  if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
4953
4959
  const all = [
4954
4960
  ...head.map((g) => Number.parseInt(g, 16)),
@@ -6352,9 +6358,8 @@ async function scanFilesystems(fs, modules) {
6352
6358
  }
6353
6359
  var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
6354
6360
  async function scanBinfmts(fs, modules, warn = () => {
6355
- }) {
6361
+ }, owners = /* @__PURE__ */ new Map()) {
6356
6362
  const found = /* @__PURE__ */ new Map();
6357
- const owners = /* @__PURE__ */ new Map();
6358
6363
  for (const pkg of await packagesIn(fs, modules)) {
6359
6364
  let manifest;
6360
6365
  try {
@@ -6379,7 +6384,7 @@ async function scanBinfmts(fs, modules, warn = () => {
6379
6384
  }
6380
6385
  return found;
6381
6386
  }
6382
- function binfmtOf(binfmts, path) {
6387
+ function binfmtSuffix(binfmts, path) {
6383
6388
  const name2 = path.slice(path.lastIndexOf("/") + 1);
6384
6389
  let best;
6385
6390
  for (const suffix of binfmts.keys()) {
@@ -6387,6 +6392,10 @@ function binfmtOf(binfmts, path) {
6387
6392
  if (best === void 0 || suffix.length > best.length) best = suffix;
6388
6393
  }
6389
6394
  }
6395
+ return best;
6396
+ }
6397
+ function binfmtOf(binfmts, path) {
6398
+ const best = binfmtSuffix(binfmts, path);
6390
6399
  return best === void 0 ? void 0 : binfmts.get(best);
6391
6400
  }
6392
6401
 
@@ -6963,8 +6972,9 @@ var ChildTable = class {
6963
6972
  if (!this.forker) throw new SpawnError("ENOSYS");
6964
6973
  return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
6965
6974
  }
6966
- async spawn(req, stdio, inherit2 = []) {
6967
- if (!this.spawner) throw new SpawnError("ENOSYS");
6975
+ async spawn(req, stdio, inherit2 = [], grant) {
6976
+ const { spawner } = this;
6977
+ if (!spawner) throw new SpawnError("ENOSYS");
6968
6978
  const fds = new FdTable();
6969
6979
  const captured = /* @__PURE__ */ new Map();
6970
6980
  try {
@@ -6982,7 +6992,7 @@ var ChildTable = class {
6982
6992
  await fds.closeAll();
6983
6993
  throw e;
6984
6994
  }
6985
- return this.track(req, fds, captured, this.spawner);
6995
+ return this.track(req, fds, captured, (r, f) => spawner(r, f, grant));
6986
6996
  }
6987
6997
  async track(req, fds, captured, start2) {
6988
6998
  let handle;
@@ -7029,34 +7039,34 @@ var ChildTable = class {
7029
7039
  }
7030
7040
  return deviceFile("null");
7031
7041
  }
7032
- async wait(pid, nohang, signal, flags = {}) {
7042
+ async wait(pid, nohang, signal, flags2 = {}) {
7033
7043
  let interrupt;
7034
7044
  try {
7035
7045
  for (; ; ) {
7036
- const candidates = this.candidates(pid, flags);
7046
+ const candidates = this.candidates(pid, flags2);
7037
7047
  if (candidates.length === 0) throw new KernelError("ECHILD");
7038
7048
  const done4 = candidates.find(([, child]) => child.code !== void 0);
7039
7049
  if (done4) return this.reap(done4[0], done4[1].code);
7040
- const changed = this.stateReport(candidates, flags);
7050
+ const changed = this.stateReport(candidates, flags2);
7041
7051
  if (changed) return changed;
7042
7052
  if (nohang) return [0, 0];
7043
7053
  interrupt ??= interrupted(signal);
7044
- await this.nextChange(candidates, flags, interrupt.promise);
7054
+ await this.nextChange(candidates, flags2, interrupt.promise);
7045
7055
  }
7046
7056
  } finally {
7047
7057
  interrupt?.done();
7048
7058
  }
7049
7059
  }
7050
- candidates(pid, flags) {
7060
+ candidates(pid, flags2) {
7051
7061
  const all = [...this.children];
7052
7062
  if (pid > 0) return all.filter(([p]) => p === pid);
7053
- const inGroup2 = flags.inGroup;
7063
+ const inGroup2 = flags2.inGroup;
7054
7064
  return pid === -1 || !inGroup2 ? all : all.filter(([p]) => inGroup2(p));
7055
7065
  }
7056
- async nextChange(candidates, flags, interrupt) {
7066
+ async nextChange(candidates, flags2, interrupt) {
7057
7067
  let wake;
7058
7068
  const stateChange = new Promise((resolve) => wake = resolve);
7059
- const watching = flags.untraced || flags.continued;
7069
+ const watching = flags2.untraced || flags2.continued;
7060
7070
  if (watching && wake) this.stateChanged.push(wake);
7061
7071
  try {
7062
7072
  await Promise.race([
@@ -7068,14 +7078,14 @@ var ChildTable = class {
7068
7078
  this.stateChanged = this.stateChanged.filter((w) => w !== wake);
7069
7079
  }
7070
7080
  }
7071
- stateReport(candidates, flags) {
7081
+ stateReport(candidates, flags2) {
7072
7082
  for (const [p, child] of candidates) {
7073
- if (flags.untraced && child.stopReport !== void 0) {
7083
+ if (flags2.untraced && child.stopReport !== void 0) {
7074
7084
  const sig = child.stopReport;
7075
7085
  child.stopReport = void 0;
7076
7086
  return [p, stoppedStatus(sig)];
7077
7087
  }
7078
- if (flags.continued && child.continueReport) {
7088
+ if (flags2.continued && child.continueReport) {
7079
7089
  child.continueReport = false;
7080
7090
  return [p, CONTINUED_STATUS];
7081
7091
  }
@@ -11036,8 +11046,8 @@ function errCode(err) {
11036
11046
  async function targetDir(fs, path) {
11037
11047
  return parentOf4(await realTarget(fs, path).catch(() => path));
11038
11048
  }
11039
- async function refuseReadonly(fs, path, flags, opts = {}) {
11040
- const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
11049
+ async function refuseReadonly(fs, path, flags2, opts = {}) {
11050
+ const writes = (flags2 & O_ACCMODE) !== 0 || opts.truncate === true;
11041
11051
  if (!writes && !opts.create || !fs.stat) return;
11042
11052
  const stat = (p) => fs.stat?.(p).catch((err) => {
11043
11053
  const code = err?.code;
@@ -11252,6 +11262,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11252
11262
  "proc-spawn",
11253
11263
  "proc-wait",
11254
11264
  "proc-captured",
11265
+ "proc-sudo",
11255
11266
  "proc-run",
11256
11267
  "proc-exit",
11257
11268
  "proc-fork",
@@ -11284,6 +11295,12 @@ var MAX_READ = 1024 * 1024;
11284
11295
  var WRITE_ACCESS = 3;
11285
11296
  var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
11286
11297
  var DEFAULT_UMASK = 18;
11298
+ function words(value) {
11299
+ if (!Array.isArray(value) || !value.every((w) => typeof w === "string")) {
11300
+ throw new KernelError("EINVAL");
11301
+ }
11302
+ return [...value];
11303
+ }
11287
11304
  var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
11288
11305
  var HOSTS_ONLY = new Resolver({ routes: new Routes() });
11289
11306
  var WasmProcess = class {
@@ -11476,17 +11493,17 @@ var WasmProcess = class {
11476
11493
  if (restart && this.options.hasPending?.()) throw new KernelError("EINTR");
11477
11494
  }
11478
11495
  async procWait(req, cancelled) {
11479
- const flags = {
11496
+ const flags2 = {
11480
11497
  untraced: req.untraced,
11481
11498
  continued: req.continued,
11482
11499
  inGroup: this.waitGroup(req.pid)
11483
11500
  };
11484
11501
  if (this.owed && !req.nohang) {
11485
- const now = await this.children.wait(req.pid, true, this.interrupt.signal, flags);
11502
+ const now = await this.children.wait(req.pid, true, this.interrupt.signal, flags2);
11486
11503
  if (now[0] !== 0) return now;
11487
11504
  }
11488
11505
  const signal = req.nohang ? this.interrupt.signal : this.blockingSignal(cancelled);
11489
- const waited = await this.children.wait(req.pid, req.nohang, signal, flags);
11506
+ const waited = await this.children.wait(req.pid, req.nohang, signal, flags2);
11490
11507
  return waited;
11491
11508
  }
11492
11509
  blockingSignal(cancelled, owing = true) {
@@ -11818,13 +11835,13 @@ var WasmProcess = class {
11818
11835
  }
11819
11836
  fdInfo(fd) {
11820
11837
  const file = this.fds.get(fd).file;
11821
- const flags = this.fds.statusFlags(fd);
11838
+ const flags2 = this.fds.statusFlags(fd);
11822
11839
  const terminal = (file.tty ?? file.pty?.slave)?.name;
11823
11840
  return {
11824
11841
  tty: file.tty !== void 0,
11825
11842
  kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
11826
11843
  ...file.heldMeta ? { meta: file.heldMeta } : {},
11827
- ...flags !== void 0 ? { flags } : {},
11844
+ ...flags2 !== void 0 ? { flags: flags2 } : {},
11828
11845
  ...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
11829
11846
  ...file.read && !file.write ? { access: "read" } : {},
11830
11847
  ...file.write && !file.read ? { access: "write" } : {},
@@ -12012,7 +12029,27 @@ var WasmProcess = class {
12012
12029
  return { ok: true, kind: "void" };
12013
12030
  case "proc-captured":
12014
12031
  return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
12015
- }
12032
+ case "proc-sudo":
12033
+ return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
12034
+ }
12035
+ }
12036
+ async sudo(req, cancelled) {
12037
+ const { sudo } = this.options;
12038
+ if (!sudo) throw new KernelError("ENOSYS");
12039
+ const call = {
12040
+ op: "proc-sudo",
12041
+ args: words(req.args),
12042
+ cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
12043
+ ...typeof req.term === "string" ? { term: req.term } : {}
12044
+ };
12045
+ const answer = await sudo(call, this.blockingSignal(cancelled));
12046
+ if (!("grant" in answer)) return answer;
12047
+ if (this.dying) throw new KernelError("EINTR");
12048
+ const { file, argv, env, cwd, cred, caller } = answer.grant;
12049
+ const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
12050
+ return {
12051
+ pid: await this.children.spawn({ file, argv, env, cwd }, stdio, [], { cred, caller })
12052
+ };
12016
12053
  }
12017
12054
  async mountSyscall(req, cancelled) {
12018
12055
  const { mount, umount } = this.options;
@@ -12020,9 +12057,9 @@ var WasmProcess = class {
12020
12057
  try {
12021
12058
  if (req.op === "umount") await umount(req.target, req.flags);
12022
12059
  else {
12023
- const { source, target, type, flags, data } = req;
12060
+ const { source, target, type, flags: flags2, data } = req;
12024
12061
  const signal = this.blockingSignal(cancelled);
12025
- await Promise.race([mount({ source, target, type, flags, data }, signal), aborted(signal)]);
12062
+ await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted(signal)]);
12026
12063
  }
12027
12064
  } catch (err) {
12028
12065
  const code = err?.code;
@@ -12165,14 +12202,14 @@ function descId(file) {
12165
12202
  function inheritedFds(fds) {
12166
12203
  return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
12167
12204
  const open = fds.get(fd);
12168
- const flags = fds.statusFlags(fd);
12205
+ const flags2 = fds.statusFlags(fd);
12169
12206
  const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
12170
12207
  const meta = open.file.heldMeta;
12171
12208
  return {
12172
12209
  fd,
12173
12210
  kind,
12174
12211
  ...kind === "device" && meta && "device" in meta ? { device: meta } : {},
12175
- ...flags !== void 0 ? { flags } : {},
12212
+ ...flags2 !== void 0 ? { flags: flags2 } : {},
12176
12213
  ...fds.closesOnExec(fd) ? { cloexec: true } : {},
12177
12214
  ...kind === "stream" ? { desc: descId(open) } : {}
12178
12215
  };
@@ -12325,6 +12362,7 @@ function processOptions(opts, header3, hooks, created3) {
12325
12362
  kill: opts.kill,
12326
12363
  ...opts.writesBack ? { writesBack: opts.writesBack } : {},
12327
12364
  processes: opts.processes,
12365
+ ...opts.sudo ? { sudo: opts.sudo } : {},
12328
12366
  openFiles: opts.openFiles,
12329
12367
  ...opts.nodes ? { nodes: opts.nodes } : {},
12330
12368
  mounts: opts.mounts,
@@ -13083,12 +13121,12 @@ function kernelFsHandlers(fs) {
13083
13121
  const attr2 = attrOf(st);
13084
13122
  return [{ name: name2, kind: attr2.kind, attr: attr2 }];
13085
13123
  }),
13086
- async open(path, flags) {
13124
+ async open(path, flags2) {
13087
13125
  const st = await existing(path);
13088
- if (st && flags.exclusive) throw fsError2("EEXIST", path);
13089
- if (st?.isDirectory && flags.write) throw fsError2("EISDIR", path);
13090
- if (!st && !flags.create) throw fsError2("ENOENT", path);
13091
- if (!st || flags.truncate) await fs.writeFile(path, new Uint8Array(0));
13126
+ if (st && flags2.exclusive) throw fsError2("EEXIST", path);
13127
+ if (st?.isDirectory && flags2.write) throw fsError2("EISDIR", path);
13128
+ if (!st && !flags2.create) throw fsError2("ENOENT", path);
13129
+ if (!st || flags2.truncate) await fs.writeFile(path, new Uint8Array(0));
13092
13130
  handles.set(++nextFh, { path });
13093
13131
  return nextFh;
13094
13132
  },
@@ -13218,24 +13256,24 @@ function absolute(target) {
13218
13256
  return normalizePath(target);
13219
13257
  }
13220
13258
  function mountCall(call) {
13221
- let flags = call.flags >>> 0;
13222
- if ((flags & MS_MGC_MSK) >>> 0 === MS_MGC_VAL) flags &= ~MS_MGC_MSK;
13223
- if (flags & ~(MS_RDONLY | IGNORED_FLAGS)) {
13224
- throw errnoError("EINVAL", `unsupported mount flags ${flags.toString(16)}`);
13259
+ let flags2 = call.flags >>> 0;
13260
+ if ((flags2 & MS_MGC_MSK) >>> 0 === MS_MGC_VAL) flags2 &= ~MS_MGC_MSK;
13261
+ if (flags2 & ~(MS_RDONLY | IGNORED_FLAGS)) {
13262
+ throw errnoError("EINVAL", `unsupported mount flags ${flags2.toString(16)}`);
13225
13263
  }
13226
13264
  if (!call.type) throw errnoError("EINVAL", "no file system type");
13227
13265
  const target = absolute(call.target);
13228
13266
  refusedTarget(target);
13229
13267
  const options = parseMountOptions(call.data);
13230
- if (flags & MS_RDONLY) options.ro = "";
13268
+ if (flags2 & MS_RDONLY) options.ro = "";
13231
13269
  return { type: call.type, source: call.source || "none", target, options };
13232
13270
  }
13233
- function umountCall(target, flags) {
13271
+ function umountCall(target, flags2) {
13234
13272
  const known = MNT_FORCE | MNT_DETACH | UMOUNT_NOFOLLOW;
13235
- if (flags >>> 0 & ~known) throw errnoError("EINVAL", `unsupported umount flags ${flags}`);
13273
+ if (flags2 >>> 0 & ~known) throw errnoError("EINVAL", `unsupported umount flags ${flags2}`);
13236
13274
  const at = absolute(target);
13237
13275
  refusedTarget(at);
13238
- return { target: at, detach: (flags & (MNT_FORCE | MNT_DETACH)) !== 0 };
13276
+ return { target: at, detach: (flags2 & (MNT_FORCE | MNT_DETACH)) !== 0 };
13239
13277
  }
13240
13278
 
13241
13279
  // src/mount/fstab.ts
@@ -13293,22 +13331,22 @@ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discar
13293
13331
  );
13294
13332
  }
13295
13333
  async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
13296
- let failed2 = invalid;
13334
+ let failed3 = invalid;
13297
13335
  for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
13298
13336
  try {
13299
13337
  const entry = await mount(spec);
13300
13338
  if (!signal.aborted) return { line, target: spec.target, entry };
13301
13339
  discard(entry);
13302
- failed2 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
13340
+ failed3 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
13303
13341
  break;
13304
13342
  } catch (err) {
13305
- failed2 = err;
13343
+ failed3 = err;
13306
13344
  if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
13307
13345
  await wait(retries[attempt], signal);
13308
13346
  }
13309
13347
  }
13310
- const code = codeOf3(failed2);
13311
- return { line, target: spec.target, code, error: String(failed2?.message ?? code) };
13348
+ const code = codeOf3(failed3);
13349
+ return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
13312
13350
  }
13313
13351
 
13314
13352
  // src/mount/hostfs.ts
@@ -13485,13 +13523,13 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13485
13523
  }, againMs);
13486
13524
  timer.unref?.();
13487
13525
  });
13488
- async function writable2(client, path, flags) {
13489
- const { ifMatch: _, ...asked } = flags;
13526
+ async function writable2(client, path, flags2) {
13527
+ const { ifMatch: _, ...asked } = flags2;
13490
13528
  const opening2 = () => client.call("open", { path: rel(path), ...asked });
13491
13529
  try {
13492
13530
  return { opened: await opening2(), restore: void 0 };
13493
13531
  } catch (err) {
13494
- if (!flags.write || codeOf4(err) !== "EACCES") throw err;
13532
+ if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
13495
13533
  const was = await client.call("stat", { path: rel(path) });
13496
13534
  const mode = was.mode ?? 0;
13497
13535
  if (mode & 128) throw err;
@@ -13513,11 +13551,11 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13513
13551
  );
13514
13552
  return entries.map(({ name: name2, attr: attr2 }) => ({ name: name2, kind: attr2.kind, attr: attr2 }));
13515
13553
  },
13516
- async open(path, flags) {
13554
+ async open(path, flags2) {
13517
13555
  const client = current();
13518
13556
  const token = client.token;
13519
- const { opened: opened2, restore } = await writable2(client, path, flags);
13520
- if (flags.ifMatch !== void 0 && opened2.attr?.etag !== flags.ifMatch) {
13557
+ const { opened: opened2, restore } = await writable2(client, path, flags2);
13558
+ if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
13521
13559
  await client.call("release", { fh: opened2.fh }, true);
13522
13560
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
13523
13561
  }
@@ -13525,7 +13563,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13525
13563
  client,
13526
13564
  token,
13527
13565
  fh: opened2.fh,
13528
- etag: flags.write ? void 0 : opened2.attr?.etag,
13566
+ etag: flags2.write ? void 0 : opened2.attr?.etag,
13529
13567
  restore
13530
13568
  });
13531
13569
  return nextFh;
@@ -14102,14 +14140,14 @@ function tmpfs() {
14102
14140
  if (!found) throw fsError2("EBADF", `handle ${fh}`);
14103
14141
  return found;
14104
14142
  };
14105
- function opened2(path, flags) {
14143
+ function opened2(path, flags2) {
14106
14144
  const { dir, name: name2 } = parent(path);
14107
14145
  const existing = dir.children.get(name2);
14108
- if (existing && flags.exclusive) throw fsError2("EEXIST", path);
14109
- if (existing?.kind === "directory" && flags.write) throw fsError2("EISDIR", path);
14146
+ if (existing && flags2.exclusive) throw fsError2("EEXIST", path);
14147
+ if (existing?.kind === "directory" && flags2.write) throw fsError2("EISDIR", path);
14110
14148
  if (existing) return existing;
14111
- if (!flags.create) throw fsError2("ENOENT", path);
14112
- const node = make("file", 33188, dir, flags.owner);
14149
+ if (!flags2.create) throw fsError2("ENOENT", path);
14150
+ const node = make("file", 33188, dir, flags2.owner);
14113
14151
  place(dir, name2, node);
14114
14152
  return node;
14115
14153
  }
@@ -14129,13 +14167,13 @@ function tmpfs() {
14129
14167
  ([name2, node]) => ({ name: name2, kind: node.kind, attr: attr(node) })
14130
14168
  );
14131
14169
  },
14132
- async open(path, flags) {
14133
- const node = opened2(path, flags);
14134
- if (flags.truncate && node.kind === "file") {
14170
+ async open(path, flags2) {
14171
+ const node = opened2(path, flags2);
14172
+ if (flags2.truncate && node.kind === "file") {
14135
14173
  node.data.truncate(0);
14136
14174
  node.mtime = Date.now();
14137
14175
  }
14138
- handles.set(++nextFh, { node, write: flags.write });
14176
+ handles.set(++nextFh, { node, write: flags2.write });
14139
14177
  return nextFh;
14140
14178
  },
14141
14179
  async read(fh, offset, size) {
@@ -14251,10 +14289,10 @@ function importedMemory(bytes) {
14251
14289
  return s;
14252
14290
  };
14253
14291
  const limits = () => {
14254
- const flags = bytes[at++];
14292
+ const flags2 = bytes[at++];
14255
14293
  const initial = u32();
14256
- const maximum = flags & 1 ? u32() : void 0;
14257
- return { initial, maximum, shared: (flags & 2) !== 0 };
14294
+ const maximum = flags2 & 1 ? u32() : void 0;
14295
+ return { initial, maximum, shared: (flags2 & 2) !== 0 };
14258
14296
  };
14259
14297
  while (at < bytes.length) {
14260
14298
  const id2 = bytes[at++];
@@ -14333,9 +14371,9 @@ var Reader = class {
14333
14371
  return code;
14334
14372
  }
14335
14373
  limits() {
14336
- const flags = this.byte();
14374
+ const flags2 = this.byte();
14337
14375
  this.u32();
14338
- if (flags & 1) this.u32();
14376
+ if (flags2 & 1) this.u32();
14339
14377
  }
14340
14378
  };
14341
14379
  function readTypes(r) {
@@ -14390,6 +14428,412 @@ function foreignImports(bytes) {
14390
14428
  return {};
14391
14429
  }
14392
14430
 
14431
+ // src/sudo.ts
14432
+ var SUDOERS = "/etc/sudoers";
14433
+ var SUDOERS_D = "/etc/sudoers.d";
14434
+ var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
14435
+ var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
14436
+ var SUDO_TIMEOUT_MS = 3e5;
14437
+ var NAME3 = /^[a-z_][a-z0-9_.-]*$/;
14438
+ var RULE = /^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?Cmnd(?:\s+(.*))?$/;
14439
+ var TERM = /^[A-Za-z0-9._+-]+$/;
14440
+ var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
14441
+ var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
14442
+ function failed2(errno, error) {
14443
+ return { error, errno };
14444
+ }
14445
+ function unescaped(double) {
14446
+ return double.replace(/\\(["\\$`])/g, "$1");
14447
+ }
14448
+ function splitWords(text) {
14449
+ const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
14450
+ const words2 = [];
14451
+ let word;
14452
+ while (piece.lastIndex < text.length) {
14453
+ const m = piece.exec(text);
14454
+ if (!m) return void 0;
14455
+ const [, single, double, escaped, plain, space] = m;
14456
+ if (space !== void 0) {
14457
+ if (word !== void 0) words2.push(word);
14458
+ word = void 0;
14459
+ continue;
14460
+ }
14461
+ word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain));
14462
+ }
14463
+ if (word !== void 0) words2.push(word);
14464
+ return words2;
14465
+ }
14466
+ function quoteWord(word) {
14467
+ return PLAIN.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
14468
+ }
14469
+ function validWho(who) {
14470
+ if (who === "ALL") return true;
14471
+ return NAME3.test(who.startsWith("%") ? who.slice(1) : who);
14472
+ }
14473
+ function parseRule(line) {
14474
+ const text = line.trim();
14475
+ if (text === "" || text.startsWith("#")) return null;
14476
+ const m = RULE.exec(text);
14477
+ if (!m) return void 0;
14478
+ const [, nopasswd, who = "", target = "root", rest2 = ""] = m;
14479
+ if (!validWho(who) || target !== "ALL" && !NAME3.test(target)) return void 0;
14480
+ if (rest2.trim() === "*") return { nopasswd: !!nopasswd, who, target, cmnd: "*" };
14481
+ const cmnd = splitWords(rest2);
14482
+ if (!cmnd?.[0]?.startsWith("/")) return void 0;
14483
+ return { nopasswd: !!nopasswd, who, target, cmnd };
14484
+ }
14485
+ function flags(parsed2, arg, next2) {
14486
+ for (let j = 1; j < arg.length; j++) {
14487
+ const flag = arg[j];
14488
+ if (flag === "n") parsed2.nonInteractive = true;
14489
+ else if (flag === "l") parsed2.list = true;
14490
+ else if (flag !== "u") return `sudo: invalid option -- '${flag}'
14491
+ ${USAGE}`;
14492
+ else {
14493
+ const user = arg.slice(j + 1) || next2;
14494
+ if (!user) return `sudo: option requires an argument -- 'u'
14495
+ ${USAGE}`;
14496
+ parsed2.user = user;
14497
+ return arg.slice(j + 1) ? 0 : 1;
14498
+ }
14499
+ }
14500
+ return 0;
14501
+ }
14502
+ function parseArgs(args) {
14503
+ const parsed2 = { list: false, nonInteractive: false, user: "root", command: [] };
14504
+ let i = 0;
14505
+ for (; i < args.length; i++) {
14506
+ const arg = args[i];
14507
+ if (arg === "--") {
14508
+ i++;
14509
+ break;
14510
+ }
14511
+ if (!arg.startsWith("-") || arg === "-") break;
14512
+ const used = flags(parsed2, arg, args[i + 1]);
14513
+ if (typeof used === "string") return used;
14514
+ i += used;
14515
+ }
14516
+ parsed2.command = args.slice(i);
14517
+ if (parsed2.list ? parsed2.command.length > 0 : parsed2.command.length === 0) return USAGE;
14518
+ return parsed2;
14519
+ }
14520
+ function described(rule) {
14521
+ const cmnd = rule.cmnd === "*" ? "ALL" : rule.cmnd.map(quoteWord).join(" ");
14522
+ return ` (${rule.target}) ${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
14523
+ }
14524
+ var Sudo = class {
14525
+ o;
14526
+ warned = /* @__PURE__ */ new Set();
14527
+ pending = /* @__PURE__ */ new Set();
14528
+ next = 0;
14529
+ constructor(options) {
14530
+ this.o = options;
14531
+ }
14532
+ warn(message3) {
14533
+ if (this.warned.has(message3)) return;
14534
+ this.warned.add(message3);
14535
+ (this.o.warn ?? console.warn)(message3);
14536
+ }
14537
+ async provision() {
14538
+ const { fs } = this.o;
14539
+ if (!await fs.exists(SUDOERS)) {
14540
+ await fs.writeFile(SUDOERS, DEFAULT_SUDOERS);
14541
+ await fs.chown?.(SUDOERS, { uid: 0, gid: 0, mode: 288, follow: false });
14542
+ }
14543
+ if (!await fs.exists(SUDOERS_D)) {
14544
+ await fs.mkdir(SUDOERS_D, { recursive: true });
14545
+ await fs.chown?.(SUDOERS_D, { uid: 0, gid: 0, mode: 488, follow: false });
14546
+ }
14547
+ }
14548
+ async trusted(path) {
14549
+ const parts2 = normalizePath(path).split("/").filter(Boolean);
14550
+ for (let n = 0; n <= parts2.length; n++) {
14551
+ const at = `/${parts2.slice(0, n).join("/")}`;
14552
+ const st = await this.o.fs.lstat(at).catch(() => void 0);
14553
+ if (!st || st.isSymbolicLink || st.uid !== 0 || (st.mode & 18) !== 0) return false;
14554
+ }
14555
+ return true;
14556
+ }
14557
+ async policyFile(path) {
14558
+ if (!await this.trusted(path)) {
14559
+ this.warn(`sudo: ${path} skipped: it must be root's, no symlink, and writable by root only`);
14560
+ return [];
14561
+ }
14562
+ const text = await this.o.fs.readFile(path).catch(() => "");
14563
+ const rules = [];
14564
+ for (const [n, line] of text.split("\n").entries()) {
14565
+ const rule = parseRule(line);
14566
+ if (rule) rules.push(rule);
14567
+ else if (rule === void 0) this.warn(`sudo: ${path}:${n + 1}: bad line skipped: ${line}`);
14568
+ }
14569
+ return rules;
14570
+ }
14571
+ async policy() {
14572
+ const rules = await this.policyFile(SUDOERS);
14573
+ const names = await this.o.fs.readdir(SUDOERS_D).catch(() => []);
14574
+ if (names.length > 0 && !await this.trusted(SUDOERS_D)) {
14575
+ this.warn(`sudo: ${SUDOERS_D} skipped: it must be root's and writable by root only`);
14576
+ return rules;
14577
+ }
14578
+ for (const name2 of [...names].sort()) {
14579
+ if (name2.startsWith(".") || name2.endsWith("~")) continue;
14580
+ rules.push(...await this.policyFile(`${SUDOERS_D}/${name2}`));
14581
+ }
14582
+ return rules;
14583
+ }
14584
+ async groupIds() {
14585
+ return new Map((await this.o.users.groups()).map((g) => [g.name, g.gid]));
14586
+ }
14587
+ async applies(caller, cred) {
14588
+ const gids = await this.groupIds();
14589
+ const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
14590
+ return (rule) => {
14591
+ if (rule.who === "ALL" || rule.who === caller.name) return true;
14592
+ const gid = rule.who.startsWith("%") ? gids.get(rule.who.slice(1)) : void 0;
14593
+ return gid !== void 0 && mine.has(gid);
14594
+ };
14595
+ }
14596
+ async lookup(user) {
14597
+ const key = /^#\d+$/.test(user) ? Number(user.slice(1)) : user;
14598
+ return this.o.users.lookup(key);
14599
+ }
14600
+ async canonical(cwd) {
14601
+ const { fs } = this.o;
14602
+ const path = normalizePath(cwd);
14603
+ return fs.realpath ? fs.realpath(path, true).catch(() => void 0) : path;
14604
+ }
14605
+ async reachable(cred, cwd) {
14606
+ const walked = await access(this.o.fs, cred, cwd, X_OK, { directory: true });
14607
+ if (walked.denied !== void 0 || walked.missing) return false;
14608
+ return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
14609
+ }
14610
+ async untrusted(path, cwd) {
14611
+ const files = await this.o.backing?.(path, cwd) ?? [];
14612
+ for (const file of [path, ...files]) if (!await this.trusted(file)) return file;
14613
+ return void 0;
14614
+ }
14615
+ async locate(word, cwd) {
14616
+ const { fs } = this.o;
14617
+ const candidates = word.includes("/") ? [fs.resolvePath(cwd, word)] : SECURE_PATH.split(":").map((dir) => `${dir}/${word}`);
14618
+ for (const path of candidates) {
14619
+ const st = await fs.stat(path).catch(() => void 0);
14620
+ if (!st?.isFile || (st.mode & 73) === 0) continue;
14621
+ return fs.realpath ? fs.realpath(path, true).catch(() => path) : path;
14622
+ }
14623
+ return void 0;
14624
+ }
14625
+ async request(caller, call, signal) {
14626
+ const parsed2 = parseArgs(call.args);
14627
+ if (typeof parsed2 === "string") return failed2("EINVAL", parsed2);
14628
+ if (this.o.mode !== "enforce") {
14629
+ const mode = this.o.mode;
14630
+ if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
14631
+ ` };
14632
+ return { run: parsed2.command };
14633
+ }
14634
+ const { cred } = caller;
14635
+ const self = await this.o.users.lookup(cred.ruid);
14636
+ if (!self) return failed2("EPERM", `sudo: uid ${cred.ruid} is not in the passwd database`);
14637
+ const target = await this.lookup(parsed2.user);
14638
+ if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
14639
+ const root = cred.euid === 0;
14640
+ const rules = root ? [] : await this.policy();
14641
+ const applies = await this.applies(self, cred);
14642
+ if (parsed2.list) return this.listed(self, root, rules.filter(applies));
14643
+ const [word = "", ...args] = parsed2.command;
14644
+ const cwd = await this.canonical(call.cwd);
14645
+ if (cwd === void 0 || !await this.reachable(cred, cwd)) {
14646
+ return failed2(
14647
+ "EACCES",
14648
+ `sudo: cannot run in ${call.cwd}: it isn't a directory you can enter`
14649
+ );
14650
+ }
14651
+ const path = await this.locate(word, cwd);
14652
+ if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
14653
+ const untrusted = await this.untrusted(path, cwd);
14654
+ if (untrusted) {
14655
+ return failed2("EPERM", `sudo: ${untrusted} must be root's, and writable by root only`);
14656
+ }
14657
+ const argv = [path, ...args];
14658
+ const matching = rules.filter(
14659
+ (rule) => applies(rule) && (rule.target === "ALL" || rule.target === target.name) && (rule.cmnd === "*" || rule.cmnd.length === argv.length && rule.cmnd.every((w, i) => w === argv[i]))
14660
+ );
14661
+ const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
14662
+ if (root || matching.some((rule) => rule.nopasswd)) return grant();
14663
+ const quoted = argv.map(quoteWord).join(" ");
14664
+ if (matching.length === 0) {
14665
+ return failed2(
14666
+ "EPERM",
14667
+ `sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
14668
+ );
14669
+ }
14670
+ if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
14671
+ if (this.pending.has(cred.ruid)) return failed2("EBUSY", "sudo: another request is pending");
14672
+ this.pending.add(cred.ruid);
14673
+ try {
14674
+ const answer = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
14675
+ if (typeof answer !== "string") return answer;
14676
+ if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
14677
+ return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
14678
+ }
14679
+ const changed = await this.untrusted(path, cwd);
14680
+ if (changed) {
14681
+ return failed2("EPERM", `sudo: ${changed} must be root's, and writable by root only`);
14682
+ }
14683
+ if (answer === "always" && !await this.remember(self, target, argv)) {
14684
+ return failed2("EIO", "sudo: the rule could not be recorded");
14685
+ }
14686
+ return grant();
14687
+ } finally {
14688
+ this.pending.delete(cred.ruid);
14689
+ }
14690
+ }
14691
+ listed(self, root, rules) {
14692
+ const host = this.o.hostname;
14693
+ if (root) {
14694
+ return {
14695
+ list: `User ${self.name} may run the following commands on ${host}:
14696
+ (ALL) NOPASSWD: ALL
14697
+ `
14698
+ };
14699
+ }
14700
+ if (rules.length === 0) {
14701
+ return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
14702
+ }
14703
+ const lines2 = rules.map(described).join("\n");
14704
+ return { list: `User ${self.name} may run the following commands on ${host}:
14705
+ ${lines2}
14706
+ ` };
14707
+ }
14708
+ async ask(caller, self, target, argv, path, cwd, requester) {
14709
+ const { approve } = this.o;
14710
+ if (!approve || requester.aborted) return failed2("EACCES", "sudo: approval denied");
14711
+ const createdAt = Date.now();
14712
+ const timeout = this.o.timeoutMs ?? SUDO_TIMEOUT_MS;
14713
+ const req = {
14714
+ id: `sudo-${++this.next}-${crypto.randomUUID()}`,
14715
+ kind: "sudo",
14716
+ user: self.name,
14717
+ uid: self.uid,
14718
+ target: target.name,
14719
+ argv: [...argv],
14720
+ path,
14721
+ cwd,
14722
+ pid: caller.pid,
14723
+ ...caller.tag !== void 0 ? { tag: caller.tag } : {},
14724
+ ...caller.tty ? { tty: caller.tty } : {},
14725
+ createdAt,
14726
+ expiresAt: createdAt + timeout,
14727
+ always: true
14728
+ };
14729
+ const control = new AbortController();
14730
+ const timer = setTimeout(() => control.abort("expired"), timeout);
14731
+ const withdraw = () => control.abort("withdrawn");
14732
+ requester.addEventListener("abort", withdraw, { once: true });
14733
+ const aborted2 = new Promise(
14734
+ (resolve) => control.signal.addEventListener("abort", () => resolve("aborted"), { once: true })
14735
+ );
14736
+ try {
14737
+ const answered2 = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
14738
+ const answer = await Promise.race([answered2, aborted2]);
14739
+ if (answer === "aborted" || control.signal.aborted) {
14740
+ return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
14741
+ }
14742
+ return answer === "once" || answer === "always" ? answer : failed2("EACCES", "sudo: approval denied");
14743
+ } finally {
14744
+ clearTimeout(timer);
14745
+ requester.removeEventListener("abort", withdraw);
14746
+ }
14747
+ }
14748
+ async remember(self, target, argv) {
14749
+ const { fs } = this.o;
14750
+ const path = `${SUDOERS_D}/${self.name}`;
14751
+ if (!NAME3.test(self.name)) return false;
14752
+ try {
14753
+ await this.provision();
14754
+ if (!await this.trusted(SUDOERS_D)) return false;
14755
+ const st = await fs.lstat(path).catch(() => void 0);
14756
+ if (st && !await this.trusted(path)) return false;
14757
+ const old = st ? await fs.readFile(path) : "";
14758
+ const line = `NOPASSWD ${self.name} (${target.name}) Cmnd ${argv.map(quoteWord).join(" ")}
14759
+ `;
14760
+ const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
14761
+ ${line}`;
14762
+ await fs.writeFile(path, text);
14763
+ await fs.chown?.(path, { uid: 0, gid: 0, mode: 288, follow: false });
14764
+ return true;
14765
+ } catch {
14766
+ return false;
14767
+ }
14768
+ }
14769
+ grant(self, target, cred, argv, file, cwd, term) {
14770
+ const env = {
14771
+ ...this.o.env(),
14772
+ ...userEnv(target, this.o.pnpmHome),
14773
+ SHELL: target.shell,
14774
+ PATH: SECURE_PATH,
14775
+ PWD: cwd,
14776
+ ...term !== void 0 && TERM.test(term) ? { TERM: term } : {},
14777
+ SUDO_USER: self.name,
14778
+ SUDO_UID: String(cred.ruid),
14779
+ SUDO_GID: String(cred.rgid),
14780
+ SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
14781
+ };
14782
+ return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
14783
+ }
14784
+ };
14785
+
14786
+ // src/sudo-command.ts
14787
+ var SUDO_PATH = "/usr/bin/sudo";
14788
+ var SUDO_COMMAND = {
14789
+ name: "sudo",
14790
+ abi: "js",
14791
+ glue: SUDO_PATH,
14792
+ wasm: SUDO_PATH,
14793
+ argv0: "sudo"
14794
+ };
14795
+ var SUDO_SOURCE = [
14796
+ "export default async function sudo(ctx) {",
14797
+ " const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);",
14798
+ " let pid;",
14799
+ " let held;",
14800
+ " const relay = (sig) => {",
14801
+ " if (pid === undefined) held = sig;",
14802
+ " else return ctx.kill(pid, sig).catch(() => undefined);",
14803
+ " };",
14804
+ ' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
14805
+ " const failed = () => (held === undefined ? 1 : 128 + held);",
14806
+ " let answer;",
14807
+ " try {",
14808
+ " answer = await ctx.sudo(ctx.argv.slice(1));",
14809
+ " } catch (err) {",
14810
+ ' await say(2, "sudo: " + (err && err.code ? err.code : String(err)) + "\\n");',
14811
+ " return failed();",
14812
+ " }",
14813
+ " if (answer.error !== undefined) {",
14814
+ ' await say(2, answer.error + "\\n");',
14815
+ " return failed();",
14816
+ " }",
14817
+ " if (answer.list !== undefined) {",
14818
+ " await say(1, answer.list);",
14819
+ " return 0;",
14820
+ " }",
14821
+ ' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
14822
+ " if (answer.run !== undefined) {",
14823
+ " try {",
14824
+ " pid = (await ctx.spawn({ argv: answer.run })).pid;",
14825
+ " } catch (err) {",
14826
+ ' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
14827
+ ' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
14828
+ " return failed();",
14829
+ " }",
14830
+ " } else pid = answer.pid;",
14831
+ " if (held !== undefined) await relay(held);",
14832
+ " return (await ctx.wait(pid)).status;",
14833
+ "}",
14834
+ ""
14835
+ ].join("\n");
14836
+
14393
14837
  // src/launcher.ts
14394
14838
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
14395
14839
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
@@ -14417,9 +14861,21 @@ function usersMode(mode = "off") {
14417
14861
  if (USERS_MODES.includes(mode)) return mode;
14418
14862
  throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
14419
14863
  }
14864
+ function tagOption(tag) {
14865
+ if (tag !== void 0 && typeof tag !== "string") throw new TypeError("a tag must be a string");
14866
+ }
14420
14867
  function baseName(path) {
14421
14868
  return path.slice(path.lastIndexOf("/") + 1);
14422
14869
  }
14870
+ function packageRootOf(path, roots) {
14871
+ for (const root of roots) {
14872
+ const base = normalizePath(root);
14873
+ if (!path.startsWith(`${base}/`)) continue;
14874
+ const [first = "", second] = path.slice(base.length + 1).split("/");
14875
+ return first.startsWith("@") && second !== void 0 ? `${base}/${first}/${second}` : `${base}/${first}`;
14876
+ }
14877
+ return void 0;
14878
+ }
14423
14879
  function modulePath(glue) {
14424
14880
  return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
14425
14881
  }
@@ -14431,11 +14887,11 @@ function shebang(head) {
14431
14887
  return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
14432
14888
  }
14433
14889
  function interpreter(head) {
14434
- const words = shebang(head);
14435
- if (!words) return "node";
14436
- if (baseName(words[0]) === "env") words.shift();
14437
- if (words[0] === "-S") words.shift();
14438
- return baseName(words[0] ?? "");
14890
+ const words2 = shebang(head);
14891
+ if (!words2) return "node";
14892
+ if (baseName(words2[0]) === "env") words2.shift();
14893
+ if (words2[0] === "-S") words2.shift();
14894
+ return baseName(words2[0] ?? "");
14439
14895
  }
14440
14896
  function expandDefaults(defaults, env) {
14441
14897
  const out = {};
@@ -14509,7 +14965,10 @@ var Launcher = class {
14509
14965
  orphans = /* @__PURE__ */ new Set();
14510
14966
  described = /* @__PURE__ */ new Map();
14511
14967
  creds = /* @__PURE__ */ new Map();
14968
+ tags = /* @__PURE__ */ new Map();
14969
+ granted = /* @__PURE__ */ new Map();
14512
14970
  users;
14971
+ sudoer;
14513
14972
  jobs = new JobTable();
14514
14973
  locks = new LockTable();
14515
14974
  settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
@@ -14610,6 +15069,17 @@ var Launcher = class {
14610
15069
  );
14611
15070
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
14612
15071
  this.users = new UserDb(this.fs, this.owners);
15072
+ this.sudoer = new Sudo({
15073
+ fs: this.fs,
15074
+ users: this.users,
15075
+ mode: this.usersMode,
15076
+ hostname,
15077
+ env: () => this.env,
15078
+ pnpmHome: this.pnpmHome,
15079
+ backing: (path, cwd) => this.backing(path, cwd),
15080
+ approve: options.approve,
15081
+ timeoutMs: options.sudoTimeoutMs
15082
+ });
14613
15083
  void this.unlinked.sweep("/");
14614
15084
  this.watchers.watch(
14615
15085
  [this.modulesDir, this.pnpmHome],
@@ -14630,7 +15100,7 @@ var Launcher = class {
14630
15100
  commands(scope = "") {
14631
15101
  let found = this.catalogs.get(scope);
14632
15102
  if (!found) {
14633
- found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
15103
+ found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
14634
15104
  this.catalogs.set(scope, found);
14635
15105
  }
14636
15106
  return found;
@@ -14753,10 +15223,10 @@ var Launcher = class {
14753
15223
  const head = await this.head(script);
14754
15224
  if (!head) return void 0;
14755
15225
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
14756
- const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
14757
- if (!words) return void 0;
14758
- if (baseName(words[0]) === "env") words.shift();
14759
- const [interp, ...rest2] = words;
15226
+ const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
15227
+ if (!words2) return void 0;
15228
+ if (baseName(words2[0]) === "env") words2.shift();
15229
+ const [interp, ...rest2] = words2;
14760
15230
  const arg = rest2.join(" ");
14761
15231
  const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
14762
15232
  const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
@@ -14793,6 +15263,41 @@ var Launcher = class {
14793
15263
  await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
14794
15264
  }
14795
15265
  }
15266
+ async grantedCwd(caller, cwd) {
15267
+ const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
15268
+ return real === cwd && await this.sudoer.reachable(caller, cwd);
15269
+ }
15270
+ async binfmtManifests(scripts) {
15271
+ const sources = /* @__PURE__ */ new Map();
15272
+ const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
15273
+ const manifests = [];
15274
+ for (const script of scripts) {
15275
+ const head = await this.head(script);
15276
+ if (!head || isWasm(head) || shebang(head)) continue;
15277
+ const pkg = sources.get(binfmtSuffix(binfmts, script) ?? "");
15278
+ if (pkg !== void 0) manifests.push(`${pkg}/package.json`);
15279
+ }
15280
+ return manifests;
15281
+ }
15282
+ async backing(file, cwd) {
15283
+ const planned = await this.plan(file, [file], cwd, "");
15284
+ if (!planned) return [];
15285
+ const { glue, wasm, imports } = planned.target;
15286
+ const files = [glue, wasm, ...imports ? [imports] : []];
15287
+ const scripts = [];
15288
+ for (const name2 of [file, ...planned.via ?? []]) {
15289
+ if (name2.includes("/") && !COMMAND2.test(name2)) scripts.push(this.fs.resolvePath(cwd, name2));
15290
+ const script = (await this.scriptCommand(name2, cwd, ""))?.script;
15291
+ if (script) scripts.push(script);
15292
+ }
15293
+ files.push(...scripts, ...await this.binfmtManifests(scripts));
15294
+ const roots = await this.roots();
15295
+ for (const at of [...files]) {
15296
+ const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
15297
+ if (root !== void 0) files.push(`${root}/package.json`);
15298
+ }
15299
+ return [...new Set(files)].filter((path) => path !== "" && path !== SUDO_PATH);
15300
+ }
14796
15301
  async unrunnable(file, cwd, scope) {
14797
15302
  const path = this.fs.resolvePath(cwd, file);
14798
15303
  const head = await this.base.readFileBuffer(path).catch(() => null);
@@ -14830,6 +15335,9 @@ var Launcher = class {
14830
15335
  return await this.base.exists(optional) ? optional : void 0;
14831
15336
  }
14832
15337
  async program(target, env) {
15338
+ if (target.abi === "js" && target.wasm === SUDO_PATH) {
15339
+ return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
15340
+ }
14833
15341
  if (target.abi === "js") {
14834
15342
  return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
14835
15343
  }
@@ -14916,6 +15424,7 @@ var Launcher = class {
14916
15424
  kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
14917
15425
  writesBack: (target) => this.processes.get(target)?.writesBack() === true,
14918
15426
  processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
15427
+ sudo: (call, signal) => this.sudo(pid, call, signal),
14919
15428
  openFiles: this.openFiles,
14920
15429
  nodes: this.nodes,
14921
15430
  held: this.held,
@@ -14923,7 +15432,7 @@ var Launcher = class {
14923
15432
  statfs: (path) => this.mounts.statfs(path),
14924
15433
  mounts: () => this.mounts.list(),
14925
15434
  mount: (call, signal) => this.processMount(pid, call, signal),
14926
- umount: (target, flags) => this.processUmount(pid, target, flags),
15435
+ umount: (target, flags2) => this.processUmount(pid, target, flags2),
14927
15436
  jobs: this.jobs,
14928
15437
  ptys: this.ptys,
14929
15438
  net: this.net,
@@ -14937,6 +15446,7 @@ var Launcher = class {
14937
15446
  this.processes.set(pid, handle);
14938
15447
  const { cred } = req;
14939
15448
  this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
15449
+ if (req.tag !== void 0) this.tags.set(pid, req.tag);
14940
15450
  if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
14941
15451
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
14942
15452
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -14964,6 +15474,8 @@ var Launcher = class {
14964
15474
  this.jobs.remove(pid);
14965
15475
  this.described.delete(pid);
14966
15476
  this.creds.delete(pid);
15477
+ this.tags.delete(pid);
15478
+ this.granted.delete(pid);
14967
15479
  }
14968
15480
  reaped(pid) {
14969
15481
  if (this.zombies.delete(pid)) this.forget(pid);
@@ -14985,8 +15497,8 @@ var Launcher = class {
14985
15497
  };
14986
15498
  const listed = [];
14987
15499
  for (const member of members.values()) {
14988
- const described = this.described.get(member.pid);
14989
- if (member.execed || !described) continue;
15500
+ const described2 = this.described.get(member.pid);
15501
+ if (member.execed || !described2) continue;
14990
15502
  const cred = this.credOf(member.pid);
14991
15503
  if (!maySee(viewer, cred)) continue;
14992
15504
  const root = rootOf(member);
@@ -14996,8 +15508,8 @@ var Launcher = class {
14996
15508
  ppid: shown(root.ppid),
14997
15509
  pgid: member.pgid,
14998
15510
  sid: member.sid,
14999
- ...described,
15000
- started: (this.described.get(root.pid) ?? described).started,
15511
+ ...described2,
15512
+ started: (this.described.get(root.pid) ?? described2).started,
15001
15513
  state: this.processes.has(member.pid) ? "S" : "Z",
15002
15514
  memory: this.processes.get(member.pid)?.memory() ?? 0,
15003
15515
  umask: this.umaskOf(member.pid),
@@ -15007,12 +15519,26 @@ var Launcher = class {
15007
15519
  }
15008
15520
  return listed;
15009
15521
  }
15522
+ sudo(pid, call, signal) {
15523
+ const caller = {
15524
+ cred: this.credOf(pid),
15525
+ pid: this.jobs.shown(pid),
15526
+ tag: this.tagOf(pid),
15527
+ tty: this.described.get(pid)?.tty
15528
+ };
15529
+ return this.sudoer.request(caller, call, signal);
15530
+ }
15010
15531
  spawner(ppid, report) {
15011
- return async (req, fds) => {
15012
- const scope = await this.scopeOf(this.credOf(ppid).euid);
15532
+ return async (req, fds, grant) => {
15533
+ if (grant && !await this.grantedCwd(grant.caller, req.cwd)) {
15534
+ await fds.closeAll();
15535
+ throw new SpawnError("EACCES");
15536
+ }
15537
+ const cred = grant ? grant.cred : this.credOf(ppid);
15538
+ const scope = grant ? "" : await this.scopeOf(cred.euid);
15013
15539
  const planned = await this.plan(req.file, req.argv, req.cwd, scope);
15014
15540
  try {
15015
- await this.mayExec(this.credOf(ppid), { ...req, pid: ppid }, planned);
15541
+ await this.mayExec(cred, { ...req, pid: ppid }, planned);
15016
15542
  } catch (err) {
15017
15543
  await fds.closeAll();
15018
15544
  throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
@@ -15032,10 +15558,12 @@ var Launcher = class {
15032
15558
  ppid,
15033
15559
  ignored: this.ignoredBy(ppid),
15034
15560
  umask: this.umaskOf(ppid),
15035
- cred: this.credOf(ppid),
15561
+ cred,
15562
+ tag: this.tagOf(ppid),
15036
15563
  ...exec
15037
15564
  });
15038
15565
  deciding?.attach();
15566
+ if (grant) this.granted.set(handle.pid, ppid);
15039
15567
  return childHandle(handle);
15040
15568
  };
15041
15569
  }
@@ -15079,7 +15607,8 @@ var Launcher = class {
15079
15607
  fork: state,
15080
15608
  ignored: this.ignoredBy(ppid, true),
15081
15609
  umask: this.umaskOf(ppid),
15082
- cred: this.credOf(ppid)
15610
+ cred: this.credOf(ppid),
15611
+ tag: this.tagOf(ppid)
15083
15612
  })
15084
15613
  );
15085
15614
  }
@@ -15104,6 +15633,9 @@ var Launcher = class {
15104
15633
  credOf(pid) {
15105
15634
  return this.creds.get(pid)?.() ?? copyCred(ROOT);
15106
15635
  }
15636
+ tagOf(pid) {
15637
+ return this.tags.get(pid);
15638
+ }
15107
15639
  released(path) {
15108
15640
  const at = normalizePath(path);
15109
15641
  this.unlinked.closed(at);
@@ -15122,7 +15654,7 @@ var Launcher = class {
15122
15654
  }
15123
15655
  kill(pid, sig, sender, from) {
15124
15656
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
15125
- 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);
15657
+ 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;
15126
15658
  if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
15127
15659
  const handle = this.processes.get(pid);
15128
15660
  if (!handle) return false;
@@ -15243,7 +15775,8 @@ var Launcher = class {
15243
15775
  if (fresh) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
15244
15776
  }
15245
15777
  }
15246
- async realLayoutPath(path) {
15778
+ async realLayoutPath(given) {
15779
+ const path = normalizePath(given);
15247
15780
  if (!await this.base.exists(path)) return void 0;
15248
15781
  if (path === "/") return path;
15249
15782
  return joinName(await realDir(this.base, parentOf4(path)), baseName(path));
@@ -15254,7 +15787,8 @@ var Launcher = class {
15254
15787
  mountedBy(spec, cred) {
15255
15788
  if (!this.owners || cred.euid === 0 && cred.egid === 0) return spec;
15256
15789
  const ids = { uid: String(cred.euid), gid: String(cred.egid) };
15257
- return { ...spec, options: { ...ids, ...spec.options } };
15790
+ const options = cred.euid === 0 ? { ...ids, ...spec.options } : { ...spec.options, ...ids };
15791
+ return { ...spec, options };
15258
15792
  }
15259
15793
  mountRoot(path) {
15260
15794
  let root = "/";
@@ -15299,13 +15833,36 @@ var Launcher = class {
15299
15833
  async processMount(pid, call, signal) {
15300
15834
  const spec = mountCall(call);
15301
15835
  await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
15836
+ await this.mountsOwn(
15837
+ this.credOf(pid),
15838
+ spec.target,
15839
+ async () => (await this.fs.stat(spec.target)).uid
15840
+ );
15302
15841
  const entry = await this.mount(this.mountedBy(spec, this.credOf(pid)));
15303
15842
  if (signal.aborted) this.umount(entry.target, true);
15304
15843
  }
15305
- async processUmount(pid, target, flags) {
15306
- const call = umountCall(target, flags);
15844
+ async mountAs(cred, spec) {
15845
+ const target = normalizePath(spec.target);
15846
+ await this.mountsOwn(cred, target, async () => (await this.fs.stat(target)).uid);
15847
+ return this.mount(this.mountedBy(spec, cred));
15848
+ }
15849
+ async umountAs(cred, target) {
15850
+ const at = normalizePath(target);
15851
+ await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
15852
+ this.umount(at);
15853
+ }
15854
+ async mountsOwn(cred, target, owner) {
15855
+ const { euid } = cred;
15856
+ if (this.usersMode !== "enforce" || euid === 0) return;
15857
+ if (await owner().catch(() => void 0) !== euid) {
15858
+ throw fsError("EPERM", `${target} is not the caller's to mount or unmount`);
15859
+ }
15860
+ }
15861
+ async processUmount(pid, target, flags2) {
15862
+ const call = umountCall(target, flags2);
15863
+ let mounted;
15307
15864
  for (; ; ) {
15308
- const mounted = this.mounts.mounted(call.target);
15865
+ mounted = this.mounts.mounted(call.target);
15309
15866
  await this.permit({
15310
15867
  op: "umount",
15311
15868
  pid,
@@ -15314,6 +15871,7 @@ var Launcher = class {
15314
15871
  });
15315
15872
  if (this.mounts.mounted(call.target) === mounted) break;
15316
15873
  }
15874
+ await this.mountsOwn(this.credOf(pid), call.target, async () => Number(mounted?.options.uid));
15317
15875
  this.umount(call.target, call.detach);
15318
15876
  }
15319
15877
  async prepare() {
@@ -15322,6 +15880,7 @@ var Launcher = class {
15322
15880
  await this.provisionPnpmHome();
15323
15881
  await this.users.ensure();
15324
15882
  if (this.owners) await this.stampLayout();
15883
+ if (this.owners) await this.sudoer.provision();
15325
15884
  await writeCaFile(this.base, this.ca).catch(() => void 0);
15326
15885
  this.fstab = this.mountFstab();
15327
15886
  }
@@ -15359,8 +15918,9 @@ var Launcher = class {
15359
15918
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
15360
15919
  return found;
15361
15920
  }
15362
- identity(account) {
15921
+ identity(account, tag) {
15363
15922
  return {
15923
+ tag,
15364
15924
  cred: credOf(account),
15365
15925
  ...account.umask !== void 0 ? { umask: account.umask } : {}
15366
15926
  };
@@ -15377,6 +15937,7 @@ var Launcher = class {
15377
15937
  return { cwd, file, planned };
15378
15938
  }
15379
15939
  async openTerminal(argv, options) {
15940
+ tagOption(options.tag);
15380
15941
  const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
15381
15942
  const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
15382
15943
  if (!planned) throw new Error(`${file}: command not found`);
@@ -15405,7 +15966,13 @@ var Launcher = class {
15405
15966
  );
15406
15967
  const report = (message3) => options.onData(encoder3.encode(`${message3}\r
15407
15968
  `));
15408
- const handle = await this.launch(planned, { env, cwd, fds, report, ...this.identity(account) });
15969
+ const handle = await this.launch(planned, {
15970
+ env,
15971
+ cwd,
15972
+ fds,
15973
+ report,
15974
+ ...this.identity(account, options.tag)
15975
+ });
15409
15976
  leader = handle.pid;
15410
15977
  void handle.exited.then(() => tty.hangup());
15411
15978
  return {
@@ -15421,6 +15988,7 @@ var Launcher = class {
15421
15988
  };
15422
15989
  }
15423
15990
  async run(argv, options = {}) {
15991
+ tagOption(options.tag);
15424
15992
  const account = await this.account(options.user);
15425
15993
  const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
15426
15994
  const env = this.environment(cwd, options.env, account);
@@ -15450,7 +16018,7 @@ var Launcher = class {
15450
16018
  fds,
15451
16019
  report,
15452
16020
  ...group,
15453
- ...this.identity(account)
16021
+ ...this.identity(account, options.tag)
15454
16022
  });
15455
16023
  options.onStarted?.(handle.pid);
15456
16024
  const status = await handle.exited;
@@ -15511,8 +16079,37 @@ function remotes(port, req) {
15511
16079
  ...req.uplink ? { uplink: new RemoteUplink(port, req.uplink.traits, req.uplink.routes) } : {}
15512
16080
  };
15513
16081
  }
15514
- function answered(data, remote, uplink) {
15515
- if (data.net !== void 0) remote?.receive(data);
16082
+ function dial(l, req) {
16083
+ const socket = dialSocket(l.net, req.port, req.host);
16084
+ const { port1, port2 } = new MessageChannel();
16085
+ serveSocket(socket, port1);
16086
+ return port2;
16087
+ }
16088
+ function sudoApprovals(port) {
16089
+ const waiting = /* @__PURE__ */ new Map();
16090
+ return {
16091
+ ask: (req, { signal }) => new Promise((resolve) => {
16092
+ waiting.set(req.id, resolve);
16093
+ signal.addEventListener(
16094
+ "abort",
16095
+ () => {
16096
+ waiting.delete(req.id);
16097
+ port.postMessage({ sudoAbort: { id: req.id, reason: String(signal.reason) } });
16098
+ resolve("deny");
16099
+ },
16100
+ { once: true }
16101
+ );
16102
+ port.postMessage({ sudoAsk: req });
16103
+ }),
16104
+ answer({ id: id2, answer }) {
16105
+ waiting.get(id2)?.(answer);
16106
+ waiting.delete(id2);
16107
+ }
16108
+ };
16109
+ }
16110
+ function answered(data, remote, uplink, approvals) {
16111
+ if (data.sudoAnswer) approvals.answer(data.sudoAnswer);
16112
+ else if (data.net !== void 0) remote?.receive(data);
15516
16113
  else if (typeof data.uplink === "string") uplink?.receive(data);
15517
16114
  else return false;
15518
16115
  return true;
@@ -15557,6 +16154,7 @@ function serveKernel(port, deps) {
15557
16154
  const terminals = /* @__PURE__ */ new Map();
15558
16155
  const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
15559
16156
  const clients = /* @__PURE__ */ new Set();
16157
+ const approvals = sudoApprovals(port);
15560
16158
  const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
15561
16159
  let holding;
15562
16160
  function holdLock(locks2) {
@@ -15570,8 +16168,9 @@ function serveKernel(port, deps) {
15570
16168
  });
15571
16169
  return holding;
15572
16170
  }
15573
- async function connect(user) {
16171
+ async function connect(user, tag) {
15574
16172
  const l = await ready2();
16173
+ tagOption(tag);
15575
16174
  if (user !== void 0 && !await l.users.lookup(user)) {
15576
16175
  throw fsError("ENOENT", `no user ${String(user)}`);
15577
16176
  }
@@ -15581,6 +16180,7 @@ function serveKernel(port, deps) {
15581
16180
  launcher: ready2,
15582
16181
  signal: signalNumber,
15583
16182
  ...user !== void 0 ? { user } : {},
16183
+ ...tag !== void 0 ? { tag } : {},
15584
16184
  ...locks ? { locks } : {},
15585
16185
  ...held ? { lock: held } : {}
15586
16186
  });
@@ -15588,12 +16188,6 @@ function serveKernel(port, deps) {
15588
16188
  void served.closed.then(() => clients.delete(served));
15589
16189
  return port2;
15590
16190
  }
15591
- async function dial(req) {
15592
- const socket = dialSocket((await ready2()).net, req.port, req.host);
15593
- const { port1, port2 } = new MessageChannel();
15594
- serveSocket(socket, port1);
15595
- return port2;
15596
- }
15597
16191
  async function ready2() {
15598
16192
  if (!launcher) throw new Error("the kernel is not initialized");
15599
16193
  return launcher;
@@ -15625,6 +16219,7 @@ function serveKernel(port, deps) {
15625
16219
  ...req.modules ? { modules: req.modules } : {},
15626
16220
  ...req.env ? { env: req.env } : {},
15627
16221
  ...hostOptions(req, port),
16222
+ ...req.approve ? { approve: approvals.ask } : {},
15628
16223
  ...net,
15629
16224
  ...media ? { media } : {},
15630
16225
  onMountPending: (medium) => port.postMessage({ medium }),
@@ -15649,13 +16244,13 @@ function serveKernel(port, deps) {
15649
16244
  async function handle(req) {
15650
16245
  if (req.op === "init") return init(req);
15651
16246
  if (req.op === "open-terminal") return terminal(req);
15652
- if (req.op === "connect") return connect(req.user);
16247
+ if (req.op === "connect") return connect(req.user, req.tag);
15653
16248
  if (isUsersRequest(req)) return usersOp(await ready2(), req);
15654
16249
  if (req.op === "routes") {
15655
16250
  (await ready2()).setRoutes(req.routes);
15656
16251
  return true;
15657
16252
  }
15658
- if (req.op === "dial") return dial(req);
16253
+ if (req.op === "dial") return dial(await ready2(), req);
15659
16254
  if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
15660
16255
  return mountOp(req);
15661
16256
  if (req.op === "terminal") {
@@ -15703,7 +16298,7 @@ function serveKernel(port, deps) {
15703
16298
  policies.delete(answer.id);
15704
16299
  return;
15705
16300
  }
15706
- if (answered(event.data, remote, uplink)) return;
16301
+ if (answered(event.data, remote, uplink, approvals)) return;
15707
16302
  const req = event.data;
15708
16303
  handle(req).then(
15709
16304
  (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),