@ai-ecoverse/slicc-kernel 1.65.2 → 1.66.1
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 +8 -3
- package/dist/driver-worker.js.map +3 -3
- package/dist/driver.js +6 -1
- package/dist/driver.js.map +3 -3
- package/dist/index.js +61 -25
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +802 -202
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +8 -3
- package/dist/node-driver-worker.js.map +3 -3
- package/dist/node-process-worker.js +6 -0
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +770 -194
- 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",
|
|
@@ -1339,6 +1339,11 @@ function versionError(theirs) {
|
|
|
1339
1339
|
return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
|
|
1340
1340
|
}
|
|
1341
1341
|
|
|
1342
|
+
// src/bytes.ts
|
|
1343
|
+
function fresh(bytes, start2 = 0, end = bytes.length) {
|
|
1344
|
+
return new Uint8Array(bytes.subarray(start2, end));
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1342
1347
|
// src/fs/walk.ts
|
|
1343
1348
|
var MAX_LINKS2 = 40;
|
|
1344
1349
|
function parentOf4(path) {
|
|
@@ -1685,6 +1690,64 @@ async function openable(g, op, path, want, create) {
|
|
|
1685
1690
|
await need(g, op, path, want, { directory: st?.isDirectory ?? false });
|
|
1686
1691
|
}
|
|
1687
1692
|
|
|
1693
|
+
// src/kernel/cred.ts
|
|
1694
|
+
var ROOT = Object.freeze({
|
|
1695
|
+
ruid: 0,
|
|
1696
|
+
euid: 0,
|
|
1697
|
+
suid: 0,
|
|
1698
|
+
rgid: 0,
|
|
1699
|
+
egid: 0,
|
|
1700
|
+
sgid: 0,
|
|
1701
|
+
groups: [0]
|
|
1702
|
+
});
|
|
1703
|
+
var MAX_ID = 4294967294;
|
|
1704
|
+
var MAX_GROUPS = 65536;
|
|
1705
|
+
var UIDS = ["ruid", "euid", "suid"];
|
|
1706
|
+
var GIDS = ["rgid", "egid", "sgid"];
|
|
1707
|
+
function userCred(uid, gid, groups = [gid]) {
|
|
1708
|
+
return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
|
|
1709
|
+
}
|
|
1710
|
+
function copyCred(cred) {
|
|
1711
|
+
return { ...cred, groups: [...cred.groups] };
|
|
1712
|
+
}
|
|
1713
|
+
function id(value) {
|
|
1714
|
+
if (value === void 0 || value === -1 || value === 4294967295) return void 0;
|
|
1715
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
|
|
1716
|
+
throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
|
|
1717
|
+
}
|
|
1718
|
+
return value;
|
|
1719
|
+
}
|
|
1720
|
+
function changeCred(cur, change) {
|
|
1721
|
+
const privileged = cur.euid === 0;
|
|
1722
|
+
const next2 = copyCred(cur);
|
|
1723
|
+
for (const keys of [UIDS, GIDS]) {
|
|
1724
|
+
const own = keys.map((key) => cur[key]);
|
|
1725
|
+
for (const key of keys) {
|
|
1726
|
+
const value = id(change[key]);
|
|
1727
|
+
if (value === void 0) continue;
|
|
1728
|
+
if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
|
|
1729
|
+
next2[key] = value;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
if (change.groups !== void 0) {
|
|
1733
|
+
if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
|
|
1734
|
+
throw fsError("EINVAL", "not a group list");
|
|
1735
|
+
}
|
|
1736
|
+
const groups = change.groups.map((g) => id(g) ?? Number.NaN);
|
|
1737
|
+
if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
|
|
1738
|
+
if (!privileged) throw fsError("EPERM", "setgroups");
|
|
1739
|
+
next2.groups = groups;
|
|
1740
|
+
}
|
|
1741
|
+
return next2;
|
|
1742
|
+
}
|
|
1743
|
+
function maySignal(sender, target) {
|
|
1744
|
+
if (sender.euid === 0) return true;
|
|
1745
|
+
return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
|
|
1746
|
+
}
|
|
1747
|
+
function maySee(viewer, target) {
|
|
1748
|
+
return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1688
1751
|
// src/kernel/pipe.ts
|
|
1689
1752
|
var PIPE_CAPACITY = 64 * 1024;
|
|
1690
1753
|
var PipeError = class extends Error {
|
|
@@ -1992,9 +2055,9 @@ var FdTable = class _FdTable {
|
|
|
1992
2055
|
closesOnExec(fd) {
|
|
1993
2056
|
return this.cloexec.has(fd);
|
|
1994
2057
|
}
|
|
1995
|
-
setStatusFlags(fd,
|
|
2058
|
+
setStatusFlags(fd, flags2) {
|
|
1996
2059
|
this.get(fd);
|
|
1997
|
-
this.status.set(fd,
|
|
2060
|
+
this.status.set(fd, flags2);
|
|
1998
2061
|
}
|
|
1999
2062
|
statusFlags(fd) {
|
|
2000
2063
|
return this.status.get(fd);
|
|
@@ -2039,7 +2102,7 @@ var FdTable = class _FdTable {
|
|
|
2039
2102
|
const child = new _FdTable();
|
|
2040
2103
|
for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
|
|
2041
2104
|
for (const fd of this.cloexec) child.cloexec.add(fd);
|
|
2042
|
-
for (const [fd,
|
|
2105
|
+
for (const [fd, flags2] of this.status) child.status.set(fd, flags2);
|
|
2043
2106
|
return child;
|
|
2044
2107
|
}
|
|
2045
2108
|
async closeAll() {
|
|
@@ -2506,9 +2569,9 @@ var KernelSocket = class _KernelSocket {
|
|
|
2506
2569
|
case SO_TYPE:
|
|
2507
2570
|
return SOCK_STREAM;
|
|
2508
2571
|
case SO_ERROR: {
|
|
2509
|
-
const
|
|
2572
|
+
const failed3 = this.failed;
|
|
2510
2573
|
this.failed = void 0;
|
|
2511
|
-
return
|
|
2574
|
+
return failed3 ? wasiErrno(failed3) : 0;
|
|
2512
2575
|
}
|
|
2513
2576
|
case SO_ACCEPTCONN:
|
|
2514
2577
|
return this.state === "listening" ? 1 : 0;
|
|
@@ -2944,7 +3007,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
2944
3007
|
}
|
|
2945
3008
|
if (typeof data === "string") peer.postMessage({ net: "ws-message", nid, data });
|
|
2946
3009
|
else {
|
|
2947
|
-
const bytes = data
|
|
3010
|
+
const bytes = fresh(data);
|
|
2948
3011
|
peer.postMessage({ net: "ws-message", nid, data: bytes }, [bytes.buffer]);
|
|
2949
3012
|
}
|
|
2950
3013
|
flow.inflight += payloadSize(data);
|
|
@@ -2982,7 +3045,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
2982
3045
|
open.delete(nid);
|
|
2983
3046
|
peer.postMessage({ net: "end", nid });
|
|
2984
3047
|
} else {
|
|
2985
|
-
const bytes = next2.value
|
|
3048
|
+
const bytes = fresh(next2.value);
|
|
2986
3049
|
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
2987
3050
|
}
|
|
2988
3051
|
} catch (e) {
|
|
@@ -3015,64 +3078,6 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
3015
3078
|
};
|
|
3016
3079
|
}
|
|
3017
3080
|
|
|
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
3081
|
// src/users.ts
|
|
3077
3082
|
var PASSWD = "/etc/passwd";
|
|
3078
3083
|
var GROUP = "/etc/group";
|
|
@@ -3334,7 +3339,7 @@ var UserDb = class {
|
|
|
3334
3339
|
joined(name2, extra);
|
|
3335
3340
|
group.push({ name: name2, gid: uid, members: [] });
|
|
3336
3341
|
passwd.push(row);
|
|
3337
|
-
const
|
|
3342
|
+
const fresh2 = !await this.fs.exists(home);
|
|
3338
3343
|
await this.fs.mkdir(home, { recursive: true });
|
|
3339
3344
|
try {
|
|
3340
3345
|
await this.provision(row);
|
|
@@ -3342,7 +3347,7 @@ var UserDb = class {
|
|
|
3342
3347
|
await this.fs.mkdir("/etc", { recursive: true });
|
|
3343
3348
|
await this.commit(formatPasswd(passwd), formatGroup(group));
|
|
3344
3349
|
} catch (err) {
|
|
3345
|
-
if (
|
|
3350
|
+
if (fresh2) await this.fs.rm(home, { recursive: true, force: true });
|
|
3346
3351
|
throw err;
|
|
3347
3352
|
}
|
|
3348
3353
|
remember();
|
|
@@ -3460,7 +3465,7 @@ async function fsCall(fs, method, args, owner, g) {
|
|
|
3460
3465
|
}
|
|
3461
3466
|
switch (method) {
|
|
3462
3467
|
case "readFile": {
|
|
3463
|
-
const bytes = (await fs.readFileBuffer(path(0)))
|
|
3468
|
+
const bytes = fresh(await fs.readFileBuffer(path(0)));
|
|
3464
3469
|
return { result: bytes, transfer: [bytes.buffer] };
|
|
3465
3470
|
}
|
|
3466
3471
|
case "writeFile":
|
|
@@ -3537,6 +3542,12 @@ async function runAs(l, self, requested) {
|
|
|
3537
3542
|
}
|
|
3538
3543
|
return target;
|
|
3539
3544
|
}
|
|
3545
|
+
function clientCred(self) {
|
|
3546
|
+
return self ? credOf(self) : copyCred(ROOT);
|
|
3547
|
+
}
|
|
3548
|
+
async function runTagged(l, self, requested, tag) {
|
|
3549
|
+
return { user: await runAs(l, self, requested), tag };
|
|
3550
|
+
}
|
|
3540
3551
|
function processes(l, self) {
|
|
3541
3552
|
const viewer = credOf(self);
|
|
3542
3553
|
return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
|
|
@@ -3671,7 +3682,7 @@ function serveClient(port, host) {
|
|
|
3671
3682
|
async function terminal(req, l) {
|
|
3672
3683
|
const session = await l.openTerminal(req.argv, {
|
|
3673
3684
|
...req.options,
|
|
3674
|
-
|
|
3685
|
+
...await runTagged(l, self, req.options.user, host.tag),
|
|
3675
3686
|
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
3676
3687
|
});
|
|
3677
3688
|
if (state === "closed") {
|
|
@@ -3693,7 +3704,7 @@ function serveClient(port, host) {
|
|
|
3693
3704
|
let leader;
|
|
3694
3705
|
const result = await l.run(req.argv, {
|
|
3695
3706
|
...req.options,
|
|
3696
|
-
|
|
3707
|
+
...await runTagged(l, self, req.options.user, host.tag),
|
|
3697
3708
|
collect: false,
|
|
3698
3709
|
onStdout: out(1),
|
|
3699
3710
|
onStderr: out(2),
|
|
@@ -3734,9 +3745,9 @@ function serveClient(port, host) {
|
|
|
3734
3745
|
case "unwatch":
|
|
3735
3746
|
return watchOp(l, req, watches, send, guardFor(l, self));
|
|
3736
3747
|
case "mount":
|
|
3737
|
-
return { result: await l.
|
|
3748
|
+
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
3738
3749
|
case "umount":
|
|
3739
|
-
l.
|
|
3750
|
+
await l.umountAs(clientCred(self), req.target);
|
|
3740
3751
|
return { result: true };
|
|
3741
3752
|
case "mounts":
|
|
3742
3753
|
return { result: l.mounts.list() };
|
|
@@ -4947,8 +4958,8 @@ function ipv6(host) {
|
|
|
4947
4958
|
const head = groups(halves[0]);
|
|
4948
4959
|
const back = halves.length === 2 ? groups(halves[1]) : [];
|
|
4949
4960
|
if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
|
|
4950
|
-
const
|
|
4951
|
-
const missing3 = 8 -
|
|
4961
|
+
const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
|
|
4962
|
+
const missing3 = 8 - words2.length - tail.length;
|
|
4952
4963
|
if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
|
|
4953
4964
|
const all = [
|
|
4954
4965
|
...head.map((g) => Number.parseInt(g, 16)),
|
|
@@ -6352,9 +6363,8 @@ async function scanFilesystems(fs, modules) {
|
|
|
6352
6363
|
}
|
|
6353
6364
|
var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
|
|
6354
6365
|
async function scanBinfmts(fs, modules, warn = () => {
|
|
6355
|
-
}) {
|
|
6366
|
+
}, owners = /* @__PURE__ */ new Map()) {
|
|
6356
6367
|
const found = /* @__PURE__ */ new Map();
|
|
6357
|
-
const owners = /* @__PURE__ */ new Map();
|
|
6358
6368
|
for (const pkg of await packagesIn(fs, modules)) {
|
|
6359
6369
|
let manifest;
|
|
6360
6370
|
try {
|
|
@@ -6379,7 +6389,7 @@ async function scanBinfmts(fs, modules, warn = () => {
|
|
|
6379
6389
|
}
|
|
6380
6390
|
return found;
|
|
6381
6391
|
}
|
|
6382
|
-
function
|
|
6392
|
+
function binfmtSuffix(binfmts, path) {
|
|
6383
6393
|
const name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
6384
6394
|
let best;
|
|
6385
6395
|
for (const suffix of binfmts.keys()) {
|
|
@@ -6387,6 +6397,10 @@ function binfmtOf(binfmts, path) {
|
|
|
6387
6397
|
if (best === void 0 || suffix.length > best.length) best = suffix;
|
|
6388
6398
|
}
|
|
6389
6399
|
}
|
|
6400
|
+
return best;
|
|
6401
|
+
}
|
|
6402
|
+
function binfmtOf(binfmts, path) {
|
|
6403
|
+
const best = binfmtSuffix(binfmts, path);
|
|
6390
6404
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
6391
6405
|
}
|
|
6392
6406
|
|
|
@@ -6963,8 +6977,9 @@ var ChildTable = class {
|
|
|
6963
6977
|
if (!this.forker) throw new SpawnError("ENOSYS");
|
|
6964
6978
|
return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
|
|
6965
6979
|
}
|
|
6966
|
-
async spawn(req, stdio, inherit2 = []) {
|
|
6967
|
-
|
|
6980
|
+
async spawn(req, stdio, inherit2 = [], grant) {
|
|
6981
|
+
const { spawner } = this;
|
|
6982
|
+
if (!spawner) throw new SpawnError("ENOSYS");
|
|
6968
6983
|
const fds = new FdTable();
|
|
6969
6984
|
const captured = /* @__PURE__ */ new Map();
|
|
6970
6985
|
try {
|
|
@@ -6982,7 +6997,7 @@ var ChildTable = class {
|
|
|
6982
6997
|
await fds.closeAll();
|
|
6983
6998
|
throw e;
|
|
6984
6999
|
}
|
|
6985
|
-
return this.track(req, fds, captured,
|
|
7000
|
+
return this.track(req, fds, captured, (r, f) => spawner(r, f, grant));
|
|
6986
7001
|
}
|
|
6987
7002
|
async track(req, fds, captured, start2) {
|
|
6988
7003
|
let handle;
|
|
@@ -7029,34 +7044,34 @@ var ChildTable = class {
|
|
|
7029
7044
|
}
|
|
7030
7045
|
return deviceFile("null");
|
|
7031
7046
|
}
|
|
7032
|
-
async wait(pid, nohang, signal,
|
|
7047
|
+
async wait(pid, nohang, signal, flags2 = {}) {
|
|
7033
7048
|
let interrupt;
|
|
7034
7049
|
try {
|
|
7035
7050
|
for (; ; ) {
|
|
7036
|
-
const candidates = this.candidates(pid,
|
|
7051
|
+
const candidates = this.candidates(pid, flags2);
|
|
7037
7052
|
if (candidates.length === 0) throw new KernelError("ECHILD");
|
|
7038
7053
|
const done4 = candidates.find(([, child]) => child.code !== void 0);
|
|
7039
7054
|
if (done4) return this.reap(done4[0], done4[1].code);
|
|
7040
|
-
const changed = this.stateReport(candidates,
|
|
7055
|
+
const changed = this.stateReport(candidates, flags2);
|
|
7041
7056
|
if (changed) return changed;
|
|
7042
7057
|
if (nohang) return [0, 0];
|
|
7043
7058
|
interrupt ??= interrupted(signal);
|
|
7044
|
-
await this.nextChange(candidates,
|
|
7059
|
+
await this.nextChange(candidates, flags2, interrupt.promise);
|
|
7045
7060
|
}
|
|
7046
7061
|
} finally {
|
|
7047
7062
|
interrupt?.done();
|
|
7048
7063
|
}
|
|
7049
7064
|
}
|
|
7050
|
-
candidates(pid,
|
|
7065
|
+
candidates(pid, flags2) {
|
|
7051
7066
|
const all = [...this.children];
|
|
7052
7067
|
if (pid > 0) return all.filter(([p]) => p === pid);
|
|
7053
|
-
const inGroup2 =
|
|
7068
|
+
const inGroup2 = flags2.inGroup;
|
|
7054
7069
|
return pid === -1 || !inGroup2 ? all : all.filter(([p]) => inGroup2(p));
|
|
7055
7070
|
}
|
|
7056
|
-
async nextChange(candidates,
|
|
7071
|
+
async nextChange(candidates, flags2, interrupt) {
|
|
7057
7072
|
let wake;
|
|
7058
7073
|
const stateChange = new Promise((resolve) => wake = resolve);
|
|
7059
|
-
const watching =
|
|
7074
|
+
const watching = flags2.untraced || flags2.continued;
|
|
7060
7075
|
if (watching && wake) this.stateChanged.push(wake);
|
|
7061
7076
|
try {
|
|
7062
7077
|
await Promise.race([
|
|
@@ -7068,14 +7083,14 @@ var ChildTable = class {
|
|
|
7068
7083
|
this.stateChanged = this.stateChanged.filter((w) => w !== wake);
|
|
7069
7084
|
}
|
|
7070
7085
|
}
|
|
7071
|
-
stateReport(candidates,
|
|
7086
|
+
stateReport(candidates, flags2) {
|
|
7072
7087
|
for (const [p, child] of candidates) {
|
|
7073
|
-
if (
|
|
7088
|
+
if (flags2.untraced && child.stopReport !== void 0) {
|
|
7074
7089
|
const sig = child.stopReport;
|
|
7075
7090
|
child.stopReport = void 0;
|
|
7076
7091
|
return [p, stoppedStatus(sig)];
|
|
7077
7092
|
}
|
|
7078
|
-
if (
|
|
7093
|
+
if (flags2.continued && child.continueReport) {
|
|
7079
7094
|
child.continueReport = false;
|
|
7080
7095
|
return [p, CONTINUED_STATUS];
|
|
7081
7096
|
}
|
|
@@ -7284,9 +7299,9 @@ var HeldPaths = class extends Map {
|
|
|
7284
7299
|
return this.keptHandles.get(handle);
|
|
7285
7300
|
}
|
|
7286
7301
|
keep(path, file) {
|
|
7287
|
-
const
|
|
7288
|
-
for (const id2 of
|
|
7289
|
-
this.uses.set(file, Math.max(
|
|
7302
|
+
const fresh2 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
|
|
7303
|
+
for (const id2 of fresh2) this.keptHandles.set(id2, file);
|
|
7304
|
+
this.uses.set(file, Math.max(fresh2.length, 1));
|
|
7290
7305
|
this.unlinked.set(path, file);
|
|
7291
7306
|
}
|
|
7292
7307
|
unkeep(path, file) {
|
|
@@ -7842,7 +7857,7 @@ var MountFs = class {
|
|
|
7842
7857
|
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
7843
7858
|
try {
|
|
7844
7859
|
for (let offset = 0; offset < bytes.length; offset += chunk2) {
|
|
7845
|
-
const part = bytes
|
|
7860
|
+
const part = fresh(bytes, offset, offset + chunk2);
|
|
7846
7861
|
await mount.conn.call({ op: "write", fh, offset, bytes: part }, [part.buffer]);
|
|
7847
7862
|
}
|
|
7848
7863
|
} finally {
|
|
@@ -7939,7 +7954,7 @@ var MountFs = class {
|
|
|
7939
7954
|
async writeRuns(mount, fh, offset, bytes, transfer) {
|
|
7940
7955
|
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
7941
7956
|
for (let at = 0; at < bytes.length; at += chunk2) {
|
|
7942
|
-
const part = handed(bytes, transfer, chunk2) ? bytes : bytes
|
|
7957
|
+
const part = handed(bytes, transfer, chunk2) ? bytes : fresh(bytes, at, at + chunk2);
|
|
7943
7958
|
await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
|
|
7944
7959
|
}
|
|
7945
7960
|
}
|
|
@@ -8171,8 +8186,8 @@ var MountFs = class {
|
|
|
8171
8186
|
}
|
|
8172
8187
|
this.writable(mount, real);
|
|
8173
8188
|
const { uid, gid, mode } = change;
|
|
8174
|
-
const
|
|
8175
|
-
const set2 =
|
|
8189
|
+
const fresh2 = change.kind === "new" && { uid, gid, mode };
|
|
8190
|
+
const set2 = fresh2 ? await this.driverOwner(found, real, fresh2, false) : change;
|
|
8176
8191
|
this.table.forget(mount, real);
|
|
8177
8192
|
const attrs = Object.entries({ uid: set2.uid, gid: set2.gid, mode: set2.mode });
|
|
8178
8193
|
await mount.conn.call({
|
|
@@ -8775,9 +8790,9 @@ var Pages = class {
|
|
|
8775
8790
|
const page = (start2 + at) / PAGE;
|
|
8776
8791
|
if (this.pages.has(page)) continue;
|
|
8777
8792
|
const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
|
|
8778
|
-
const
|
|
8779
|
-
|
|
8780
|
-
this.pages.set(page,
|
|
8793
|
+
const fresh2 = this.take(part.length);
|
|
8794
|
+
fresh2.set(part);
|
|
8795
|
+
this.pages.set(page, fresh2);
|
|
8781
8796
|
}
|
|
8782
8797
|
}
|
|
8783
8798
|
read(at, length2) {
|
|
@@ -11036,8 +11051,8 @@ function errCode(err) {
|
|
|
11036
11051
|
async function targetDir(fs, path) {
|
|
11037
11052
|
return parentOf4(await realTarget(fs, path).catch(() => path));
|
|
11038
11053
|
}
|
|
11039
|
-
async function refuseReadonly(fs, path,
|
|
11040
|
-
const writes = (
|
|
11054
|
+
async function refuseReadonly(fs, path, flags2, opts = {}) {
|
|
11055
|
+
const writes = (flags2 & O_ACCMODE) !== 0 || opts.truncate === true;
|
|
11041
11056
|
if (!writes && !opts.create || !fs.stat) return;
|
|
11042
11057
|
const stat = (p) => fs.stat?.(p).catch((err) => {
|
|
11043
11058
|
const code = err?.code;
|
|
@@ -11252,6 +11267,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11252
11267
|
"proc-spawn",
|
|
11253
11268
|
"proc-wait",
|
|
11254
11269
|
"proc-captured",
|
|
11270
|
+
"proc-sudo",
|
|
11255
11271
|
"proc-run",
|
|
11256
11272
|
"proc-exit",
|
|
11257
11273
|
"proc-fork",
|
|
@@ -11284,6 +11300,12 @@ var MAX_READ = 1024 * 1024;
|
|
|
11284
11300
|
var WRITE_ACCESS = 3;
|
|
11285
11301
|
var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
|
|
11286
11302
|
var DEFAULT_UMASK = 18;
|
|
11303
|
+
function words(value) {
|
|
11304
|
+
if (!Array.isArray(value) || !value.every((w) => typeof w === "string")) {
|
|
11305
|
+
throw new KernelError("EINVAL");
|
|
11306
|
+
}
|
|
11307
|
+
return [...value];
|
|
11308
|
+
}
|
|
11287
11309
|
var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
|
|
11288
11310
|
var HOSTS_ONLY = new Resolver({ routes: new Routes() });
|
|
11289
11311
|
var WasmProcess = class {
|
|
@@ -11476,17 +11498,17 @@ var WasmProcess = class {
|
|
|
11476
11498
|
if (restart && this.options.hasPending?.()) throw new KernelError("EINTR");
|
|
11477
11499
|
}
|
|
11478
11500
|
async procWait(req, cancelled) {
|
|
11479
|
-
const
|
|
11501
|
+
const flags2 = {
|
|
11480
11502
|
untraced: req.untraced,
|
|
11481
11503
|
continued: req.continued,
|
|
11482
11504
|
inGroup: this.waitGroup(req.pid)
|
|
11483
11505
|
};
|
|
11484
11506
|
if (this.owed && !req.nohang) {
|
|
11485
|
-
const now = await this.children.wait(req.pid, true, this.interrupt.signal,
|
|
11507
|
+
const now = await this.children.wait(req.pid, true, this.interrupt.signal, flags2);
|
|
11486
11508
|
if (now[0] !== 0) return now;
|
|
11487
11509
|
}
|
|
11488
11510
|
const signal = req.nohang ? this.interrupt.signal : this.blockingSignal(cancelled);
|
|
11489
|
-
const waited = await this.children.wait(req.pid, req.nohang, signal,
|
|
11511
|
+
const waited = await this.children.wait(req.pid, req.nohang, signal, flags2);
|
|
11490
11512
|
return waited;
|
|
11491
11513
|
}
|
|
11492
11514
|
blockingSignal(cancelled, owing = true) {
|
|
@@ -11548,10 +11570,10 @@ var WasmProcess = class {
|
|
|
11548
11570
|
this.fds.installAt(fd, this.openVfsFile(open));
|
|
11549
11571
|
} else if (req.create) {
|
|
11550
11572
|
await nodes.onEntry(path, async () => {
|
|
11551
|
-
const
|
|
11573
|
+
const fresh2 = this.options.owners && !await present2(this.options.fs, path, true);
|
|
11552
11574
|
this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
|
|
11553
11575
|
await this.fds.get(fd).file.stat?.();
|
|
11554
|
-
if (
|
|
11576
|
+
if (fresh2) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
|
|
11555
11577
|
});
|
|
11556
11578
|
} else {
|
|
11557
11579
|
this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
|
|
@@ -11818,13 +11840,13 @@ var WasmProcess = class {
|
|
|
11818
11840
|
}
|
|
11819
11841
|
fdInfo(fd) {
|
|
11820
11842
|
const file = this.fds.get(fd).file;
|
|
11821
|
-
const
|
|
11843
|
+
const flags2 = this.fds.statusFlags(fd);
|
|
11822
11844
|
const terminal = (file.tty ?? file.pty?.slave)?.name;
|
|
11823
11845
|
return {
|
|
11824
11846
|
tty: file.tty !== void 0,
|
|
11825
11847
|
kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
|
|
11826
11848
|
...file.heldMeta ? { meta: file.heldMeta } : {},
|
|
11827
|
-
...
|
|
11849
|
+
...flags2 !== void 0 ? { flags: flags2 } : {},
|
|
11828
11850
|
...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
11829
11851
|
...file.read && !file.write ? { access: "read" } : {},
|
|
11830
11852
|
...file.write && !file.read ? { access: "write" } : {},
|
|
@@ -12012,7 +12034,27 @@ var WasmProcess = class {
|
|
|
12012
12034
|
return { ok: true, kind: "void" };
|
|
12013
12035
|
case "proc-captured":
|
|
12014
12036
|
return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
|
|
12015
|
-
|
|
12037
|
+
case "proc-sudo":
|
|
12038
|
+
return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
|
|
12039
|
+
}
|
|
12040
|
+
}
|
|
12041
|
+
async sudo(req, cancelled) {
|
|
12042
|
+
const { sudo } = this.options;
|
|
12043
|
+
if (!sudo) throw new KernelError("ENOSYS");
|
|
12044
|
+
const call = {
|
|
12045
|
+
op: "proc-sudo",
|
|
12046
|
+
args: words(req.args),
|
|
12047
|
+
cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
|
|
12048
|
+
...typeof req.term === "string" ? { term: req.term } : {}
|
|
12049
|
+
};
|
|
12050
|
+
const answer = await sudo(call, this.blockingSignal(cancelled));
|
|
12051
|
+
if (!("grant" in answer)) return answer;
|
|
12052
|
+
if (this.dying) throw new KernelError("EINTR");
|
|
12053
|
+
const { file, argv, env, cwd, cred, caller } = answer.grant;
|
|
12054
|
+
const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
|
|
12055
|
+
return {
|
|
12056
|
+
pid: await this.children.spawn({ file, argv, env, cwd }, stdio, [], { cred, caller })
|
|
12057
|
+
};
|
|
12016
12058
|
}
|
|
12017
12059
|
async mountSyscall(req, cancelled) {
|
|
12018
12060
|
const { mount, umount } = this.options;
|
|
@@ -12020,9 +12062,9 @@ var WasmProcess = class {
|
|
|
12020
12062
|
try {
|
|
12021
12063
|
if (req.op === "umount") await umount(req.target, req.flags);
|
|
12022
12064
|
else {
|
|
12023
|
-
const { source, target, type, flags, data } = req;
|
|
12065
|
+
const { source, target, type, flags: flags2, data } = req;
|
|
12024
12066
|
const signal = this.blockingSignal(cancelled);
|
|
12025
|
-
await Promise.race([mount({ source, target, type, flags, data }, signal), aborted(signal)]);
|
|
12067
|
+
await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted(signal)]);
|
|
12026
12068
|
}
|
|
12027
12069
|
} catch (err) {
|
|
12028
12070
|
const code = err?.code;
|
|
@@ -12165,14 +12207,14 @@ function descId(file) {
|
|
|
12165
12207
|
function inheritedFds(fds) {
|
|
12166
12208
|
return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
|
|
12167
12209
|
const open = fds.get(fd);
|
|
12168
|
-
const
|
|
12210
|
+
const flags2 = fds.statusFlags(fd);
|
|
12169
12211
|
const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
|
|
12170
12212
|
const meta = open.file.heldMeta;
|
|
12171
12213
|
return {
|
|
12172
12214
|
fd,
|
|
12173
12215
|
kind,
|
|
12174
12216
|
...kind === "device" && meta && "device" in meta ? { device: meta } : {},
|
|
12175
|
-
...
|
|
12217
|
+
...flags2 !== void 0 ? { flags: flags2 } : {},
|
|
12176
12218
|
...fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
12177
12219
|
...kind === "stream" ? { desc: descId(open) } : {}
|
|
12178
12220
|
};
|
|
@@ -12325,6 +12367,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
12325
12367
|
kill: opts.kill,
|
|
12326
12368
|
...opts.writesBack ? { writesBack: opts.writesBack } : {},
|
|
12327
12369
|
processes: opts.processes,
|
|
12370
|
+
...opts.sudo ? { sudo: opts.sudo } : {},
|
|
12328
12371
|
openFiles: opts.openFiles,
|
|
12329
12372
|
...opts.nodes ? { nodes: opts.nodes } : {},
|
|
12330
12373
|
mounts: opts.mounts,
|
|
@@ -12974,7 +13017,7 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
|
|
|
12974
13017
|
(result) => {
|
|
12975
13018
|
if (result instanceof Uint8Array) {
|
|
12976
13019
|
const whole = result.byteOffset === 0 && result.byteLength === result.buffer.byteLength;
|
|
12977
|
-
const bytes = options.owned && whole ? result : result
|
|
13020
|
+
const bytes = options.owned && whole ? result : fresh(result);
|
|
12978
13021
|
reply({ id: data.id, result: bytes }, [bytes.buffer]);
|
|
12979
13022
|
} else reply({ id: data.id, result: result ?? null });
|
|
12980
13023
|
},
|
|
@@ -13083,12 +13126,12 @@ function kernelFsHandlers(fs) {
|
|
|
13083
13126
|
const attr2 = attrOf(st);
|
|
13084
13127
|
return [{ name: name2, kind: attr2.kind, attr: attr2 }];
|
|
13085
13128
|
}),
|
|
13086
|
-
async open(path,
|
|
13129
|
+
async open(path, flags2) {
|
|
13087
13130
|
const st = await existing(path);
|
|
13088
|
-
if (st &&
|
|
13089
|
-
if (st?.isDirectory &&
|
|
13090
|
-
if (!st && !
|
|
13091
|
-
if (!st ||
|
|
13131
|
+
if (st && flags2.exclusive) throw fsError2("EEXIST", path);
|
|
13132
|
+
if (st?.isDirectory && flags2.write) throw fsError2("EISDIR", path);
|
|
13133
|
+
if (!st && !flags2.create) throw fsError2("ENOENT", path);
|
|
13134
|
+
if (!st || flags2.truncate) await fs.writeFile(path, new Uint8Array(0));
|
|
13092
13135
|
handles.set(++nextFh, { path });
|
|
13093
13136
|
return nextFh;
|
|
13094
13137
|
},
|
|
@@ -13218,24 +13261,24 @@ function absolute(target) {
|
|
|
13218
13261
|
return normalizePath(target);
|
|
13219
13262
|
}
|
|
13220
13263
|
function mountCall(call) {
|
|
13221
|
-
let
|
|
13222
|
-
if ((
|
|
13223
|
-
if (
|
|
13224
|
-
throw errnoError("EINVAL", `unsupported mount flags ${
|
|
13264
|
+
let flags2 = call.flags >>> 0;
|
|
13265
|
+
if ((flags2 & MS_MGC_MSK) >>> 0 === MS_MGC_VAL) flags2 &= ~MS_MGC_MSK;
|
|
13266
|
+
if (flags2 & ~(MS_RDONLY | IGNORED_FLAGS)) {
|
|
13267
|
+
throw errnoError("EINVAL", `unsupported mount flags ${flags2.toString(16)}`);
|
|
13225
13268
|
}
|
|
13226
13269
|
if (!call.type) throw errnoError("EINVAL", "no file system type");
|
|
13227
13270
|
const target = absolute(call.target);
|
|
13228
13271
|
refusedTarget(target);
|
|
13229
13272
|
const options = parseMountOptions(call.data);
|
|
13230
|
-
if (
|
|
13273
|
+
if (flags2 & MS_RDONLY) options.ro = "";
|
|
13231
13274
|
return { type: call.type, source: call.source || "none", target, options };
|
|
13232
13275
|
}
|
|
13233
|
-
function umountCall(target,
|
|
13276
|
+
function umountCall(target, flags2) {
|
|
13234
13277
|
const known = MNT_FORCE | MNT_DETACH | UMOUNT_NOFOLLOW;
|
|
13235
|
-
if (
|
|
13278
|
+
if (flags2 >>> 0 & ~known) throw errnoError("EINVAL", `unsupported umount flags ${flags2}`);
|
|
13236
13279
|
const at = absolute(target);
|
|
13237
13280
|
refusedTarget(at);
|
|
13238
|
-
return { target: at, detach: (
|
|
13281
|
+
return { target: at, detach: (flags2 & (MNT_FORCE | MNT_DETACH)) !== 0 };
|
|
13239
13282
|
}
|
|
13240
13283
|
|
|
13241
13284
|
// src/mount/fstab.ts
|
|
@@ -13293,22 +13336,22 @@ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discar
|
|
|
13293
13336
|
);
|
|
13294
13337
|
}
|
|
13295
13338
|
async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
|
|
13296
|
-
let
|
|
13339
|
+
let failed3 = invalid;
|
|
13297
13340
|
for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
|
|
13298
13341
|
try {
|
|
13299
13342
|
const entry = await mount(spec);
|
|
13300
13343
|
if (!signal.aborted) return { line, target: spec.target, entry };
|
|
13301
13344
|
discard(entry);
|
|
13302
|
-
|
|
13345
|
+
failed3 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
|
|
13303
13346
|
break;
|
|
13304
13347
|
} catch (err) {
|
|
13305
|
-
|
|
13348
|
+
failed3 = err;
|
|
13306
13349
|
if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
|
|
13307
13350
|
await wait(retries[attempt], signal);
|
|
13308
13351
|
}
|
|
13309
13352
|
}
|
|
13310
|
-
const code = codeOf3(
|
|
13311
|
-
return { line, target: spec.target, code, error: String(
|
|
13353
|
+
const code = codeOf3(failed3);
|
|
13354
|
+
return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
|
|
13312
13355
|
}
|
|
13313
13356
|
|
|
13314
13357
|
// src/mount/hostfs.ts
|
|
@@ -13462,14 +13505,14 @@ var RESTORE_AGAIN_MS = 3e4;
|
|
|
13462
13505
|
function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
13463
13506
|
const handles = /* @__PURE__ */ new Map();
|
|
13464
13507
|
let nextFh = 0;
|
|
13465
|
-
const
|
|
13508
|
+
const fresh2 = (remote) => {
|
|
13466
13509
|
if (remote.client.token === remote.token && !remote.client.gone) return remote;
|
|
13467
13510
|
throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
|
|
13468
13511
|
};
|
|
13469
13512
|
const live = (fh) => {
|
|
13470
13513
|
const remote = handles.get(fh);
|
|
13471
13514
|
if (!remote) throw fsError2("EBADF", `handle ${fh}`);
|
|
13472
|
-
return
|
|
13515
|
+
return fresh2(remote);
|
|
13473
13516
|
};
|
|
13474
13517
|
const call = (op, body = {}) => current().call(op, body);
|
|
13475
13518
|
const same = (was, mode, seen) => seen?.mode === (mode | 128) && (was.ino !== void 0 && seen.ino !== void 0 ? seen.ino === was.ino : seen.size === was.size && seen.mtime === was.mtime);
|
|
@@ -13485,13 +13528,13 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13485
13528
|
}, againMs);
|
|
13486
13529
|
timer.unref?.();
|
|
13487
13530
|
});
|
|
13488
|
-
async function writable2(client, path,
|
|
13489
|
-
const { ifMatch: _, ...asked } =
|
|
13531
|
+
async function writable2(client, path, flags2) {
|
|
13532
|
+
const { ifMatch: _, ...asked } = flags2;
|
|
13490
13533
|
const opening2 = () => client.call("open", { path: rel(path), ...asked });
|
|
13491
13534
|
try {
|
|
13492
13535
|
return { opened: await opening2(), restore: void 0 };
|
|
13493
13536
|
} catch (err) {
|
|
13494
|
-
if (!
|
|
13537
|
+
if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
|
|
13495
13538
|
const was = await client.call("stat", { path: rel(path) });
|
|
13496
13539
|
const mode = was.mode ?? 0;
|
|
13497
13540
|
if (mode & 128) throw err;
|
|
@@ -13513,11 +13556,11 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13513
13556
|
);
|
|
13514
13557
|
return entries.map(({ name: name2, attr: attr2 }) => ({ name: name2, kind: attr2.kind, attr: attr2 }));
|
|
13515
13558
|
},
|
|
13516
|
-
async open(path,
|
|
13559
|
+
async open(path, flags2) {
|
|
13517
13560
|
const client = current();
|
|
13518
13561
|
const token = client.token;
|
|
13519
|
-
const { opened: opened2, restore } = await writable2(client, path,
|
|
13520
|
-
if (
|
|
13562
|
+
const { opened: opened2, restore } = await writable2(client, path, flags2);
|
|
13563
|
+
if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
|
|
13521
13564
|
await client.call("release", { fh: opened2.fh }, true);
|
|
13522
13565
|
throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
|
|
13523
13566
|
}
|
|
@@ -13525,7 +13568,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13525
13568
|
client,
|
|
13526
13569
|
token,
|
|
13527
13570
|
fh: opened2.fh,
|
|
13528
|
-
etag:
|
|
13571
|
+
etag: flags2.write ? void 0 : opened2.attr?.etag,
|
|
13529
13572
|
restore
|
|
13530
13573
|
});
|
|
13531
13574
|
return nextFh;
|
|
@@ -13551,7 +13594,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13551
13594
|
if (!remote) throw fsError2("EBADF", `handle ${fh}`);
|
|
13552
13595
|
handles.delete(fh);
|
|
13553
13596
|
try {
|
|
13554
|
-
await
|
|
13597
|
+
await fresh2(remote).client.call("release", { fh: remote.fh }, true);
|
|
13555
13598
|
} finally {
|
|
13556
13599
|
if (remote.restore) await settle3(current(), remote.restore);
|
|
13557
13600
|
}
|
|
@@ -14102,14 +14145,14 @@ function tmpfs() {
|
|
|
14102
14145
|
if (!found) throw fsError2("EBADF", `handle ${fh}`);
|
|
14103
14146
|
return found;
|
|
14104
14147
|
};
|
|
14105
|
-
function opened2(path,
|
|
14148
|
+
function opened2(path, flags2) {
|
|
14106
14149
|
const { dir, name: name2 } = parent(path);
|
|
14107
14150
|
const existing = dir.children.get(name2);
|
|
14108
|
-
if (existing &&
|
|
14109
|
-
if (existing?.kind === "directory" &&
|
|
14151
|
+
if (existing && flags2.exclusive) throw fsError2("EEXIST", path);
|
|
14152
|
+
if (existing?.kind === "directory" && flags2.write) throw fsError2("EISDIR", path);
|
|
14110
14153
|
if (existing) return existing;
|
|
14111
|
-
if (!
|
|
14112
|
-
const node = make("file", 33188, dir,
|
|
14154
|
+
if (!flags2.create) throw fsError2("ENOENT", path);
|
|
14155
|
+
const node = make("file", 33188, dir, flags2.owner);
|
|
14113
14156
|
place(dir, name2, node);
|
|
14114
14157
|
return node;
|
|
14115
14158
|
}
|
|
@@ -14129,13 +14172,13 @@ function tmpfs() {
|
|
|
14129
14172
|
([name2, node]) => ({ name: name2, kind: node.kind, attr: attr(node) })
|
|
14130
14173
|
);
|
|
14131
14174
|
},
|
|
14132
|
-
async open(path,
|
|
14133
|
-
const node = opened2(path,
|
|
14134
|
-
if (
|
|
14175
|
+
async open(path, flags2) {
|
|
14176
|
+
const node = opened2(path, flags2);
|
|
14177
|
+
if (flags2.truncate && node.kind === "file") {
|
|
14135
14178
|
node.data.truncate(0);
|
|
14136
14179
|
node.mtime = Date.now();
|
|
14137
14180
|
}
|
|
14138
|
-
handles.set(++nextFh, { node, write:
|
|
14181
|
+
handles.set(++nextFh, { node, write: flags2.write });
|
|
14139
14182
|
return nextFh;
|
|
14140
14183
|
},
|
|
14141
14184
|
async read(fh, offset, size) {
|
|
@@ -14251,10 +14294,10 @@ function importedMemory(bytes) {
|
|
|
14251
14294
|
return s;
|
|
14252
14295
|
};
|
|
14253
14296
|
const limits = () => {
|
|
14254
|
-
const
|
|
14297
|
+
const flags2 = bytes[at++];
|
|
14255
14298
|
const initial = u32();
|
|
14256
|
-
const maximum =
|
|
14257
|
-
return { initial, maximum, shared: (
|
|
14299
|
+
const maximum = flags2 & 1 ? u32() : void 0;
|
|
14300
|
+
return { initial, maximum, shared: (flags2 & 2) !== 0 };
|
|
14258
14301
|
};
|
|
14259
14302
|
while (at < bytes.length) {
|
|
14260
14303
|
const id2 = bytes[at++];
|
|
@@ -14333,9 +14376,9 @@ var Reader = class {
|
|
|
14333
14376
|
return code;
|
|
14334
14377
|
}
|
|
14335
14378
|
limits() {
|
|
14336
|
-
const
|
|
14379
|
+
const flags2 = this.byte();
|
|
14337
14380
|
this.u32();
|
|
14338
|
-
if (
|
|
14381
|
+
if (flags2 & 1) this.u32();
|
|
14339
14382
|
}
|
|
14340
14383
|
};
|
|
14341
14384
|
function readTypes(r) {
|
|
@@ -14390,6 +14433,412 @@ function foreignImports(bytes) {
|
|
|
14390
14433
|
return {};
|
|
14391
14434
|
}
|
|
14392
14435
|
|
|
14436
|
+
// src/sudo.ts
|
|
14437
|
+
var SUDOERS = "/etc/sudoers";
|
|
14438
|
+
var SUDOERS_D = "/etc/sudoers.d";
|
|
14439
|
+
var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
14440
|
+
var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
|
|
14441
|
+
var SUDO_TIMEOUT_MS = 3e5;
|
|
14442
|
+
var NAME3 = /^[a-z_][a-z0-9_.-]*$/;
|
|
14443
|
+
var RULE = /^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?Cmnd(?:\s+(.*))?$/;
|
|
14444
|
+
var TERM = /^[A-Za-z0-9._+-]+$/;
|
|
14445
|
+
var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
14446
|
+
var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
|
|
14447
|
+
function failed2(errno, error) {
|
|
14448
|
+
return { error, errno };
|
|
14449
|
+
}
|
|
14450
|
+
function unescaped(double) {
|
|
14451
|
+
return double.replace(/\\(["\\$`])/g, "$1");
|
|
14452
|
+
}
|
|
14453
|
+
function splitWords(text) {
|
|
14454
|
+
const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
|
|
14455
|
+
const words2 = [];
|
|
14456
|
+
let word;
|
|
14457
|
+
while (piece.lastIndex < text.length) {
|
|
14458
|
+
const m = piece.exec(text);
|
|
14459
|
+
if (!m) return void 0;
|
|
14460
|
+
const [, single, double, escaped, plain, space] = m;
|
|
14461
|
+
if (space !== void 0) {
|
|
14462
|
+
if (word !== void 0) words2.push(word);
|
|
14463
|
+
word = void 0;
|
|
14464
|
+
continue;
|
|
14465
|
+
}
|
|
14466
|
+
word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain));
|
|
14467
|
+
}
|
|
14468
|
+
if (word !== void 0) words2.push(word);
|
|
14469
|
+
return words2;
|
|
14470
|
+
}
|
|
14471
|
+
function quoteWord(word) {
|
|
14472
|
+
return PLAIN.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
14473
|
+
}
|
|
14474
|
+
function validWho(who) {
|
|
14475
|
+
if (who === "ALL") return true;
|
|
14476
|
+
return NAME3.test(who.startsWith("%") ? who.slice(1) : who);
|
|
14477
|
+
}
|
|
14478
|
+
function parseRule(line) {
|
|
14479
|
+
const text = line.trim();
|
|
14480
|
+
if (text === "" || text.startsWith("#")) return null;
|
|
14481
|
+
const m = RULE.exec(text);
|
|
14482
|
+
if (!m) return void 0;
|
|
14483
|
+
const [, nopasswd, who = "", target = "root", rest2 = ""] = m;
|
|
14484
|
+
if (!validWho(who) || target !== "ALL" && !NAME3.test(target)) return void 0;
|
|
14485
|
+
if (rest2.trim() === "*") return { nopasswd: !!nopasswd, who, target, cmnd: "*" };
|
|
14486
|
+
const cmnd = splitWords(rest2);
|
|
14487
|
+
if (!cmnd?.[0]?.startsWith("/")) return void 0;
|
|
14488
|
+
return { nopasswd: !!nopasswd, who, target, cmnd };
|
|
14489
|
+
}
|
|
14490
|
+
function flags(parsed2, arg, next2) {
|
|
14491
|
+
for (let j = 1; j < arg.length; j++) {
|
|
14492
|
+
const flag = arg[j];
|
|
14493
|
+
if (flag === "n") parsed2.nonInteractive = true;
|
|
14494
|
+
else if (flag === "l") parsed2.list = true;
|
|
14495
|
+
else if (flag !== "u") return `sudo: invalid option -- '${flag}'
|
|
14496
|
+
${USAGE}`;
|
|
14497
|
+
else {
|
|
14498
|
+
const user = arg.slice(j + 1) || next2;
|
|
14499
|
+
if (!user) return `sudo: option requires an argument -- 'u'
|
|
14500
|
+
${USAGE}`;
|
|
14501
|
+
parsed2.user = user;
|
|
14502
|
+
return arg.slice(j + 1) ? 0 : 1;
|
|
14503
|
+
}
|
|
14504
|
+
}
|
|
14505
|
+
return 0;
|
|
14506
|
+
}
|
|
14507
|
+
function parseArgs(args) {
|
|
14508
|
+
const parsed2 = { list: false, nonInteractive: false, user: "root", command: [] };
|
|
14509
|
+
let i = 0;
|
|
14510
|
+
for (; i < args.length; i++) {
|
|
14511
|
+
const arg = args[i];
|
|
14512
|
+
if (arg === "--") {
|
|
14513
|
+
i++;
|
|
14514
|
+
break;
|
|
14515
|
+
}
|
|
14516
|
+
if (!arg.startsWith("-") || arg === "-") break;
|
|
14517
|
+
const used = flags(parsed2, arg, args[i + 1]);
|
|
14518
|
+
if (typeof used === "string") return used;
|
|
14519
|
+
i += used;
|
|
14520
|
+
}
|
|
14521
|
+
parsed2.command = args.slice(i);
|
|
14522
|
+
if (parsed2.list ? parsed2.command.length > 0 : parsed2.command.length === 0) return USAGE;
|
|
14523
|
+
return parsed2;
|
|
14524
|
+
}
|
|
14525
|
+
function described(rule) {
|
|
14526
|
+
const cmnd = rule.cmnd === "*" ? "ALL" : rule.cmnd.map(quoteWord).join(" ");
|
|
14527
|
+
return ` (${rule.target}) ${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
|
|
14528
|
+
}
|
|
14529
|
+
var Sudo = class {
|
|
14530
|
+
o;
|
|
14531
|
+
warned = /* @__PURE__ */ new Set();
|
|
14532
|
+
pending = /* @__PURE__ */ new Set();
|
|
14533
|
+
next = 0;
|
|
14534
|
+
constructor(options) {
|
|
14535
|
+
this.o = options;
|
|
14536
|
+
}
|
|
14537
|
+
warn(message3) {
|
|
14538
|
+
if (this.warned.has(message3)) return;
|
|
14539
|
+
this.warned.add(message3);
|
|
14540
|
+
(this.o.warn ?? console.warn)(message3);
|
|
14541
|
+
}
|
|
14542
|
+
async provision() {
|
|
14543
|
+
const { fs } = this.o;
|
|
14544
|
+
if (!await fs.exists(SUDOERS)) {
|
|
14545
|
+
await fs.writeFile(SUDOERS, DEFAULT_SUDOERS);
|
|
14546
|
+
await fs.chown?.(SUDOERS, { uid: 0, gid: 0, mode: 288, follow: false });
|
|
14547
|
+
}
|
|
14548
|
+
if (!await fs.exists(SUDOERS_D)) {
|
|
14549
|
+
await fs.mkdir(SUDOERS_D, { recursive: true });
|
|
14550
|
+
await fs.chown?.(SUDOERS_D, { uid: 0, gid: 0, mode: 488, follow: false });
|
|
14551
|
+
}
|
|
14552
|
+
}
|
|
14553
|
+
async trusted(path) {
|
|
14554
|
+
const parts2 = normalizePath(path).split("/").filter(Boolean);
|
|
14555
|
+
for (let n = 0; n <= parts2.length; n++) {
|
|
14556
|
+
const at = `/${parts2.slice(0, n).join("/")}`;
|
|
14557
|
+
const st = await this.o.fs.lstat(at).catch(() => void 0);
|
|
14558
|
+
if (!st || st.isSymbolicLink || st.uid !== 0 || (st.mode & 18) !== 0) return false;
|
|
14559
|
+
}
|
|
14560
|
+
return true;
|
|
14561
|
+
}
|
|
14562
|
+
async policyFile(path) {
|
|
14563
|
+
if (!await this.trusted(path)) {
|
|
14564
|
+
this.warn(`sudo: ${path} skipped: it must be root's, no symlink, and writable by root only`);
|
|
14565
|
+
return [];
|
|
14566
|
+
}
|
|
14567
|
+
const text = await this.o.fs.readFile(path).catch(() => "");
|
|
14568
|
+
const rules = [];
|
|
14569
|
+
for (const [n, line] of text.split("\n").entries()) {
|
|
14570
|
+
const rule = parseRule(line);
|
|
14571
|
+
if (rule) rules.push(rule);
|
|
14572
|
+
else if (rule === void 0) this.warn(`sudo: ${path}:${n + 1}: bad line skipped: ${line}`);
|
|
14573
|
+
}
|
|
14574
|
+
return rules;
|
|
14575
|
+
}
|
|
14576
|
+
async policy() {
|
|
14577
|
+
const rules = await this.policyFile(SUDOERS);
|
|
14578
|
+
const names = await this.o.fs.readdir(SUDOERS_D).catch(() => []);
|
|
14579
|
+
if (names.length > 0 && !await this.trusted(SUDOERS_D)) {
|
|
14580
|
+
this.warn(`sudo: ${SUDOERS_D} skipped: it must be root's and writable by root only`);
|
|
14581
|
+
return rules;
|
|
14582
|
+
}
|
|
14583
|
+
for (const name2 of [...names].sort()) {
|
|
14584
|
+
if (name2.startsWith(".") || name2.endsWith("~")) continue;
|
|
14585
|
+
rules.push(...await this.policyFile(`${SUDOERS_D}/${name2}`));
|
|
14586
|
+
}
|
|
14587
|
+
return rules;
|
|
14588
|
+
}
|
|
14589
|
+
async groupIds() {
|
|
14590
|
+
return new Map((await this.o.users.groups()).map((g) => [g.name, g.gid]));
|
|
14591
|
+
}
|
|
14592
|
+
async applies(caller, cred) {
|
|
14593
|
+
const gids = await this.groupIds();
|
|
14594
|
+
const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
|
|
14595
|
+
return (rule) => {
|
|
14596
|
+
if (rule.who === "ALL" || rule.who === caller.name) return true;
|
|
14597
|
+
const gid = rule.who.startsWith("%") ? gids.get(rule.who.slice(1)) : void 0;
|
|
14598
|
+
return gid !== void 0 && mine.has(gid);
|
|
14599
|
+
};
|
|
14600
|
+
}
|
|
14601
|
+
async lookup(user) {
|
|
14602
|
+
const key = /^#\d+$/.test(user) ? Number(user.slice(1)) : user;
|
|
14603
|
+
return this.o.users.lookup(key);
|
|
14604
|
+
}
|
|
14605
|
+
async canonical(cwd) {
|
|
14606
|
+
const { fs } = this.o;
|
|
14607
|
+
const path = normalizePath(cwd);
|
|
14608
|
+
return fs.realpath ? fs.realpath(path, true).catch(() => void 0) : path;
|
|
14609
|
+
}
|
|
14610
|
+
async reachable(cred, cwd) {
|
|
14611
|
+
const walked = await access(this.o.fs, cred, cwd, X_OK, { directory: true });
|
|
14612
|
+
if (walked.denied !== void 0 || walked.missing) return false;
|
|
14613
|
+
return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
|
|
14614
|
+
}
|
|
14615
|
+
async untrusted(path, cwd) {
|
|
14616
|
+
const files = await this.o.backing?.(path, cwd) ?? [];
|
|
14617
|
+
for (const file of [path, ...files]) if (!await this.trusted(file)) return file;
|
|
14618
|
+
return void 0;
|
|
14619
|
+
}
|
|
14620
|
+
async locate(word, cwd) {
|
|
14621
|
+
const { fs } = this.o;
|
|
14622
|
+
const candidates = word.includes("/") ? [fs.resolvePath(cwd, word)] : SECURE_PATH.split(":").map((dir) => `${dir}/${word}`);
|
|
14623
|
+
for (const path of candidates) {
|
|
14624
|
+
const st = await fs.stat(path).catch(() => void 0);
|
|
14625
|
+
if (!st?.isFile || (st.mode & 73) === 0) continue;
|
|
14626
|
+
return fs.realpath ? fs.realpath(path, true).catch(() => path) : path;
|
|
14627
|
+
}
|
|
14628
|
+
return void 0;
|
|
14629
|
+
}
|
|
14630
|
+
async request(caller, call, signal) {
|
|
14631
|
+
const parsed2 = parseArgs(call.args);
|
|
14632
|
+
if (typeof parsed2 === "string") return failed2("EINVAL", parsed2);
|
|
14633
|
+
if (this.o.mode !== "enforce") {
|
|
14634
|
+
const mode = this.o.mode;
|
|
14635
|
+
if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
|
|
14636
|
+
` };
|
|
14637
|
+
return { run: parsed2.command };
|
|
14638
|
+
}
|
|
14639
|
+
const { cred } = caller;
|
|
14640
|
+
const self = await this.o.users.lookup(cred.ruid);
|
|
14641
|
+
if (!self) return failed2("EPERM", `sudo: uid ${cred.ruid} is not in the passwd database`);
|
|
14642
|
+
const target = await this.lookup(parsed2.user);
|
|
14643
|
+
if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
|
|
14644
|
+
const root = cred.euid === 0;
|
|
14645
|
+
const rules = root ? [] : await this.policy();
|
|
14646
|
+
const applies = await this.applies(self, cred);
|
|
14647
|
+
if (parsed2.list) return this.listed(self, root, rules.filter(applies));
|
|
14648
|
+
const [word = "", ...args] = parsed2.command;
|
|
14649
|
+
const cwd = await this.canonical(call.cwd);
|
|
14650
|
+
if (cwd === void 0 || !await this.reachable(cred, cwd)) {
|
|
14651
|
+
return failed2(
|
|
14652
|
+
"EACCES",
|
|
14653
|
+
`sudo: cannot run in ${call.cwd}: it isn't a directory you can enter`
|
|
14654
|
+
);
|
|
14655
|
+
}
|
|
14656
|
+
const path = await this.locate(word, cwd);
|
|
14657
|
+
if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
|
|
14658
|
+
const untrusted = await this.untrusted(path, cwd);
|
|
14659
|
+
if (untrusted) {
|
|
14660
|
+
return failed2("EPERM", `sudo: ${untrusted} must be root's, and writable by root only`);
|
|
14661
|
+
}
|
|
14662
|
+
const argv = [path, ...args];
|
|
14663
|
+
const matching = rules.filter(
|
|
14664
|
+
(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]))
|
|
14665
|
+
);
|
|
14666
|
+
const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
|
|
14667
|
+
if (root || matching.some((rule) => rule.nopasswd)) return grant();
|
|
14668
|
+
const quoted = argv.map(quoteWord).join(" ");
|
|
14669
|
+
if (matching.length === 0) {
|
|
14670
|
+
return failed2(
|
|
14671
|
+
"EPERM",
|
|
14672
|
+
`sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
|
|
14673
|
+
);
|
|
14674
|
+
}
|
|
14675
|
+
if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
|
|
14676
|
+
if (this.pending.has(cred.ruid)) return failed2("EBUSY", "sudo: another request is pending");
|
|
14677
|
+
this.pending.add(cred.ruid);
|
|
14678
|
+
try {
|
|
14679
|
+
const answer = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
|
|
14680
|
+
if (typeof answer !== "string") return answer;
|
|
14681
|
+
if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
|
|
14682
|
+
return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
|
|
14683
|
+
}
|
|
14684
|
+
const changed = await this.untrusted(path, cwd);
|
|
14685
|
+
if (changed) {
|
|
14686
|
+
return failed2("EPERM", `sudo: ${changed} must be root's, and writable by root only`);
|
|
14687
|
+
}
|
|
14688
|
+
if (answer === "always" && !await this.remember(self, target, argv)) {
|
|
14689
|
+
return failed2("EIO", "sudo: the rule could not be recorded");
|
|
14690
|
+
}
|
|
14691
|
+
return grant();
|
|
14692
|
+
} finally {
|
|
14693
|
+
this.pending.delete(cred.ruid);
|
|
14694
|
+
}
|
|
14695
|
+
}
|
|
14696
|
+
listed(self, root, rules) {
|
|
14697
|
+
const host = this.o.hostname;
|
|
14698
|
+
if (root) {
|
|
14699
|
+
return {
|
|
14700
|
+
list: `User ${self.name} may run the following commands on ${host}:
|
|
14701
|
+
(ALL) NOPASSWD: ALL
|
|
14702
|
+
`
|
|
14703
|
+
};
|
|
14704
|
+
}
|
|
14705
|
+
if (rules.length === 0) {
|
|
14706
|
+
return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
|
|
14707
|
+
}
|
|
14708
|
+
const lines2 = rules.map(described).join("\n");
|
|
14709
|
+
return { list: `User ${self.name} may run the following commands on ${host}:
|
|
14710
|
+
${lines2}
|
|
14711
|
+
` };
|
|
14712
|
+
}
|
|
14713
|
+
async ask(caller, self, target, argv, path, cwd, requester) {
|
|
14714
|
+
const { approve } = this.o;
|
|
14715
|
+
if (!approve || requester.aborted) return failed2("EACCES", "sudo: approval denied");
|
|
14716
|
+
const createdAt = Date.now();
|
|
14717
|
+
const timeout = this.o.timeoutMs ?? SUDO_TIMEOUT_MS;
|
|
14718
|
+
const req = {
|
|
14719
|
+
id: `sudo-${++this.next}-${crypto.randomUUID()}`,
|
|
14720
|
+
kind: "sudo",
|
|
14721
|
+
user: self.name,
|
|
14722
|
+
uid: self.uid,
|
|
14723
|
+
target: target.name,
|
|
14724
|
+
argv: [...argv],
|
|
14725
|
+
path,
|
|
14726
|
+
cwd,
|
|
14727
|
+
pid: caller.pid,
|
|
14728
|
+
...caller.tag !== void 0 ? { tag: caller.tag } : {},
|
|
14729
|
+
...caller.tty ? { tty: caller.tty } : {},
|
|
14730
|
+
createdAt,
|
|
14731
|
+
expiresAt: createdAt + timeout,
|
|
14732
|
+
always: true
|
|
14733
|
+
};
|
|
14734
|
+
const control = new AbortController();
|
|
14735
|
+
const timer = setTimeout(() => control.abort("expired"), timeout);
|
|
14736
|
+
const withdraw = () => control.abort("withdrawn");
|
|
14737
|
+
requester.addEventListener("abort", withdraw, { once: true });
|
|
14738
|
+
const aborted2 = new Promise(
|
|
14739
|
+
(resolve) => control.signal.addEventListener("abort", () => resolve("aborted"), { once: true })
|
|
14740
|
+
);
|
|
14741
|
+
try {
|
|
14742
|
+
const answered2 = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
|
|
14743
|
+
const answer = await Promise.race([answered2, aborted2]);
|
|
14744
|
+
if (answer === "aborted" || control.signal.aborted) {
|
|
14745
|
+
return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
|
|
14746
|
+
}
|
|
14747
|
+
return answer === "once" || answer === "always" ? answer : failed2("EACCES", "sudo: approval denied");
|
|
14748
|
+
} finally {
|
|
14749
|
+
clearTimeout(timer);
|
|
14750
|
+
requester.removeEventListener("abort", withdraw);
|
|
14751
|
+
}
|
|
14752
|
+
}
|
|
14753
|
+
async remember(self, target, argv) {
|
|
14754
|
+
const { fs } = this.o;
|
|
14755
|
+
const path = `${SUDOERS_D}/${self.name}`;
|
|
14756
|
+
if (!NAME3.test(self.name)) return false;
|
|
14757
|
+
try {
|
|
14758
|
+
await this.provision();
|
|
14759
|
+
if (!await this.trusted(SUDOERS_D)) return false;
|
|
14760
|
+
const st = await fs.lstat(path).catch(() => void 0);
|
|
14761
|
+
if (st && !await this.trusted(path)) return false;
|
|
14762
|
+
const old = st ? await fs.readFile(path) : "";
|
|
14763
|
+
const line = `NOPASSWD ${self.name} (${target.name}) Cmnd ${argv.map(quoteWord).join(" ")}
|
|
14764
|
+
`;
|
|
14765
|
+
const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
|
|
14766
|
+
${line}`;
|
|
14767
|
+
await fs.writeFile(path, text);
|
|
14768
|
+
await fs.chown?.(path, { uid: 0, gid: 0, mode: 288, follow: false });
|
|
14769
|
+
return true;
|
|
14770
|
+
} catch {
|
|
14771
|
+
return false;
|
|
14772
|
+
}
|
|
14773
|
+
}
|
|
14774
|
+
grant(self, target, cred, argv, file, cwd, term) {
|
|
14775
|
+
const env = {
|
|
14776
|
+
...this.o.env(),
|
|
14777
|
+
...userEnv(target, this.o.pnpmHome),
|
|
14778
|
+
SHELL: target.shell,
|
|
14779
|
+
PATH: SECURE_PATH,
|
|
14780
|
+
PWD: cwd,
|
|
14781
|
+
...term !== void 0 && TERM.test(term) ? { TERM: term } : {},
|
|
14782
|
+
SUDO_USER: self.name,
|
|
14783
|
+
SUDO_UID: String(cred.ruid),
|
|
14784
|
+
SUDO_GID: String(cred.rgid),
|
|
14785
|
+
SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
|
|
14786
|
+
};
|
|
14787
|
+
return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
|
|
14788
|
+
}
|
|
14789
|
+
};
|
|
14790
|
+
|
|
14791
|
+
// src/sudo-command.ts
|
|
14792
|
+
var SUDO_PATH = "/usr/bin/sudo";
|
|
14793
|
+
var SUDO_COMMAND = {
|
|
14794
|
+
name: "sudo",
|
|
14795
|
+
abi: "js",
|
|
14796
|
+
glue: SUDO_PATH,
|
|
14797
|
+
wasm: SUDO_PATH,
|
|
14798
|
+
argv0: "sudo"
|
|
14799
|
+
};
|
|
14800
|
+
var SUDO_SOURCE = [
|
|
14801
|
+
"export default async function sudo(ctx) {",
|
|
14802
|
+
" const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);",
|
|
14803
|
+
" let pid;",
|
|
14804
|
+
" let held;",
|
|
14805
|
+
" const relay = (sig) => {",
|
|
14806
|
+
" if (pid === undefined) held = sig;",
|
|
14807
|
+
" else return ctx.kill(pid, sig).catch(() => undefined);",
|
|
14808
|
+
" };",
|
|
14809
|
+
' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
|
|
14810
|
+
" const failed = () => (held === undefined ? 1 : 128 + held);",
|
|
14811
|
+
" let answer;",
|
|
14812
|
+
" try {",
|
|
14813
|
+
" answer = await ctx.sudo(ctx.argv.slice(1));",
|
|
14814
|
+
" } catch (err) {",
|
|
14815
|
+
' await say(2, "sudo: " + (err && err.code ? err.code : String(err)) + "\\n");',
|
|
14816
|
+
" return failed();",
|
|
14817
|
+
" }",
|
|
14818
|
+
" if (answer.error !== undefined) {",
|
|
14819
|
+
' await say(2, answer.error + "\\n");',
|
|
14820
|
+
" return failed();",
|
|
14821
|
+
" }",
|
|
14822
|
+
" if (answer.list !== undefined) {",
|
|
14823
|
+
" await say(1, answer.list);",
|
|
14824
|
+
" return 0;",
|
|
14825
|
+
" }",
|
|
14826
|
+
' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
|
|
14827
|
+
" if (answer.run !== undefined) {",
|
|
14828
|
+
" try {",
|
|
14829
|
+
" pid = (await ctx.spawn({ argv: answer.run })).pid;",
|
|
14830
|
+
" } catch (err) {",
|
|
14831
|
+
' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
|
|
14832
|
+
' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
|
|
14833
|
+
" return failed();",
|
|
14834
|
+
" }",
|
|
14835
|
+
" } else pid = answer.pid;",
|
|
14836
|
+
" if (held !== undefined) await relay(held);",
|
|
14837
|
+
" return (await ctx.wait(pid)).status;",
|
|
14838
|
+
"}",
|
|
14839
|
+
""
|
|
14840
|
+
].join("\n");
|
|
14841
|
+
|
|
14393
14842
|
// src/launcher.ts
|
|
14394
14843
|
var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
14395
14844
|
var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
|
|
@@ -14417,9 +14866,21 @@ function usersMode(mode = "off") {
|
|
|
14417
14866
|
if (USERS_MODES.includes(mode)) return mode;
|
|
14418
14867
|
throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
|
|
14419
14868
|
}
|
|
14869
|
+
function tagOption(tag) {
|
|
14870
|
+
if (tag !== void 0 && typeof tag !== "string") throw new TypeError("a tag must be a string");
|
|
14871
|
+
}
|
|
14420
14872
|
function baseName(path) {
|
|
14421
14873
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
14422
14874
|
}
|
|
14875
|
+
function packageRootOf(path, roots) {
|
|
14876
|
+
for (const root of roots) {
|
|
14877
|
+
const base = normalizePath(root);
|
|
14878
|
+
if (!path.startsWith(`${base}/`)) continue;
|
|
14879
|
+
const [first = "", second] = path.slice(base.length + 1).split("/");
|
|
14880
|
+
return first.startsWith("@") && second !== void 0 ? `${base}/${first}/${second}` : `${base}/${first}`;
|
|
14881
|
+
}
|
|
14882
|
+
return void 0;
|
|
14883
|
+
}
|
|
14423
14884
|
function modulePath(glue) {
|
|
14424
14885
|
return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
|
|
14425
14886
|
}
|
|
@@ -14431,11 +14892,11 @@ function shebang(head) {
|
|
|
14431
14892
|
return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
|
|
14432
14893
|
}
|
|
14433
14894
|
function interpreter(head) {
|
|
14434
|
-
const
|
|
14435
|
-
if (!
|
|
14436
|
-
if (baseName(
|
|
14437
|
-
if (
|
|
14438
|
-
return baseName(
|
|
14895
|
+
const words2 = shebang(head);
|
|
14896
|
+
if (!words2) return "node";
|
|
14897
|
+
if (baseName(words2[0]) === "env") words2.shift();
|
|
14898
|
+
if (words2[0] === "-S") words2.shift();
|
|
14899
|
+
return baseName(words2[0] ?? "");
|
|
14439
14900
|
}
|
|
14440
14901
|
function expandDefaults(defaults, env) {
|
|
14441
14902
|
const out = {};
|
|
@@ -14509,7 +14970,10 @@ var Launcher = class {
|
|
|
14509
14970
|
orphans = /* @__PURE__ */ new Set();
|
|
14510
14971
|
described = /* @__PURE__ */ new Map();
|
|
14511
14972
|
creds = /* @__PURE__ */ new Map();
|
|
14973
|
+
tags = /* @__PURE__ */ new Map();
|
|
14974
|
+
granted = /* @__PURE__ */ new Map();
|
|
14512
14975
|
users;
|
|
14976
|
+
sudoer;
|
|
14513
14977
|
jobs = new JobTable();
|
|
14514
14978
|
locks = new LockTable();
|
|
14515
14979
|
settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
|
|
@@ -14610,6 +15074,17 @@ var Launcher = class {
|
|
|
14610
15074
|
);
|
|
14611
15075
|
this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
|
|
14612
15076
|
this.users = new UserDb(this.fs, this.owners);
|
|
15077
|
+
this.sudoer = new Sudo({
|
|
15078
|
+
fs: this.fs,
|
|
15079
|
+
users: this.users,
|
|
15080
|
+
mode: this.usersMode,
|
|
15081
|
+
hostname,
|
|
15082
|
+
env: () => this.env,
|
|
15083
|
+
pnpmHome: this.pnpmHome,
|
|
15084
|
+
backing: (path, cwd) => this.backing(path, cwd),
|
|
15085
|
+
approve: options.approve,
|
|
15086
|
+
timeoutMs: options.sudoTimeoutMs
|
|
15087
|
+
});
|
|
14613
15088
|
void this.unlinked.sweep("/");
|
|
14614
15089
|
this.watchers.watch(
|
|
14615
15090
|
[this.modulesDir, this.pnpmHome],
|
|
@@ -14630,7 +15105,7 @@ var Launcher = class {
|
|
|
14630
15105
|
commands(scope = "") {
|
|
14631
15106
|
let found = this.catalogs.get(scope);
|
|
14632
15107
|
if (!found) {
|
|
14633
|
-
found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
|
|
15108
|
+
found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
|
|
14634
15109
|
this.catalogs.set(scope, found);
|
|
14635
15110
|
}
|
|
14636
15111
|
return found;
|
|
@@ -14753,10 +15228,10 @@ var Launcher = class {
|
|
|
14753
15228
|
const head = await this.head(script);
|
|
14754
15229
|
if (!head) return void 0;
|
|
14755
15230
|
const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
|
|
14756
|
-
const
|
|
14757
|
-
if (!
|
|
14758
|
-
if (baseName(
|
|
14759
|
-
const [interp, ...rest2] =
|
|
15231
|
+
const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
|
|
15232
|
+
if (!words2) return void 0;
|
|
15233
|
+
if (baseName(words2[0]) === "env") words2.shift();
|
|
15234
|
+
const [interp, ...rest2] = words2;
|
|
14760
15235
|
const arg = rest2.join(" ");
|
|
14761
15236
|
const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
|
|
14762
15237
|
const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
|
|
@@ -14793,6 +15268,41 @@ var Launcher = class {
|
|
|
14793
15268
|
await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
|
|
14794
15269
|
}
|
|
14795
15270
|
}
|
|
15271
|
+
async grantedCwd(caller, cwd) {
|
|
15272
|
+
const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
|
|
15273
|
+
return real === cwd && await this.sudoer.reachable(caller, cwd);
|
|
15274
|
+
}
|
|
15275
|
+
async binfmtManifests(scripts) {
|
|
15276
|
+
const sources = /* @__PURE__ */ new Map();
|
|
15277
|
+
const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
|
|
15278
|
+
const manifests = [];
|
|
15279
|
+
for (const script of scripts) {
|
|
15280
|
+
const head = await this.head(script);
|
|
15281
|
+
if (!head || isWasm(head) || shebang(head)) continue;
|
|
15282
|
+
const pkg = sources.get(binfmtSuffix(binfmts, script) ?? "");
|
|
15283
|
+
if (pkg !== void 0) manifests.push(`${pkg}/package.json`);
|
|
15284
|
+
}
|
|
15285
|
+
return manifests;
|
|
15286
|
+
}
|
|
15287
|
+
async backing(file, cwd) {
|
|
15288
|
+
const planned = await this.plan(file, [file], cwd, "");
|
|
15289
|
+
if (!planned) return [];
|
|
15290
|
+
const { glue, wasm, imports } = planned.target;
|
|
15291
|
+
const files = [glue, wasm, ...imports ? [imports] : []];
|
|
15292
|
+
const scripts = [];
|
|
15293
|
+
for (const name2 of [file, ...planned.via ?? []]) {
|
|
15294
|
+
if (name2.includes("/") && !COMMAND2.test(name2)) scripts.push(this.fs.resolvePath(cwd, name2));
|
|
15295
|
+
const script = (await this.scriptCommand(name2, cwd, ""))?.script;
|
|
15296
|
+
if (script) scripts.push(script);
|
|
15297
|
+
}
|
|
15298
|
+
files.push(...scripts, ...await this.binfmtManifests(scripts));
|
|
15299
|
+
const roots = await this.roots();
|
|
15300
|
+
for (const at of [...files]) {
|
|
15301
|
+
const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
|
|
15302
|
+
if (root !== void 0) files.push(`${root}/package.json`);
|
|
15303
|
+
}
|
|
15304
|
+
return [...new Set(files)].filter((path) => path !== "" && path !== SUDO_PATH);
|
|
15305
|
+
}
|
|
14796
15306
|
async unrunnable(file, cwd, scope) {
|
|
14797
15307
|
const path = this.fs.resolvePath(cwd, file);
|
|
14798
15308
|
const head = await this.base.readFileBuffer(path).catch(() => null);
|
|
@@ -14830,6 +15340,9 @@ var Launcher = class {
|
|
|
14830
15340
|
return await this.base.exists(optional) ? optional : void 0;
|
|
14831
15341
|
}
|
|
14832
15342
|
async program(target, env) {
|
|
15343
|
+
if (target.abi === "js" && target.wasm === SUDO_PATH) {
|
|
15344
|
+
return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
|
|
15345
|
+
}
|
|
14833
15346
|
if (target.abi === "js") {
|
|
14834
15347
|
return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
|
|
14835
15348
|
}
|
|
@@ -14916,6 +15429,7 @@ var Launcher = class {
|
|
|
14916
15429
|
kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
|
|
14917
15430
|
writesBack: (target) => this.processes.get(target)?.writesBack() === true,
|
|
14918
15431
|
processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
|
|
15432
|
+
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
14919
15433
|
openFiles: this.openFiles,
|
|
14920
15434
|
nodes: this.nodes,
|
|
14921
15435
|
held: this.held,
|
|
@@ -14923,7 +15437,7 @@ var Launcher = class {
|
|
|
14923
15437
|
statfs: (path) => this.mounts.statfs(path),
|
|
14924
15438
|
mounts: () => this.mounts.list(),
|
|
14925
15439
|
mount: (call, signal) => this.processMount(pid, call, signal),
|
|
14926
|
-
umount: (target,
|
|
15440
|
+
umount: (target, flags2) => this.processUmount(pid, target, flags2),
|
|
14927
15441
|
jobs: this.jobs,
|
|
14928
15442
|
ptys: this.ptys,
|
|
14929
15443
|
net: this.net,
|
|
@@ -14937,6 +15451,7 @@ var Launcher = class {
|
|
|
14937
15451
|
this.processes.set(pid, handle);
|
|
14938
15452
|
const { cred } = req;
|
|
14939
15453
|
this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
|
|
15454
|
+
if (req.tag !== void 0) this.tags.set(pid, req.tag);
|
|
14940
15455
|
if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
|
|
14941
15456
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
14942
15457
|
if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
|
|
@@ -14964,6 +15479,8 @@ var Launcher = class {
|
|
|
14964
15479
|
this.jobs.remove(pid);
|
|
14965
15480
|
this.described.delete(pid);
|
|
14966
15481
|
this.creds.delete(pid);
|
|
15482
|
+
this.tags.delete(pid);
|
|
15483
|
+
this.granted.delete(pid);
|
|
14967
15484
|
}
|
|
14968
15485
|
reaped(pid) {
|
|
14969
15486
|
if (this.zombies.delete(pid)) this.forget(pid);
|
|
@@ -14985,8 +15502,8 @@ var Launcher = class {
|
|
|
14985
15502
|
};
|
|
14986
15503
|
const listed = [];
|
|
14987
15504
|
for (const member of members.values()) {
|
|
14988
|
-
const
|
|
14989
|
-
if (member.execed || !
|
|
15505
|
+
const described2 = this.described.get(member.pid);
|
|
15506
|
+
if (member.execed || !described2) continue;
|
|
14990
15507
|
const cred = this.credOf(member.pid);
|
|
14991
15508
|
if (!maySee(viewer, cred)) continue;
|
|
14992
15509
|
const root = rootOf(member);
|
|
@@ -14996,8 +15513,8 @@ var Launcher = class {
|
|
|
14996
15513
|
ppid: shown(root.ppid),
|
|
14997
15514
|
pgid: member.pgid,
|
|
14998
15515
|
sid: member.sid,
|
|
14999
|
-
...
|
|
15000
|
-
started: (this.described.get(root.pid) ??
|
|
15516
|
+
...described2,
|
|
15517
|
+
started: (this.described.get(root.pid) ?? described2).started,
|
|
15001
15518
|
state: this.processes.has(member.pid) ? "S" : "Z",
|
|
15002
15519
|
memory: this.processes.get(member.pid)?.memory() ?? 0,
|
|
15003
15520
|
umask: this.umaskOf(member.pid),
|
|
@@ -15007,12 +15524,26 @@ var Launcher = class {
|
|
|
15007
15524
|
}
|
|
15008
15525
|
return listed;
|
|
15009
15526
|
}
|
|
15527
|
+
sudo(pid, call, signal) {
|
|
15528
|
+
const caller = {
|
|
15529
|
+
cred: this.credOf(pid),
|
|
15530
|
+
pid: this.jobs.shown(pid),
|
|
15531
|
+
tag: this.tagOf(pid),
|
|
15532
|
+
tty: this.described.get(pid)?.tty
|
|
15533
|
+
};
|
|
15534
|
+
return this.sudoer.request(caller, call, signal);
|
|
15535
|
+
}
|
|
15010
15536
|
spawner(ppid, report) {
|
|
15011
|
-
return async (req, fds) => {
|
|
15012
|
-
|
|
15537
|
+
return async (req, fds, grant) => {
|
|
15538
|
+
if (grant && !await this.grantedCwd(grant.caller, req.cwd)) {
|
|
15539
|
+
await fds.closeAll();
|
|
15540
|
+
throw new SpawnError("EACCES");
|
|
15541
|
+
}
|
|
15542
|
+
const cred = grant ? grant.cred : this.credOf(ppid);
|
|
15543
|
+
const scope = grant ? "" : await this.scopeOf(cred.euid);
|
|
15013
15544
|
const planned = await this.plan(req.file, req.argv, req.cwd, scope);
|
|
15014
15545
|
try {
|
|
15015
|
-
await this.mayExec(
|
|
15546
|
+
await this.mayExec(cred, { ...req, pid: ppid }, planned);
|
|
15016
15547
|
} catch (err) {
|
|
15017
15548
|
await fds.closeAll();
|
|
15018
15549
|
throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
|
|
@@ -15032,10 +15563,12 @@ var Launcher = class {
|
|
|
15032
15563
|
ppid,
|
|
15033
15564
|
ignored: this.ignoredBy(ppid),
|
|
15034
15565
|
umask: this.umaskOf(ppid),
|
|
15035
|
-
cred
|
|
15566
|
+
cred,
|
|
15567
|
+
tag: this.tagOf(ppid),
|
|
15036
15568
|
...exec
|
|
15037
15569
|
});
|
|
15038
15570
|
deciding?.attach();
|
|
15571
|
+
if (grant) this.granted.set(handle.pid, ppid);
|
|
15039
15572
|
return childHandle(handle);
|
|
15040
15573
|
};
|
|
15041
15574
|
}
|
|
@@ -15079,7 +15612,8 @@ var Launcher = class {
|
|
|
15079
15612
|
fork: state,
|
|
15080
15613
|
ignored: this.ignoredBy(ppid, true),
|
|
15081
15614
|
umask: this.umaskOf(ppid),
|
|
15082
|
-
cred: this.credOf(ppid)
|
|
15615
|
+
cred: this.credOf(ppid),
|
|
15616
|
+
tag: this.tagOf(ppid)
|
|
15083
15617
|
})
|
|
15084
15618
|
);
|
|
15085
15619
|
}
|
|
@@ -15104,6 +15638,9 @@ var Launcher = class {
|
|
|
15104
15638
|
credOf(pid) {
|
|
15105
15639
|
return this.creds.get(pid)?.() ?? copyCred(ROOT);
|
|
15106
15640
|
}
|
|
15641
|
+
tagOf(pid) {
|
|
15642
|
+
return this.tags.get(pid);
|
|
15643
|
+
}
|
|
15107
15644
|
released(path) {
|
|
15108
15645
|
const at = normalizePath(path);
|
|
15109
15646
|
this.unlinked.closed(at);
|
|
@@ -15122,7 +15659,7 @@ var Launcher = class {
|
|
|
15122
15659
|
}
|
|
15123
15660
|
kill(pid, sig, sender, from) {
|
|
15124
15661
|
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);
|
|
15662
|
+
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
15663
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
15127
15664
|
const handle = this.processes.get(pid);
|
|
15128
15665
|
if (!handle) return false;
|
|
@@ -15239,11 +15776,12 @@ var Launcher = class {
|
|
|
15239
15776
|
];
|
|
15240
15777
|
for (const [path, mode] of layout) {
|
|
15241
15778
|
const real = await this.realLayoutPath(path);
|
|
15242
|
-
const
|
|
15243
|
-
if (
|
|
15779
|
+
const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
|
|
15780
|
+
if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
|
|
15244
15781
|
}
|
|
15245
15782
|
}
|
|
15246
|
-
async realLayoutPath(
|
|
15783
|
+
async realLayoutPath(given) {
|
|
15784
|
+
const path = normalizePath(given);
|
|
15247
15785
|
if (!await this.base.exists(path)) return void 0;
|
|
15248
15786
|
if (path === "/") return path;
|
|
15249
15787
|
return joinName(await realDir(this.base, parentOf4(path)), baseName(path));
|
|
@@ -15254,7 +15792,8 @@ var Launcher = class {
|
|
|
15254
15792
|
mountedBy(spec, cred) {
|
|
15255
15793
|
if (!this.owners || cred.euid === 0 && cred.egid === 0) return spec;
|
|
15256
15794
|
const ids = { uid: String(cred.euid), gid: String(cred.egid) };
|
|
15257
|
-
|
|
15795
|
+
const options = cred.euid === 0 ? { ...ids, ...spec.options } : { ...spec.options, ...ids };
|
|
15796
|
+
return { ...spec, options };
|
|
15258
15797
|
}
|
|
15259
15798
|
mountRoot(path) {
|
|
15260
15799
|
let root = "/";
|
|
@@ -15299,13 +15838,36 @@ var Launcher = class {
|
|
|
15299
15838
|
async processMount(pid, call, signal) {
|
|
15300
15839
|
const spec = mountCall(call);
|
|
15301
15840
|
await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
|
|
15841
|
+
await this.mountsOwn(
|
|
15842
|
+
this.credOf(pid),
|
|
15843
|
+
spec.target,
|
|
15844
|
+
async () => (await this.fs.stat(spec.target)).uid
|
|
15845
|
+
);
|
|
15302
15846
|
const entry = await this.mount(this.mountedBy(spec, this.credOf(pid)));
|
|
15303
15847
|
if (signal.aborted) this.umount(entry.target, true);
|
|
15304
15848
|
}
|
|
15305
|
-
async
|
|
15306
|
-
const
|
|
15849
|
+
async mountAs(cred, spec) {
|
|
15850
|
+
const target = normalizePath(spec.target);
|
|
15851
|
+
await this.mountsOwn(cred, target, async () => (await this.fs.stat(target)).uid);
|
|
15852
|
+
return this.mount(this.mountedBy(spec, cred));
|
|
15853
|
+
}
|
|
15854
|
+
async umountAs(cred, target) {
|
|
15855
|
+
const at = normalizePath(target);
|
|
15856
|
+
await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
|
|
15857
|
+
this.umount(at);
|
|
15858
|
+
}
|
|
15859
|
+
async mountsOwn(cred, target, owner) {
|
|
15860
|
+
const { euid } = cred;
|
|
15861
|
+
if (this.usersMode !== "enforce" || euid === 0) return;
|
|
15862
|
+
if (await owner().catch(() => void 0) !== euid) {
|
|
15863
|
+
throw fsError("EPERM", `${target} is not the caller's to mount or unmount`);
|
|
15864
|
+
}
|
|
15865
|
+
}
|
|
15866
|
+
async processUmount(pid, target, flags2) {
|
|
15867
|
+
const call = umountCall(target, flags2);
|
|
15868
|
+
let mounted;
|
|
15307
15869
|
for (; ; ) {
|
|
15308
|
-
|
|
15870
|
+
mounted = this.mounts.mounted(call.target);
|
|
15309
15871
|
await this.permit({
|
|
15310
15872
|
op: "umount",
|
|
15311
15873
|
pid,
|
|
@@ -15314,6 +15876,7 @@ var Launcher = class {
|
|
|
15314
15876
|
});
|
|
15315
15877
|
if (this.mounts.mounted(call.target) === mounted) break;
|
|
15316
15878
|
}
|
|
15879
|
+
await this.mountsOwn(this.credOf(pid), call.target, async () => Number(mounted?.options.uid));
|
|
15317
15880
|
this.umount(call.target, call.detach);
|
|
15318
15881
|
}
|
|
15319
15882
|
async prepare() {
|
|
@@ -15322,6 +15885,7 @@ var Launcher = class {
|
|
|
15322
15885
|
await this.provisionPnpmHome();
|
|
15323
15886
|
await this.users.ensure();
|
|
15324
15887
|
if (this.owners) await this.stampLayout();
|
|
15888
|
+
if (this.owners) await this.sudoer.provision();
|
|
15325
15889
|
await writeCaFile(this.base, this.ca).catch(() => void 0);
|
|
15326
15890
|
this.fstab = this.mountFstab();
|
|
15327
15891
|
}
|
|
@@ -15359,8 +15923,9 @@ var Launcher = class {
|
|
|
15359
15923
|
if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
|
|
15360
15924
|
return found;
|
|
15361
15925
|
}
|
|
15362
|
-
identity(account) {
|
|
15926
|
+
identity(account, tag) {
|
|
15363
15927
|
return {
|
|
15928
|
+
tag,
|
|
15364
15929
|
cred: credOf(account),
|
|
15365
15930
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
15366
15931
|
};
|
|
@@ -15377,6 +15942,7 @@ var Launcher = class {
|
|
|
15377
15942
|
return { cwd, file, planned };
|
|
15378
15943
|
}
|
|
15379
15944
|
async openTerminal(argv, options) {
|
|
15945
|
+
tagOption(options.tag);
|
|
15380
15946
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
15381
15947
|
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
15382
15948
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
@@ -15405,7 +15971,13 @@ var Launcher = class {
|
|
|
15405
15971
|
);
|
|
15406
15972
|
const report = (message3) => options.onData(encoder3.encode(`${message3}\r
|
|
15407
15973
|
`));
|
|
15408
|
-
const handle = await this.launch(planned, {
|
|
15974
|
+
const handle = await this.launch(planned, {
|
|
15975
|
+
env,
|
|
15976
|
+
cwd,
|
|
15977
|
+
fds,
|
|
15978
|
+
report,
|
|
15979
|
+
...this.identity(account, options.tag)
|
|
15980
|
+
});
|
|
15409
15981
|
leader = handle.pid;
|
|
15410
15982
|
void handle.exited.then(() => tty.hangup());
|
|
15411
15983
|
return {
|
|
@@ -15421,6 +15993,7 @@ var Launcher = class {
|
|
|
15421
15993
|
};
|
|
15422
15994
|
}
|
|
15423
15995
|
async run(argv, options = {}) {
|
|
15996
|
+
tagOption(options.tag);
|
|
15424
15997
|
const account = await this.account(options.user);
|
|
15425
15998
|
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
15426
15999
|
const env = this.environment(cwd, options.env, account);
|
|
@@ -15450,7 +16023,7 @@ var Launcher = class {
|
|
|
15450
16023
|
fds,
|
|
15451
16024
|
report,
|
|
15452
16025
|
...group,
|
|
15453
|
-
...this.identity(account)
|
|
16026
|
+
...this.identity(account, options.tag)
|
|
15454
16027
|
});
|
|
15455
16028
|
options.onStarted?.(handle.pid);
|
|
15456
16029
|
const status = await handle.exited;
|
|
@@ -15511,8 +16084,37 @@ function remotes(port, req) {
|
|
|
15511
16084
|
...req.uplink ? { uplink: new RemoteUplink(port, req.uplink.traits, req.uplink.routes) } : {}
|
|
15512
16085
|
};
|
|
15513
16086
|
}
|
|
15514
|
-
function
|
|
15515
|
-
|
|
16087
|
+
function dial(l, req) {
|
|
16088
|
+
const socket = dialSocket(l.net, req.port, req.host);
|
|
16089
|
+
const { port1, port2 } = new MessageChannel();
|
|
16090
|
+
serveSocket(socket, port1);
|
|
16091
|
+
return port2;
|
|
16092
|
+
}
|
|
16093
|
+
function sudoApprovals(port) {
|
|
16094
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
16095
|
+
return {
|
|
16096
|
+
ask: (req, { signal }) => new Promise((resolve) => {
|
|
16097
|
+
waiting.set(req.id, resolve);
|
|
16098
|
+
signal.addEventListener(
|
|
16099
|
+
"abort",
|
|
16100
|
+
() => {
|
|
16101
|
+
waiting.delete(req.id);
|
|
16102
|
+
port.postMessage({ sudoAbort: { id: req.id, reason: String(signal.reason) } });
|
|
16103
|
+
resolve("deny");
|
|
16104
|
+
},
|
|
16105
|
+
{ once: true }
|
|
16106
|
+
);
|
|
16107
|
+
port.postMessage({ sudoAsk: req });
|
|
16108
|
+
}),
|
|
16109
|
+
answer({ id: id2, answer }) {
|
|
16110
|
+
waiting.get(id2)?.(answer);
|
|
16111
|
+
waiting.delete(id2);
|
|
16112
|
+
}
|
|
16113
|
+
};
|
|
16114
|
+
}
|
|
16115
|
+
function answered(data, remote, uplink, approvals) {
|
|
16116
|
+
if (data.sudoAnswer) approvals.answer(data.sudoAnswer);
|
|
16117
|
+
else if (data.net !== void 0) remote?.receive(data);
|
|
15516
16118
|
else if (typeof data.uplink === "string") uplink?.receive(data);
|
|
15517
16119
|
else return false;
|
|
15518
16120
|
return true;
|
|
@@ -15557,6 +16159,7 @@ function serveKernel(port, deps) {
|
|
|
15557
16159
|
const terminals = /* @__PURE__ */ new Map();
|
|
15558
16160
|
const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
|
|
15559
16161
|
const clients = /* @__PURE__ */ new Set();
|
|
16162
|
+
const approvals = sudoApprovals(port);
|
|
15560
16163
|
const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
|
|
15561
16164
|
let holding;
|
|
15562
16165
|
function holdLock(locks2) {
|
|
@@ -15570,8 +16173,9 @@ function serveKernel(port, deps) {
|
|
|
15570
16173
|
});
|
|
15571
16174
|
return holding;
|
|
15572
16175
|
}
|
|
15573
|
-
async function connect(user) {
|
|
16176
|
+
async function connect(user, tag) {
|
|
15574
16177
|
const l = await ready2();
|
|
16178
|
+
tagOption(tag);
|
|
15575
16179
|
if (user !== void 0 && !await l.users.lookup(user)) {
|
|
15576
16180
|
throw fsError("ENOENT", `no user ${String(user)}`);
|
|
15577
16181
|
}
|
|
@@ -15581,6 +16185,7 @@ function serveKernel(port, deps) {
|
|
|
15581
16185
|
launcher: ready2,
|
|
15582
16186
|
signal: signalNumber,
|
|
15583
16187
|
...user !== void 0 ? { user } : {},
|
|
16188
|
+
...tag !== void 0 ? { tag } : {},
|
|
15584
16189
|
...locks ? { locks } : {},
|
|
15585
16190
|
...held ? { lock: held } : {}
|
|
15586
16191
|
});
|
|
@@ -15588,12 +16193,6 @@ function serveKernel(port, deps) {
|
|
|
15588
16193
|
void served.closed.then(() => clients.delete(served));
|
|
15589
16194
|
return port2;
|
|
15590
16195
|
}
|
|
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
16196
|
async function ready2() {
|
|
15598
16197
|
if (!launcher) throw new Error("the kernel is not initialized");
|
|
15599
16198
|
return launcher;
|
|
@@ -15625,6 +16224,7 @@ function serveKernel(port, deps) {
|
|
|
15625
16224
|
...req.modules ? { modules: req.modules } : {},
|
|
15626
16225
|
...req.env ? { env: req.env } : {},
|
|
15627
16226
|
...hostOptions(req, port),
|
|
16227
|
+
...req.approve ? { approve: approvals.ask } : {},
|
|
15628
16228
|
...net,
|
|
15629
16229
|
...media ? { media } : {},
|
|
15630
16230
|
onMountPending: (medium) => port.postMessage({ medium }),
|
|
@@ -15649,13 +16249,13 @@ function serveKernel(port, deps) {
|
|
|
15649
16249
|
async function handle(req) {
|
|
15650
16250
|
if (req.op === "init") return init(req);
|
|
15651
16251
|
if (req.op === "open-terminal") return terminal(req);
|
|
15652
|
-
if (req.op === "connect") return connect(req.user);
|
|
16252
|
+
if (req.op === "connect") return connect(req.user, req.tag);
|
|
15653
16253
|
if (isUsersRequest(req)) return usersOp(await ready2(), req);
|
|
15654
16254
|
if (req.op === "routes") {
|
|
15655
16255
|
(await ready2()).setRoutes(req.routes);
|
|
15656
16256
|
return true;
|
|
15657
16257
|
}
|
|
15658
|
-
if (req.op === "dial") return dial(req);
|
|
16258
|
+
if (req.op === "dial") return dial(await ready2(), req);
|
|
15659
16259
|
if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
|
|
15660
16260
|
return mountOp(req);
|
|
15661
16261
|
if (req.op === "terminal") {
|
|
@@ -15703,7 +16303,7 @@ function serveKernel(port, deps) {
|
|
|
15703
16303
|
policies.delete(answer.id);
|
|
15704
16304
|
return;
|
|
15705
16305
|
}
|
|
15706
|
-
if (answered(event.data, remote, uplink)) return;
|
|
16306
|
+
if (answered(event.data, remote, uplink, approvals)) return;
|
|
15707
16307
|
const req = event.data;
|
|
15708
16308
|
handle(req).then(
|
|
15709
16309
|
(result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
|