@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/node.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/node.ts
|
|
2
2
|
import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
|
|
3
3
|
|
|
4
|
+
// src/bytes.ts
|
|
5
|
+
function fresh(bytes, start2 = 0, end = bytes.length) {
|
|
6
|
+
return new Uint8Array(bytes.subarray(start2, end));
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
// src/cdp/port.ts
|
|
5
10
|
function serveCdpPort(port, hook, request, onDone) {
|
|
6
11
|
let conn;
|
|
@@ -532,6 +537,64 @@ async function openable(g, op, path, want, create) {
|
|
|
532
537
|
await need(g, op, path, want, { directory: st?.isDirectory ?? false });
|
|
533
538
|
}
|
|
534
539
|
|
|
540
|
+
// src/kernel/cred.ts
|
|
541
|
+
var ROOT = Object.freeze({
|
|
542
|
+
ruid: 0,
|
|
543
|
+
euid: 0,
|
|
544
|
+
suid: 0,
|
|
545
|
+
rgid: 0,
|
|
546
|
+
egid: 0,
|
|
547
|
+
sgid: 0,
|
|
548
|
+
groups: [0]
|
|
549
|
+
});
|
|
550
|
+
var MAX_ID = 4294967294;
|
|
551
|
+
var MAX_GROUPS = 65536;
|
|
552
|
+
var UIDS = ["ruid", "euid", "suid"];
|
|
553
|
+
var GIDS = ["rgid", "egid", "sgid"];
|
|
554
|
+
function userCred(uid, gid, groups = [gid]) {
|
|
555
|
+
return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
|
|
556
|
+
}
|
|
557
|
+
function copyCred(cred) {
|
|
558
|
+
return { ...cred, groups: [...cred.groups] };
|
|
559
|
+
}
|
|
560
|
+
function id(value) {
|
|
561
|
+
if (value === void 0 || value === -1 || value === 4294967295) return void 0;
|
|
562
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
|
|
563
|
+
throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
|
|
564
|
+
}
|
|
565
|
+
return value;
|
|
566
|
+
}
|
|
567
|
+
function changeCred(cur, change) {
|
|
568
|
+
const privileged = cur.euid === 0;
|
|
569
|
+
const next2 = copyCred(cur);
|
|
570
|
+
for (const keys of [UIDS, GIDS]) {
|
|
571
|
+
const own = keys.map((key) => cur[key]);
|
|
572
|
+
for (const key of keys) {
|
|
573
|
+
const value = id(change[key]);
|
|
574
|
+
if (value === void 0) continue;
|
|
575
|
+
if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
|
|
576
|
+
next2[key] = value;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (change.groups !== void 0) {
|
|
580
|
+
if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
|
|
581
|
+
throw fsError("EINVAL", "not a group list");
|
|
582
|
+
}
|
|
583
|
+
const groups = change.groups.map((g) => id(g) ?? Number.NaN);
|
|
584
|
+
if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
|
|
585
|
+
if (!privileged) throw fsError("EPERM", "setgroups");
|
|
586
|
+
next2.groups = groups;
|
|
587
|
+
}
|
|
588
|
+
return next2;
|
|
589
|
+
}
|
|
590
|
+
function maySignal(sender, target) {
|
|
591
|
+
if (sender.euid === 0) return true;
|
|
592
|
+
return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
|
|
593
|
+
}
|
|
594
|
+
function maySee(viewer, target) {
|
|
595
|
+
return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
|
|
596
|
+
}
|
|
597
|
+
|
|
535
598
|
// src/kernel/pipe.ts
|
|
536
599
|
var PIPE_CAPACITY = 64 * 1024;
|
|
537
600
|
var PipeError = class extends Error {
|
|
@@ -839,9 +902,9 @@ var FdTable = class _FdTable {
|
|
|
839
902
|
closesOnExec(fd) {
|
|
840
903
|
return this.cloexec.has(fd);
|
|
841
904
|
}
|
|
842
|
-
setStatusFlags(fd,
|
|
905
|
+
setStatusFlags(fd, flags2) {
|
|
843
906
|
this.get(fd);
|
|
844
|
-
this.status.set(fd,
|
|
907
|
+
this.status.set(fd, flags2);
|
|
845
908
|
}
|
|
846
909
|
statusFlags(fd) {
|
|
847
910
|
return this.status.get(fd);
|
|
@@ -886,7 +949,7 @@ var FdTable = class _FdTable {
|
|
|
886
949
|
const child = new _FdTable();
|
|
887
950
|
for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
|
|
888
951
|
for (const fd of this.cloexec) child.cloexec.add(fd);
|
|
889
|
-
for (const [fd,
|
|
952
|
+
for (const [fd, flags2] of this.status) child.status.set(fd, flags2);
|
|
890
953
|
return child;
|
|
891
954
|
}
|
|
892
955
|
async closeAll() {
|
|
@@ -1353,9 +1416,9 @@ var KernelSocket = class _KernelSocket {
|
|
|
1353
1416
|
case SO_TYPE:
|
|
1354
1417
|
return SOCK_STREAM;
|
|
1355
1418
|
case SO_ERROR: {
|
|
1356
|
-
const
|
|
1419
|
+
const failed3 = this.failed;
|
|
1357
1420
|
this.failed = void 0;
|
|
1358
|
-
return
|
|
1421
|
+
return failed3 ? wasiErrno(failed3) : 0;
|
|
1359
1422
|
}
|
|
1360
1423
|
case SO_ACCEPTCONN:
|
|
1361
1424
|
return this.state === "listening" ? 1 : 0;
|
|
@@ -1976,7 +2039,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
1976
2039
|
}
|
|
1977
2040
|
if (typeof data === "string") peer.postMessage({ net: "ws-message", nid, data });
|
|
1978
2041
|
else {
|
|
1979
|
-
const bytes = data
|
|
2042
|
+
const bytes = fresh(data);
|
|
1980
2043
|
peer.postMessage({ net: "ws-message", nid, data: bytes }, [bytes.buffer]);
|
|
1981
2044
|
}
|
|
1982
2045
|
flow.inflight += payloadSize(data);
|
|
@@ -2014,7 +2077,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
2014
2077
|
open.delete(nid);
|
|
2015
2078
|
peer.postMessage({ net: "end", nid });
|
|
2016
2079
|
} else {
|
|
2017
|
-
const bytes = next2.value
|
|
2080
|
+
const bytes = fresh(next2.value);
|
|
2018
2081
|
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
2019
2082
|
}
|
|
2020
2083
|
} catch (e) {
|
|
@@ -2047,64 +2110,6 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
2047
2110
|
};
|
|
2048
2111
|
}
|
|
2049
2112
|
|
|
2050
|
-
// src/kernel/cred.ts
|
|
2051
|
-
var ROOT = Object.freeze({
|
|
2052
|
-
ruid: 0,
|
|
2053
|
-
euid: 0,
|
|
2054
|
-
suid: 0,
|
|
2055
|
-
rgid: 0,
|
|
2056
|
-
egid: 0,
|
|
2057
|
-
sgid: 0,
|
|
2058
|
-
groups: [0]
|
|
2059
|
-
});
|
|
2060
|
-
var MAX_ID = 4294967294;
|
|
2061
|
-
var MAX_GROUPS = 65536;
|
|
2062
|
-
var UIDS = ["ruid", "euid", "suid"];
|
|
2063
|
-
var GIDS = ["rgid", "egid", "sgid"];
|
|
2064
|
-
function userCred(uid, gid, groups = [gid]) {
|
|
2065
|
-
return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
|
|
2066
|
-
}
|
|
2067
|
-
function copyCred(cred) {
|
|
2068
|
-
return { ...cred, groups: [...cred.groups] };
|
|
2069
|
-
}
|
|
2070
|
-
function id(value) {
|
|
2071
|
-
if (value === void 0 || value === -1 || value === 4294967295) return void 0;
|
|
2072
|
-
if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
|
|
2073
|
-
throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
|
|
2074
|
-
}
|
|
2075
|
-
return value;
|
|
2076
|
-
}
|
|
2077
|
-
function changeCred(cur, change) {
|
|
2078
|
-
const privileged = cur.euid === 0;
|
|
2079
|
-
const next2 = copyCred(cur);
|
|
2080
|
-
for (const keys of [UIDS, GIDS]) {
|
|
2081
|
-
const own = keys.map((key) => cur[key]);
|
|
2082
|
-
for (const key of keys) {
|
|
2083
|
-
const value = id(change[key]);
|
|
2084
|
-
if (value === void 0) continue;
|
|
2085
|
-
if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
|
|
2086
|
-
next2[key] = value;
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
if (change.groups !== void 0) {
|
|
2090
|
-
if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
|
|
2091
|
-
throw fsError("EINVAL", "not a group list");
|
|
2092
|
-
}
|
|
2093
|
-
const groups = change.groups.map((g) => id(g) ?? Number.NaN);
|
|
2094
|
-
if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
|
|
2095
|
-
if (!privileged) throw fsError("EPERM", "setgroups");
|
|
2096
|
-
next2.groups = groups;
|
|
2097
|
-
}
|
|
2098
|
-
return next2;
|
|
2099
|
-
}
|
|
2100
|
-
function maySignal(sender, target) {
|
|
2101
|
-
if (sender.euid === 0) return true;
|
|
2102
|
-
return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
|
|
2103
|
-
}
|
|
2104
|
-
function maySee(viewer, target) {
|
|
2105
|
-
return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
2113
|
// src/users.ts
|
|
2109
2114
|
var PASSWD = "/etc/passwd";
|
|
2110
2115
|
var GROUP = "/etc/group";
|
|
@@ -2366,7 +2371,7 @@ var UserDb = class {
|
|
|
2366
2371
|
joined(name2, extra);
|
|
2367
2372
|
group.push({ name: name2, gid: uid, members: [] });
|
|
2368
2373
|
passwd.push(row);
|
|
2369
|
-
const
|
|
2374
|
+
const fresh2 = !await this.fs.exists(home);
|
|
2370
2375
|
await this.fs.mkdir(home, { recursive: true });
|
|
2371
2376
|
try {
|
|
2372
2377
|
await this.provision(row);
|
|
@@ -2374,7 +2379,7 @@ var UserDb = class {
|
|
|
2374
2379
|
await this.fs.mkdir("/etc", { recursive: true });
|
|
2375
2380
|
await this.commit(formatPasswd(passwd), formatGroup(group));
|
|
2376
2381
|
} catch (err) {
|
|
2377
|
-
if (
|
|
2382
|
+
if (fresh2) await this.fs.rm(home, { recursive: true, force: true });
|
|
2378
2383
|
throw err;
|
|
2379
2384
|
}
|
|
2380
2385
|
remember();
|
|
@@ -2438,7 +2443,7 @@ var UserDb = class {
|
|
|
2438
2443
|
};
|
|
2439
2444
|
|
|
2440
2445
|
// src/client/protocol.ts
|
|
2441
|
-
var PROTOCOL = [1,
|
|
2446
|
+
var PROTOCOL = [1, 9];
|
|
2442
2447
|
var FS_METHODS = [
|
|
2443
2448
|
"readFile",
|
|
2444
2449
|
"writeFile",
|
|
@@ -2519,7 +2524,7 @@ async function fsCall(fs, method, args, owner, g) {
|
|
|
2519
2524
|
}
|
|
2520
2525
|
switch (method) {
|
|
2521
2526
|
case "readFile": {
|
|
2522
|
-
const bytes = (await fs.readFileBuffer(path(0)))
|
|
2527
|
+
const bytes = fresh(await fs.readFileBuffer(path(0)));
|
|
2523
2528
|
return { result: bytes, transfer: [bytes.buffer] };
|
|
2524
2529
|
}
|
|
2525
2530
|
case "writeFile":
|
|
@@ -2596,6 +2601,12 @@ async function runAs(l, self, requested) {
|
|
|
2596
2601
|
}
|
|
2597
2602
|
return target;
|
|
2598
2603
|
}
|
|
2604
|
+
function clientCred(self) {
|
|
2605
|
+
return self ? credOf(self) : copyCred(ROOT);
|
|
2606
|
+
}
|
|
2607
|
+
async function runTagged(l, self, requested, tag) {
|
|
2608
|
+
return { user: await runAs(l, self, requested), tag };
|
|
2609
|
+
}
|
|
2599
2610
|
function processes(l, self) {
|
|
2600
2611
|
const viewer = credOf(self);
|
|
2601
2612
|
return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
|
|
@@ -2730,7 +2741,7 @@ function serveClient(port, host) {
|
|
|
2730
2741
|
async function terminal(req, l) {
|
|
2731
2742
|
const session = await l.openTerminal(req.argv, {
|
|
2732
2743
|
...req.options,
|
|
2733
|
-
|
|
2744
|
+
...await runTagged(l, self, req.options.user, host.tag),
|
|
2734
2745
|
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
2735
2746
|
});
|
|
2736
2747
|
if (state === "closed") {
|
|
@@ -2752,7 +2763,7 @@ function serveClient(port, host) {
|
|
|
2752
2763
|
let leader;
|
|
2753
2764
|
const result = await l.run(req.argv, {
|
|
2754
2765
|
...req.options,
|
|
2755
|
-
|
|
2766
|
+
...await runTagged(l, self, req.options.user, host.tag),
|
|
2756
2767
|
collect: false,
|
|
2757
2768
|
onStdout: out(1),
|
|
2758
2769
|
onStderr: out(2),
|
|
@@ -2793,9 +2804,9 @@ function serveClient(port, host) {
|
|
|
2793
2804
|
case "unwatch":
|
|
2794
2805
|
return watchOp(l, req, watches, send, guardFor(l, self));
|
|
2795
2806
|
case "mount":
|
|
2796
|
-
return { result: await l.
|
|
2807
|
+
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
2797
2808
|
case "umount":
|
|
2798
|
-
l.
|
|
2809
|
+
await l.umountAs(clientCred(self), req.target);
|
|
2799
2810
|
return { result: true };
|
|
2800
2811
|
case "mounts":
|
|
2801
2812
|
return { result: l.mounts.list() };
|
|
@@ -5001,9 +5012,8 @@ async function scanFilesystems(fs, modules) {
|
|
|
5001
5012
|
}
|
|
5002
5013
|
var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
|
|
5003
5014
|
async function scanBinfmts(fs, modules, warn = () => {
|
|
5004
|
-
}) {
|
|
5015
|
+
}, owners = /* @__PURE__ */ new Map()) {
|
|
5005
5016
|
const found = /* @__PURE__ */ new Map();
|
|
5006
|
-
const owners = /* @__PURE__ */ new Map();
|
|
5007
5017
|
for (const pkg of await packagesIn(fs, modules)) {
|
|
5008
5018
|
let manifest;
|
|
5009
5019
|
try {
|
|
@@ -5028,7 +5038,7 @@ async function scanBinfmts(fs, modules, warn = () => {
|
|
|
5028
5038
|
}
|
|
5029
5039
|
return found;
|
|
5030
5040
|
}
|
|
5031
|
-
function
|
|
5041
|
+
function binfmtSuffix(binfmts, path) {
|
|
5032
5042
|
const name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
5033
5043
|
let best;
|
|
5034
5044
|
for (const suffix of binfmts.keys()) {
|
|
@@ -5036,6 +5046,10 @@ function binfmtOf(binfmts, path) {
|
|
|
5036
5046
|
if (best === void 0 || suffix.length > best.length) best = suffix;
|
|
5037
5047
|
}
|
|
5038
5048
|
}
|
|
5049
|
+
return best;
|
|
5050
|
+
}
|
|
5051
|
+
function binfmtOf(binfmts, path) {
|
|
5052
|
+
const best = binfmtSuffix(binfmts, path);
|
|
5039
5053
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
5040
5054
|
}
|
|
5041
5055
|
|
|
@@ -5390,8 +5404,9 @@ var ChildTable = class {
|
|
|
5390
5404
|
if (!this.forker) throw new SpawnError("ENOSYS");
|
|
5391
5405
|
return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
|
|
5392
5406
|
}
|
|
5393
|
-
async spawn(req, stdio, inherit2 = []) {
|
|
5394
|
-
|
|
5407
|
+
async spawn(req, stdio, inherit2 = [], grant) {
|
|
5408
|
+
const { spawner } = this;
|
|
5409
|
+
if (!spawner) throw new SpawnError("ENOSYS");
|
|
5395
5410
|
const fds = new FdTable();
|
|
5396
5411
|
const captured = /* @__PURE__ */ new Map();
|
|
5397
5412
|
try {
|
|
@@ -5409,7 +5424,7 @@ var ChildTable = class {
|
|
|
5409
5424
|
await fds.closeAll();
|
|
5410
5425
|
throw e;
|
|
5411
5426
|
}
|
|
5412
|
-
return this.track(req, fds, captured,
|
|
5427
|
+
return this.track(req, fds, captured, (r, f) => spawner(r, f, grant));
|
|
5413
5428
|
}
|
|
5414
5429
|
async track(req, fds, captured, start2) {
|
|
5415
5430
|
let handle;
|
|
@@ -5456,34 +5471,34 @@ var ChildTable = class {
|
|
|
5456
5471
|
}
|
|
5457
5472
|
return deviceFile("null");
|
|
5458
5473
|
}
|
|
5459
|
-
async wait(pid, nohang, signal,
|
|
5474
|
+
async wait(pid, nohang, signal, flags2 = {}) {
|
|
5460
5475
|
let interrupt;
|
|
5461
5476
|
try {
|
|
5462
5477
|
for (; ; ) {
|
|
5463
|
-
const candidates = this.candidates(pid,
|
|
5478
|
+
const candidates = this.candidates(pid, flags2);
|
|
5464
5479
|
if (candidates.length === 0) throw new KernelError("ECHILD");
|
|
5465
5480
|
const done4 = candidates.find(([, child]) => child.code !== void 0);
|
|
5466
5481
|
if (done4) return this.reap(done4[0], done4[1].code);
|
|
5467
|
-
const changed = this.stateReport(candidates,
|
|
5482
|
+
const changed = this.stateReport(candidates, flags2);
|
|
5468
5483
|
if (changed) return changed;
|
|
5469
5484
|
if (nohang) return [0, 0];
|
|
5470
5485
|
interrupt ??= interrupted(signal);
|
|
5471
|
-
await this.nextChange(candidates,
|
|
5486
|
+
await this.nextChange(candidates, flags2, interrupt.promise);
|
|
5472
5487
|
}
|
|
5473
5488
|
} finally {
|
|
5474
5489
|
interrupt?.done();
|
|
5475
5490
|
}
|
|
5476
5491
|
}
|
|
5477
|
-
candidates(pid,
|
|
5492
|
+
candidates(pid, flags2) {
|
|
5478
5493
|
const all = [...this.children];
|
|
5479
5494
|
if (pid > 0) return all.filter(([p]) => p === pid);
|
|
5480
|
-
const inGroup2 =
|
|
5495
|
+
const inGroup2 = flags2.inGroup;
|
|
5481
5496
|
return pid === -1 || !inGroup2 ? all : all.filter(([p]) => inGroup2(p));
|
|
5482
5497
|
}
|
|
5483
|
-
async nextChange(candidates,
|
|
5498
|
+
async nextChange(candidates, flags2, interrupt) {
|
|
5484
5499
|
let wake;
|
|
5485
5500
|
const stateChange = new Promise((resolve) => wake = resolve);
|
|
5486
|
-
const watching =
|
|
5501
|
+
const watching = flags2.untraced || flags2.continued;
|
|
5487
5502
|
if (watching && wake) this.stateChanged.push(wake);
|
|
5488
5503
|
try {
|
|
5489
5504
|
await Promise.race([
|
|
@@ -5495,14 +5510,14 @@ var ChildTable = class {
|
|
|
5495
5510
|
this.stateChanged = this.stateChanged.filter((w) => w !== wake);
|
|
5496
5511
|
}
|
|
5497
5512
|
}
|
|
5498
|
-
stateReport(candidates,
|
|
5513
|
+
stateReport(candidates, flags2) {
|
|
5499
5514
|
for (const [p, child] of candidates) {
|
|
5500
|
-
if (
|
|
5515
|
+
if (flags2.untraced && child.stopReport !== void 0) {
|
|
5501
5516
|
const sig = child.stopReport;
|
|
5502
5517
|
child.stopReport = void 0;
|
|
5503
5518
|
return [p, stoppedStatus(sig)];
|
|
5504
5519
|
}
|
|
5505
|
-
if (
|
|
5520
|
+
if (flags2.continued && child.continueReport) {
|
|
5506
5521
|
child.continueReport = false;
|
|
5507
5522
|
return [p, CONTINUED_STATUS];
|
|
5508
5523
|
}
|
|
@@ -5711,9 +5726,9 @@ var HeldPaths = class extends Map {
|
|
|
5711
5726
|
return this.keptHandles.get(handle);
|
|
5712
5727
|
}
|
|
5713
5728
|
keep(path, file) {
|
|
5714
|
-
const
|
|
5715
|
-
for (const id2 of
|
|
5716
|
-
this.uses.set(file, Math.max(
|
|
5729
|
+
const fresh2 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
|
|
5730
|
+
for (const id2 of fresh2) this.keptHandles.set(id2, file);
|
|
5731
|
+
this.uses.set(file, Math.max(fresh2.length, 1));
|
|
5717
5732
|
this.unlinked.set(path, file);
|
|
5718
5733
|
}
|
|
5719
5734
|
unkeep(path, file) {
|
|
@@ -6269,7 +6284,7 @@ var MountFs = class {
|
|
|
6269
6284
|
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
6270
6285
|
try {
|
|
6271
6286
|
for (let offset = 0; offset < bytes.length; offset += chunk2) {
|
|
6272
|
-
const part = bytes
|
|
6287
|
+
const part = fresh(bytes, offset, offset + chunk2);
|
|
6273
6288
|
await mount.conn.call({ op: "write", fh, offset, bytes: part }, [part.buffer]);
|
|
6274
6289
|
}
|
|
6275
6290
|
} finally {
|
|
@@ -6366,7 +6381,7 @@ var MountFs = class {
|
|
|
6366
6381
|
async writeRuns(mount, fh, offset, bytes, transfer) {
|
|
6367
6382
|
const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
|
|
6368
6383
|
for (let at = 0; at < bytes.length; at += chunk2) {
|
|
6369
|
-
const part = handed(bytes, transfer, chunk2) ? bytes : bytes
|
|
6384
|
+
const part = handed(bytes, transfer, chunk2) ? bytes : fresh(bytes, at, at + chunk2);
|
|
6370
6385
|
await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
|
|
6371
6386
|
}
|
|
6372
6387
|
}
|
|
@@ -6598,8 +6613,8 @@ var MountFs = class {
|
|
|
6598
6613
|
}
|
|
6599
6614
|
this.writable(mount, real);
|
|
6600
6615
|
const { uid, gid, mode } = change;
|
|
6601
|
-
const
|
|
6602
|
-
const set2 =
|
|
6616
|
+
const fresh2 = change.kind === "new" && { uid, gid, mode };
|
|
6617
|
+
const set2 = fresh2 ? await this.driverOwner(found, real, fresh2, false) : change;
|
|
6603
6618
|
this.table.forget(mount, real);
|
|
6604
6619
|
const attrs = Object.entries({ uid: set2.uid, gid: set2.gid, mode: set2.mode });
|
|
6605
6620
|
await mount.conn.call({
|
|
@@ -7229,9 +7244,9 @@ var Pages = class {
|
|
|
7229
7244
|
const page = (start2 + at) / PAGE;
|
|
7230
7245
|
if (this.pages.has(page)) continue;
|
|
7231
7246
|
const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
|
|
7232
|
-
const
|
|
7233
|
-
|
|
7234
|
-
this.pages.set(page,
|
|
7247
|
+
const fresh2 = this.take(part.length);
|
|
7248
|
+
fresh2.set(part);
|
|
7249
|
+
this.pages.set(page, fresh2);
|
|
7235
7250
|
}
|
|
7236
7251
|
}
|
|
7237
7252
|
read(at, length2) {
|
|
@@ -8661,8 +8676,8 @@ function ipv6(host) {
|
|
|
8661
8676
|
const head = groups(halves[0]);
|
|
8662
8677
|
const back = halves.length === 2 ? groups(halves[1]) : [];
|
|
8663
8678
|
if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
|
|
8664
|
-
const
|
|
8665
|
-
const missing3 = 8 -
|
|
8679
|
+
const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
|
|
8680
|
+
const missing3 = 8 - words2.length - tail.length;
|
|
8666
8681
|
if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
|
|
8667
8682
|
const all = [
|
|
8668
8683
|
...head.map((g) => Number.parseInt(g, 16)),
|
|
@@ -10811,8 +10826,8 @@ function errCode(err) {
|
|
|
10811
10826
|
async function targetDir(fs, path) {
|
|
10812
10827
|
return parentOf(await realTarget(fs, path).catch(() => path));
|
|
10813
10828
|
}
|
|
10814
|
-
async function refuseReadonly(fs, path,
|
|
10815
|
-
const writes = (
|
|
10829
|
+
async function refuseReadonly(fs, path, flags2, opts = {}) {
|
|
10830
|
+
const writes = (flags2 & O_ACCMODE) !== 0 || opts.truncate === true;
|
|
10816
10831
|
if (!writes && !opts.create || !fs.stat) return;
|
|
10817
10832
|
const stat = (p) => fs.stat?.(p).catch((err) => {
|
|
10818
10833
|
const code = err?.code;
|
|
@@ -11027,6 +11042,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11027
11042
|
"proc-spawn",
|
|
11028
11043
|
"proc-wait",
|
|
11029
11044
|
"proc-captured",
|
|
11045
|
+
"proc-sudo",
|
|
11030
11046
|
"proc-run",
|
|
11031
11047
|
"proc-exit",
|
|
11032
11048
|
"proc-fork",
|
|
@@ -11059,6 +11075,12 @@ var MAX_READ = 1024 * 1024;
|
|
|
11059
11075
|
var WRITE_ACCESS = 3;
|
|
11060
11076
|
var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
|
|
11061
11077
|
var DEFAULT_UMASK = 18;
|
|
11078
|
+
function words(value) {
|
|
11079
|
+
if (!Array.isArray(value) || !value.every((w) => typeof w === "string")) {
|
|
11080
|
+
throw new KernelError("EINVAL");
|
|
11081
|
+
}
|
|
11082
|
+
return [...value];
|
|
11083
|
+
}
|
|
11062
11084
|
var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
|
|
11063
11085
|
var HOSTS_ONLY = new Resolver({ routes: new Routes() });
|
|
11064
11086
|
var WasmProcess = class {
|
|
@@ -11251,17 +11273,17 @@ var WasmProcess = class {
|
|
|
11251
11273
|
if (restart && this.options.hasPending?.()) throw new KernelError("EINTR");
|
|
11252
11274
|
}
|
|
11253
11275
|
async procWait(req, cancelled) {
|
|
11254
|
-
const
|
|
11276
|
+
const flags2 = {
|
|
11255
11277
|
untraced: req.untraced,
|
|
11256
11278
|
continued: req.continued,
|
|
11257
11279
|
inGroup: this.waitGroup(req.pid)
|
|
11258
11280
|
};
|
|
11259
11281
|
if (this.owed && !req.nohang) {
|
|
11260
|
-
const now = await this.children.wait(req.pid, true, this.interrupt.signal,
|
|
11282
|
+
const now = await this.children.wait(req.pid, true, this.interrupt.signal, flags2);
|
|
11261
11283
|
if (now[0] !== 0) return now;
|
|
11262
11284
|
}
|
|
11263
11285
|
const signal = req.nohang ? this.interrupt.signal : this.blockingSignal(cancelled);
|
|
11264
|
-
const waited = await this.children.wait(req.pid, req.nohang, signal,
|
|
11286
|
+
const waited = await this.children.wait(req.pid, req.nohang, signal, flags2);
|
|
11265
11287
|
return waited;
|
|
11266
11288
|
}
|
|
11267
11289
|
blockingSignal(cancelled, owing = true) {
|
|
@@ -11323,10 +11345,10 @@ var WasmProcess = class {
|
|
|
11323
11345
|
this.fds.installAt(fd, this.openVfsFile(open));
|
|
11324
11346
|
} else if (req.create) {
|
|
11325
11347
|
await nodes.onEntry(path, async () => {
|
|
11326
|
-
const
|
|
11348
|
+
const fresh2 = this.options.owners && !await present2(this.options.fs, path, true);
|
|
11327
11349
|
this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
|
|
11328
11350
|
await this.fds.get(fd).file.stat?.();
|
|
11329
|
-
if (
|
|
11351
|
+
if (fresh2) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
|
|
11330
11352
|
});
|
|
11331
11353
|
} else {
|
|
11332
11354
|
this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
|
|
@@ -11593,13 +11615,13 @@ var WasmProcess = class {
|
|
|
11593
11615
|
}
|
|
11594
11616
|
fdInfo(fd) {
|
|
11595
11617
|
const file = this.fds.get(fd).file;
|
|
11596
|
-
const
|
|
11618
|
+
const flags2 = this.fds.statusFlags(fd);
|
|
11597
11619
|
const terminal = (file.tty ?? file.pty?.slave)?.name;
|
|
11598
11620
|
return {
|
|
11599
11621
|
tty: file.tty !== void 0,
|
|
11600
11622
|
kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
|
|
11601
11623
|
...file.heldMeta ? { meta: file.heldMeta } : {},
|
|
11602
|
-
...
|
|
11624
|
+
...flags2 !== void 0 ? { flags: flags2 } : {},
|
|
11603
11625
|
...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
11604
11626
|
...file.read && !file.write ? { access: "read" } : {},
|
|
11605
11627
|
...file.write && !file.read ? { access: "write" } : {},
|
|
@@ -11787,7 +11809,27 @@ var WasmProcess = class {
|
|
|
11787
11809
|
return { ok: true, kind: "void" };
|
|
11788
11810
|
case "proc-captured":
|
|
11789
11811
|
return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
|
|
11790
|
-
|
|
11812
|
+
case "proc-sudo":
|
|
11813
|
+
return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
|
|
11814
|
+
}
|
|
11815
|
+
}
|
|
11816
|
+
async sudo(req, cancelled) {
|
|
11817
|
+
const { sudo } = this.options;
|
|
11818
|
+
if (!sudo) throw new KernelError("ENOSYS");
|
|
11819
|
+
const call = {
|
|
11820
|
+
op: "proc-sudo",
|
|
11821
|
+
args: words(req.args),
|
|
11822
|
+
cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
|
|
11823
|
+
...typeof req.term === "string" ? { term: req.term } : {}
|
|
11824
|
+
};
|
|
11825
|
+
const answer = await sudo(call, this.blockingSignal(cancelled));
|
|
11826
|
+
if (!("grant" in answer)) return answer;
|
|
11827
|
+
if (this.dying) throw new KernelError("EINTR");
|
|
11828
|
+
const { file, argv, env, cwd, cred, caller } = answer.grant;
|
|
11829
|
+
const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
|
|
11830
|
+
return {
|
|
11831
|
+
pid: await this.children.spawn({ file, argv, env, cwd }, stdio, [], { cred, caller })
|
|
11832
|
+
};
|
|
11791
11833
|
}
|
|
11792
11834
|
async mountSyscall(req, cancelled) {
|
|
11793
11835
|
const { mount, umount } = this.options;
|
|
@@ -11795,9 +11837,9 @@ var WasmProcess = class {
|
|
|
11795
11837
|
try {
|
|
11796
11838
|
if (req.op === "umount") await umount(req.target, req.flags);
|
|
11797
11839
|
else {
|
|
11798
|
-
const { source, target, type, flags, data } = req;
|
|
11840
|
+
const { source, target, type, flags: flags2, data } = req;
|
|
11799
11841
|
const signal = this.blockingSignal(cancelled);
|
|
11800
|
-
await Promise.race([mount({ source, target, type, flags, data }, signal), aborted(signal)]);
|
|
11842
|
+
await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted(signal)]);
|
|
11801
11843
|
}
|
|
11802
11844
|
} catch (err) {
|
|
11803
11845
|
const code = err?.code;
|
|
@@ -11940,14 +11982,14 @@ function descId(file) {
|
|
|
11940
11982
|
function inheritedFds(fds) {
|
|
11941
11983
|
return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
|
|
11942
11984
|
const open = fds.get(fd);
|
|
11943
|
-
const
|
|
11985
|
+
const flags2 = fds.statusFlags(fd);
|
|
11944
11986
|
const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
|
|
11945
11987
|
const meta = open.file.heldMeta;
|
|
11946
11988
|
return {
|
|
11947
11989
|
fd,
|
|
11948
11990
|
kind,
|
|
11949
11991
|
...kind === "device" && meta && "device" in meta ? { device: meta } : {},
|
|
11950
|
-
...
|
|
11992
|
+
...flags2 !== void 0 ? { flags: flags2 } : {},
|
|
11951
11993
|
...fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
11952
11994
|
...kind === "stream" ? { desc: descId(open) } : {}
|
|
11953
11995
|
};
|
|
@@ -12100,6 +12142,7 @@ function processOptions(opts, header4, hooks, created3) {
|
|
|
12100
12142
|
kill: opts.kill,
|
|
12101
12143
|
...opts.writesBack ? { writesBack: opts.writesBack } : {},
|
|
12102
12144
|
processes: opts.processes,
|
|
12145
|
+
...opts.sudo ? { sudo: opts.sudo } : {},
|
|
12103
12146
|
openFiles: opts.openFiles,
|
|
12104
12147
|
...opts.nodes ? { nodes: opts.nodes } : {},
|
|
12105
12148
|
mounts: opts.mounts,
|
|
@@ -12749,7 +12792,7 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
|
|
|
12749
12792
|
(result) => {
|
|
12750
12793
|
if (result instanceof Uint8Array) {
|
|
12751
12794
|
const whole = result.byteOffset === 0 && result.byteLength === result.buffer.byteLength;
|
|
12752
|
-
const bytes = options.owned && whole ? result : result
|
|
12795
|
+
const bytes = options.owned && whole ? result : fresh(result);
|
|
12753
12796
|
reply({ id: data.id, result: bytes }, [bytes.buffer]);
|
|
12754
12797
|
} else reply({ id: data.id, result: result ?? null });
|
|
12755
12798
|
},
|
|
@@ -12858,12 +12901,12 @@ function kernelFsHandlers(fs) {
|
|
|
12858
12901
|
const attr2 = attrOf(st);
|
|
12859
12902
|
return [{ name: name2, kind: attr2.kind, attr: attr2 }];
|
|
12860
12903
|
}),
|
|
12861
|
-
async open(path,
|
|
12904
|
+
async open(path, flags2) {
|
|
12862
12905
|
const st = await existing(path);
|
|
12863
|
-
if (st &&
|
|
12864
|
-
if (st?.isDirectory &&
|
|
12865
|
-
if (!st && !
|
|
12866
|
-
if (!st ||
|
|
12906
|
+
if (st && flags2.exclusive) throw fsError2("EEXIST", path);
|
|
12907
|
+
if (st?.isDirectory && flags2.write) throw fsError2("EISDIR", path);
|
|
12908
|
+
if (!st && !flags2.create) throw fsError2("ENOENT", path);
|
|
12909
|
+
if (!st || flags2.truncate) await fs.writeFile(path, new Uint8Array(0));
|
|
12867
12910
|
handles.set(++nextFh, { path });
|
|
12868
12911
|
return nextFh;
|
|
12869
12912
|
},
|
|
@@ -12993,24 +13036,24 @@ function absolute(target) {
|
|
|
12993
13036
|
return normalizePath(target);
|
|
12994
13037
|
}
|
|
12995
13038
|
function mountCall(call) {
|
|
12996
|
-
let
|
|
12997
|
-
if ((
|
|
12998
|
-
if (
|
|
12999
|
-
throw errnoError("EINVAL", `unsupported mount flags ${
|
|
13039
|
+
let flags2 = call.flags >>> 0;
|
|
13040
|
+
if ((flags2 & MS_MGC_MSK) >>> 0 === MS_MGC_VAL) flags2 &= ~MS_MGC_MSK;
|
|
13041
|
+
if (flags2 & ~(MS_RDONLY | IGNORED_FLAGS)) {
|
|
13042
|
+
throw errnoError("EINVAL", `unsupported mount flags ${flags2.toString(16)}`);
|
|
13000
13043
|
}
|
|
13001
13044
|
if (!call.type) throw errnoError("EINVAL", "no file system type");
|
|
13002
13045
|
const target = absolute(call.target);
|
|
13003
13046
|
refusedTarget(target);
|
|
13004
13047
|
const options = parseMountOptions(call.data);
|
|
13005
|
-
if (
|
|
13048
|
+
if (flags2 & MS_RDONLY) options.ro = "";
|
|
13006
13049
|
return { type: call.type, source: call.source || "none", target, options };
|
|
13007
13050
|
}
|
|
13008
|
-
function umountCall(target,
|
|
13051
|
+
function umountCall(target, flags2) {
|
|
13009
13052
|
const known = MNT_FORCE | MNT_DETACH | UMOUNT_NOFOLLOW;
|
|
13010
|
-
if (
|
|
13053
|
+
if (flags2 >>> 0 & ~known) throw errnoError("EINVAL", `unsupported umount flags ${flags2}`);
|
|
13011
13054
|
const at = absolute(target);
|
|
13012
13055
|
refusedTarget(at);
|
|
13013
|
-
return { target: at, detach: (
|
|
13056
|
+
return { target: at, detach: (flags2 & (MNT_FORCE | MNT_DETACH)) !== 0 };
|
|
13014
13057
|
}
|
|
13015
13058
|
|
|
13016
13059
|
// src/mount/fstab.ts
|
|
@@ -13068,22 +13111,22 @@ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discar
|
|
|
13068
13111
|
);
|
|
13069
13112
|
}
|
|
13070
13113
|
async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
|
|
13071
|
-
let
|
|
13114
|
+
let failed3 = invalid;
|
|
13072
13115
|
for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
|
|
13073
13116
|
try {
|
|
13074
13117
|
const entry = await mount(spec);
|
|
13075
13118
|
if (!signal.aborted) return { line, target: spec.target, entry };
|
|
13076
13119
|
discard(entry);
|
|
13077
|
-
|
|
13120
|
+
failed3 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
|
|
13078
13121
|
break;
|
|
13079
13122
|
} catch (err) {
|
|
13080
|
-
|
|
13123
|
+
failed3 = err;
|
|
13081
13124
|
if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
|
|
13082
13125
|
await wait(retries[attempt], signal);
|
|
13083
13126
|
}
|
|
13084
13127
|
}
|
|
13085
|
-
const code = codeOf3(
|
|
13086
|
-
return { line, target: spec.target, code, error: String(
|
|
13128
|
+
const code = codeOf3(failed3);
|
|
13129
|
+
return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
|
|
13087
13130
|
}
|
|
13088
13131
|
|
|
13089
13132
|
// src/mount/hostfs.ts
|
|
@@ -13237,14 +13280,14 @@ var RESTORE_AGAIN_MS = 3e4;
|
|
|
13237
13280
|
function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
13238
13281
|
const handles = /* @__PURE__ */ new Map();
|
|
13239
13282
|
let nextFh = 0;
|
|
13240
|
-
const
|
|
13283
|
+
const fresh2 = (remote) => {
|
|
13241
13284
|
if (remote.client.token === remote.token && !remote.client.gone) return remote;
|
|
13242
13285
|
throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
|
|
13243
13286
|
};
|
|
13244
13287
|
const live = (fh) => {
|
|
13245
13288
|
const remote = handles.get(fh);
|
|
13246
13289
|
if (!remote) throw fsError2("EBADF", `handle ${fh}`);
|
|
13247
|
-
return
|
|
13290
|
+
return fresh2(remote);
|
|
13248
13291
|
};
|
|
13249
13292
|
const call = (op, body = {}) => current().call(op, body);
|
|
13250
13293
|
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);
|
|
@@ -13260,13 +13303,13 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13260
13303
|
}, againMs);
|
|
13261
13304
|
timer.unref?.();
|
|
13262
13305
|
});
|
|
13263
|
-
async function writable2(client, path,
|
|
13264
|
-
const { ifMatch: _, ...asked } =
|
|
13306
|
+
async function writable2(client, path, flags2) {
|
|
13307
|
+
const { ifMatch: _, ...asked } = flags2;
|
|
13265
13308
|
const opening2 = () => client.call("open", { path: rel(path), ...asked });
|
|
13266
13309
|
try {
|
|
13267
13310
|
return { opened: await opening2(), restore: void 0 };
|
|
13268
13311
|
} catch (err) {
|
|
13269
|
-
if (!
|
|
13312
|
+
if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
|
|
13270
13313
|
const was = await client.call("stat", { path: rel(path) });
|
|
13271
13314
|
const mode = was.mode ?? 0;
|
|
13272
13315
|
if (mode & 128) throw err;
|
|
@@ -13288,11 +13331,11 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13288
13331
|
);
|
|
13289
13332
|
return entries.map(({ name: name2, attr: attr2 }) => ({ name: name2, kind: attr2.kind, attr: attr2 }));
|
|
13290
13333
|
},
|
|
13291
|
-
async open(path,
|
|
13334
|
+
async open(path, flags2) {
|
|
13292
13335
|
const client = current();
|
|
13293
13336
|
const token = client.token;
|
|
13294
|
-
const { opened: opened2, restore } = await writable2(client, path,
|
|
13295
|
-
if (
|
|
13337
|
+
const { opened: opened2, restore } = await writable2(client, path, flags2);
|
|
13338
|
+
if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
|
|
13296
13339
|
await client.call("release", { fh: opened2.fh }, true);
|
|
13297
13340
|
throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
|
|
13298
13341
|
}
|
|
@@ -13300,7 +13343,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13300
13343
|
client,
|
|
13301
13344
|
token,
|
|
13302
13345
|
fh: opened2.fh,
|
|
13303
|
-
etag:
|
|
13346
|
+
etag: flags2.write ? void 0 : opened2.attr?.etag,
|
|
13304
13347
|
restore
|
|
13305
13348
|
});
|
|
13306
13349
|
return nextFh;
|
|
@@ -13326,7 +13369,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13326
13369
|
if (!remote) throw fsError2("EBADF", `handle ${fh}`);
|
|
13327
13370
|
handles.delete(fh);
|
|
13328
13371
|
try {
|
|
13329
|
-
await
|
|
13372
|
+
await fresh2(remote).client.call("release", { fh: remote.fh }, true);
|
|
13330
13373
|
} finally {
|
|
13331
13374
|
if (remote.restore) await settle(current(), remote.restore);
|
|
13332
13375
|
}
|
|
@@ -13980,14 +14023,14 @@ function tmpfs() {
|
|
|
13980
14023
|
if (!found) throw fsError2("EBADF", `handle ${fh}`);
|
|
13981
14024
|
return found;
|
|
13982
14025
|
};
|
|
13983
|
-
function opened2(path,
|
|
14026
|
+
function opened2(path, flags2) {
|
|
13984
14027
|
const { dir, name: name2 } = parent(path);
|
|
13985
14028
|
const existing = dir.children.get(name2);
|
|
13986
|
-
if (existing &&
|
|
13987
|
-
if (existing?.kind === "directory" &&
|
|
14029
|
+
if (existing && flags2.exclusive) throw fsError2("EEXIST", path);
|
|
14030
|
+
if (existing?.kind === "directory" && flags2.write) throw fsError2("EISDIR", path);
|
|
13988
14031
|
if (existing) return existing;
|
|
13989
|
-
if (!
|
|
13990
|
-
const node = make("file", 33188, dir,
|
|
14032
|
+
if (!flags2.create) throw fsError2("ENOENT", path);
|
|
14033
|
+
const node = make("file", 33188, dir, flags2.owner);
|
|
13991
14034
|
place(dir, name2, node);
|
|
13992
14035
|
return node;
|
|
13993
14036
|
}
|
|
@@ -14007,13 +14050,13 @@ function tmpfs() {
|
|
|
14007
14050
|
([name2, node]) => ({ name: name2, kind: node.kind, attr: attr(node) })
|
|
14008
14051
|
);
|
|
14009
14052
|
},
|
|
14010
|
-
async open(path,
|
|
14011
|
-
const node = opened2(path,
|
|
14012
|
-
if (
|
|
14053
|
+
async open(path, flags2) {
|
|
14054
|
+
const node = opened2(path, flags2);
|
|
14055
|
+
if (flags2.truncate && node.kind === "file") {
|
|
14013
14056
|
node.data.truncate(0);
|
|
14014
14057
|
node.mtime = Date.now();
|
|
14015
14058
|
}
|
|
14016
|
-
handles.set(++nextFh, { node, write:
|
|
14059
|
+
handles.set(++nextFh, { node, write: flags2.write });
|
|
14017
14060
|
return nextFh;
|
|
14018
14061
|
},
|
|
14019
14062
|
async read(fh, offset, size) {
|
|
@@ -14129,10 +14172,10 @@ function importedMemory(bytes) {
|
|
|
14129
14172
|
return s;
|
|
14130
14173
|
};
|
|
14131
14174
|
const limits = () => {
|
|
14132
|
-
const
|
|
14175
|
+
const flags2 = bytes[at++];
|
|
14133
14176
|
const initial = u32();
|
|
14134
|
-
const maximum =
|
|
14135
|
-
return { initial, maximum, shared: (
|
|
14177
|
+
const maximum = flags2 & 1 ? u32() : void 0;
|
|
14178
|
+
return { initial, maximum, shared: (flags2 & 2) !== 0 };
|
|
14136
14179
|
};
|
|
14137
14180
|
while (at < bytes.length) {
|
|
14138
14181
|
const id2 = bytes[at++];
|
|
@@ -14211,9 +14254,9 @@ var Reader = class {
|
|
|
14211
14254
|
return code;
|
|
14212
14255
|
}
|
|
14213
14256
|
limits() {
|
|
14214
|
-
const
|
|
14257
|
+
const flags2 = this.byte();
|
|
14215
14258
|
this.u32();
|
|
14216
|
-
if (
|
|
14259
|
+
if (flags2 & 1) this.u32();
|
|
14217
14260
|
}
|
|
14218
14261
|
};
|
|
14219
14262
|
function readTypes(r) {
|
|
@@ -14268,6 +14311,412 @@ function foreignImports(bytes) {
|
|
|
14268
14311
|
return {};
|
|
14269
14312
|
}
|
|
14270
14313
|
|
|
14314
|
+
// src/sudo.ts
|
|
14315
|
+
var SUDOERS = "/etc/sudoers";
|
|
14316
|
+
var SUDOERS_D = "/etc/sudoers.d";
|
|
14317
|
+
var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
14318
|
+
var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
|
|
14319
|
+
var SUDO_TIMEOUT_MS = 3e5;
|
|
14320
|
+
var NAME3 = /^[a-z_][a-z0-9_.-]*$/;
|
|
14321
|
+
var RULE = /^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?Cmnd(?:\s+(.*))?$/;
|
|
14322
|
+
var TERM = /^[A-Za-z0-9._+-]+$/;
|
|
14323
|
+
var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
14324
|
+
var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
|
|
14325
|
+
function failed2(errno, error) {
|
|
14326
|
+
return { error, errno };
|
|
14327
|
+
}
|
|
14328
|
+
function unescaped(double) {
|
|
14329
|
+
return double.replace(/\\(["\\$`])/g, "$1");
|
|
14330
|
+
}
|
|
14331
|
+
function splitWords(text) {
|
|
14332
|
+
const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
|
|
14333
|
+
const words2 = [];
|
|
14334
|
+
let word;
|
|
14335
|
+
while (piece.lastIndex < text.length) {
|
|
14336
|
+
const m = piece.exec(text);
|
|
14337
|
+
if (!m) return void 0;
|
|
14338
|
+
const [, single, double, escaped, plain, space] = m;
|
|
14339
|
+
if (space !== void 0) {
|
|
14340
|
+
if (word !== void 0) words2.push(word);
|
|
14341
|
+
word = void 0;
|
|
14342
|
+
continue;
|
|
14343
|
+
}
|
|
14344
|
+
word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain));
|
|
14345
|
+
}
|
|
14346
|
+
if (word !== void 0) words2.push(word);
|
|
14347
|
+
return words2;
|
|
14348
|
+
}
|
|
14349
|
+
function quoteWord(word) {
|
|
14350
|
+
return PLAIN.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
14351
|
+
}
|
|
14352
|
+
function validWho(who) {
|
|
14353
|
+
if (who === "ALL") return true;
|
|
14354
|
+
return NAME3.test(who.startsWith("%") ? who.slice(1) : who);
|
|
14355
|
+
}
|
|
14356
|
+
function parseRule(line) {
|
|
14357
|
+
const text = line.trim();
|
|
14358
|
+
if (text === "" || text.startsWith("#")) return null;
|
|
14359
|
+
const m = RULE.exec(text);
|
|
14360
|
+
if (!m) return void 0;
|
|
14361
|
+
const [, nopasswd, who = "", target = "root", rest2 = ""] = m;
|
|
14362
|
+
if (!validWho(who) || target !== "ALL" && !NAME3.test(target)) return void 0;
|
|
14363
|
+
if (rest2.trim() === "*") return { nopasswd: !!nopasswd, who, target, cmnd: "*" };
|
|
14364
|
+
const cmnd = splitWords(rest2);
|
|
14365
|
+
if (!cmnd?.[0]?.startsWith("/")) return void 0;
|
|
14366
|
+
return { nopasswd: !!nopasswd, who, target, cmnd };
|
|
14367
|
+
}
|
|
14368
|
+
function flags(parsed2, arg, next2) {
|
|
14369
|
+
for (let j = 1; j < arg.length; j++) {
|
|
14370
|
+
const flag = arg[j];
|
|
14371
|
+
if (flag === "n") parsed2.nonInteractive = true;
|
|
14372
|
+
else if (flag === "l") parsed2.list = true;
|
|
14373
|
+
else if (flag !== "u") return `sudo: invalid option -- '${flag}'
|
|
14374
|
+
${USAGE}`;
|
|
14375
|
+
else {
|
|
14376
|
+
const user = arg.slice(j + 1) || next2;
|
|
14377
|
+
if (!user) return `sudo: option requires an argument -- 'u'
|
|
14378
|
+
${USAGE}`;
|
|
14379
|
+
parsed2.user = user;
|
|
14380
|
+
return arg.slice(j + 1) ? 0 : 1;
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14383
|
+
return 0;
|
|
14384
|
+
}
|
|
14385
|
+
function parseArgs(args) {
|
|
14386
|
+
const parsed2 = { list: false, nonInteractive: false, user: "root", command: [] };
|
|
14387
|
+
let i = 0;
|
|
14388
|
+
for (; i < args.length; i++) {
|
|
14389
|
+
const arg = args[i];
|
|
14390
|
+
if (arg === "--") {
|
|
14391
|
+
i++;
|
|
14392
|
+
break;
|
|
14393
|
+
}
|
|
14394
|
+
if (!arg.startsWith("-") || arg === "-") break;
|
|
14395
|
+
const used = flags(parsed2, arg, args[i + 1]);
|
|
14396
|
+
if (typeof used === "string") return used;
|
|
14397
|
+
i += used;
|
|
14398
|
+
}
|
|
14399
|
+
parsed2.command = args.slice(i);
|
|
14400
|
+
if (parsed2.list ? parsed2.command.length > 0 : parsed2.command.length === 0) return USAGE;
|
|
14401
|
+
return parsed2;
|
|
14402
|
+
}
|
|
14403
|
+
function described(rule) {
|
|
14404
|
+
const cmnd = rule.cmnd === "*" ? "ALL" : rule.cmnd.map(quoteWord).join(" ");
|
|
14405
|
+
return ` (${rule.target}) ${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
|
|
14406
|
+
}
|
|
14407
|
+
var Sudo = class {
|
|
14408
|
+
o;
|
|
14409
|
+
warned = /* @__PURE__ */ new Set();
|
|
14410
|
+
pending = /* @__PURE__ */ new Set();
|
|
14411
|
+
next = 0;
|
|
14412
|
+
constructor(options) {
|
|
14413
|
+
this.o = options;
|
|
14414
|
+
}
|
|
14415
|
+
warn(message3) {
|
|
14416
|
+
if (this.warned.has(message3)) return;
|
|
14417
|
+
this.warned.add(message3);
|
|
14418
|
+
(this.o.warn ?? console.warn)(message3);
|
|
14419
|
+
}
|
|
14420
|
+
async provision() {
|
|
14421
|
+
const { fs } = this.o;
|
|
14422
|
+
if (!await fs.exists(SUDOERS)) {
|
|
14423
|
+
await fs.writeFile(SUDOERS, DEFAULT_SUDOERS);
|
|
14424
|
+
await fs.chown?.(SUDOERS, { uid: 0, gid: 0, mode: 288, follow: false });
|
|
14425
|
+
}
|
|
14426
|
+
if (!await fs.exists(SUDOERS_D)) {
|
|
14427
|
+
await fs.mkdir(SUDOERS_D, { recursive: true });
|
|
14428
|
+
await fs.chown?.(SUDOERS_D, { uid: 0, gid: 0, mode: 488, follow: false });
|
|
14429
|
+
}
|
|
14430
|
+
}
|
|
14431
|
+
async trusted(path) {
|
|
14432
|
+
const parts2 = normalizePath(path).split("/").filter(Boolean);
|
|
14433
|
+
for (let n = 0; n <= parts2.length; n++) {
|
|
14434
|
+
const at = `/${parts2.slice(0, n).join("/")}`;
|
|
14435
|
+
const st = await this.o.fs.lstat(at).catch(() => void 0);
|
|
14436
|
+
if (!st || st.isSymbolicLink || st.uid !== 0 || (st.mode & 18) !== 0) return false;
|
|
14437
|
+
}
|
|
14438
|
+
return true;
|
|
14439
|
+
}
|
|
14440
|
+
async policyFile(path) {
|
|
14441
|
+
if (!await this.trusted(path)) {
|
|
14442
|
+
this.warn(`sudo: ${path} skipped: it must be root's, no symlink, and writable by root only`);
|
|
14443
|
+
return [];
|
|
14444
|
+
}
|
|
14445
|
+
const text = await this.o.fs.readFile(path).catch(() => "");
|
|
14446
|
+
const rules = [];
|
|
14447
|
+
for (const [n, line] of text.split("\n").entries()) {
|
|
14448
|
+
const rule = parseRule(line);
|
|
14449
|
+
if (rule) rules.push(rule);
|
|
14450
|
+
else if (rule === void 0) this.warn(`sudo: ${path}:${n + 1}: bad line skipped: ${line}`);
|
|
14451
|
+
}
|
|
14452
|
+
return rules;
|
|
14453
|
+
}
|
|
14454
|
+
async policy() {
|
|
14455
|
+
const rules = await this.policyFile(SUDOERS);
|
|
14456
|
+
const names = await this.o.fs.readdir(SUDOERS_D).catch(() => []);
|
|
14457
|
+
if (names.length > 0 && !await this.trusted(SUDOERS_D)) {
|
|
14458
|
+
this.warn(`sudo: ${SUDOERS_D} skipped: it must be root's and writable by root only`);
|
|
14459
|
+
return rules;
|
|
14460
|
+
}
|
|
14461
|
+
for (const name2 of [...names].sort()) {
|
|
14462
|
+
if (name2.startsWith(".") || name2.endsWith("~")) continue;
|
|
14463
|
+
rules.push(...await this.policyFile(`${SUDOERS_D}/${name2}`));
|
|
14464
|
+
}
|
|
14465
|
+
return rules;
|
|
14466
|
+
}
|
|
14467
|
+
async groupIds() {
|
|
14468
|
+
return new Map((await this.o.users.groups()).map((g) => [g.name, g.gid]));
|
|
14469
|
+
}
|
|
14470
|
+
async applies(caller, cred) {
|
|
14471
|
+
const gids = await this.groupIds();
|
|
14472
|
+
const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
|
|
14473
|
+
return (rule) => {
|
|
14474
|
+
if (rule.who === "ALL" || rule.who === caller.name) return true;
|
|
14475
|
+
const gid = rule.who.startsWith("%") ? gids.get(rule.who.slice(1)) : void 0;
|
|
14476
|
+
return gid !== void 0 && mine.has(gid);
|
|
14477
|
+
};
|
|
14478
|
+
}
|
|
14479
|
+
async lookup(user) {
|
|
14480
|
+
const key = /^#\d+$/.test(user) ? Number(user.slice(1)) : user;
|
|
14481
|
+
return this.o.users.lookup(key);
|
|
14482
|
+
}
|
|
14483
|
+
async canonical(cwd) {
|
|
14484
|
+
const { fs } = this.o;
|
|
14485
|
+
const path = normalizePath(cwd);
|
|
14486
|
+
return fs.realpath ? fs.realpath(path, true).catch(() => void 0) : path;
|
|
14487
|
+
}
|
|
14488
|
+
async reachable(cred, cwd) {
|
|
14489
|
+
const walked = await access(this.o.fs, cred, cwd, X_OK, { directory: true });
|
|
14490
|
+
if (walked.denied !== void 0 || walked.missing) return false;
|
|
14491
|
+
return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
|
|
14492
|
+
}
|
|
14493
|
+
async untrusted(path, cwd) {
|
|
14494
|
+
const files = await this.o.backing?.(path, cwd) ?? [];
|
|
14495
|
+
for (const file of [path, ...files]) if (!await this.trusted(file)) return file;
|
|
14496
|
+
return void 0;
|
|
14497
|
+
}
|
|
14498
|
+
async locate(word, cwd) {
|
|
14499
|
+
const { fs } = this.o;
|
|
14500
|
+
const candidates = word.includes("/") ? [fs.resolvePath(cwd, word)] : SECURE_PATH.split(":").map((dir) => `${dir}/${word}`);
|
|
14501
|
+
for (const path of candidates) {
|
|
14502
|
+
const st = await fs.stat(path).catch(() => void 0);
|
|
14503
|
+
if (!st?.isFile || (st.mode & 73) === 0) continue;
|
|
14504
|
+
return fs.realpath ? fs.realpath(path, true).catch(() => path) : path;
|
|
14505
|
+
}
|
|
14506
|
+
return void 0;
|
|
14507
|
+
}
|
|
14508
|
+
async request(caller, call, signal) {
|
|
14509
|
+
const parsed2 = parseArgs(call.args);
|
|
14510
|
+
if (typeof parsed2 === "string") return failed2("EINVAL", parsed2);
|
|
14511
|
+
if (this.o.mode !== "enforce") {
|
|
14512
|
+
const mode = this.o.mode;
|
|
14513
|
+
if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
|
|
14514
|
+
` };
|
|
14515
|
+
return { run: parsed2.command };
|
|
14516
|
+
}
|
|
14517
|
+
const { cred } = caller;
|
|
14518
|
+
const self = await this.o.users.lookup(cred.ruid);
|
|
14519
|
+
if (!self) return failed2("EPERM", `sudo: uid ${cred.ruid} is not in the passwd database`);
|
|
14520
|
+
const target = await this.lookup(parsed2.user);
|
|
14521
|
+
if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
|
|
14522
|
+
const root = cred.euid === 0;
|
|
14523
|
+
const rules = root ? [] : await this.policy();
|
|
14524
|
+
const applies = await this.applies(self, cred);
|
|
14525
|
+
if (parsed2.list) return this.listed(self, root, rules.filter(applies));
|
|
14526
|
+
const [word = "", ...args] = parsed2.command;
|
|
14527
|
+
const cwd = await this.canonical(call.cwd);
|
|
14528
|
+
if (cwd === void 0 || !await this.reachable(cred, cwd)) {
|
|
14529
|
+
return failed2(
|
|
14530
|
+
"EACCES",
|
|
14531
|
+
`sudo: cannot run in ${call.cwd}: it isn't a directory you can enter`
|
|
14532
|
+
);
|
|
14533
|
+
}
|
|
14534
|
+
const path = await this.locate(word, cwd);
|
|
14535
|
+
if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
|
|
14536
|
+
const untrusted = await this.untrusted(path, cwd);
|
|
14537
|
+
if (untrusted) {
|
|
14538
|
+
return failed2("EPERM", `sudo: ${untrusted} must be root's, and writable by root only`);
|
|
14539
|
+
}
|
|
14540
|
+
const argv = [path, ...args];
|
|
14541
|
+
const matching = rules.filter(
|
|
14542
|
+
(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]))
|
|
14543
|
+
);
|
|
14544
|
+
const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
|
|
14545
|
+
if (root || matching.some((rule) => rule.nopasswd)) return grant();
|
|
14546
|
+
const quoted = argv.map(quoteWord).join(" ");
|
|
14547
|
+
if (matching.length === 0) {
|
|
14548
|
+
return failed2(
|
|
14549
|
+
"EPERM",
|
|
14550
|
+
`sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
|
|
14551
|
+
);
|
|
14552
|
+
}
|
|
14553
|
+
if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
|
|
14554
|
+
if (this.pending.has(cred.ruid)) return failed2("EBUSY", "sudo: another request is pending");
|
|
14555
|
+
this.pending.add(cred.ruid);
|
|
14556
|
+
try {
|
|
14557
|
+
const answer = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
|
|
14558
|
+
if (typeof answer !== "string") return answer;
|
|
14559
|
+
if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
|
|
14560
|
+
return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
|
|
14561
|
+
}
|
|
14562
|
+
const changed = await this.untrusted(path, cwd);
|
|
14563
|
+
if (changed) {
|
|
14564
|
+
return failed2("EPERM", `sudo: ${changed} must be root's, and writable by root only`);
|
|
14565
|
+
}
|
|
14566
|
+
if (answer === "always" && !await this.remember(self, target, argv)) {
|
|
14567
|
+
return failed2("EIO", "sudo: the rule could not be recorded");
|
|
14568
|
+
}
|
|
14569
|
+
return grant();
|
|
14570
|
+
} finally {
|
|
14571
|
+
this.pending.delete(cred.ruid);
|
|
14572
|
+
}
|
|
14573
|
+
}
|
|
14574
|
+
listed(self, root, rules) {
|
|
14575
|
+
const host = this.o.hostname;
|
|
14576
|
+
if (root) {
|
|
14577
|
+
return {
|
|
14578
|
+
list: `User ${self.name} may run the following commands on ${host}:
|
|
14579
|
+
(ALL) NOPASSWD: ALL
|
|
14580
|
+
`
|
|
14581
|
+
};
|
|
14582
|
+
}
|
|
14583
|
+
if (rules.length === 0) {
|
|
14584
|
+
return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
|
|
14585
|
+
}
|
|
14586
|
+
const lines2 = rules.map(described).join("\n");
|
|
14587
|
+
return { list: `User ${self.name} may run the following commands on ${host}:
|
|
14588
|
+
${lines2}
|
|
14589
|
+
` };
|
|
14590
|
+
}
|
|
14591
|
+
async ask(caller, self, target, argv, path, cwd, requester) {
|
|
14592
|
+
const { approve } = this.o;
|
|
14593
|
+
if (!approve || requester.aborted) return failed2("EACCES", "sudo: approval denied");
|
|
14594
|
+
const createdAt = Date.now();
|
|
14595
|
+
const timeout = this.o.timeoutMs ?? SUDO_TIMEOUT_MS;
|
|
14596
|
+
const req = {
|
|
14597
|
+
id: `sudo-${++this.next}-${crypto.randomUUID()}`,
|
|
14598
|
+
kind: "sudo",
|
|
14599
|
+
user: self.name,
|
|
14600
|
+
uid: self.uid,
|
|
14601
|
+
target: target.name,
|
|
14602
|
+
argv: [...argv],
|
|
14603
|
+
path,
|
|
14604
|
+
cwd,
|
|
14605
|
+
pid: caller.pid,
|
|
14606
|
+
...caller.tag !== void 0 ? { tag: caller.tag } : {},
|
|
14607
|
+
...caller.tty ? { tty: caller.tty } : {},
|
|
14608
|
+
createdAt,
|
|
14609
|
+
expiresAt: createdAt + timeout,
|
|
14610
|
+
always: true
|
|
14611
|
+
};
|
|
14612
|
+
const control = new AbortController();
|
|
14613
|
+
const timer = setTimeout(() => control.abort("expired"), timeout);
|
|
14614
|
+
const withdraw = () => control.abort("withdrawn");
|
|
14615
|
+
requester.addEventListener("abort", withdraw, { once: true });
|
|
14616
|
+
const aborted2 = new Promise(
|
|
14617
|
+
(resolve) => control.signal.addEventListener("abort", () => resolve("aborted"), { once: true })
|
|
14618
|
+
);
|
|
14619
|
+
try {
|
|
14620
|
+
const answered = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
|
|
14621
|
+
const answer = await Promise.race([answered, aborted2]);
|
|
14622
|
+
if (answer === "aborted" || control.signal.aborted) {
|
|
14623
|
+
return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
|
|
14624
|
+
}
|
|
14625
|
+
return answer === "once" || answer === "always" ? answer : failed2("EACCES", "sudo: approval denied");
|
|
14626
|
+
} finally {
|
|
14627
|
+
clearTimeout(timer);
|
|
14628
|
+
requester.removeEventListener("abort", withdraw);
|
|
14629
|
+
}
|
|
14630
|
+
}
|
|
14631
|
+
async remember(self, target, argv) {
|
|
14632
|
+
const { fs } = this.o;
|
|
14633
|
+
const path = `${SUDOERS_D}/${self.name}`;
|
|
14634
|
+
if (!NAME3.test(self.name)) return false;
|
|
14635
|
+
try {
|
|
14636
|
+
await this.provision();
|
|
14637
|
+
if (!await this.trusted(SUDOERS_D)) return false;
|
|
14638
|
+
const st = await fs.lstat(path).catch(() => void 0);
|
|
14639
|
+
if (st && !await this.trusted(path)) return false;
|
|
14640
|
+
const old = st ? await fs.readFile(path) : "";
|
|
14641
|
+
const line = `NOPASSWD ${self.name} (${target.name}) Cmnd ${argv.map(quoteWord).join(" ")}
|
|
14642
|
+
`;
|
|
14643
|
+
const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
|
|
14644
|
+
${line}`;
|
|
14645
|
+
await fs.writeFile(path, text);
|
|
14646
|
+
await fs.chown?.(path, { uid: 0, gid: 0, mode: 288, follow: false });
|
|
14647
|
+
return true;
|
|
14648
|
+
} catch {
|
|
14649
|
+
return false;
|
|
14650
|
+
}
|
|
14651
|
+
}
|
|
14652
|
+
grant(self, target, cred, argv, file, cwd, term) {
|
|
14653
|
+
const env = {
|
|
14654
|
+
...this.o.env(),
|
|
14655
|
+
...userEnv(target, this.o.pnpmHome),
|
|
14656
|
+
SHELL: target.shell,
|
|
14657
|
+
PATH: SECURE_PATH,
|
|
14658
|
+
PWD: cwd,
|
|
14659
|
+
...term !== void 0 && TERM.test(term) ? { TERM: term } : {},
|
|
14660
|
+
SUDO_USER: self.name,
|
|
14661
|
+
SUDO_UID: String(cred.ruid),
|
|
14662
|
+
SUDO_GID: String(cred.rgid),
|
|
14663
|
+
SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
|
|
14664
|
+
};
|
|
14665
|
+
return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
|
|
14666
|
+
}
|
|
14667
|
+
};
|
|
14668
|
+
|
|
14669
|
+
// src/sudo-command.ts
|
|
14670
|
+
var SUDO_PATH = "/usr/bin/sudo";
|
|
14671
|
+
var SUDO_COMMAND = {
|
|
14672
|
+
name: "sudo",
|
|
14673
|
+
abi: "js",
|
|
14674
|
+
glue: SUDO_PATH,
|
|
14675
|
+
wasm: SUDO_PATH,
|
|
14676
|
+
argv0: "sudo"
|
|
14677
|
+
};
|
|
14678
|
+
var SUDO_SOURCE = [
|
|
14679
|
+
"export default async function sudo(ctx) {",
|
|
14680
|
+
" const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);",
|
|
14681
|
+
" let pid;",
|
|
14682
|
+
" let held;",
|
|
14683
|
+
" const relay = (sig) => {",
|
|
14684
|
+
" if (pid === undefined) held = sig;",
|
|
14685
|
+
" else return ctx.kill(pid, sig).catch(() => undefined);",
|
|
14686
|
+
" };",
|
|
14687
|
+
' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
|
|
14688
|
+
" const failed = () => (held === undefined ? 1 : 128 + held);",
|
|
14689
|
+
" let answer;",
|
|
14690
|
+
" try {",
|
|
14691
|
+
" answer = await ctx.sudo(ctx.argv.slice(1));",
|
|
14692
|
+
" } catch (err) {",
|
|
14693
|
+
' await say(2, "sudo: " + (err && err.code ? err.code : String(err)) + "\\n");',
|
|
14694
|
+
" return failed();",
|
|
14695
|
+
" }",
|
|
14696
|
+
" if (answer.error !== undefined) {",
|
|
14697
|
+
' await say(2, answer.error + "\\n");',
|
|
14698
|
+
" return failed();",
|
|
14699
|
+
" }",
|
|
14700
|
+
" if (answer.list !== undefined) {",
|
|
14701
|
+
" await say(1, answer.list);",
|
|
14702
|
+
" return 0;",
|
|
14703
|
+
" }",
|
|
14704
|
+
' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
|
|
14705
|
+
" if (answer.run !== undefined) {",
|
|
14706
|
+
" try {",
|
|
14707
|
+
" pid = (await ctx.spawn({ argv: answer.run })).pid;",
|
|
14708
|
+
" } catch (err) {",
|
|
14709
|
+
' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
|
|
14710
|
+
' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
|
|
14711
|
+
" return failed();",
|
|
14712
|
+
" }",
|
|
14713
|
+
" } else pid = answer.pid;",
|
|
14714
|
+
" if (held !== undefined) await relay(held);",
|
|
14715
|
+
" return (await ctx.wait(pid)).status;",
|
|
14716
|
+
"}",
|
|
14717
|
+
""
|
|
14718
|
+
].join("\n");
|
|
14719
|
+
|
|
14271
14720
|
// src/launcher.ts
|
|
14272
14721
|
var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
14273
14722
|
var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
|
|
@@ -14295,9 +14744,21 @@ function usersMode(mode = "off") {
|
|
|
14295
14744
|
if (USERS_MODES.includes(mode)) return mode;
|
|
14296
14745
|
throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
|
|
14297
14746
|
}
|
|
14747
|
+
function tagOption(tag) {
|
|
14748
|
+
if (tag !== void 0 && typeof tag !== "string") throw new TypeError("a tag must be a string");
|
|
14749
|
+
}
|
|
14298
14750
|
function baseName(path) {
|
|
14299
14751
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
14300
14752
|
}
|
|
14753
|
+
function packageRootOf(path, roots) {
|
|
14754
|
+
for (const root of roots) {
|
|
14755
|
+
const base = normalizePath(root);
|
|
14756
|
+
if (!path.startsWith(`${base}/`)) continue;
|
|
14757
|
+
const [first = "", second] = path.slice(base.length + 1).split("/");
|
|
14758
|
+
return first.startsWith("@") && second !== void 0 ? `${base}/${first}/${second}` : `${base}/${first}`;
|
|
14759
|
+
}
|
|
14760
|
+
return void 0;
|
|
14761
|
+
}
|
|
14301
14762
|
function modulePath(glue) {
|
|
14302
14763
|
return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
|
|
14303
14764
|
}
|
|
@@ -14309,11 +14770,11 @@ function shebang(head) {
|
|
|
14309
14770
|
return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
|
|
14310
14771
|
}
|
|
14311
14772
|
function interpreter(head) {
|
|
14312
|
-
const
|
|
14313
|
-
if (!
|
|
14314
|
-
if (baseName(
|
|
14315
|
-
if (
|
|
14316
|
-
return baseName(
|
|
14773
|
+
const words2 = shebang(head);
|
|
14774
|
+
if (!words2) return "node";
|
|
14775
|
+
if (baseName(words2[0]) === "env") words2.shift();
|
|
14776
|
+
if (words2[0] === "-S") words2.shift();
|
|
14777
|
+
return baseName(words2[0] ?? "");
|
|
14317
14778
|
}
|
|
14318
14779
|
function expandDefaults(defaults, env) {
|
|
14319
14780
|
const out = {};
|
|
@@ -14387,7 +14848,10 @@ var Launcher = class {
|
|
|
14387
14848
|
orphans = /* @__PURE__ */ new Set();
|
|
14388
14849
|
described = /* @__PURE__ */ new Map();
|
|
14389
14850
|
creds = /* @__PURE__ */ new Map();
|
|
14851
|
+
tags = /* @__PURE__ */ new Map();
|
|
14852
|
+
granted = /* @__PURE__ */ new Map();
|
|
14390
14853
|
users;
|
|
14854
|
+
sudoer;
|
|
14391
14855
|
jobs = new JobTable();
|
|
14392
14856
|
locks = new LockTable();
|
|
14393
14857
|
settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
|
|
@@ -14488,6 +14952,17 @@ var Launcher = class {
|
|
|
14488
14952
|
);
|
|
14489
14953
|
this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
|
|
14490
14954
|
this.users = new UserDb(this.fs, this.owners);
|
|
14955
|
+
this.sudoer = new Sudo({
|
|
14956
|
+
fs: this.fs,
|
|
14957
|
+
users: this.users,
|
|
14958
|
+
mode: this.usersMode,
|
|
14959
|
+
hostname,
|
|
14960
|
+
env: () => this.env,
|
|
14961
|
+
pnpmHome: this.pnpmHome,
|
|
14962
|
+
backing: (path, cwd) => this.backing(path, cwd),
|
|
14963
|
+
approve: options.approve,
|
|
14964
|
+
timeoutMs: options.sudoTimeoutMs
|
|
14965
|
+
});
|
|
14491
14966
|
void this.unlinked.sweep("/");
|
|
14492
14967
|
this.watchers.watch(
|
|
14493
14968
|
[this.modulesDir, this.pnpmHome],
|
|
@@ -14508,7 +14983,7 @@ var Launcher = class {
|
|
|
14508
14983
|
commands(scope = "") {
|
|
14509
14984
|
let found = this.catalogs.get(scope);
|
|
14510
14985
|
if (!found) {
|
|
14511
|
-
found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
|
|
14986
|
+
found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
|
|
14512
14987
|
this.catalogs.set(scope, found);
|
|
14513
14988
|
}
|
|
14514
14989
|
return found;
|
|
@@ -14631,10 +15106,10 @@ var Launcher = class {
|
|
|
14631
15106
|
const head = await this.head(script);
|
|
14632
15107
|
if (!head) return void 0;
|
|
14633
15108
|
const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
|
|
14634
|
-
const
|
|
14635
|
-
if (!
|
|
14636
|
-
if (baseName(
|
|
14637
|
-
const [interp, ...rest2] =
|
|
15109
|
+
const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
|
|
15110
|
+
if (!words2) return void 0;
|
|
15111
|
+
if (baseName(words2[0]) === "env") words2.shift();
|
|
15112
|
+
const [interp, ...rest2] = words2;
|
|
14638
15113
|
const arg = rest2.join(" ");
|
|
14639
15114
|
const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
|
|
14640
15115
|
const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
|
|
@@ -14671,6 +15146,41 @@ var Launcher = class {
|
|
|
14671
15146
|
await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
|
|
14672
15147
|
}
|
|
14673
15148
|
}
|
|
15149
|
+
async grantedCwd(caller, cwd) {
|
|
15150
|
+
const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
|
|
15151
|
+
return real === cwd && await this.sudoer.reachable(caller, cwd);
|
|
15152
|
+
}
|
|
15153
|
+
async binfmtManifests(scripts) {
|
|
15154
|
+
const sources = /* @__PURE__ */ new Map();
|
|
15155
|
+
const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
|
|
15156
|
+
const manifests = [];
|
|
15157
|
+
for (const script of scripts) {
|
|
15158
|
+
const head = await this.head(script);
|
|
15159
|
+
if (!head || isWasm(head) || shebang(head)) continue;
|
|
15160
|
+
const pkg = sources.get(binfmtSuffix(binfmts, script) ?? "");
|
|
15161
|
+
if (pkg !== void 0) manifests.push(`${pkg}/package.json`);
|
|
15162
|
+
}
|
|
15163
|
+
return manifests;
|
|
15164
|
+
}
|
|
15165
|
+
async backing(file, cwd) {
|
|
15166
|
+
const planned = await this.plan(file, [file], cwd, "");
|
|
15167
|
+
if (!planned) return [];
|
|
15168
|
+
const { glue, wasm, imports } = planned.target;
|
|
15169
|
+
const files = [glue, wasm, ...imports ? [imports] : []];
|
|
15170
|
+
const scripts = [];
|
|
15171
|
+
for (const name2 of [file, ...planned.via ?? []]) {
|
|
15172
|
+
if (name2.includes("/") && !COMMAND2.test(name2)) scripts.push(this.fs.resolvePath(cwd, name2));
|
|
15173
|
+
const script = (await this.scriptCommand(name2, cwd, ""))?.script;
|
|
15174
|
+
if (script) scripts.push(script);
|
|
15175
|
+
}
|
|
15176
|
+
files.push(...scripts, ...await this.binfmtManifests(scripts));
|
|
15177
|
+
const roots = await this.roots();
|
|
15178
|
+
for (const at of [...files]) {
|
|
15179
|
+
const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
|
|
15180
|
+
if (root !== void 0) files.push(`${root}/package.json`);
|
|
15181
|
+
}
|
|
15182
|
+
return [...new Set(files)].filter((path) => path !== "" && path !== SUDO_PATH);
|
|
15183
|
+
}
|
|
14674
15184
|
async unrunnable(file, cwd, scope) {
|
|
14675
15185
|
const path = this.fs.resolvePath(cwd, file);
|
|
14676
15186
|
const head = await this.base.readFileBuffer(path).catch(() => null);
|
|
@@ -14708,6 +15218,9 @@ var Launcher = class {
|
|
|
14708
15218
|
return await this.base.exists(optional) ? optional : void 0;
|
|
14709
15219
|
}
|
|
14710
15220
|
async program(target, env) {
|
|
15221
|
+
if (target.abi === "js" && target.wasm === SUDO_PATH) {
|
|
15222
|
+
return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
|
|
15223
|
+
}
|
|
14711
15224
|
if (target.abi === "js") {
|
|
14712
15225
|
return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
|
|
14713
15226
|
}
|
|
@@ -14794,6 +15307,7 @@ var Launcher = class {
|
|
|
14794
15307
|
kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
|
|
14795
15308
|
writesBack: (target) => this.processes.get(target)?.writesBack() === true,
|
|
14796
15309
|
processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
|
|
15310
|
+
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
14797
15311
|
openFiles: this.openFiles,
|
|
14798
15312
|
nodes: this.nodes,
|
|
14799
15313
|
held: this.held,
|
|
@@ -14801,7 +15315,7 @@ var Launcher = class {
|
|
|
14801
15315
|
statfs: (path) => this.mounts.statfs(path),
|
|
14802
15316
|
mounts: () => this.mounts.list(),
|
|
14803
15317
|
mount: (call, signal) => this.processMount(pid, call, signal),
|
|
14804
|
-
umount: (target,
|
|
15318
|
+
umount: (target, flags2) => this.processUmount(pid, target, flags2),
|
|
14805
15319
|
jobs: this.jobs,
|
|
14806
15320
|
ptys: this.ptys,
|
|
14807
15321
|
net: this.net,
|
|
@@ -14815,6 +15329,7 @@ var Launcher = class {
|
|
|
14815
15329
|
this.processes.set(pid, handle);
|
|
14816
15330
|
const { cred } = req;
|
|
14817
15331
|
this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
|
|
15332
|
+
if (req.tag !== void 0) this.tags.set(pid, req.tag);
|
|
14818
15333
|
if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
|
|
14819
15334
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
14820
15335
|
if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
|
|
@@ -14842,6 +15357,8 @@ var Launcher = class {
|
|
|
14842
15357
|
this.jobs.remove(pid);
|
|
14843
15358
|
this.described.delete(pid);
|
|
14844
15359
|
this.creds.delete(pid);
|
|
15360
|
+
this.tags.delete(pid);
|
|
15361
|
+
this.granted.delete(pid);
|
|
14845
15362
|
}
|
|
14846
15363
|
reaped(pid) {
|
|
14847
15364
|
if (this.zombies.delete(pid)) this.forget(pid);
|
|
@@ -14863,8 +15380,8 @@ var Launcher = class {
|
|
|
14863
15380
|
};
|
|
14864
15381
|
const listed = [];
|
|
14865
15382
|
for (const member of members.values()) {
|
|
14866
|
-
const
|
|
14867
|
-
if (member.execed || !
|
|
15383
|
+
const described2 = this.described.get(member.pid);
|
|
15384
|
+
if (member.execed || !described2) continue;
|
|
14868
15385
|
const cred = this.credOf(member.pid);
|
|
14869
15386
|
if (!maySee(viewer, cred)) continue;
|
|
14870
15387
|
const root = rootOf(member);
|
|
@@ -14874,8 +15391,8 @@ var Launcher = class {
|
|
|
14874
15391
|
ppid: shown(root.ppid),
|
|
14875
15392
|
pgid: member.pgid,
|
|
14876
15393
|
sid: member.sid,
|
|
14877
|
-
...
|
|
14878
|
-
started: (this.described.get(root.pid) ??
|
|
15394
|
+
...described2,
|
|
15395
|
+
started: (this.described.get(root.pid) ?? described2).started,
|
|
14879
15396
|
state: this.processes.has(member.pid) ? "S" : "Z",
|
|
14880
15397
|
memory: this.processes.get(member.pid)?.memory() ?? 0,
|
|
14881
15398
|
umask: this.umaskOf(member.pid),
|
|
@@ -14885,12 +15402,26 @@ var Launcher = class {
|
|
|
14885
15402
|
}
|
|
14886
15403
|
return listed;
|
|
14887
15404
|
}
|
|
15405
|
+
sudo(pid, call, signal) {
|
|
15406
|
+
const caller = {
|
|
15407
|
+
cred: this.credOf(pid),
|
|
15408
|
+
pid: this.jobs.shown(pid),
|
|
15409
|
+
tag: this.tagOf(pid),
|
|
15410
|
+
tty: this.described.get(pid)?.tty
|
|
15411
|
+
};
|
|
15412
|
+
return this.sudoer.request(caller, call, signal);
|
|
15413
|
+
}
|
|
14888
15414
|
spawner(ppid, report) {
|
|
14889
|
-
return async (req, fds) => {
|
|
14890
|
-
|
|
15415
|
+
return async (req, fds, grant) => {
|
|
15416
|
+
if (grant && !await this.grantedCwd(grant.caller, req.cwd)) {
|
|
15417
|
+
await fds.closeAll();
|
|
15418
|
+
throw new SpawnError("EACCES");
|
|
15419
|
+
}
|
|
15420
|
+
const cred = grant ? grant.cred : this.credOf(ppid);
|
|
15421
|
+
const scope = grant ? "" : await this.scopeOf(cred.euid);
|
|
14891
15422
|
const planned = await this.plan(req.file, req.argv, req.cwd, scope);
|
|
14892
15423
|
try {
|
|
14893
|
-
await this.mayExec(
|
|
15424
|
+
await this.mayExec(cred, { ...req, pid: ppid }, planned);
|
|
14894
15425
|
} catch (err) {
|
|
14895
15426
|
await fds.closeAll();
|
|
14896
15427
|
throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
|
|
@@ -14910,10 +15441,12 @@ var Launcher = class {
|
|
|
14910
15441
|
ppid,
|
|
14911
15442
|
ignored: this.ignoredBy(ppid),
|
|
14912
15443
|
umask: this.umaskOf(ppid),
|
|
14913
|
-
cred
|
|
15444
|
+
cred,
|
|
15445
|
+
tag: this.tagOf(ppid),
|
|
14914
15446
|
...exec
|
|
14915
15447
|
});
|
|
14916
15448
|
deciding?.attach();
|
|
15449
|
+
if (grant) this.granted.set(handle.pid, ppid);
|
|
14917
15450
|
return childHandle(handle);
|
|
14918
15451
|
};
|
|
14919
15452
|
}
|
|
@@ -14957,7 +15490,8 @@ var Launcher = class {
|
|
|
14957
15490
|
fork: state,
|
|
14958
15491
|
ignored: this.ignoredBy(ppid, true),
|
|
14959
15492
|
umask: this.umaskOf(ppid),
|
|
14960
|
-
cred: this.credOf(ppid)
|
|
15493
|
+
cred: this.credOf(ppid),
|
|
15494
|
+
tag: this.tagOf(ppid)
|
|
14961
15495
|
})
|
|
14962
15496
|
);
|
|
14963
15497
|
}
|
|
@@ -14982,6 +15516,9 @@ var Launcher = class {
|
|
|
14982
15516
|
credOf(pid) {
|
|
14983
15517
|
return this.creds.get(pid)?.() ?? copyCred(ROOT);
|
|
14984
15518
|
}
|
|
15519
|
+
tagOf(pid) {
|
|
15520
|
+
return this.tags.get(pid);
|
|
15521
|
+
}
|
|
14985
15522
|
released(path) {
|
|
14986
15523
|
const at = normalizePath(path);
|
|
14987
15524
|
this.unlinked.closed(at);
|
|
@@ -15000,7 +15537,7 @@ var Launcher = class {
|
|
|
15000
15537
|
}
|
|
15001
15538
|
kill(pid, sig, sender, from) {
|
|
15002
15539
|
const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
|
|
15003
|
-
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);
|
|
15540
|
+
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;
|
|
15004
15541
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
15005
15542
|
const handle = this.processes.get(pid);
|
|
15006
15543
|
if (!handle) return false;
|
|
@@ -15117,11 +15654,12 @@ var Launcher = class {
|
|
|
15117
15654
|
];
|
|
15118
15655
|
for (const [path, mode] of layout) {
|
|
15119
15656
|
const real = await this.realLayoutPath(path);
|
|
15120
|
-
const
|
|
15121
|
-
if (
|
|
15657
|
+
const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
|
|
15658
|
+
if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
|
|
15122
15659
|
}
|
|
15123
15660
|
}
|
|
15124
|
-
async realLayoutPath(
|
|
15661
|
+
async realLayoutPath(given) {
|
|
15662
|
+
const path = normalizePath(given);
|
|
15125
15663
|
if (!await this.base.exists(path)) return void 0;
|
|
15126
15664
|
if (path === "/") return path;
|
|
15127
15665
|
return joinName(await realDir(this.base, parentOf(path)), baseName(path));
|
|
@@ -15132,7 +15670,8 @@ var Launcher = class {
|
|
|
15132
15670
|
mountedBy(spec, cred) {
|
|
15133
15671
|
if (!this.owners || cred.euid === 0 && cred.egid === 0) return spec;
|
|
15134
15672
|
const ids = { uid: String(cred.euid), gid: String(cred.egid) };
|
|
15135
|
-
|
|
15673
|
+
const options = cred.euid === 0 ? { ...ids, ...spec.options } : { ...spec.options, ...ids };
|
|
15674
|
+
return { ...spec, options };
|
|
15136
15675
|
}
|
|
15137
15676
|
mountRoot(path) {
|
|
15138
15677
|
let root = "/";
|
|
@@ -15177,13 +15716,36 @@ var Launcher = class {
|
|
|
15177
15716
|
async processMount(pid, call, signal) {
|
|
15178
15717
|
const spec = mountCall(call);
|
|
15179
15718
|
await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
|
|
15719
|
+
await this.mountsOwn(
|
|
15720
|
+
this.credOf(pid),
|
|
15721
|
+
spec.target,
|
|
15722
|
+
async () => (await this.fs.stat(spec.target)).uid
|
|
15723
|
+
);
|
|
15180
15724
|
const entry = await this.mount(this.mountedBy(spec, this.credOf(pid)));
|
|
15181
15725
|
if (signal.aborted) this.umount(entry.target, true);
|
|
15182
15726
|
}
|
|
15183
|
-
async
|
|
15184
|
-
const
|
|
15727
|
+
async mountAs(cred, spec) {
|
|
15728
|
+
const target = normalizePath(spec.target);
|
|
15729
|
+
await this.mountsOwn(cred, target, async () => (await this.fs.stat(target)).uid);
|
|
15730
|
+
return this.mount(this.mountedBy(spec, cred));
|
|
15731
|
+
}
|
|
15732
|
+
async umountAs(cred, target) {
|
|
15733
|
+
const at = normalizePath(target);
|
|
15734
|
+
await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
|
|
15735
|
+
this.umount(at);
|
|
15736
|
+
}
|
|
15737
|
+
async mountsOwn(cred, target, owner) {
|
|
15738
|
+
const { euid } = cred;
|
|
15739
|
+
if (this.usersMode !== "enforce" || euid === 0) return;
|
|
15740
|
+
if (await owner().catch(() => void 0) !== euid) {
|
|
15741
|
+
throw fsError("EPERM", `${target} is not the caller's to mount or unmount`);
|
|
15742
|
+
}
|
|
15743
|
+
}
|
|
15744
|
+
async processUmount(pid, target, flags2) {
|
|
15745
|
+
const call = umountCall(target, flags2);
|
|
15746
|
+
let mounted;
|
|
15185
15747
|
for (; ; ) {
|
|
15186
|
-
|
|
15748
|
+
mounted = this.mounts.mounted(call.target);
|
|
15187
15749
|
await this.permit({
|
|
15188
15750
|
op: "umount",
|
|
15189
15751
|
pid,
|
|
@@ -15192,6 +15754,7 @@ var Launcher = class {
|
|
|
15192
15754
|
});
|
|
15193
15755
|
if (this.mounts.mounted(call.target) === mounted) break;
|
|
15194
15756
|
}
|
|
15757
|
+
await this.mountsOwn(this.credOf(pid), call.target, async () => Number(mounted?.options.uid));
|
|
15195
15758
|
this.umount(call.target, call.detach);
|
|
15196
15759
|
}
|
|
15197
15760
|
async prepare() {
|
|
@@ -15200,6 +15763,7 @@ var Launcher = class {
|
|
|
15200
15763
|
await this.provisionPnpmHome();
|
|
15201
15764
|
await this.users.ensure();
|
|
15202
15765
|
if (this.owners) await this.stampLayout();
|
|
15766
|
+
if (this.owners) await this.sudoer.provision();
|
|
15203
15767
|
await writeCaFile(this.base, this.ca).catch(() => void 0);
|
|
15204
15768
|
this.fstab = this.mountFstab();
|
|
15205
15769
|
}
|
|
@@ -15237,8 +15801,9 @@ var Launcher = class {
|
|
|
15237
15801
|
if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
|
|
15238
15802
|
return found;
|
|
15239
15803
|
}
|
|
15240
|
-
identity(account) {
|
|
15804
|
+
identity(account, tag) {
|
|
15241
15805
|
return {
|
|
15806
|
+
tag,
|
|
15242
15807
|
cred: credOf(account),
|
|
15243
15808
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
15244
15809
|
};
|
|
@@ -15255,6 +15820,7 @@ var Launcher = class {
|
|
|
15255
15820
|
return { cwd, file, planned };
|
|
15256
15821
|
}
|
|
15257
15822
|
async openTerminal(argv, options) {
|
|
15823
|
+
tagOption(options.tag);
|
|
15258
15824
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
15259
15825
|
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
15260
15826
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
@@ -15283,7 +15849,13 @@ var Launcher = class {
|
|
|
15283
15849
|
);
|
|
15284
15850
|
const report = (message3) => options.onData(encoder3.encode(`${message3}\r
|
|
15285
15851
|
`));
|
|
15286
|
-
const handle = await this.launch(planned, {
|
|
15852
|
+
const handle = await this.launch(planned, {
|
|
15853
|
+
env,
|
|
15854
|
+
cwd,
|
|
15855
|
+
fds,
|
|
15856
|
+
report,
|
|
15857
|
+
...this.identity(account, options.tag)
|
|
15858
|
+
});
|
|
15287
15859
|
leader = handle.pid;
|
|
15288
15860
|
void handle.exited.then(() => tty.hangup());
|
|
15289
15861
|
return {
|
|
@@ -15299,6 +15871,7 @@ var Launcher = class {
|
|
|
15299
15871
|
};
|
|
15300
15872
|
}
|
|
15301
15873
|
async run(argv, options = {}) {
|
|
15874
|
+
tagOption(options.tag);
|
|
15302
15875
|
const account = await this.account(options.user);
|
|
15303
15876
|
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
15304
15877
|
const env = this.environment(cwd, options.env, account);
|
|
@@ -15328,7 +15901,7 @@ var Launcher = class {
|
|
|
15328
15901
|
fds,
|
|
15329
15902
|
report,
|
|
15330
15903
|
...group,
|
|
15331
|
-
...this.identity(account)
|
|
15904
|
+
...this.identity(account, options.tag)
|
|
15332
15905
|
});
|
|
15333
15906
|
options.onStarted?.(handle.pid);
|
|
15334
15907
|
const status = await handle.exited;
|
|
@@ -15827,7 +16400,7 @@ function dialStream(port, onClose) {
|
|
|
15827
16400
|
const writable2 = new WritableStream({
|
|
15828
16401
|
write(chunk2) {
|
|
15829
16402
|
if (closed) throw errorWithCode("EPIPE: the connection is closed");
|
|
15830
|
-
const copy = chunk2
|
|
16403
|
+
const copy = fresh(chunk2);
|
|
15831
16404
|
const acked = new Promise((resolve, reject) => acks.push({ resolve, reject }));
|
|
15832
16405
|
port.postMessage(copy, [copy.buffer]);
|
|
15833
16406
|
return acked;
|
|
@@ -16480,7 +17053,8 @@ async function createNodeKernel(options = {}) {
|
|
|
16480
17053
|
...options.cdp ? { cdp: options.cdp } : {},
|
|
16481
17054
|
...options.hostname !== void 0 ? { hostname: options.hostname } : {},
|
|
16482
17055
|
...options.users !== void 0 ? { users: options.users } : {},
|
|
16483
|
-
...options.onDenial ? { onDenial: options.onDenial } : {}
|
|
17056
|
+
...options.onDenial ? { onDenial: options.onDenial } : {},
|
|
17057
|
+
...options.approve ? { approve: options.approve } : {}
|
|
16484
17058
|
});
|
|
16485
17059
|
await launcher.prepare();
|
|
16486
17060
|
const guard2 = (work) => new Promise((resolve, reject) => {
|
|
@@ -16556,8 +17130,9 @@ async function createNodeKernel(options = {}) {
|
|
|
16556
17130
|
addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
|
|
16557
17131
|
removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
|
|
16558
17132
|
},
|
|
16559
|
-
async connect({ user } = {}) {
|
|
17133
|
+
async connect({ user, tag } = {}) {
|
|
16560
17134
|
if (terminated) throw new Error(TERMINATED);
|
|
17135
|
+
tagOption(tag);
|
|
16561
17136
|
if (user !== void 0 && !await launcher.users.lookup(user)) {
|
|
16562
17137
|
throw fsError("ENOENT", `no user ${String(user)}`);
|
|
16563
17138
|
}
|
|
@@ -16565,7 +17140,8 @@ async function createNodeKernel(options = {}) {
|
|
|
16565
17140
|
const served = serveClient(port1, {
|
|
16566
17141
|
launcher: async () => launcher,
|
|
16567
17142
|
signal: signalNumber,
|
|
16568
|
-
...user !== void 0 ? { user } : {}
|
|
17143
|
+
...user !== void 0 ? { user } : {},
|
|
17144
|
+
...tag !== void 0 ? { tag } : {}
|
|
16569
17145
|
});
|
|
16570
17146
|
clients.add(served);
|
|
16571
17147
|
void served.closed.then(() => clients.delete(served));
|