@ai-ecoverse/slicc-kernel 1.43.1 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -1061,8 +1061,8 @@ function serveSocket(socket, port, onClose) {
1061
1061
  let writes = Promise.resolve();
1062
1062
  let credit = 0;
1063
1063
  let granted2;
1064
- const send = (message2, transfer) => {
1065
- if (!closed) port.postMessage(message2, transfer);
1064
+ const send = (message3, transfer) => {
1065
+ if (!closed) port.postMessage(message3, transfer);
1066
1066
  };
1067
1067
  const close = (reset = false) => {
1068
1068
  if (closed) return;
@@ -1194,8 +1194,8 @@ async function readWithin(reader, ms) {
1194
1194
  var INBOUND_LIMIT = 16 * 1024 * 1024;
1195
1195
  var MESSAGE_LIMIT = 64 * 1024 * 1024;
1196
1196
  var TOO_BIG = 1009;
1197
- function payloadSize(message2) {
1198
- return typeof message2 === "string" ? new TextEncoder().encode(message2).length : message2.length;
1197
+ function payloadSize(message3) {
1198
+ return typeof message3 === "string" ? new TextEncoder().encode(message3).length : message3.length;
1199
1199
  }
1200
1200
  var MessageQueue = class {
1201
1201
  items = [];
@@ -1215,9 +1215,9 @@ var MessageQueue = class {
1215
1215
  this.settle = resolve;
1216
1216
  });
1217
1217
  }
1218
- push(message2) {
1218
+ push(message3) {
1219
1219
  if (this.ended) return;
1220
- const size = payloadSize(message2);
1220
+ const size = payloadSize(message3);
1221
1221
  const huge = size > MESSAGE_LIMIT;
1222
1222
  if (huge || this.items.length > 0 && this.size + size > this.limit) {
1223
1223
  const reason = huge ? `a message is over ${MESSAGE_LIMIT} bytes` : `over ${this.limit} bytes of messages are waiting`;
@@ -1226,7 +1226,7 @@ var MessageQueue = class {
1226
1226
  return;
1227
1227
  }
1228
1228
  this.size += size;
1229
- this.items.push(message2);
1229
+ this.items.push(message3);
1230
1230
  this.wake();
1231
1231
  }
1232
1232
  end(close) {
@@ -1413,7 +1413,7 @@ function fetchTransport(options = {}) {
1413
1413
  }
1414
1414
  function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1415
1415
  const open = /* @__PURE__ */ new Map();
1416
- const fail2 = (nid, e) => {
1416
+ const fail3 = (nid, e) => {
1417
1417
  open.delete(nid);
1418
1418
  const { status, code } = e;
1419
1419
  peer.postMessage({
@@ -1449,7 +1449,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1449
1449
  headers: response.headers
1450
1450
  });
1451
1451
  } catch (e) {
1452
- fail2(call.nid, e);
1452
+ fail3(call.nid, e);
1453
1453
  }
1454
1454
  };
1455
1455
  const sockets = /* @__PURE__ */ new Map();
@@ -1483,7 +1483,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1483
1483
  peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
1484
1484
  void relay(call.nid, socket);
1485
1485
  } catch (e) {
1486
- fail2(call.nid, e);
1486
+ fail3(call.nid, e);
1487
1487
  }
1488
1488
  };
1489
1489
  const relay = async (nid, socket) => {
@@ -1555,7 +1555,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1555
1555
  peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
1556
1556
  }
1557
1557
  } catch (e) {
1558
- fail2(nid, e);
1558
+ fail3(nid, e);
1559
1559
  }
1560
1560
  };
1561
1561
  const drop = (nid) => {
@@ -1584,8 +1584,289 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1584
1584
  };
1585
1585
  }
1586
1586
 
1587
+ // src/kernel/cred.ts
1588
+ var ROOT = Object.freeze({
1589
+ ruid: 0,
1590
+ euid: 0,
1591
+ suid: 0,
1592
+ rgid: 0,
1593
+ egid: 0,
1594
+ sgid: 0,
1595
+ groups: [0]
1596
+ });
1597
+ var MAX_ID = 4294967294;
1598
+ var MAX_GROUPS = 65536;
1599
+ var UIDS = ["ruid", "euid", "suid"];
1600
+ var GIDS = ["rgid", "egid", "sgid"];
1601
+ function userCred(uid, gid, groups = [gid]) {
1602
+ return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
1603
+ }
1604
+ function copyCred(cred) {
1605
+ return { ...cred, groups: [...cred.groups] };
1606
+ }
1607
+ function id(value) {
1608
+ if (value === void 0 || value === -1 || value === 4294967295) return void 0;
1609
+ if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
1610
+ throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
1611
+ }
1612
+ return value;
1613
+ }
1614
+ function changeCred(cur, change) {
1615
+ const privileged = cur.euid === 0;
1616
+ const next2 = copyCred(cur);
1617
+ for (const keys of [UIDS, GIDS]) {
1618
+ const own = keys.map((key) => cur[key]);
1619
+ for (const key of keys) {
1620
+ const value = id(change[key]);
1621
+ if (value === void 0) continue;
1622
+ if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
1623
+ next2[key] = value;
1624
+ }
1625
+ }
1626
+ if (change.groups !== void 0) {
1627
+ if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
1628
+ throw fsError("EINVAL", "not a group list");
1629
+ }
1630
+ const groups = change.groups.map((g) => id(g) ?? Number.NaN);
1631
+ if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
1632
+ if (!privileged) throw fsError("EPERM", "setgroups");
1633
+ next2.groups = groups;
1634
+ }
1635
+ return next2;
1636
+ }
1637
+ function maySignal(sender, target) {
1638
+ if (sender.euid === 0) return true;
1639
+ return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
1640
+ }
1641
+ function maySee(viewer, target) {
1642
+ return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
1643
+ }
1644
+
1645
+ // src/users.ts
1646
+ var PASSWD = "/etc/passwd";
1647
+ var GROUP = "/etc/group";
1648
+ var DEFAULT_PASSWD = "root:x:0:0:root:/root:/bin/bash\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n";
1649
+ var DEFAULT_GROUP = "root:x:0:\nsudo:x:27:\nusers:x:100:\nnogroup:x:65534:\n";
1650
+ var NAME = /^[a-z_][a-z0-9_.-]*$/;
1651
+ var MAX_NAME = 255;
1652
+ var FIRST_UID = 1e3;
1653
+ var LAST_UID = 6e4;
1654
+ var USERS_GROUP = "users";
1655
+ var HOME_DIR = /^\/home\/[^/]+$/;
1656
+ var SYSTEM_PNPM_HOME = "/usr/local/share/pnpm";
1657
+ var isId = (text) => text !== void 0 && /^\d+$/.test(text);
1658
+ function lines(text) {
1659
+ return text.split("\n").filter((line, i, all) => line !== "" || i < all.length - 1);
1660
+ }
1661
+ function parsePasswd(text) {
1662
+ return lines(text).map((line) => {
1663
+ const [name2, , uid, gid, gecos = "", home = "/", shell = "/bin/sh"] = line.split(":");
1664
+ if (!name2 || !isId(uid) || !isId(gid)) return line;
1665
+ return { name: name2, uid: Number(uid), gid: Number(gid), gecos, home, shell };
1666
+ });
1667
+ }
1668
+ function parseGroup(text) {
1669
+ return lines(text).map((line) => {
1670
+ const [name2, , gid, members = ""] = line.split(":");
1671
+ if (!name2 || !isId(gid)) return line;
1672
+ return { name: name2, gid: Number(gid), members: members.split(",").filter(Boolean) };
1673
+ });
1674
+ }
1675
+ function formatPasswd(rows) {
1676
+ const line = (r) => `${r.name}:x:${r.uid}:${r.gid}:${r.gecos}:${r.home}:${r.shell}`;
1677
+ return `${rows.map((r) => typeof r === "string" ? r : line(r)).join("\n")}
1678
+ `;
1679
+ }
1680
+ function formatGroup(rows) {
1681
+ const line = (r) => `${r.name}:x:${r.gid}:${r.members.join(",")}`;
1682
+ return `${rows.map((r) => typeof r === "string" ? r : line(r)).join("\n")}
1683
+ `;
1684
+ }
1685
+ var parsed = (rows) => rows.filter((r) => typeof r !== "string");
1686
+ function homeOf(home) {
1687
+ if (typeof home !== "string" || !home.startsWith("/") || /[:\r\n]/.test(home)) {
1688
+ throw fsError("EINVAL", `not a home directory: ${String(home)}`);
1689
+ }
1690
+ return home;
1691
+ }
1692
+ function freeId(users, groups) {
1693
+ const taken = /* @__PURE__ */ new Set([...users.map((r) => r.uid), ...groups.map((r) => r.gid)]);
1694
+ let uid = FIRST_UID;
1695
+ while (taken.has(uid)) uid++;
1696
+ if (uid > LAST_UID) throw fsError("ENOSPC", "no free user id");
1697
+ return uid;
1698
+ }
1699
+ function credOf(account) {
1700
+ return userCred(account.uid, account.gid, account.groups);
1701
+ }
1702
+ function pnpmHomeOf(account, system = SYSTEM_PNPM_HOME) {
1703
+ return account.uid === 0 ? system : `${account.home}/.local/share/pnpm`;
1704
+ }
1705
+ function userEnv(account, system) {
1706
+ const pnpm = pnpmHomeOf(account, system);
1707
+ return {
1708
+ HOME: account.home,
1709
+ USER: account.name,
1710
+ LOGNAME: account.name,
1711
+ PNPM_HOME: pnpm,
1712
+ PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
1713
+ };
1714
+ }
1715
+ var UserDb = class {
1716
+ fs;
1717
+ umasks = /* @__PURE__ */ new Map();
1718
+ writing = Promise.resolve();
1719
+ constructor(fs) {
1720
+ this.fs = fs;
1721
+ }
1722
+ async read(path, fallback) {
1723
+ try {
1724
+ return await this.fs.readFile(path);
1725
+ } catch (err) {
1726
+ if (err?.code === "ENOENT") return fallback;
1727
+ throw err;
1728
+ }
1729
+ }
1730
+ async tables() {
1731
+ return {
1732
+ passwd: parsePasswd(await this.read(PASSWD, DEFAULT_PASSWD)),
1733
+ group: parseGroup(await this.read(GROUP, DEFAULT_GROUP))
1734
+ };
1735
+ }
1736
+ async commit(passwd, group, then) {
1737
+ const before = [await this.read(PASSWD, DEFAULT_PASSWD), await this.read(GROUP, DEFAULT_GROUP)];
1738
+ try {
1739
+ await this.fs.writeFile(GROUP, group);
1740
+ await this.fs.writeFile(PASSWD, passwd);
1741
+ await then?.();
1742
+ } catch (err) {
1743
+ await this.fs.writeFile(GROUP, before[1]);
1744
+ await this.fs.writeFile(PASSWD, before[0]);
1745
+ throw err;
1746
+ }
1747
+ }
1748
+ serial(work) {
1749
+ const next2 = this.writing.then(work);
1750
+ this.writing = next2.catch(() => void 0);
1751
+ return next2;
1752
+ }
1753
+ async ensure() {
1754
+ await this.fs.mkdir("/etc", { recursive: true });
1755
+ for (const [path, text] of [
1756
+ [PASSWD, DEFAULT_PASSWD],
1757
+ [GROUP, DEFAULT_GROUP]
1758
+ ]) {
1759
+ if (!await this.fs.exists(path)) await this.fs.writeFile(path, text);
1760
+ }
1761
+ if (!await this.fs.exists("/root")) {
1762
+ await this.fs.mkdir("/root", { recursive: true });
1763
+ try {
1764
+ await this.fs.chmod("/root", 448);
1765
+ } catch (err) {
1766
+ await this.fs.rm("/root", { recursive: true, force: true });
1767
+ throw err;
1768
+ }
1769
+ }
1770
+ }
1771
+ account(row, groups) {
1772
+ const extra = groups.filter((g) => g.members.includes(row.name) && g.gid !== row.gid);
1773
+ const umask = this.umasks.get(row.name);
1774
+ return {
1775
+ name: row.name,
1776
+ uid: row.uid,
1777
+ gid: row.gid,
1778
+ home: row.home,
1779
+ shell: row.shell,
1780
+ groups: [row.gid, ...extra.map((g) => g.gid)],
1781
+ ...umask !== void 0 ? { umask } : {}
1782
+ };
1783
+ }
1784
+ async lookup(user) {
1785
+ const { passwd, group } = await this.tables();
1786
+ const key = typeof user === "number" ? "uid" : "name";
1787
+ const row = parsed(passwd).find((r) => r[key] === user);
1788
+ return row && this.account(row, parsed(group));
1789
+ }
1790
+ async list() {
1791
+ const { passwd, group } = await this.tables();
1792
+ const groups = parsed(group);
1793
+ return parsed(passwd).map((row) => this.account(row, groups));
1794
+ }
1795
+ add(options) {
1796
+ return this.serial(async () => {
1797
+ const { name: name2 } = options;
1798
+ if (typeof name2 !== "string" || name2.length > MAX_NAME || !NAME.test(name2)) {
1799
+ throw fsError("EINVAL", `not a user name: ${String(name2)}`);
1800
+ }
1801
+ const remember = () => {
1802
+ if (options.umask !== void 0) this.umasks.set(name2, options.umask & 511);
1803
+ };
1804
+ const { passwd, group } = await this.tables();
1805
+ const users = parsed(passwd);
1806
+ const groups = parsed(group);
1807
+ const found = users.find((r) => r.name === name2);
1808
+ if (found) {
1809
+ remember();
1810
+ return this.account(found, groups);
1811
+ }
1812
+ const home = homeOf(options.home ?? `/home/${name2}`);
1813
+ const extra = [USERS_GROUP, ...options.groups ?? []].map((g) => {
1814
+ const row2 = groups.find((r) => r.name === g);
1815
+ if (!row2) throw fsError("ENOENT", `no group ${g}`);
1816
+ return row2;
1817
+ });
1818
+ if (groups.some((r) => r.name === name2)) throw fsError("EEXIST", `group ${name2} exists`);
1819
+ const uid = freeId(users, groups);
1820
+ const row = { name: name2, uid, gid: uid, gecos: name2, home, shell: "/bin/bash" };
1821
+ for (const g of extra) if (!g.members.includes(name2)) g.members.push(name2);
1822
+ group.push({ name: name2, gid: uid, members: [] });
1823
+ passwd.push(row);
1824
+ const fresh = !await this.fs.exists(home);
1825
+ await this.fs.mkdir(home, { recursive: true });
1826
+ try {
1827
+ await this.fs.mkdir("/etc", { recursive: true });
1828
+ await this.commit(formatPasswd(passwd), formatGroup(group));
1829
+ } catch (err) {
1830
+ if (fresh) await this.fs.rm(home, { recursive: true, force: true });
1831
+ throw err;
1832
+ }
1833
+ remember();
1834
+ return this.account(row, parsed(group));
1835
+ });
1836
+ }
1837
+ remove(name2, options = {}) {
1838
+ return this.serial(async () => {
1839
+ const { passwd, group } = await this.tables();
1840
+ const row = parsed(passwd).find((r) => r.name === name2);
1841
+ if (!row) return false;
1842
+ if (row.uid < FIRST_UID || row.uid > LAST_UID) {
1843
+ throw fsError("EPERM", `${name2} is a system account`);
1844
+ }
1845
+ const keptGroups = group.filter(
1846
+ (r) => typeof r === "string" || !(r.name === name2 && r.gid === row.gid)
1847
+ );
1848
+ for (const r of parsed(keptGroups)) r.members = r.members.filter((m) => m !== name2);
1849
+ const home = this.fs.resolvePath("/", row.home);
1850
+ const shared = parsed(passwd).some(
1851
+ (r) => r !== row && this.fs.resolvePath("/", r.home) === home
1852
+ );
1853
+ const removeHome = !options.keepHome && !shared && HOME_DIR.test(home) && await this.fs.exists(home);
1854
+ const staged = `/home/.${name2}.removed-${Date.now()}`;
1855
+ await this.commit(
1856
+ formatPasswd(passwd.filter((r) => r !== row)),
1857
+ formatGroup(keptGroups),
1858
+ removeHome ? () => this.fs.rename(home, staged) : void 0
1859
+ );
1860
+ this.umasks.delete(name2);
1861
+ if (removeHome)
1862
+ await this.fs.rm(staged, { recursive: true, force: true }).catch(() => void 0);
1863
+ return true;
1864
+ });
1865
+ }
1866
+ };
1867
+
1587
1868
  // src/client/protocol.ts
