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