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