1588
- var PROTOCOL = [1, 7];
1869
+ var PROTOCOL = [1, 8];
1589
1870
  var FS_METHODS = [
1590
1871
  "readFile",
1591
1872
  "writeFile",
@@ -1688,6 +1969,39 @@ function dialFor(l, req, open) {
1688
1969
  );
1689
1970
  return { result: port2, transfer: [port2] };
1690
1971
  }
1972
+ async function accountOf(l, user) {
1973
+ const found = await l.users.lookup(user);
1974
+ if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
1975
+ return found;
1976
+ }
1977
+ async function runAs(l, self, requested) {
1978
+ if (requested === void 0) return self;
1979
+ const target = await accountOf(l, requested);
1980
+ if (self.uid !== 0 && target.uid !== self.uid) {
1981
+ throw fsError("EPERM", `${self.name} may not run as ${target.name}`);
1982
+ }
1983
+ return target;
1984
+ }
1985
+ function processes(l, self) {
1986
+ const viewer = credOf(self);
1987
+ return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
1988
+ ...entry,
1989
+ uid: cred.euid,
1990
+ gid: cred.egid
1991
+ }));
1992
+ }
1993
+ function killAs(l, host, self, req) {
1994
+ if (!l.kill(req.pid, signalOf(host)(req.signal), credOf(self))) {
1995
+ throw fsError("ESRCH", `pid ${req.pid}`);
1996
+ }
1997
+ return { result: true };
1998
+ }
1999
+ function terminalAction(terminals, req, host) {
2000
+ const session = terminals.get(req.terminal);
2001
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
2002
+ act(session, req, signalOf(host));
2003
+ return { result: true };
2004
+ }
1691
2005
  function killGroups(host, pgids) {
1692
2006
  if (pgids.length === 0) return;
1693
2007
  void host.launcher().then((launcher) => {
@@ -1701,9 +2015,9 @@ function joinable(l, pgid, ours) {
1701
2015
  }
1702
2016
  function cdpRegistry(send, gone) {
1703
2017
  const registered = /* @__PURE__ */ new Map();
1704
- const drop = (id) => {
1705
- registered.get(id)?.();
1706
- return registered.delete(id);
2018
+ const drop = (id2) => {
2019
+ registered.get(id2)?.();
2020
+ return registered.delete(id2);
1707
2021
  };
1708
2022
  return {
1709
2023
  serve(l, req) {
@@ -1718,7 +2032,7 @@ function cdpRegistry(send, gone) {
1718
2032
  },
1719
2033
  drop,
1720
2034
  close: () => {
1721
- for (const id of [...registered.keys()]) drop(id);
2035
+ for (const id2 of [...registered.keys()]) drop(id2);
1722
2036
  }
1723
2037
  };
1724
2038
  }
@@ -1738,16 +2052,17 @@ function watchOp(l, req, watches, send) {
1738
2052
  }
1739
2053
  function serveClient(port, host) {
1740
2054
  let state = "new";
2055
+ let self;
1741
2056
  const terminals = /* @__PURE__ */ new Map();
1742
2057
  const groups = /* @__PURE__ */ new Set();
1743
2058
  const sessions = /* @__PURE__ */ new Set();
1744
2059
  const watches = /* @__PURE__ */ new Map();
1745
2060
  let net;
1746
2061
  const ended = Promise.withResolvers();
1747
- const send = (message2, transfer = []) => {
1748
- if (state !== "closed") port.postMessage(message2, transfer);
2062
+ const send = (message3, transfer = []) => {
2063
+ if (state !== "closed") port.postMessage(message3, transfer);
1749
2064
  };
1750
- const reply = (id, body, transfer) => send({ id, ...body }, transfer);
2065
+ const reply = (id2, body, transfer) => send({ id: id2, ...body }, transfer);
1751
2066
  const cdps = cdpRegistry(send, ended.promise);
1752
2067
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
1753
2068
  if (state === "closed") return;
@@ -1764,18 +2079,19 @@ function serveClient(port, host) {
1764
2079
  port.close?.();
1765
2080
  ended.resolve();
1766
2081
  }
1767
- async function hello(message2) {
1768
- const refused = versionError(message2.protocol);
2082
+ async function hello(message3) {
2083
+ const refused = versionError(message3.protocol);
1769
2084
  if (refused) {
1770
2085
  send({ hello: { protocol: PROTOCOL, error: refused } });
1771
2086
  detach();
1772
2087
  return;
1773
2088
  }
1774
2089
  const launcher = await host.launcher();
2090
+ if (host.scope !== "transport") self = await accountOf(launcher, host.user ?? 0);
1775
2091
  const transport = host.transport?.() ?? launcher.transport;
1776
2092
  net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
1777
2093
  state = "open";
1778
- if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
2094
+ if (message3.lock && host.locks) void host.locks.request(message3.lock, () => detach());
1779
2095
  send({
1780
2096
  hello: {
1781
2097
  protocol: PROTOCOL,
@@ -1787,6 +2103,7 @@ function serveClient(port, host) {
1787
2103
  async function terminal(req, l) {
1788
2104
  const session = await l.openTerminal(req.argv, {
1789
2105
  ...req.options,
2106
+ user: await runAs(l, self, req.options.user),
1790
2107
  onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
1791
2108
  });
1792
2109
  if (state === "closed") {
@@ -1808,6 +2125,7 @@ function serveClient(port, host) {
1808
2125
  let leader;
1809
2126
  const result = await l.run(req.argv, {
1810
2127
  ...req.options,
2128
+ user: await runAs(l, self, req.options.user),
1811
2129
  collect: false,
1812
2130
  onStdout: out(1),
1813
2131
  onStderr: out(2),
@@ -1833,19 +2151,12 @@ function serveClient(port, host) {
1833
2151
  return spawn(req, l);
1834
2152
  case "open-terminal":
1835
2153
  return terminal(req, l);
1836
- case "terminal": {
1837
- const session = terminals.get(req.terminal);
1838
- if (!session) throw new Error(`no terminal ${req.terminal}`);
1839
- act(session, req, signalOf(host));
1840
- return { result: true };
1841
- }
2154
+ case "terminal":
2155
+ return terminalAction(terminals, req, host);
1842
2156
  case "kill":
1843
- if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
1844
- return { result: true };
2157
+ return killAs(l, host, self, req);
1845
2158
  case "ps":
1846
- return {
1847
- result: l.list().map(({ tid: _, ...entry }) => entry)
1848
- };
2159
+ return { result: processes(l, self) };
1849
2160
  case "fs":
1850
2161
  if (!FS_METHODS.includes(req.method)) {
1851
2162
  throw new Error(`unknown file system call ${req.method}`);
@@ -2611,9 +2922,9 @@ var UNLINKED_DIR = ".slicc-unlinked";
2611
2922
  function pidOwner(proc) {
2612
2923
  return {
2613
2924
  id: `p${proc.pid}`,
2614
- alive(id) {
2925
+ alive(id2) {
2615
2926
  try {
2616
- proc.kill(Number(id.slice(1)), 0);
2927
+ proc.kill(Number(id2.slice(1)), 0);
2617
2928
  return true;
2618
2929
  } catch (err) {
2619
2930
  return err.code !== "ESRCH";
@@ -2800,9 +3111,9 @@ var UnlinkedKeeper = class {
2800
3111
  if (!names) return;
2801
3112
  const held = query && new Set(((await query()).held ?? []).map((lock) => lock.name));
2802
3113
  for (const name2 of names) {
2803
- const id = name2.slice(0, name2.lastIndexOf("."));
2804
- const at = id.indexOf("~");
2805
- const dead = id !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id)) : owner?.alive(id.slice(0, at)) === false);
3114
+ const id2 = name2.slice(0, name2.lastIndexOf("."));
3115
+ const at = id2.indexOf("~");
3116
+ const dead = id2 !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id2)) : owner?.alive(id2.slice(0, at)) === false);
2806
3117
  if (dead) await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
2807
3118
  }
2808
3119
  await this.tidy(dir);
@@ -2813,8 +3124,8 @@ var UnlinkedKeeper = class {
2813
3124
  // src/kernel/net/http1.ts
2814
3125
  var HttpError = class extends Error {
2815
3126
  status;
2816
- constructor(status, message2) {
2817
- super(message2);
3127
+ constructor(status, message3) {
3128
+ super(message3);
2818
3129
  this.status = status;
2819
3130
  }
2820
3131
  };
@@ -2938,21 +3249,21 @@ function latin1Bytes(text) {
2938
3249
  return out;
2939
3250
  }
2940
3251
  function parseRequestHead(bytes) {
2941
- const lines = latin1(bytes).split(/\r?\n/);
2942
- while (lines.length > 0 && lines[0] === "") lines.shift();
2943
- const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
3252
+ const lines2 = latin1(bytes).split(/\r?\n/);
3253
+ while (lines2.length > 0 && lines2[0] === "") lines2.shift();
3254
+ const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines2.shift() ?? "");
2944
3255
  if (!match) throw new HttpError(400, "malformed request line");
2945
3256
  if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
2946
3257
  return {
2947
3258
  method: match[1],
2948
3259
  target: match[2],
2949
3260
  minor: Number(match[4]),
2950
- headers: parseFields(lines)
3261
+ headers: parseFields(lines2)
2951
3262
  };
2952
3263
  }
2953
- function parseFields(lines) {
3264
+ function parseFields(lines2) {
2954
3265
  const headers = [];
2955
- for (const line of lines) {
3266
+ for (const line of lines2) {
2956
3267
  if (line === "") continue;
2957
3268
  if (line.startsWith(" ") || line.startsWith(" ")) {
2958
3269
  throw new HttpError(400, "obsolete line folding");
@@ -3081,8 +3392,8 @@ var OP = {
3081
3392
  };
3082
3393
  var WsError = class extends Error {
3083
3394
  code;
3084
- constructor(code, message2) {
3085
- super(message2);
3395
+ constructor(code, message3) {
3396
+ super(message3);
3086
3397
  this.code = code;
3087
3398
  }
3088
3399
  };
@@ -3379,11 +3690,11 @@ var CdpFacade = class {
3379
3690
  clearTimeout(timer);
3380
3691
  reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
3381
3692
  };
3382
- conn.onmessage = (message2) => {
3383
- const parsed = JSON.parse(message2);
3384
- if (parsed.id !== 1) return;
3693
+ conn.onmessage = (message3) => {
3694
+ const parsed2 = JSON.parse(message3);
3695
+ if (parsed2.id !== 1) return;
3385
3696
  clearTimeout(timer);
3386
- resolve(parsed);
3697
+ resolve(parsed2);
3387
3698
  };
3388
3699
  conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
3389
3700
  }
@@ -3448,9 +3759,9 @@ var Relay = class {
3448
3759
  void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
3449
3760
  };
3450
3761
  });
3451
- this.host.onmessage = (message2) => {
3762
+ this.host.onmessage = (message3) => {
3452
3763
  if (this.ended) return;
3453
- const bytes = new TextEncoder().encode(message2);
3764
+ const bytes = new TextEncoder().encode(message3);
3454
3765
  if (bytes.length > this.max)
3455
3766
  void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
3456
3767
  else void this.send(frame(OP_TEXT, bytes));
@@ -3556,7 +3867,7 @@ function closeAll(entry) {
3556
3867
  }
3557
3868
 
3558
3869
  // src/commands.ts
3559
- var NAME = /^[A-Za-z0-9._+-]+$/;
3870
+ var NAME2 = /^[A-Za-z0-9._+-]+$/;
3560
3871
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
3561
3872
  var PACKAGE = /\$\{package\}/g;
3562
3873
  function inside(pkg, rel2) {
@@ -3645,7 +3956,7 @@ function commandsOf(pkg, manifest) {
3645
3956
  const shared = envOf(pkg, slicc.env);
3646
3957
  const out = [];
3647
3958
  for (const [name2, raw] of Object.entries(entries)) {
3648
- if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
3959
+ if (!NAME2.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
3649
3960
  continue;
3650
3961
  }
3651
3962
  const command = commandOf(pkg, name2, raw, packageAbi, shared);
@@ -3729,7 +4040,7 @@ async function scanFilesystems(fs, modules) {
3729
4040
  if (!declared || typeof declared !== "object") continue;
3730
4041
  for (const [type, entry] of Object.entries(declared)) {
3731
4042
  const module = inside(pkg, entry?.module);
3732
- if (NAME.test(type) && module && !found.has(type)) found.set(type, module);
4043
+ if (NAME2.test(type) && module && !found.has(type)) found.set(type, module);
3733
4044
  }
3734
4045
  }
3735
4046
  return found;
@@ -3749,7 +4060,7 @@ async function scanBinfmts(fs, modules, warn = () => {
3749
4060
  const declared = manifest.slicc?.binfmt;
3750
4061
  if (!declared || typeof declared !== "object") continue;
3751
4062
  for (const [suffix, command] of Object.entries(declared)) {
3752
- if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME.test(command)) continue;
4063
+ if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME2.test(command)) continue;
3753
4064
  const owner = owners.get(suffix);
3754
4065
  if (owner === void 0) {
3755
4066
  found.set(suffix, command);
@@ -4038,10 +4349,10 @@ var SpawnError = class extends Error {
4038
4349
  };
4039
4350
  function interrupted(signal) {
4040
4351
  const { promise, reject } = Promise.withResolvers();
4041
- const fail2 = () => reject(new KernelError("EINTR"));
4042
- if (signal?.aborted) fail2();
4043
- else signal?.addEventListener("abort", fail2, { once: true });
4044
- return { promise, done: () => signal?.removeEventListener("abort", fail2) };
4352
+ const fail3 = () => reject(new KernelError("EINTR"));
4353
+ if (signal?.aborted) fail3();
4354
+ else signal?.addEventListener("abort", fail3, { once: true });
4355
+ return { promise, done: () => signal?.removeEventListener("abort", fail3) };
4045
4356
  }
4046
4357
  function stoppedStatus(sig) {
4047
4358
  return (sig & 255) << 8 | 127;
@@ -4232,8 +4543,8 @@ function unref(timer) {
4232
4543
  timer.unref?.();
4233
4544
  return timer;
4234
4545
  }
4235
- function errnoError(code, message2) {
4236
- return Object.assign(new Error(`${code}: ${message2}`), { code });
4546
+ function errnoError(code, message3) {
4547
+ return Object.assign(new Error(`${code}: ${message3}`), { code });
4237
4548
  }
4238
4549
  var DriverConnection = class _DriverConnection {
4239
4550
  capabilities;
@@ -4286,11 +4597,11 @@ var DriverConnection = class _DriverConnection {
4286
4597
  }
4287
4598
  call(call, transfer = []) {
4288
4599
  if (this.failure) return Promise.reject(this.failure);
4289
- const id = ++this.nextId;
4600
+ const id2 = ++this.nextId;
4290
4601
  return new Promise((resolve, reject) => {
4291
4602
  const timer = unref(
4292
4603
  setTimeout(() => {
4293
- this.waiting.delete(id);
4604
+ this.waiting.delete(id2);
4294
4605
  const error = errnoError(
4295
4606
  "EIO",
4296
4607
  `the driver did not answer ${call.op} within ${this.timeoutMs} ms`
@@ -4300,8 +4611,8 @@ var DriverConnection = class _DriverConnection {
4300
4611
  this.onFail?.(error);
4301
4612
  }, this.timeoutMs)
4302
4613
  );
4303
- this.waiting.set(id, { resolve, reject, timer });
4304
- this.port.postMessage({ ...call, id }, transfer);
4614
+ this.waiting.set(id2, { resolve, reject, timer });
4615
+ this.port.postMessage({ ...call, id: id2 }, transfer);
4305
4616
  });
4306
4617
  }
4307
4618
  fail(error) {
@@ -4376,22 +4687,22 @@ var HeldPaths = class extends Map {
4376
4687
  }
4377
4688
  isKept(path) {
4378
4689
  const ids = this.handlesAt(path);
4379
- return ids.length === 0 ? this.unlinked.has(path) : ids.every((id) => this.keptHandles.has(id));
4690
+ return ids.length === 0 ? this.unlinked.has(path) : ids.every((id2) => this.keptHandles.has(id2));
4380
4691
  }
4381
4692
  handlesAt(path) {
4382
- return [...this.handles].filter(([, key]) => key === path).map(([id]) => id);
4693
+ return [...this.handles].filter(([, key]) => key === path).map(([id2]) => id2);
4383
4694
  }
4384
4695
  keptFor(handle) {
4385
4696
  return this.keptHandles.get(handle);
4386
4697
  }
4387
4698
  keep(path, file) {
4388
- const fresh = this.handlesAt(path).filter((id) => !this.keptHandles.has(id));
4389
- for (const id of fresh) this.keptHandles.set(id, file);
4699
+ const fresh = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
4700
+ for (const id2 of fresh) this.keptHandles.set(id2, file);
4390
4701
  this.uses.set(file, Math.max(fresh.length, 1));
4391
4702
  this.unlinked.set(path, file);
4392
4703
  }
4393
4704
  unkeep(path, file) {
4394
- for (const [id, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id);
4705
+ for (const [id2, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id2);
4395
4706
  this.uses.delete(file);
4396
4707
  if (this.unlinked.get(path) === file) this.unlinked.delete(path);
4397
4708
  }
@@ -4428,7 +4739,7 @@ var HeldPaths = class extends Map {
4428
4739
  if (count > 0) this.opens.set(key, count);
4429
4740
  else {
4430
4741
  this.opens.delete(key);
4431
- for (const id of this.handlesAt(key)) this.handles.delete(id);
4742
+ for (const id2 of this.handlesAt(key)) this.handles.delete(id2);
4432
4743
  const kept = this.unlinked.get(key);
4433
4744
  if (kept) this.letGo(kept, true);
4434
4745
  for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
@@ -4475,8 +4786,8 @@ var HeldPaths = class extends Map {
4475
4786
  this.opens.delete(path);
4476
4787
  this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
4477
4788
  }
4478
- for (const [id, key] of this.handles) {
4479
- if (within3(key, from)) this.handles.set(id, to + key.slice(from.length));
4789
+ for (const [id2, key] of this.handles) {
4790
+ if (within3(key, from)) this.handles.set(id2, to + key.slice(from.length));
4480
4791
  }
4481
4792
  }
4482
4793
  isRevoked(path) {
@@ -5215,12 +5526,12 @@ function syncError(code, label) {
5215
5526
 
5216
5527
  // src/realm/sync-fs-dispatch.ts
5217
5528
  function toErrno(err) {
5218
- const message2 = err instanceof Error ? err.message : String(err);
5529
+ const message3 = err instanceof Error ? err.message : String(err);
5219
5530
  const code = err?.code;
5220
5531
  if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
5221
- return { ok: false, errno: code, message: message2 };
5532
+ return { ok: false, errno: code, message: message3 };
5222
5533
  }
5223
- return { ok: false, errno: "EIO", message: message2 };
5534
+ return { ok: false, errno: "EIO", message: message3 };
5224
5535
  }
5225
5536
  function statJson(s) {
5226
5537
  return {
@@ -5445,15 +5756,15 @@ function attachSyncSabResponder(port, sab, token, opts) {
5445
5756
  const pending = /* @__PURE__ */ new Map();
5446
5757
  let disposed = false;
5447
5758
  const { dispatch: dispatch2 } = opts;
5448
- function drop(id) {
5449
- const entry = pending.get(id);
5759
+ function drop(id2) {
5760
+ const entry = pending.get(id2);
5450
5761
  if (!entry) return;
5451
5762
  clearTimeout(entry.timer);
5452
- pending.delete(id);
5763
+ pending.delete(id2);
5453
5764
  }
5454
- function publish(id, entry, offset) {
5455
- if (Atomics.load(header3, SAB_I_REQ) !== id) {
5456
- drop(id);
5765
+ function publish(id2, entry, offset) {
5766
+ if (Atomics.load(header3, SAB_I_REQ) !== id2) {
5767
+ drop(id2);
5457
5768
  return;
5458
5769
  }
5459
5770
  const chunk2 = Math.min(window.byteLength, entry.payload.byteLength - offset);
@@ -5462,23 +5773,23 @@ function attachSyncSabResponder(port, sab, token, opts) {
5462
5773
  storeU53(header3, SAB_I_TOTAL, SAB_I_TOTAL_HI, entry.payload.byteLength);
5463
5774
  Atomics.store(header3, SAB_I_CHUNK, chunk2);
5464
5775
  storeU53(header3, SAB_I_OFFSET, SAB_I_OFFSET_HI, offset);
5465
- Atomics.store(header3, SAB_I_SEQ, id);
5776
+ Atomics.store(header3, SAB_I_SEQ, id2);
5466
5777
  Atomics.store(header3, SAB_I_STATE, SAB_STATE_READY);
5467
5778
  Atomics.add(header3, SAB_I_PUB, 1);
5468
5779
  Atomics.notify(header3, SAB_I_PUB);
5469
- if (offset + chunk2 >= entry.payload.byteLength) drop(id);
5780
+ if (offset + chunk2 >= entry.payload.byteLength) drop(id2);
5470
5781
  }
5471
- function settle(id, result) {
5782
+ function settle(id2, result) {
5472
5783
  if (disposed) return;
5473
5784
  const { status, payload } = encodeSabResult(result);
5474
- drop(id);
5785
+ drop(id2);
5475
5786
  const entry = {
5476
5787
  status,
5477
5788
  payload,
5478
- timer: setTimeout(() => drop(id), PENDING_TTL_MS)
5789
+ timer: setTimeout(() => drop(id2), PENDING_TTL_MS)
5479
5790
  };
5480
- pending.set(id, entry);
5481
- publish(id, entry, 0);
5791
+ pending.set(id2, entry);
5792
+ publish(id2, entry, 0);
5482
5793
  }
5483
5794
  const handler = (event) => {
5484
5795
  const data = event.data;
@@ -5494,14 +5805,14 @@ function attachSyncSabResponder(port, sab, token, opts) {
5494
5805
  return;
5495
5806
  }
5496
5807
  if (data.type !== SYNC_SAB_REQ_MSG || !data.req || typeof data.req !== "object") return;
5497
- const id = data.id;
5808
+ const id2 = data.id;
5498
5809
  const body = data.req;
5499
5810
  const req = { ...body, token };
5500
5811
  let dispatched;
5501
5812
  try {
5502
5813
  dispatched = dispatch2(req);
5503
5814
  } catch (err) {
5504
- settle(id, {
5815
+ settle(id2, {
5505
5816
  ok: false,
5506
5817
  errno: "EIO",
5507
5818
  message: err instanceof Error ? err.message : String(err)
@@ -5509,8 +5820,8 @@ function attachSyncSabResponder(port, sab, token, opts) {
5509
5820
  return;
5510
5821
  }
5511
5822
  void dispatched.then(
5512
- (result) => settle(id, result),
5513
- (err) => settle(id, {
5823
+ (result) => settle(id2, result),
5824
+ (err) => settle(id2, {
5514
5825
  ok: false,
5515
5826
  errno: "EIO",
5516
5827
  message: err instanceof Error ? err.message : String(err)
@@ -5523,7 +5834,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
5523
5834
  if (disposed) return;
5524
5835
  disposed = true;
5525
5836
  port.removeEventListener("message", handler);
5526
- for (const id of [...pending.keys()]) drop(id);
5837
+ for (const id2 of [...pending.keys()]) drop(id2);
5527
5838
  }
5528
5839
  };
5529
5840
  }
@@ -5583,47 +5894,47 @@ var AsyncOps = class {
5583
5894
  this.onReady = onReady;
5584
5895
  }
5585
5896
  submit(req) {
5586
- const id = this.hold();
5897
+ const id2 = this.hold();
5587
5898
  const running = new AbortController();
5588
- this.running.set(id, running);
5899
+ this.running.set(id2, running);
5589
5900
  this.run(req, running.signal).then(
5590
- (result) => this.complete(id, result),
5591
- (err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
5901
+ (result) => this.complete(id2, result),
5902
+ (err) => this.complete(id2, { ok: false, errno: "EIO", message: String(err) })
5592
5903
  );
5593
- return id;
5904
+ return id2;
5594
5905
  }
5595
5906
  resolve(value) {
5596
- const id = this.hold();
5597
- this.complete(id, json3(value));
5598
- return id;
5907
+ const id2 = this.hold();
5908
+ this.complete(id2, json3(value));
5909
+ return id2;
5599
5910
  }
5600
5911
  hold() {
5601
- const id = this.next++;
5602
- this.results.set(id, void 0);
5603
- return id;
5912
+ const id2 = this.next++;
5913
+ this.results.set(id2, void 0);
5914
+ return id2;
5604
5915
  }
5605
- take(id) {
5606
- if (!this.results.has(id)) throw new KernelError("EINVAL");
5607
- const result = this.results.get(id);
5916
+ take(id2) {
5917
+ if (!this.results.has(id2)) throw new KernelError("EINVAL");
5918
+ const result = this.results.get(id2);
5608
5919
  if (!result) throw new KernelError("EAGAIN");
5609
- this.forget(id);
5920
+ this.forget(id2);
5610
5921
  return result;
5611
5922
  }
5612
- cancel(id) {
5613
- this.running.get(id)?.abort();
5614
- this.forget(id);
5923
+ cancel(id2) {
5924
+ this.running.get(id2)?.abort();
5925
+ this.forget(id2);
5615
5926
  }
5616
- forget(id) {
5617
- this.running.delete(id);
5618
- this.results.delete(id);
5619
- const at = this.ready.indexOf(id);
5927
+ forget(id2) {
5928
+ this.running.delete(id2);
5929
+ this.results.delete(id2);
5930
+ const at = this.ready.indexOf(id2);
5620
5931
  if (at >= 0) this.ready.splice(at, 1);
5621
5932
  }
5622
5933
  async wait(timeoutMs, signal) {
5623
5934
  for (; ; ) {
5624
5935
  if (this.closed) return -1;
5625
- const id = this.ready.shift();
5626
- if (id !== void 0) return id;
5936
+ const id2 = this.ready.shift();
5937
+ if (id2 !== void 0) return id2;
5627
5938
  if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
5628
5939
  const woke = await this.sleep(timeoutMs, signal);
5629
5940
  if (!woke) return 0;
@@ -5659,11 +5970,11 @@ var AsyncOps = class {
5659
5970
  }
5660
5971
  });
5661
5972
  }
5662
- complete(id, result) {
5663
- this.running.delete(id);
5664
- if (!this.results.has(id)) return;
5665
- this.results.set(id, result);
5666
- this.ready.push(id);
5973
+ complete(id2, result) {
5974
+ this.running.delete(id2);
5975
+ if (!this.results.has(id2)) return;
5976
+ this.results.set(id2, result);
5977
+ this.ready.push(id2);
5667
5978
  this.wake();
5668
5979
  this.onReady?.();
5669
5980
  }
@@ -5917,8 +6228,8 @@ function untilAborted(response, signal) {
5917
6228
  response.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
5918
6229
  });
5919
6230
  }
5920
- function plainResponse(status, message2) {
5921
- const body = latin1Bytes(`slicc realm proxy: ${message2}
6231
+ function plainResponse(status, message3) {
6232
+ const body = latin1Bytes(`slicc realm proxy: ${message3}
5922
6233
  `);
5923
6234
  return {
5924
6235
  status,
@@ -6100,10 +6411,10 @@ var RealmProxy = class {
6100
6411
  return await untilAborted(fetching, signal);
6101
6412
  } catch (e) {
6102
6413
  if (signal.aborted) throw e;
6103
- const message2 = e instanceof Error ? e.message : String(e);
6414
+ const message3 = e instanceof Error ? e.message : String(e);
6104
6415
  const status = e?.status;
6105
6416
  const own = typeof status === "number" && status >= 400 && status <= 599 ? status : 502;
6106
- return plainResponse(own, message2 || "upstream request failed");
6417
+ return plainResponse(own, message3 || "upstream request failed");
6107
6418
  }
6108
6419
  }
6109
6420
  async relay(conn, method, minor, response, keepAlive) {
@@ -6340,8 +6651,8 @@ function name(dn) {
6340
6651
  rdns.push(set(seq(oid("2.5.4.3"), utf8(dn.commonName))));
6341
6652
  return seq(...rdns);
6342
6653
  }
6343
- function extension(id, critical, value) {
6344
- return critical ? seq(oid(id), TRUE, octets(value)) : seq(oid(id), octets(value));
6654
+ function extension(id2, critical, value) {
6655
+ return critical ? seq(oid(id2), TRUE, octets(value)) : seq(oid(id2), octets(value));
6345
6656
  }
6346
6657
  function children(der) {
6347
6658
  const out = [];
@@ -6457,9 +6768,9 @@ function pem(der) {
6457
6768
  for (let i = 0; i < der.length; i += 32768) {
6458
6769
  b64 += String.fromCharCode(...der.subarray(i, i + 32768));
6459
6770
  }
6460
- const lines = btoa(b64).match(/.{1,64}/g) ?? [];
6771
+ const lines2 = btoa(b64).match(/.{1,64}/g) ?? [];
6461
6772
  return `-----BEGIN CERTIFICATE-----
6462
- ${lines.join("\n")}
6773
+ ${lines2.join("\n")}
6463
6774
  -----END CERTIFICATE-----
6464
6775
  `;
6465
6776
  }
@@ -6499,11 +6810,11 @@ var RealmCa = class _RealmCa {
6499
6810
  static async create(owner, now) {
6500
6811
  const pair = await crypto.subtle.generateKey(ECDSA, false, ["sign", "verify"]);
6501
6812
  const spki = new Uint8Array(await crypto.subtle.exportKey("spki", pair.publicKey));
6502
- const id = Array.from(
6813
+ const id2 = Array.from(
6503
6814
  crypto.getRandomValues(new Uint8Array(4)),
6504
6815
  (b) => b.toString(16).padStart(2, "0")
6505
6816
  ).join("");
6506
- const name2 = { organization: "SLICC", commonName: `SLICC realm CA ${owner} ${id}` };
6817
+ const name2 = { organization: "SLICC", commonName: `SLICC realm CA ${owner} ${id2}` };
6507
6818
  const notAfter = now + CA_LIFETIME;
6508
6819
  const cert = await certificate({
6509
6820
  serial: randomSerial(),
@@ -6564,8 +6875,8 @@ var SCRATCH = 64 * 1024;
6564
6875
  var EOF = -1;
6565
6876
  var TlsError = class extends Error {
6566
6877
  code;
6567
- constructor(code, message2) {
6568
- super(message2);
6878
+ constructor(code, message3) {
6879
+ super(message3);
6569
6880
  this.code = code;
6570
6881
  }
6571
6882
  };
@@ -6946,10 +7257,133 @@ function enableNetwork(net, options) {
6946
7257
  return () => proxy;
6947
7258
  }
6948
7259
 
7260
+ // src/kernel/net/ws-syscalls.ts
7261
+ var WS_OPS = [
7262
+ "net-ws-open",
7263
+ "net-ws-ready",
7264
+ "net-ws-recv",
7265
+ "net-ws-wait",
7266
+ "net-ws-send",
7267
+ "net-ws-close"
7268
+ ];
7269
+ var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
7270
+ function message(e) {
7271
+ return e instanceof Error ? e.message : String(e);
7272
+ }
7273
+ function fail(errno, text) {
7274
+ return { ok: false, errno, message: text };
7275
+ }
7276
+ var WsHandles = class {
7277
+ transport;
7278
+ open = /* @__PURE__ */ new Map();
7279
+ next;
7280
+ constructor(transport, next2) {
7281
+ this.transport = transport;
7282
+ this.next = next2;
7283
+ }
7284
+ owns(handle) {
7285
+ return this.open.has(handle);
7286
+ }
7287
+ syscall(req) {
7288
+ if (req.op === "net-ws-open") return Promise.resolve(this.start(req));
7289
+ const entry = this.open.get(req.handle);
7290
+ if (!entry) return Promise.resolve(fail("EBADF", `no websocket ${req.handle}`));
7291
+ if (req.op === "net-ws-ready") return entry.ready;
7292
+ if (!entry.socket) return Promise.resolve(fail("ENOTCONN", "websocket not open"));
7293
+ if (req.op === "net-ws-recv") return this.recv(entry.socket, entry);
7294
+ if (req.op === "net-ws-wait") return this.closed(entry.socket);
7295
+ if (req.op === "net-ws-send") return Promise.resolve(this.send(entry.socket, req, entry));
7296
+ entry.ended = true;
7297
+ entry.socket.close(req.code ?? 1e3, req.reason ?? "");
7298
+ return Promise.resolve({ ok: true, kind: "void" });
7299
+ }
7300
+ start(req) {
7301
+ const handle = this.next();
7302
+ const entry = {
7303
+ abort: new AbortController(),
7304
+ ready: Promise.resolve({ ok: true, kind: "void" })
7305
+ };
7306
+ this.open.set(handle, entry);
7307
+ entry.ready = this.connect(handle, req, entry);
7308
+ return { ok: true, kind: "json", json: handle };
7309
+ }
7310
+ async connect(handle, req, entry) {
7311
+ const { transport } = this;
7312
+ if (!transport.traits.websocket || !transport.websocket) return fail("ENOTSUP", NO_WEBSOCKET2);
7313
+ let socket;
7314
+ try {
7315
+ socket = await transport.websocket({
7316
+ url: req.url,
7317
+ protocols: req.protocols,
7318
+ headers: req.headers,
7319
+ signal: entry.abort.signal
7320
+ });
7321
+ } catch (e) {
7322
+ return fail("ECONNREFUSED", message(e));
7323
+ }
7324
+ if (!this.open.has(handle)) {
7325
+ socket.close(1001, "the program went away");
7326
+ return fail("EBADF", `no websocket ${handle}`);
7327
+ }
7328
+ if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN3.test(socket.protocol))) {
7329
+ socket.close(1002, "subprotocol not offered");
7330
+ return fail("EPROTO", "the far end chose a subprotocol the program did not offer");
7331
+ }
7332
+ entry.socket = socket;
7333
+ void socket.closed.then(() => {
7334
+ entry.ended = true;
7335
+ });
7336
+ entry.messages = socket.messages[Symbol.asyncIterator]();
7337
+ return { ok: true, kind: "json", json: { protocol: socket.protocol } };
7338
+ }
7339
+ async recv(socket, entry) {
7340
+ try {
7341
+ const next2 = await entry.messages.next();
7342
+ if (!next2.done) {
7343
+ return typeof next2.value === "string" ? { ok: true, kind: "json", json: { text: next2.value } } : { ok: true, kind: "bytes", bytes: next2.value };
7344
+ }
7345
+ const closed = await socket.closed;
7346
+ return { ok: true, kind: "json", json: { code: closed.code, reason: closed.reason } };
7347
+ } catch (e) {
7348
+ return fail("ECONNRESET", message(e));
7349
+ }
7350
+ }
7351
+ async closed(socket) {
7352
+ const { code, reason } = await socket.closed;
7353
+ return { ok: true, kind: "json", json: { code, reason } };
7354
+ }
7355
+ send(socket, req, entry) {
7356
+ const data = req.text ?? req.body;
7357
+ if (entry.ended) return fail("EPIPE", "the websocket is closed");
7358
+ try {
7359
+ if (data !== void 0) socket.send(data);
7360
+ } catch (e) {
7361
+ return fail("EPIPE", message(e));
7362
+ }
7363
+ return { ok: true, kind: "json", json: socket.buffered };
7364
+ }
7365
+ close(handle) {
7366
+ const entry = this.open.get(handle);
7367
+ if (!entry) return;
7368
+ this.open.delete(handle);
7369
+ entry.abort.abort();
7370
+ entry.socket?.close(1001, "the program went away");
7371
+ }
7372
+ closeAll() {
7373
+ for (const handle of [...this.open.keys()]) this.close(handle);
7374
+ }
7375
+ };
7376
+
6949
7377
  // src/kernel/net/http-syscalls.ts
6950
- var HTTP_OPS = ["net-request", "net-read", "net-close", "net-traits"];
7378
+ var HTTP_OPS = [
7379
+ "net-request",
7380
+ "net-read",
7381
+ "net-close",
7382
+ "net-traits",
7383
+ ...WS_OPS
7384
+ ];
6951
7385
  var EMPTY = new Uint8Array(0);
6952
- function message(e) {
7386
+ function message2(e) {
6953
7387
  return e instanceof Error ? e.message : String(e);
6954
7388
  }
6955
7389
  async function* once(bytes) {
@@ -6959,10 +7393,13 @@ var HttpHandles = class {
6959
7393
  transport;
6960
7394
  open = /* @__PURE__ */ new Map();
6961
7395
  nextHandle = 0;
7396
+ ws;
6962
7397
  constructor(transport) {
6963
7398
  this.transport = transport;
7399
+ this.ws = new WsHandles(transport, () => ++this.nextHandle);
6964
7400
  }
6965
7401
  syscall(req) {
7402
+ if (req.op.startsWith("net-ws-")) return this.ws.syscall(req);
6966
7403
  if (req.op === "net-request") return this.request(req);
6967
7404
  if (req.op === "net-read") return this.read(req.handle, req.max);
6968
7405
  if (req.op === "net-traits") return Promise.resolve(this.traits());
@@ -7005,7 +7442,7 @@ var HttpHandles = class {
7005
7442
  };
7006
7443
  return { ok: true, kind: "json", json: head };
7007
7444
  } catch (e) {
7008
- const text = message(e);
7445
+ const text = message2(e);
7009
7446
  if (text === NO_TRANSPORT) return { ok: false, errno: "ENETUNREACH", message: text };
7010
7447
  const status = e.status;
7011
7448
  if (typeof status !== "number" || status === 502) {
@@ -7039,7 +7476,7 @@ var HttpHandles = class {
7039
7476
  } catch (e) {
7040
7477
  entry.done = true;
7041
7478
  const timedOut = e?.code === "ETIMEDOUT";
7042
- return { ok: false, errno: timedOut ? "ETIMEDOUT" : "EIO", message: message(e) };
7479
+ return { ok: false, errno: timedOut ? "ETIMEDOUT" : "EIO", message: message2(e) };
7043
7480
  }
7044
7481
  }
7045
7482
  const bytes = entry.left.subarray(0, Math.max(0, max));
@@ -7047,6 +7484,10 @@ var HttpHandles = class {
7047
7484
  return { ok: true, kind: "bytes", bytes };
7048
7485
  }
7049
7486
  async close(handle) {
7487
+ if (this.ws.owns(handle)) {
7488
+ this.ws.close(handle);
7489
+ return { ok: true, kind: "void" };
7490
+ }
7050
7491
  const entry = this.open.get(handle);
7051
7492
  if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
7052
7493
  this.open.delete(handle);
@@ -7055,6 +7496,7 @@ var HttpHandles = class {
7055
7496
  return { ok: true, kind: "void" };
7056
7497
  }
7057
7498
  async closeAll() {
7499
+ this.ws.closeAll();
7058
7500
  await Promise.all([...this.open.keys()].map((handle) => this.close(handle)));
7059
7501
  }
7060
7502
  };
@@ -7094,12 +7536,12 @@ function parsePrefix(text) {
7094
7536
  const parts = text.trim().split("/");
7095
7537
  if (parts.length > 2) return void 0;
7096
7538
  const [address = "", size] = parts;
7097
- const parsed = addressBits(address);
7098
- if (!parsed) return void 0;
7099
- const max = parsed.v6 ? 128 : 32;
7539
+ const parsed2 = addressBits(address);
7540
+ if (!parsed2) return void 0;
7541
+ const max = parsed2.v6 ? 128 : 32;
7100
7542
  const length2 = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
7101
7543
  if (length2 < 0 || length2 > max) return void 0;
7102
- return { ...parsed, length: length2 };
7544
+ return { ...parsed2, length: length2 };
7103
7545
  }
7104
7546
  function within4(prefix, addr) {
7105
7547
  if (prefix.v6 !== addr.v6) return false;
@@ -7125,10 +7567,10 @@ var Routes = class {
7125
7567
  this.ipv6 = ipv62;
7126
7568
  }
7127
7569
  set(table) {
7128
- const parsed = (table.prefixes ?? []).map(parsePrefix);
7129
- const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
7570
+ const parsed2 = (table.prefixes ?? []).map(parsePrefix);
7571
+ const bad = (table.prefixes ?? []).find((_, i) => !parsed2[i]);
7130
7572
  if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
7131
- this.table = parsed;
7573
+ this.table = parsed2;
7132
7574
  this.exit = table.exit === true;
7133
7575
  }
7134
7576
  classify(host) {
@@ -7187,7 +7629,7 @@ var Resolver = class {
7187
7629
  this.routes = options.routes;
7188
7630
  this.now = options.now ?? Date.now;
7189
7631
  this.timeoutMs = options.timeoutMs ?? RESOLVE_TIMEOUT_MS;
7190
- this.log = options.log ?? ((message2) => console.warn(message2));
7632
+ this.log = options.log ?? ((message3) => console.warn(message3));
7191
7633
  this.own = ownNames(options.hostname ?? DEFAULT_HOSTNAME);
7192
7634
  }
7193
7635
  async resolve(name2, family, blocking) {
@@ -7204,21 +7646,21 @@ var Resolver = class {
7204
7646
  if (!this.uplink || key === "") return [];
7205
7647
  const asked = this.uplink.traits.ipv6 ? family : 4;
7206
7648
  if (family === 6 && asked !== 6) return [];
7207
- const id = `${asked}:${key}`;
7208
- const hit = this.cache.get(id);
7649
+ const id2 = `${asked}:${key}`;
7650
+ const hit = this.cache.get(id2);
7209
7651
  if (hit && hit.expires > this.now()) return hit.addresses;
7210
7652
  const signal = blocking?.();
7211
7653
  if (signal?.aborted) throw new KernelError("EINTR");
7212
- let lookup = this.pending.get(id);
7654
+ let lookup = this.pending.get(id2);
7213
7655
  if (!lookup) {
7214
7656
  const abort = new AbortController();
7215
- lookup = { work: this.lookup(key, asked, id, abort.signal), abort, waiters: 0 };
7216
- this.pending.set(id, lookup);
7217
- void lookup.work.then(() => this.pending.delete(id));
7657
+ lookup = { work: this.lookup(key, asked, id2, abort.signal), abort, waiters: 0 };
7658
+ this.pending.set(id2, lookup);
7659
+ void lookup.work.then(() => this.pending.delete(id2));
7218
7660
  }
7219
7661
  return waitFor(lookup, signal);
7220
7662
  }
7221
- async lookup(name2, family, id, abandoned) {
7663
+ async lookup(name2, family, id2, abandoned) {
7222
7664
  const uplink = this.uplink;
7223
7665
  const timeout = AbortSignal.any([AbortSignal.timeout(this.timeoutMs), abandoned]);
7224
7666
  let answer;
@@ -7235,7 +7677,7 @@ var Resolver = class {
7235
7677
  return [];
7236
7678
  }
7237
7679
  const addresses = answer.addresses.filter((a) => this.usable(name2, a, family));
7238
- if (addresses.length > 0) this.remember(id, addresses, answer.ttl);
7680
+ if (addresses.length > 0) this.remember(id2, addresses, answer.ttl);
7239
7681
  return addresses;
7240
7682
  }
7241
7683
  usable(name2, address, family) {
@@ -7247,11 +7689,11 @@ var Resolver = class {
7247
7689
  this.log(`slicc-kernel: dropped the uplink's answer ${address} for ${name2}: it is ${kind}`);
7248
7690
  return false;
7249
7691
  }
7250
- remember(id, addresses, ttl) {
7692
+ remember(id2, addresses, ttl) {
7251
7693
  const seconds = Math.min(ttl ?? RESOLVE_TTL_MAX, RESOLVE_TTL_MAX);
7252
7694
  if (seconds <= 0) return;
7253
- this.cache.delete(id);
7254
- this.cache.set(id, { addresses, expires: this.now() + seconds * 1e3 });
7695
+ this.cache.delete(id2);
7696
+ this.cache.set(id2, { addresses, expires: this.now() + seconds * 1e3 });
7255
7697
  if (this.cache.size > RESOLVE_CACHE_MAX) {
7256
7698
  this.cache.delete(this.cache.keys().next().value);
7257
7699
  }
@@ -7747,10 +8189,10 @@ function ready(fds, read, write) {
7747
8189
  }
7748
8190
  function interruption(signal) {
7749
8191
  const { promise, reject } = Promise.withResolvers();
7750
- const fail2 = () => reject(new KernelError("EINTR"));
7751
- if (signal.aborted) fail2();
7752
- else signal.addEventListener("abort", fail2, { once: true });
7753
- return { promise, done: () => signal.removeEventListener("abort", fail2) };
8192
+ const fail3 = () => reject(new KernelError("EINTR"));
8193
+ if (signal.aborted) fail3();
8194
+ else signal.addEventListener("abort", fail3, { once: true });
8195
+ return { promise, done: () => signal.removeEventListener("abort", fail3) };
7754
8196
  }
7755
8197
  async function selectFds(fds, read, write, timeoutMs, interrupt) {
7756
8198
  const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
@@ -8693,6 +9135,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
8693
9135
  "fd-list",
8694
9136
  "proc-alarm",
8695
9137
  "proc-umask",
9138
+ "proc-cred",
9139
+ "proc-setcred",
8696
9140
  "dl-log",
8697
9141
  "fd-renumber",
8698
9142
  "fd-promote",
@@ -8743,6 +9187,7 @@ var WasmProcess = class {
8743
9187
  interrupt = new AbortController();
8744
9188
  dying = false;
8745
9189
  umask = DEFAULT_UMASK;
9190
+ cred;
8746
9191
  execChild;
8747
9192
  killed = false;
8748
9193
  execTermsig;
@@ -8762,6 +9207,7 @@ var WasmProcess = class {
8762
9207
  this.options = options;
8763
9208
  this.inherited = options.ignored ?? 0;
8764
9209
  this.umask = options.umask ?? DEFAULT_UMASK;
9210
+ this.cred = copyCred(options.cred ?? ROOT);
8765
9211
  this.children = new ChildTable(fds, options.spawner, options.forker);
8766
9212
  this.nodes = options.nodes ?? new VfsNodes(options.fs);
8767
9213
  if (!options.nodes) options.openFiles?.add(this.nodes);
@@ -9278,6 +9724,11 @@ var WasmProcess = class {
9278
9724
  return { ok: true, kind: "void" };
9279
9725
  case "proc-umask":
9280
9726
  return { ok: true, kind: "json", json: this.setUmask(req.mask) };
9727
+ case "proc-cred":
9728
+ return { ok: true, kind: "json", json: copyCred(this.cred) };
9729
+ case "proc-setcred":
9730
+ this.cred = changeCred(this.cred, req.change);
9731
+ return { ok: true, kind: "json", json: copyCred(this.cred) };
9281
9732
  case "dl-log":
9282
9733
  if (req.append) this.dlLog.push(req.append);
9283
9734
  return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
@@ -9372,9 +9823,9 @@ var KILL_GRACE_MS = 2e3;
9372
9823
  var descIds = /* @__PURE__ */ new WeakMap();
9373
9824
  var nextDescId = 1;
9374
9825
  function descId(file) {
9375
- const id = descIds.get(file) ?? nextDescId++;
9376
- descIds.set(file, id);
9377
- return id;
9826
+ const id2 = descIds.get(file) ?? nextDescId++;
9827
+ descIds.set(file, id2);
9828
+ return id2;
9378
9829
  }
9379
9830
  function inheritedFds(fds) {
9380
9831
  return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
@@ -9491,7 +9942,8 @@ function processInit(opts, sab, umask, lane) {
9491
9942
  function inherited(opts) {
9492
9943
  return {
9493
9944
  ...opts.ignored ? { ignored: opts.ignored } : {},
9494
- ...opts.umask !== void 0 ? { umask: opts.umask } : {}
9945
+ ...opts.umask !== void 0 ? { umask: opts.umask } : {},
9946
+ ...opts.cred ? { cred: opts.cred } : {}
9495
9947
  };
9496
9948
  }
9497
9949
  function holdingToken(opts, holds) {
@@ -9655,6 +10107,7 @@ function spawnWasmProcess(opts) {
9655
10107
  ignoredSignals: (fork) => process2.inheritable(fork),
9656
10108
  signalMasks: () => process2.signalMasks(),
9657
10109
  umask: () => process2.umask,
10110
+ cred: () => copyCred(process2.cred),
9658
10111
  writesBack: () => opts.program.abi === "wasi" && !done4
9659
10112
  };
9660
10113
  }
@@ -9713,6 +10166,9 @@ var JobTable = class {
9713
10166
  pgidOf(pid) {
9714
10167
  return this.members.get(pid)?.pgid;
9715
10168
  }
10169
+ sidOf(pid) {
10170
+ return this.members.get(pid)?.sid;
10171
+ }
9716
10172
  member(pid) {
9717
10173
  const member = this.members.get(pid);
9718
10174
  if (!member) throw new KernelError("ESRCH");
@@ -9721,27 +10177,27 @@ var JobTable = class {
9721
10177
  idOf(pid) {
9722
10178
  return this.members.get(pid)?.shownAs ?? pid;
9723
10179
  }
9724
- byId(id) {
9725
- for (const member of this.members.values()) if (member.shownAs === id) return member;
9726
- return this.member(id);
10180
+ byId(id2) {
10181
+ for (const member of this.members.values()) if (member.shownAs === id2) return member;
10182
+ return this.member(id2);
9727
10183
  }
9728
10184
  setpgid(caller, pid, pgid) {
9729
10185
  const me = this.idOf(caller);
9730
10186
  const target = this.byId(pid || me);
9731
- const id = this.idOf(target.pid);
9732
- const group = pgid || id;
10187
+ const id2 = this.idOf(target.pid);
10188
+ const group = pgid || id2;
9733
10189
  if (group < 0) throw new KernelError("EINVAL");
9734
10190
  const self = this.member(caller);
9735
- if (id !== me) {
10191
+ if (id2 !== me) {
9736
10192
  if (target.ppid === void 0 || this.idOf(target.ppid) !== me) {
9737
10193
  throw new KernelError("ESRCH");
9738
10194
  }
9739
10195
  if (target.execed) throw new KernelError("EACCES");
9740
10196
  }
9741
10197
  if (target.sid !== self.sid) throw new KernelError("EPERM");
9742
- if (id === target.sid) throw new KernelError("EPERM");
10198
+ if (id2 === target.sid) throw new KernelError("EPERM");
9743
10199
  const exists = [...this.members.values()].some((m) => m.pgid === group && m.sid === target.sid);
9744
- if (group !== id && !exists) throw new KernelError("EPERM");
10200
+ if (group !== id2 && !exists) throw new KernelError("EPERM");
9745
10201
  target.pgid = group;
9746
10202
  }
9747
10203
  getpgid(caller, pid) {
@@ -9752,12 +10208,12 @@ var JobTable = class {
9752
10208
  }
9753
10209
  setsid(pid) {
9754
10210
  const member = this.member(pid);
9755
- const id = this.idOf(pid);
9756
- if (member.pgid === id) throw new KernelError("EPERM");
9757
- member.sid = id;
9758
- member.pgid = id;
9759
- this.terminals.delete(id);
9760
- return id;
10211
+ const id2 = this.idOf(pid);
10212
+ if (member.pgid === id2) throw new KernelError("EPERM");
10213
+ member.sid = id2;
10214
+ member.pgid = id2;
10215
+ this.terminals.delete(id2);
10216
+ return id2;
9761
10217
  }
9762
10218
  terminalNamed(name2) {
9763
10219
  return this.knownTerminals().find((tty) => tty.name === name2);
@@ -9774,15 +10230,17 @@ var JobTable = class {
9774
10230
  }
9775
10231
  acquireTerminal(pid, tty) {
9776
10232
  const member = this.members.get(pid);
9777
- const id = this.idOf(pid);
9778
- if (!member || member.sid !== id || this.terminals.has(id)) return false;
10233
+ const id2 = this.idOf(pid);
10234
+ if (!member || member.sid !== id2 || this.terminals.has(id2)) return false;
9779
10235
  if ([...this.terminals.values()].includes(tty)) return false;
9780
- this.terminals.set(id, tty);
10236
+ this.terminals.set(id2, tty);
9781
10237
  this.foreground.set(tty, member.pgid);
9782
10238
  return true;
9783
10239
  }
9784
- killGroup(pgid, sig) {
9785
- const targets = [...this.members.values()].filter((m) => m.pgid === pgid);
10240
+ killGroup(pgid, sig, may = () => true) {
10241
+ const group = [...this.members.values()].filter((m) => m.pgid === pgid);
10242
+ const targets = group.filter((m) => may(m.pid));
10243
+ if (targets.length === 0 && group.length > 0) throw new KernelError("EPERM");
9786
10244
  const pids = new Set(targets.map((m) => m.pid));
9787
10245
  if (sig !== 0) {
9788
10246
  for (const m of targets)
@@ -9883,8 +10341,8 @@ var SettlingChildren = class {
9883
10341
  };
9884
10342
 
9885
10343
  // src/mount/driver.ts
9886
- function fsError2(code, message2 = code) {
9887
- return Object.assign(new Error(message2), { code });
10344
+ function fsError2(code, message3 = code) {
10345
+ return Object.assign(new Error(message3), { code });
9888
10346
  }
9889
10347
  async function dispatch(h, req) {
9890
10348
  switch (req.op) {
@@ -9934,15 +10392,15 @@ async function dispatch(h, req) {
9934
10392
  }
9935
10393
  function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
9936
10394
  let open = false;
9937
- const reply = (message2, transfer = []) => port.postMessage(message2, transfer);
9938
- async function hello(message2) {
9939
- const refused = driverVersionError(message2.protocol);
10395
+ const reply = (message3, transfer = []) => port.postMessage(message3, transfer);
10396
+ async function hello(message3) {
10397
+ const refused = driverVersionError(message3.protocol);
9940
10398
  if (refused) {
9941
10399
  reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused, errno: "EPROTO" } });
9942
10400
  return;
9943
10401
  }
9944
10402
  try {
9945
- const refined = await handlers.mount?.(message2.mount);
10403
+ const refined = await handlers.mount?.(message3.mount);
9946
10404
  open = true;
9947
10405
  reply({
9948
10406
  hello: { protocol: DRIVER_PROTOCOL, capabilities: { ...capabilities, ...refined } }
@@ -10244,7 +10702,7 @@ function unescape(field2) {
10244
10702
  );
10245
10703
  }
10246
10704
  function parseFstab(text) {
10247
- const lines = [];
10705
+ const lines2 = [];
10248
10706
  text.split("\n").forEach((raw, index) => {
10249
10707
  const content = raw.replace(/#.*$/, "").trim();
10250
10708
  if (!content) return;
@@ -10253,12 +10711,12 @@ function parseFstab(text) {
10253
10711
  if (options.split(",").includes("noauto")) return;
10254
10712
  const spec = { source, target, type, options: {} };
10255
10713
  try {
10256
- lines.push({ line: index + 1, spec: { ...spec, options: parseMountOptions(options) } });
10714
+ lines2.push({ line: index + 1, spec: { ...spec, options: parseMountOptions(options) } });
10257
10715
  } catch (err) {
10258
- lines.push({ line: index + 1, spec, invalid: err });
10716
+ lines2.push({ line: index + 1, spec, invalid: err });
10259
10717
  }
10260
10718
  });
10261
- return lines;
10719
+ return lines2;
10262
10720
  }
10263
10721
  function codeOf3(err) {
10264
10722
  const code = err?.code;
@@ -10276,11 +10734,11 @@ function wait(ms, signal) {
10276
10734
  signal.addEventListener("abort", done4, { once: true });
10277
10735
  });
10278
10736
  }
10279
- async function mountFstab(lines, mount, signal, retries = FSTAB_RETRIES, discard = () => {
10737
+ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discard = () => {
10280
10738
  }, settled = () => {
10281
10739
  }) {
10282
10740
  return Promise.all(
10283
- lines.map(async (each) => {
10741
+ lines2.map(async (each) => {
10284
10742
  const result = await mountLine(each, mount, signal, retries, discard);
10285
10743
  settled(each, result);
10286
10744
  return result;
@@ -10691,8 +11149,8 @@ async function openHostfs(spec, hook, fetch2, timing = {}) {
10691
11149
  function memoryMedia() {
10692
11150
  const handles = /* @__PURE__ */ new Map();
10693
11151
  return {
10694
- get: async (id) => handles.get(id),
10695
- put: async (id, handle) => void handles.set(id, handle)
11152
+ get: async (id2) => handles.get(id2),
11153
+ put: async (id2, handle) => void handles.set(id2, handle)
10696
11154
  };
10697
11155
  }
10698
11156
 
@@ -10936,10 +11394,10 @@ function importedMemory(bytes) {
10936
11394
  return { initial, maximum, shared: (flags & 2) !== 0 };
10937
11395
  };
10938
11396
  while (at < bytes.length) {
10939
- const id = bytes[at++];
11397
+ const id2 = bytes[at++];
10940
11398
  const size = u32();
10941
11399
  const end = at + size;
10942
- if (id !== 2) {
11400
+ if (id2 !== 2) {
10943
11401
  at = end;
10944
11402
  continue;
10945
11403
  }
@@ -11059,18 +11517,17 @@ function foreignImports(bytes) {
11059
11517
  const r = new Reader(bytes);
11060
11518
  let types = [];
11061
11519
  while (r.at < bytes.length) {
11062
- const id = r.byte();
11520
+ const id2 = r.byte();
11063
11521
  const end = r.u32() + r.at;
11064
- if (id === 1) types = readTypes(r);
11522
+ if (id2 === 1) types = readTypes(r);
11065
11523
  if (!types) return void 0;
11066
- if (id === 2) return readImports(r, types);
11524
+ if (id2 === 2) return readImports(r, types);
11067
11525
  r.at = end;
11068
11526
  }
11069
11527
  return {};
11070
11528
  }
11071
11529
 
11072
11530
  // src/launcher.ts
11073
- var PNPM_HOME = "/home/.local/share/pnpm";
11074
11531
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
11075
11532
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
11076
11533
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -11170,6 +11627,8 @@ var Launcher = class {
11170
11627
  zombies = /* @__PURE__ */ new Set();
11171
11628
  orphans = /* @__PURE__ */ new Set();
11172
11629
  described = /* @__PURE__ */ new Map();
11630
+ creds = /* @__PURE__ */ new Map();
11631
+ users;
11173
11632
  jobs = new JobTable();
11174
11633
  locks = new LockTable();
11175
11634
  settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
@@ -11221,7 +11680,7 @@ var Launcher = class {
11221
11680
  this.hostfs = options;
11222
11681
  this.fstabRetries = options.fstabRetries ?? FSTAB_RETRIES;
11223
11682
  this.processMounts = options.processMounts ?? true;
11224
- this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
11683
+ this.pnpmHome = options.env?.PNPM_HOME ?? SYSTEM_PNPM_HOME;
11225
11684
  this.modulesDir = options.modules ?? "/node_modules";
11226
11685
  this.cdp = new CdpHosts(options.cdp);
11227
11686
  const hostname = options.hostname ?? DEFAULT_HOSTNAME;
@@ -11257,6 +11716,7 @@ var Launcher = class {
11257
11716
  }
11258
11717
  );
11259
11718
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
11719
+ this.users = new UserDb(this.fs);
11260
11720
  void this.unlinked.sweep("/");
11261
11721
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
11262
11722
  this.catalog = void 0;
@@ -11272,10 +11732,10 @@ var Launcher = class {
11272
11732
  }
11273
11733
  interpreters() {
11274
11734
  this.binfmts ??= this.roots().then(
11275
- (roots) => scanBinfmts(this.base, roots, (message2) => {
11276
- if (this.warned.has(message2)) return;
11277
- this.warned.add(message2);
11278
- console.warn(message2);
11735
+ (roots) => scanBinfmts(this.base, roots, (message3) => {
11736
+ if (this.warned.has(message3)) return;
11737
+ this.warned.add(message3);
11738
+ console.warn(message3);
11279
11739
  })
11280
11740
  );
11281
11741
  return this.binfmts;
@@ -11457,6 +11917,7 @@ var Launcher = class {
11457
11917
  },
11458
11918
  ...req.ignored ? { ignored: req.ignored } : {},
11459
11919
  ...req.umask !== void 0 ? { umask: req.umask } : {},
11920
+ cred: req.cred,
11460
11921
  program: req.program,
11461
11922
  argv0: req.argv0,
11462
11923
  args: req.args,
@@ -11468,9 +11929,9 @@ var Launcher = class {
11468
11929
  onError: req.report,
11469
11930
  spawner: this.spawner(pid, req.report),
11470
11931
  forker: this.forker(pid, req),
11471
- kill: (target, sig) => this.kill(target, sig),
11932
+ kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
11472
11933
  writesBack: (target) => this.processes.get(target)?.writesBack() === true,
11473
- processes: () => ({ boot: this.boot, processes: this.list() }),
11934
+ processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
11474
11935
  openFiles: this.openFiles,
11475
11936
  nodes: this.nodes,
11476
11937
  held: this.held,
@@ -11490,6 +11951,8 @@ var Launcher = class {
11490
11951
  ...this.shownIds(req)
11491
11952
  });
11492
11953
  this.processes.set(pid, handle);
11954
+ const { cred } = req;
11955
+ this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
11493
11956
  if (req.ppid !== void 0) this.settling.started(pid, req.ppid);
11494
11957
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
11495
11958
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
@@ -11516,12 +11979,13 @@ var Launcher = class {
11516
11979
  forget(pid) {
11517
11980
  this.jobs.remove(pid);
11518
11981
  this.described.delete(pid);
11982
+ this.creds.delete(pid);
11519
11983
  }
11520
11984
  reaped(pid) {
11521
11985
  if (this.zombies.delete(pid)) this.forget(pid);
11522
11986
  else this.orphans.add(pid);
11523
11987
  }
11524
- list() {
11988
+ list(viewer = ROOT) {
11525
11989
  const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
11526
11990
  const rootOf = (member) => {
11527
11991
  let root = member;
@@ -11539,6 +12003,8 @@ var Launcher = class {
11539
12003
  for (const member of members.values()) {
11540
12004
  const described = this.described.get(member.pid);
11541
12005
  if (member.execed || !described) continue;
12006
+ const cred = this.credOf(member.pid);
12007
+ if (!maySee(viewer, cred)) continue;
11542
12008
  const root = rootOf(member);
11543
12009
  listed.push({
11544
12010
  pid: root.pid,
@@ -11551,6 +12017,7 @@ var Launcher = class {
11551
12017
  state: this.processes.has(member.pid) ? "S" : "Z",
11552
12018
  memory: this.processes.get(member.pid)?.memory() ?? 0,
11553
12019
  umask: this.umaskOf(member.pid),
12020
+ cred,
11554
12021
  ...this.signalMasksOf(member.pid)
11555
12022
  });
11556
12023
  }
@@ -11574,6 +12041,7 @@ var Launcher = class {
11574
12041
  ppid,
11575
12042
  ignored: this.ignoredBy(ppid),
11576
12043
  umask: this.umaskOf(ppid),
12044
+ cred: this.credOf(ppid),
11577
12045
  ...exec
11578
12046
  });
11579
12047
  deciding?.attach();
@@ -11619,7 +12087,8 @@ var Launcher = class {
11619
12087
  ppid,
11620
12088
  fork: state,
11621
12089
  ignored: this.ignoredBy(ppid, true),
11622
- umask: this.umaskOf(ppid)
12090
+ umask: this.umaskOf(ppid),
12091
+ cred: this.credOf(ppid)
11623
12092
  })
11624
12093
  );
11625
12094
  }
@@ -11631,6 +12100,18 @@ var Launcher = class {
11631
12100
  umaskOf(pid) {
11632
12101
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
11633
12102
  }
12103
+ imageOf(pid) {
12104
+ const members = this.jobs.list();
12105
+ let image = pid;
12106
+ for (; ; ) {
12107
+ const next2 = members.find((m) => m.execParent === image);
12108
+ if (!next2) return image;
12109
+ image = next2.pid;
12110
+ }
12111
+ }
12112
+ credOf(pid) {
12113
+ return this.creds.get(pid)?.() ?? copyCred(ROOT);
12114
+ }
11634
12115
  released(path) {
11635
12116
  const at = normalizePath(path);
11636
12117
  this.unlinked.closed(at);
@@ -11644,10 +12125,13 @@ var Launcher = class {
11644
12125
  ignoredBy(pid, fork = false) {
11645
12126
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
11646
12127
  }
11647
- kill(pid, sig) {
11648
- if (pid < 0) return this.jobs.killGroup(-pid, sig);
12128
+ kill(pid, sig, sender, from) {
12129
+ const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
12130
+ 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);
12131
+ if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
11649
12132
  const handle = this.processes.get(pid);
11650
12133
  if (!handle) return false;
12134
+ if (!may(this.imageOf(pid))) throw fsError("EPERM", `pid ${pid}`);
11651
12135
  if (sig !== 0) handle.signal(sig);
11652
12136
  return true;
11653
12137
  }
@@ -11718,9 +12202,9 @@ var Launcher = class {
11718
12202
  ask(handle);
11719
12203
  });
11720
12204
  const served = serveFilesystem(port2, medium.handlers, FSA_CAPABILITIES);
11721
- const id = source.slice("fsa:".length);
11722
- this.removable.set(target, { id, medium, served });
11723
- const stored = this.inserted.get(id) ?? await this.media.get(id);
12205
+ const id2 = source.slice("fsa:".length);
12206
+ this.removable.set(target, { id: id2, medium, served });
12207
+ const stored = this.inserted.get(id2) ?? await this.media.get(id2);
11724
12208
  if (stored && await granted(stored)) medium.insert(stored);
11725
12209
  else ask(stored);
11726
12210
  return {
@@ -11804,15 +12288,16 @@ var Launcher = class {
11804
12288
  }
11805
12289
  async prepare() {
11806
12290
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
12291
+ await this.users.ensure();
11807
12292
  await writeCaFile(this.base, this.ca).catch(() => void 0);
11808
12293
  this.fstab = this.mountFstab();
11809
12294
  }
11810
12295
  async mountFstab() {
11811
12296
  const text = await this.base.readFile(FSTAB_PATH).catch(() => "");
11812
- const lines = parseFstab(text);
11813
- for (const { spec } of lines) this.mounts.note(spec, "pending");
12297
+ const lines2 = parseFstab(text);
12298
+ for (const { spec } of lines2) this.mounts.note(spec, "pending");
11814
12299
  return mountFstab(
11815
- lines,
12300
+ lines2,
11816
12301
  (spec) => this.mount(spec).catch((err) => {
11817
12302
  this.mounts.note(spec, "pending", err?.message ?? String(err));
11818
12303
  throw err;
@@ -11827,14 +12312,19 @@ var Launcher = class {
11827
12312
  }
11828
12313
  );
11829
12314
  }
11830
- environment(cwd, extra) {
12315
+ environment(cwd, extra, account) {
12316
+ return { ...this.env, ...userEnv(account, this.pnpmHome), PWD: cwd, ...extra };
12317
+ }
12318
+ async account(user) {
12319
+ if (typeof user === "object") return user;
12320
+ const found = await this.users.lookup(user ?? 0);
12321
+ if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
12322
+ return found;
12323
+ }
12324
+ identity(account) {
11831
12325
  return {
11832
- PATH: `/usr/bin:/bin:${this.pnpmHome}/bin`,
11833
- HOME: "/home",
11834
- PNPM_HOME: this.pnpmHome,
11835
- ...this.env,
11836
- PWD: cwd,
11837
- ...extra
12326
+ cred: credOf(account),
12327
+ ...account.umask !== void 0 ? { umask: account.umask } : {}
11838
12328
  };
11839
12329
  }
11840
12330
  async starting(argv, dir) {
@@ -11847,6 +12337,7 @@ var Launcher = class {
11847
12337
  return { cwd, file, planned: await this.plan(file, argv, cwd) };
11848
12338
  }
11849
12339
  async openTerminal(argv, options) {
12340
+ const account = await this.account(options.user);
11850
12341
  const { cwd, file, planned } = await this.starting(argv, options.cwd);
11851
12342
  if (!planned) throw new Error(`${file}: command not found`);
11852
12343
  let leader = 0;
@@ -11862,14 +12353,18 @@ var Launcher = class {
11862
12353
  fds.installAt(0, stdio);
11863
12354
  fds.installAt(1, stdio.retain());
11864
12355
  fds.installAt(2, stdio.retain());
11865
- const env = this.environment(cwd, {
11866
- TERM: "xterm-256color",
11867
- COLORTERM: "truecolor",
11868
- ...options.env
11869
- });
11870
- const report = (message2) => options.onData(encoder3.encode(`${message2}\r
12356
+ const env = this.environment(
12357
+ cwd,
12358
+ {
12359
+ TERM: "xterm-256color",
12360
+ COLORTERM: "truecolor",
12361
+ ...options.env
12362
+ },
12363
+ account
12364
+ );
12365
+ const report = (message3) => options.onData(encoder3.encode(`${message3}\r
11871
12366
  `));
11872
- const handle = await this.launch(planned, { env, cwd, fds, report });
12367
+ const handle = await this.launch(planned, { env, cwd, fds, report, ...this.identity(account) });
11873
12368
  leader = handle.pid;
11874
12369
  void handle.exited.then(() => tty.hangup());
11875
12370
  return {
@@ -11885,8 +12380,9 @@ var Launcher = class {
11885
12380
  };
11886
12381
  }
11887
12382
  async run(argv, options = {}) {
12383
+ const account = await this.account(options.user);
11888
12384
  const { cwd, file, planned } = await this.starting(argv, options.cwd);
11889
- const env = this.environment(cwd, options.env);
12385
+ const env = this.environment(cwd, options.env, account);
11890
12386
  const out = [];
11891
12387
  const err = [];
11892
12388
  const keep = options.collect !== false;
@@ -11896,7 +12392,7 @@ var Launcher = class {
11896
12392
  };
11897
12393
  const stdout = collect(out, options.onStdout);
11898
12394
  const stderr = collect(err, options.onStderr);
11899
- const report = (message2) => stderr(encoder3.encode(`${message2}
12395
+ const report = (message3) => stderr(encoder3.encode(`${message3}
11900
12396
  `));
11901
12397
  if (!planned) {
11902
12398
  report(`${file}: command not found`);
@@ -11907,7 +12403,14 @@ var Launcher = class {
11907
12403
  fds.installAt(1, sinkFile(stdout));
11908
12404
  fds.installAt(2, sinkFile(stderr));
11909
12405
  const group = options.pgid !== void 0 ? { pgid: options.pgid } : {};
11910
- const handle = await this.launch(planned, { env, cwd, fds, report, ...group });
12406
+ const handle = await this.launch(planned, {
12407
+ env,
12408
+ cwd,
12409
+ fds,
12410
+ report,
12411
+ ...group,
12412
+ ...this.identity(account)
12413
+ });
11911
12414
  options.onStarted?.(handle.pid);
11912
12415
  const status = await handle.exited;
11913
12416
  return { status, stdout: concat5(out), stderr: concat5(err) };
@@ -11915,7 +12418,7 @@ var Launcher = class {
11915
12418
  };
11916
12419
 
11917
12420
  // src/node/memory-root.ts
11918
- var fail = (name2) => {
12421
+ var fail2 = (name2) => {
11919
12422
  throw new DOMException(name2, name2);
11920
12423
  };
11921
12424
  function valid(name2) {
@@ -12028,7 +12531,7 @@ var MemoryFile = class {
12028
12531
  }
12029
12532
  async move(parent, name2) {
12030
12533
  valid(name2);
12031
- if (parent.children.get(name2)?.kind === "directory") fail("InvalidModificationError");
12534
+ if (parent.children.get(name2)?.kind === "directory") fail2("InvalidModificationError");
12032
12535
  this.parent.children.delete(this.name);
12033
12536
  this.name = name2;
12034
12537
  this.parent = parent;
@@ -12046,15 +12549,15 @@ var MemoryDirectory = class _MemoryDirectory {
12046
12549
  this.parent = parent;
12047
12550
  }
12048
12551
  present() {
12049
- if (this.removed) fail("NotFoundError");
12552
+ if (this.removed) fail2("NotFoundError");
12050
12553
  }
12051
12554
  async getDirectoryHandle(name2, { create = false } = {}) {
12052
12555
  this.present();
12053
12556
  valid(name2);
12054
12557
  const found = this.children.get(name2);
12055
- if (found?.kind === "file") fail("TypeMismatchError");
12558
+ if (found?.kind === "file") fail2("TypeMismatchError");
12056
12559
  if (found) return found;
12057
- if (!create) fail("NotFoundError");
12560
+ if (!create) fail2("NotFoundError");
12058
12561
  const dir = new _MemoryDirectory(name2, this);
12059
12562
  this.children.set(name2, dir);
12060
12563
  return dir;
@@ -12063,9 +12566,9 @@ var MemoryDirectory = class _MemoryDirectory {
12063
12566
  this.present();
12064
12567
  valid(name2);
12065
12568
  const found = this.children.get(name2);
12066
- if (found?.kind === "directory") fail("TypeMismatchError");
12569
+ if (found?.kind === "directory") fail2("TypeMismatchError");
12067
12570
  if (found) return found;
12068
- if (!create) fail("NotFoundError");
12571
+ if (!create) fail2("NotFoundError");
12069
12572
  const file = new MemoryFile(name2, this);
12070
12573
  this.children.set(name2, file);
12071
12574
  return file;
@@ -12074,9 +12577,9 @@ var MemoryDirectory = class _MemoryDirectory {
12074
12577
  this.present();
12075
12578
  valid(name2);
12076
12579
  const found = this.children.get(name2);
12077
- if (!found) return fail("NotFoundError");
12580
+ if (!found) return fail2("NotFoundError");
12078
12581
  if (found.kind === "directory" && found.children.size > 0 && !recursive) {
12079
- fail("InvalidModificationError");
12582
+ fail2("InvalidModificationError");
12080
12583
  }
12081
12584
  this.children.delete(name2);
12082
12585
  if (found.kind === "directory") found.remove();
@@ -12105,7 +12608,7 @@ var MemoryDirectory = class _MemoryDirectory {
12105
12608
  }
12106
12609
  async move(parent, name2) {
12107
12610
  valid(name2);
12108
- if (parent.children.has(name2)) fail("InvalidModificationError");
12611
+ if (parent.children.has(name2)) fail2("InvalidModificationError");
12109
12612
  this.parent?.children.delete(this.name);
12110
12613
  this.name = name2;
12111
12614
  this.parent = parent;
@@ -12123,8 +12626,8 @@ function isMemoryRoot(handle) {
12123
12626
  var TransportError = class extends Error {
12124
12627
  status;
12125
12628
  code;
12126
- constructor(message2, status, code) {
12127
- super(message2);
12629
+ constructor(message3, status, code) {
12630
+ super(message3);
12128
12631
  this.status = status;
12129
12632
  this.code = code;
12130
12633
  }
@@ -12295,9 +12798,9 @@ function signalNumber(name2) {
12295
12798
  }
12296
12799
 
12297
12800
  // src/client/dial-stream.ts
12298
- function errorWithCode(message2) {
12299
- const code = /^E[A-Z]+/.exec(message2)?.[0];
12300
- return Object.assign(new Error(message2), code ? { code } : {});
12801
+ function errorWithCode(message3) {
12802
+ const code = /^E[A-Z]+/.exec(message3)?.[0];
12803
+ return Object.assign(new Error(message3), code ? { code } : {});
12301
12804
  }
12302
12805
  function dialStream(port, onClose) {
12303
12806
  let closed = false;
@@ -12315,15 +12818,15 @@ function dialStream(port, onClose) {
12315
12818
  close();
12316
12819
  }
12317
12820
  });
12318
- const finish = (fail3) => {
12821
+ const finish = (fail4) => {
12319
12822
  const controller = reader;
12320
12823
  reader = void 0;
12321
12824
  for (const ack of acks.splice(0)) {
12322
- if (fail3) ack.reject(fail3);
12825
+ if (fail4) ack.reject(fail4);
12323
12826
  else ack.resolve();
12324
12827
  }
12325
12828
  if (!controller) return;
12326
- if (fail3) controller.error(fail3);
12829
+ if (fail4) controller.error(fail4);
12327
12830
  else controller.close();
12328
12831
  };
12329
12832
  function shut() {
@@ -12359,11 +12862,11 @@ function dialStream(port, onClose) {
12359
12862
  close();
12360
12863
  }
12361
12864
  });
12362
- const fail2 = (error) => {
12865
+ const fail3 = (error) => {
12363
12866
  shut();
12364
12867
  finish(error);
12365
12868
  };
12366
- return { readable, writable, close, fail: fail2 };
12869
+ return { readable, writable, close, fail: fail3 };
12367
12870
  }
12368
12871
 
12369
12872
  // src/client/loopback-fetch.ts
@@ -12415,11 +12918,11 @@ function indexOf(haystack, needle) {
12415
12918
  return -1;
12416
12919
  }
12417
12920
  function parseHead(bytes) {
12418
- const [statusLine = "", ...lines] = latin12.decode(bytes).split("\r\n");
12921
+ const [statusLine = "", ...lines2] = latin12.decode(bytes).split("\r\n");
12419
12922
  const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
12420
12923
  if (!match) throw errorWithCode(`EPROTO: not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
12421
12924
  const headers = [];
12422
- for (const line of lines) {
12925
+ for (const line of lines2) {
12423
12926
  const colon = line.indexOf(":");
12424
12927
  if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
12425
12928
  }
@@ -12488,13 +12991,13 @@ function stream(chunks2, socket, signal) {
12488
12991
  async function requestBytes(request) {
12489
12992
  const url = new URL(request.url);
12490
12993
  const body = request.body ? new Uint8Array(await request.arrayBuffer()) : void 0;
12491
- const lines = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
12994
+ const lines2 = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
12492
12995
  request.headers.forEach((value, name2) => {
12493
- if (!OWN_HEADERS.has(name2)) lines.push(`${name2}: ${value}`);
12996
+ if (!OWN_HEADERS.has(name2)) lines2.push(`${name2}: ${value}`);
12494
12997
  });
12495
- if (body) lines.push(`Content-Length: ${body.length}`);
12496
- lines.push("Connection: close", "", "");
12497
- const head = encoder4.encode(lines.join("\r\n"));
12998
+ if (body) lines2.push(`Content-Length: ${body.length}`);
12999
+ lines2.push("Connection: close", "", "");
13000
+ const head = encoder4.encode(lines2.join("\r\n"));
12498
13001
  const out = new Uint8Array(head.length + (body?.length ?? 0));
12499
13002
  out.set(head);
12500
13003
  if (body) out.set(body, head.length);
@@ -12535,15 +13038,15 @@ async function loopbackFetch(dial, input, options) {
12535
13038
 
12536
13039
  // src/client/attach.ts
12537
13040
  var KernelGoneError = class extends Error {
12538
- constructor(message2 = "the slicc-kernel this client was attached to is gone") {
12539
- super(message2);
13041
+ constructor(message3 = "the slicc-kernel this client was attached to is gone") {
13042
+ super(message3);
12540
13043
  this.name = "KernelGoneError";
12541
13044
  }
12542
13045
  };
12543
13046
  var KernelCallError = class extends Error {
12544
13047
  code;
12545
- constructor(message2, code) {
12546
- super(message2);
13048
+ constructor(message3, code) {
13049
+ super(message3);
12547
13050
  this.name = "KernelCallError";
12548
13051
  this.code = code;
12549
13052
  }
@@ -12599,8 +13102,8 @@ var Channel = class {
12599
13102
  port.addEventListener("close", () => this.end(new KernelGoneError()));
12600
13103
  port.start?.();
12601
13104
  }
12602
- post(message2) {
12603
- if (!this.failure) this.port.postMessage(message2);
13105
+ post(message3) {
13106
+ if (!this.failure) this.port.postMessage(message3);
12604
13107
  }
12605
13108
  end(error) {
12606
13109
  if (this.failure) return;
@@ -12616,14 +13119,14 @@ var Channel = class {
12616
13119
  this.ended.resolve(error);
12617
13120
  }
12618
13121
  request(call, handlers = {}) {
12619
- const id = ++this.nextId;
13122
+ const id2 = ++this.nextId;
12620
13123
  const { failure } = this;
12621
- if (failure) return { id, done: Promise.reject(failure) };
13124
+ if (failure) return { id: id2, done: Promise.reject(failure) };
12622
13125
  const done4 = new Promise((resolve, reject) => {
12623
- this.pending.set(id, { resolve, reject, ...handlers });
12624
- this.port.postMessage({ ...call, id });
13126
+ this.pending.set(id2, { resolve, reject, ...handlers });
13127
+ this.port.postMessage({ ...call, id: id2 });
12625
13128
  });
12626
- return { id, done: done4 };
13129
+ return { id: id2, done: done4 };
12627
13130
  }
12628
13131
  call(call) {
12629
13132
  return this.request(call).done;
@@ -12682,18 +13185,18 @@ async function serveCdpOn(channel, hook, options) {
12682
13185
  op: "serve-cdp",
12683
13186
  ...options.runtime ? { runtime: options.runtime } : {}
12684
13187
  };
12685
- const { id, done: done4 } = channel.request(req);
12686
- channel.cdpHooks.set(id, hook);
13188
+ const { id: id2, done: done4 } = channel.request(req);
13189
+ channel.cdpHooks.set(id2, hook);
12687
13190
  try {
12688
13191
  await done4;
12689
13192
  } catch (error) {
12690
- channel.cdpHooks.delete(id);
13193
+ channel.cdpHooks.delete(id2);
12691
13194
  throw error;
12692
13195
  }
12693
13196
  return {
12694
13197
  close: () => {
12695
- if (!channel.cdpHooks.delete(id)) return;
12696
- void channel.call({ op: "unserve-cdp", registration: id }).catch(() => void 0);
13198
+ if (!channel.cdpHooks.delete(id2)) return;
13199
+ void channel.call({ op: "unserve-cdp", registration: id2 }).catch(() => void 0);
12697
13200
  }
12698
13201
  };
12699
13202
  }
@@ -12706,6 +13209,8 @@ async function spawnOn(channel, argv, opts) {
12706
13209
  if (opts.pgid !== void 0)
12707
13210
  await since(channel, 3, "spawn into a process group", async () => {
12708
13211
  });
13212
+ if (opts.user !== void 0) await since(channel, 8, "spawn as a user", async () => {
13213
+ });
12709
13214
  const started = Promise.withResolvers();
12710
13215
  const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
12711
13216
  const { done: done4 } = channel.request(
@@ -12744,10 +13249,12 @@ async function runOn(channel, argv, opts) {
12744
13249
  }
12745
13250
  async function terminalOn(channel, argv, opts) {
12746
13251
  const { onData, ...options } = opts;
13252
+ if (opts.user !== void 0) await since(channel, 8, "a terminal as a user", async () => {
13253
+ });
12747
13254
  let listener = onData ?? null;
12748
13255
  const backlog = [];
12749
13256
  const started = Promise.withResolvers();
12750
- const { id, done: done4 } = channel.request(
13257
+ const { id: id2, done: done4 } = channel.request(
12751
13258
  { op: "open-terminal", argv, options },
12752
13259
  {
12753
13260
  output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
@@ -12756,7 +13263,7 @@ async function terminalOn(channel, argv, opts) {
12756
13263
  );
12757
13264
  done4.catch(started.reject);
12758
13265
  const pid = await started.promise;
12759
- const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
13266
+ const send = (action) => void channel.call({ op: "terminal", terminal: id2, ...action }).catch(() => void 0);
12760
13267
  return {
12761
13268
  pid,
12762
13269
  exited: done4,
@@ -12797,22 +13304,22 @@ function fsOn(channel) {
12797
13304
  "ENOSYS"
12798
13305
  );
12799
13306
  }
12800
- const { id, done: done5 } = channel.request({
13307
+ const { id: id2, done: done5 } = channel.request({
12801
13308
  op: "watch",
12802
13309
  paths,
12803
13310
  recursive: options.recursive === true
12804
13311
  });
12805
- channel.watching.set(id, onChange);
13312
+ channel.watching.set(id2, onChange);
12806
13313
  try {
12807
13314
  await done5;
12808
13315
  } catch (error) {
12809
- channel.watching.delete(id);
13316
+ channel.watching.delete(id2);
12810
13317
  throw error;
12811
13318
  }
12812
13319
  return {
12813
13320
  close: () => {
12814
- if (!channel.watching.delete(id)) return;
12815
- void channel.call({ op: "unwatch", watch: id }).catch(() => void 0);
13321
+ if (!channel.watching.delete(id2)) return;
13322
+ void channel.call({ op: "unwatch", watch: id2 }).catch(() => void 0);
12816
13323
  }
12817
13324
  };
12818
13325
  }
@@ -12912,9 +13419,9 @@ var TERMINATED = "the kernel is terminated";
12912
13419
  function nodeWorker(file, live, closed) {
12913
13420
  const errors = /* @__PURE__ */ new Set();
12914
13421
  const messages = /* @__PURE__ */ new Map();
12915
- const fail2 = (message2) => {
13422
+ const fail3 = (message3) => {
12916
13423
  for (const handler of [...errors]) {
12917
- handler({ message: message2, preventDefault() {
13424
+ handler({ message: message3, preventDefault() {
12918
13425
  } });
12919
13426
  }
12920
13427
  };
@@ -12922,16 +13429,16 @@ function nodeWorker(file, live, closed) {
12922
13429
  if (worker) {
12923
13430
  const kill = () => {
12924
13431
  void worker.terminate();
12925
- fail2(TERMINATED);
13432
+ fail3(TERMINATED);
12926
13433
  };
12927
13434
  live.add(kill);
12928
13435
  worker.once("exit", () => live.delete(kill));
12929
- worker.on("error", (err) => fail2(String(err)));
13436
+ worker.on("error", (err) => fail3(String(err)));
12930
13437
  } else {
12931
- setTimeout(() => fail2(TERMINATED), 0);
13438
+ setTimeout(() => fail3(TERMINATED), 0);
12932
13439
  }
12933
13440
  return {
12934
- postMessage: (message2, transfer) => worker?.postMessage(message2, transfer),
13441
+ postMessage: (message3, transfer) => worker?.postMessage(message3, transfer),
12935
13442
  addEventListener(type, handler) {
12936
13443
  if (type === "error") {
12937
13444
  errors.add(handler);
@@ -13047,12 +13554,21 @@ async function createNodeKernel(options = {}) {
13047
13554
  umount: (target) => guard(async () => launcher.umount(target)),
13048
13555
  mounts: async () => launcher.mounts.list(),
13049
13556
  setRoutes: (routes) => guard(async () => launcher.setRoutes(routes)),
13050
- async connect() {
13557
+ users: {
13558
+ add: (user) => guard(() => launcher.users.add(user)),
13559
+ remove: (name2, options2) => guard(() => launcher.users.remove(name2, options2)),
13560
+ list: () => guard(() => launcher.users.list())
13561
+ },
13562
+ async connect({ user } = {}) {
13051
13563
  if (terminated) throw new Error(TERMINATED);
13564
+ if (user !== void 0 && !await launcher.users.lookup(user)) {
13565
+ throw fsError("ENOENT", `no user ${String(user)}`);
13566
+ }
13052
13567
  const { port1, port2 } = new MessageChannel2();
13053
13568
  const served = serveClient(port1, {
13054
13569
  launcher: async () => launcher,
13055
- signal: signalNumber
13570
+ signal: signalNumber,
13571
+ ...user !== void 0 ? { user } : {}
13056
13572
  });
13057
13573
  clients.add(served);
13058
13574
  void served.closed.then(() => clients.delete(served));