@ai-ecoverse/slicc-kernel 1.65.1 → 1.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -10
- package/dist/driver-worker.js +1 -1
- package/dist/driver-worker.js.map +1 -1
- package/dist/index.js +53 -22
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +780 -183
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +1 -1
- package/dist/node-driver-worker.js.map +1 -1
- package/dist/node-process-worker.js +6 -0
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +747 -174
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +6 -0
- package/dist/process-worker.js.map +2 -2
- package/package.json +2 -2
package/dist/node.js
CHANGED
|
@@ -532,6 +532,64 @@ async function openable(g, op, path, want, create) {
|
|
|
532
532
|
await need(g, op, path, want, { directory: st?.isDirectory ?? false });
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
+
// src/kernel/cred.ts
|
|
536
|
+
var ROOT = Object.freeze({
|
|
537
|
+
ruid: 0,
|
|
538
|
+
euid: 0,
|
|
539
|
+
suid: 0,
|
|
540
|
+
rgid: 0,
|
|
541
|
+
egid: 0,
|
|
542
|
+
sgid: 0,
|
|
543
|
+
groups: [0]
|
|
544
|
+
});
|
|
545
|
+
var MAX_ID = 4294967294;
|
|
546
|
+
var MAX_GROUPS = 65536;
|
|
547
|
+
var UIDS = ["ruid", "euid", "suid"];
|
|
548
|
+
var GIDS = ["rgid", "egid", "sgid"];
|
|
549
|
+
function userCred(uid, gid, groups = [gid]) {
|
|
550
|
+
return { ruid: uid, euid: uid, suid: uid, rgid: gid, egid: gid, sgid: gid, groups: [...groups] };
|
|
551
|
+
}
|
|
552
|
+
function copyCred(cred) {
|
|
553
|
+
return { ...cred, groups: [...cred.groups] };
|
|
554
|
+
}
|
|
555
|
+
function id(value) {
|
|
556
|
+
if (value === void 0 || value === -1 || value === 4294967295) return void 0;
|
|
557
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_ID) {
|
|
558
|
+
throw fsError("EINVAL", `not a user or group id: ${String(value)}`);
|
|
559
|
+
}
|
|
560
|
+
return value;
|
|
561
|
+
}
|
|
562
|
+
function changeCred(cur, change) {
|
|
563
|
+
const privileged = cur.euid === 0;
|
|
564
|
+
const next2 = copyCred(cur);
|
|
565
|
+
for (const keys of [UIDS, GIDS]) {
|
|
566
|
+
const own = keys.map((key) => cur[key]);
|
|
567
|
+
for (const key of keys) {
|
|
568
|
+
const value = id(change[key]);
|
|
569
|
+
if (value === void 0) continue;
|
|
570
|
+
if (!privileged && !own.includes(value)) throw fsError("EPERM", `${key} ${value}`);
|
|
571
|
+
next2[key] = value;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (change.groups !== void 0) {
|
|
575
|
+
if (!Array.isArray(change.groups) || change.groups.length > MAX_GROUPS) {
|
|
576
|
+
throw fsError("EINVAL", "not a group list");
|
|
577
|
+
}
|
|
578
|
+
const groups = change.groups.map((g) => id(g) ?? Number.NaN);
|
|
579
|
+
if (groups.some(Number.isNaN)) throw fsError("EINVAL", "not a group list");
|
|
580
|
+
if (!privileged) throw fsError("EPERM", "setgroups");
|
|
581
|
+
next2.groups = groups;
|
|
582
|
+
}
|
|
583
|
+
return next2;
|
|
584
|
+
}
|
|
585
|
+
function maySignal(sender, target) {
|
|
586
|
+
if (sender.euid === 0) return true;
|
|
587
|
+
return [sender.ruid, sender.euid].some((uid) => uid === target.ruid || uid === target.suid);
|
|
588
|
+
}
|
|
589
|
+
function maySee(viewer, target) {
|
|
590
|
+
return viewer.euid === 0 || target.ruid === viewer.euid || target.euid === viewer.euid;
|
|
591
|
+
}
|
|
592
|
+
|
|
535
593
|
// src/kernel/pipe.ts
|
|
536
594
|
var PIPE_CAPACITY = 64 * 1024;
|
|
537
595
|
var PipeError = class extends Error {
|
|
@@ -839,9 +897,9 @@ var FdTable = class _FdTable {
|
|
|
839
897
|
closesOnExec(fd) {
|
|
840
898
|
return this.cloexec.has(fd);
|
|
841
899
|
}
|
|
842
|
-
setStatusFlags(fd,
|
|
900
|
+
setStatusFlags(fd, flags2) {
|
|
843
901
|
this.get(fd);
|
|
844
|
-
this.status.set(fd,
|
|
902
|
+
this.status.set(fd, flags2);
|
|
845
903
|
}
|
|
846
904
|
statusFlags(fd) {
|
|
847
905
|
return this.status.get(fd);
|
|
@@ -886,7 +944,7 @@ var FdTable = class _FdTable {
|
|
|
886
944
|
const child = new _FdTable();
|
|
887
945
|
for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
|
|
888
946
|
for (const fd of this.cloexec) child.cloexec.add(fd);
|
|
889
|
-
for (const [fd,
|
|
947
|
+
for (const [fd, flags2] of this.status) child.status.set(fd, flags2);
|
|
890
948
|
return child;
|
|
891
949
|
}
|
|
892
950
|
async closeAll() {
|
|
@@ -1353,9 +1411,9 @@ var KernelSocket = class _KernelSocket {
|
|
|
1353
1411
|
case SO_TYPE:
|
|
1354
1412
|
return SOCK_STREAM;
|
|
1355
1413
|
case SO_ERROR: {
|
|
1356
|
-
const
|
|
1414
|
+
const failed3 = this.failed;
|
|
1357
1415
|
this.failed = void 0;
|
|
1358
|
-
return
|
|
1416
|
+
return failed3 ? wasiErrno(failed3) : 0;
|
|
1359
1417
|
}
|
|
1360
1418
|
case SO_ACCEPTCONN:
|
|
1361
1419
|
return this.state === "listening" ? 1 : 0;
|
|
@@ -2047,64 +2105,6 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
2047
2105
|
};
|
|
2048
2106
|
}
|
|
2049
2107
|
|
|
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
2108
|
// src/users.ts
|
|
2109
2109
|
var PASSWD = "/etc/passwd";
|
|
2110
2110
|
var GROUP = "/etc/group";
|
|
@@ -2438,7 +2438,7 @@ var UserDb = class {
|
|
|
2438
2438
|
};
|
|
2439
2439
|
|
|
2440
2440
|
// src/client/protocol.ts
|
|
2441
|
-
var PROTOCOL = [1,
|
|
2441
|
+
var PROTOCOL = [1, 9];
|
|
2442
2442
|
var FS_METHODS = [
|
|
2443
2443
|
"readFile",
|
|
2444
2444
|
"writeFile",
|
|
@@ -2596,6 +2596,12 @@ async function runAs(l, self, requested) {
|
|
|
2596
2596
|
}
|
|
2597
2597
|
return target;
|
|
2598
2598
|
}
|
|
2599
|
+
function clientCred(self) {
|
|
2600
|
+
return self ? credOf(self) : copyCred(ROOT);
|
|
2601
|
+
}
|
|
2602
|
+
async function runTagged(l, self, requested, tag) {
|
|
2603
|
+
return { user: await runAs(l, self, requested), tag };
|
|
2604
|
+
}
|
|
2599
2605
|
function processes(l, self) {
|
|
2600
2606
|
const viewer = credOf(self);
|
|
2601
2607
|
return l.list(viewer).map(({ tid: _, cred = viewer, umask: _umask, ...entry }) => ({
|
|
@@ -2730,7 +2736,7 @@ function serveClient(port, host) {
|
|
|
2730
2736
|
async function terminal(req, l) {
|
|
2731
2737
|
const session = await l.openTerminal(req.argv, {
|
|
2732
2738
|
...req.options,
|
|
2733
|
-
|
|
2739
|
+
...await runTagged(l, self, req.options.user, host.tag),
|
|
2734
2740
|
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
2735
2741
|
});
|
|
2736
2742
|
if (state === "closed") {
|
|
@@ -2752,7 +2758,7 @@ function serveClient(port, host) {
|
|
|
2752
2758
|
let leader;
|
|
2753
2759
|
const result = await l.run(req.argv, {
|
|
2754
2760
|
...req.options,
|
|
2755
|
-
|
|
2761
|
+
...await runTagged(l, self, req.options.user, host.tag),
|
|
2756
2762
|
collect: false,
|
|
2757
2763
|
onStdout: out(1),
|
|
2758
2764
|
onStderr: out(2),
|
|
@@ -2793,9 +2799,9 @@ function serveClient(port, host) {
|
|
|
2793
2799
|
case "unwatch":
|
|
2794
2800
|
return watchOp(l, req, watches, send, guardFor(l, self));
|
|
2795
2801
|
case "mount":
|
|
2796
|
-
return { result: await l.
|
|
2802
|
+
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
2797
2803
|
case "umount":
|
|
2798
|
-
l.
|
|
2804
|
+
await l.umountAs(clientCred(self), req.target);
|
|
2799
2805
|
return { result: true };
|
|
2800
2806
|
case "mounts":
|
|
2801
2807
|
return { result: l.mounts.list() };
|
|
@@ -2998,15 +3004,15 @@ function translate(err, path, op = "lookup", mapped = true) {
|
|
|
2998
3004
|
if (typeof err?.code === "string") return err;
|
|
2999
3005
|
const raw = err?.name;
|
|
3000
3006
|
const name2 = typeof raw === "string" ? raw : "Error";
|
|
3001
|
-
const code = mapped && ERRNO_BY_DOM_ERROR[name2] || "EIO";
|
|
3007
|
+
const code = name2 === "QuotaExceededError" ? "ENOSPC" : mapped && ERRNO_BY_DOM_ERROR[name2] || "EIO";
|
|
3002
3008
|
const error = Object.assign(fsError(code, path), { domName: name2, op, cause: err });
|
|
3003
|
-
if (code !== "EIO") return error;
|
|
3009
|
+
if (code !== "EIO" && code !== "ENOSPC") return error;
|
|
3004
3010
|
const detail = err?.message;
|
|
3005
3011
|
error.message += ` (OPFS ${op}: ${name2}${typeof detail === "string" && detail ? `: ${detail}` : ""})`;
|
|
3006
3012
|
if (!reported.has(`${name2}/${op}`)) {
|
|
3007
3013
|
reported.add(`${name2}/${op}`);
|
|
3008
3014
|
console.warn(
|
|
3009
|
-
`slicc-kernel: ${error.message}, reported as
|
|
3015
|
+
`slicc-kernel: ${error.message}, reported as ${code}; logged once per error and operation`
|
|
3010
3016
|
);
|
|
3011
3017
|
}
|
|
3012
3018
|
return error;
|
|
@@ -3512,11 +3518,13 @@ var OpfsFs = class {
|
|
|
3512
3518
|
const [file, existed, target] = await this.fileFor(path);
|
|
3513
3519
|
if (!existed) await this.stampNew(target, owner, false);
|
|
3514
3520
|
if (!existed && content.length === 0) return;
|
|
3521
|
+
let writable2;
|
|
3515
3522
|
try {
|
|
3516
|
-
|
|
3523
|
+
writable2 = await file.createWritable();
|
|
3517
3524
|
await writable2.write(content);
|
|
3518
3525
|
await writable2.close();
|
|
3519
3526
|
} catch (err) {
|
|
3527
|
+
await writable2?.abort().catch(() => void 0);
|
|
3520
3528
|
throw translate(err, path, "commit", false);
|
|
3521
3529
|
}
|
|
3522
3530
|
}
|
|
@@ -4999,9 +5007,8 @@ async function scanFilesystems(fs, modules) {
|
|
|
4999
5007
|
}
|
|
5000
5008
|
var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
|
|
5001
5009
|
async function scanBinfmts(fs, modules, warn = () => {
|
|
5002
|
-
}) {
|
|
5010
|
+
}, owners = /* @__PURE__ */ new Map()) {
|
|
5003
5011
|
const found = /* @__PURE__ */ new Map();
|
|
5004
|
-
const owners = /* @__PURE__ */ new Map();
|
|
5005
5012
|
for (const pkg of await packagesIn(fs, modules)) {
|
|
5006
5013
|
let manifest;
|
|
5007
5014
|
try {
|
|
@@ -5026,7 +5033,7 @@ async function scanBinfmts(fs, modules, warn = () => {
|
|
|
5026
5033
|
}
|
|
5027
5034
|
return found;
|
|
5028
5035
|
}
|
|
5029
|
-
function
|
|
5036
|
+
function binfmtSuffix(binfmts, path) {
|
|
5030
5037
|
const name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
5031
5038
|
let best;
|
|
5032
5039
|
for (const suffix of binfmts.keys()) {
|
|
@@ -5034,6 +5041,10 @@ function binfmtOf(binfmts, path) {
|
|
|
5034
5041
|
if (best === void 0 || suffix.length > best.length) best = suffix;
|
|
5035
5042
|
}
|
|
5036
5043
|
}
|
|
5044
|
+
return best;
|
|
5045
|
+
}
|
|
5046
|
+
function binfmtOf(binfmts, path) {
|
|
5047
|
+
const best = binfmtSuffix(binfmts, path);
|
|
5037
5048
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
5038
5049
|
}
|
|
5039
5050
|
|
|
@@ -5388,8 +5399,9 @@ var ChildTable = class {
|
|
|
5388
5399
|
if (!this.forker) throw new SpawnError("ENOSYS");
|
|
5389
5400
|
return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
|
|
5390
5401
|
}
|
|
5391
|
-
async spawn(req, stdio, inherit2 = []) {
|
|
5392
|
-
|
|
5402
|
+
async spawn(req, stdio, inherit2 = [], grant) {
|
|
5403
|
+
const { spawner } = this;
|
|
5404
|
+
if (!spawner) throw new SpawnError("ENOSYS");
|
|
5393
5405
|
const fds = new FdTable();
|
|
5394
5406
|
const captured = /* @__PURE__ */ new Map();
|
|
5395
5407
|
try {
|
|
@@ -5407,7 +5419,7 @@ var ChildTable = class {
|
|
|
5407
5419
|
await fds.closeAll();
|
|
5408
5420
|
throw e;
|
|
5409
5421
|
}
|
|
5410
|
-
return this.track(req, fds, captured,
|
|
5422
|
+
return this.track(req, fds, captured, (r, f) => spawner(r, f, grant));
|
|
5411
5423
|
}
|
|
5412
5424
|
async track(req, fds, captured, start2) {
|
|
5413
5425
|
let handle;
|
|
@@ -5454,34 +5466,34 @@ var ChildTable = class {
|
|
|
5454
5466
|
}
|
|
5455
5467
|
return deviceFile("null");
|
|
5456
5468
|
}
|
|
5457
|
-
async wait(pid, nohang, signal,
|
|
5469
|
+
async wait(pid, nohang, signal, flags2 = {}) {
|
|
5458
5470
|
let interrupt;
|
|
5459
5471
|
try {
|
|
5460
5472
|
for (; ; ) {
|
|
5461
|
-
const candidates = this.candidates(pid,
|
|
5473
|
+
const candidates = this.candidates(pid, flags2);
|
|
5462
5474
|
if (candidates.length === 0) throw new KernelError("ECHILD");
|
|
5463
5475
|
const done4 = candidates.find(([, child]) => child.code !== void 0);
|
|
5464
5476
|
if (done4) return this.reap(done4[0], done4[1].code);
|
|
5465
|
-
const changed = this.stateReport(candidates,
|
|
5477
|
+
const changed = this.stateReport(candidates, flags2);
|
|
5466
5478
|
if (changed) return changed;
|
|
5467
5479
|
if (nohang) return [0, 0];
|
|
5468
5480
|
interrupt ??= interrupted(signal);
|
|
5469
|
-
await this.nextChange(candidates,
|
|
5481
|
+
await this.nextChange(candidates, flags2, interrupt.promise);
|
|
5470
5482
|
}
|
|
5471
5483
|
} finally {
|
|
5472
5484
|
interrupt?.done();
|
|
5473
5485
|
}
|
|
5474
5486
|
}
|
|
5475
|
-
candidates(pid,
|
|
5487
|
+
candidates(pid, flags2) {
|
|
5476
5488
|
const all = [...this.children];
|
|
5477
5489
|
if (pid > 0) return all.filter(([p]) => p === pid);
|
|
5478
|
-
const inGroup2 =
|
|
5490
|
+
const inGroup2 = flags2.inGroup;
|
|
5479
5491
|
return pid === -1 || !inGroup2 ? all : all.filter(([p]) => inGroup2(p));
|
|
5480
5492
|
}
|
|
5481
|
-
async nextChange(candidates,
|
|
5493
|
+
async nextChange(candidates, flags2, interrupt) {
|
|
5482
5494
|
let wake;
|
|
5483
5495
|
const stateChange = new Promise((resolve) => wake = resolve);
|
|
5484
|
-
const watching =
|
|
5496
|
+
const watching = flags2.untraced || flags2.continued;
|
|
5485
5497
|
if (watching && wake) this.stateChanged.push(wake);
|
|
5486
5498
|
try {
|
|
5487
5499
|
await Promise.race([
|
|
@@ -5493,14 +5505,14 @@ var ChildTable = class {
|
|
|
5493
5505
|
this.stateChanged = this.stateChanged.filter((w) => w !== wake);
|
|
5494
5506
|
}
|
|
5495
5507
|
}
|
|
5496
|
-
stateReport(candidates,
|
|
5508
|
+
stateReport(candidates, flags2) {
|
|
5497
5509
|
for (const [p, child] of candidates) {
|
|
5498
|
-
if (
|
|
5510
|
+
if (flags2.untraced && child.stopReport !== void 0) {
|
|
5499
5511
|
const sig = child.stopReport;
|
|
5500
5512
|
child.stopReport = void 0;
|
|
5501
5513
|
return [p, stoppedStatus(sig)];
|
|
5502
5514
|
}
|
|
5503
|
-
if (
|
|
5515
|
+
if (flags2.continued && child.continueReport) {
|
|
5504
5516
|
child.continueReport = false;
|
|
5505
5517
|
return [p, CONTINUED_STATUS];
|
|
5506
5518
|
}
|
|
@@ -8659,8 +8671,8 @@ function ipv6(host) {
|
|
|
8659
8671
|
const head = groups(halves[0]);
|
|
8660
8672
|
const back = halves.length === 2 ? groups(halves[1]) : [];
|
|
8661
8673
|
if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
|
|
8662
|
-
const
|
|
8663
|
-
const missing3 = 8 -
|
|
8674
|
+
const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
|
|
8675
|
+
const missing3 = 8 - words2.length - tail.length;
|
|
8664
8676
|
if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
|
|
8665
8677
|
const all = [
|
|
8666
8678
|
...head.map((g) => Number.parseInt(g, 16)),
|
|
@@ -10809,8 +10821,8 @@ function errCode(err) {
|
|
|
10809
10821
|
async function targetDir(fs, path) {
|
|
10810
10822
|
return parentOf(await realTarget(fs, path).catch(() => path));
|
|
10811
10823
|
}
|
|
10812
|
-
async function refuseReadonly(fs, path,
|
|
10813
|
-
const writes = (
|
|
10824
|
+
async function refuseReadonly(fs, path, flags2, opts = {}) {
|
|
10825
|
+
const writes = (flags2 & O_ACCMODE) !== 0 || opts.truncate === true;
|
|
10814
10826
|
if (!writes && !opts.create || !fs.stat) return;
|
|
10815
10827
|
const stat = (p) => fs.stat?.(p).catch((err) => {
|
|
10816
10828
|
const code = err?.code;
|
|
@@ -11025,6 +11037,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11025
11037
|
"proc-spawn",
|
|
11026
11038
|
"proc-wait",
|
|
11027
11039
|
"proc-captured",
|
|
11040
|
+
"proc-sudo",
|
|
11028
11041
|
"proc-run",
|
|
11029
11042
|
"proc-exit",
|
|
11030
11043
|
"proc-fork",
|
|
@@ -11057,6 +11070,12 @@ var MAX_READ = 1024 * 1024;
|
|
|
11057
11070
|
var WRITE_ACCESS = 3;
|
|
11058
11071
|
var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
|
|
11059
11072
|
var DEFAULT_UMASK = 18;
|
|
11073
|
+
function words(value) {
|
|
11074
|
+
if (!Array.isArray(value) || !value.every((w) => typeof w === "string")) {
|
|
11075
|
+
throw new KernelError("EINVAL");
|
|
11076
|
+
}
|
|
11077
|
+
return [...value];
|
|
11078
|
+
}
|
|
11060
11079
|
var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
|
|
11061
11080
|
var HOSTS_ONLY = new Resolver({ routes: new Routes() });
|
|
11062
11081
|
var WasmProcess = class {
|
|
@@ -11249,17 +11268,17 @@ var WasmProcess = class {
|
|
|
11249
11268
|
if (restart && this.options.hasPending?.()) throw new KernelError("EINTR");
|
|
11250
11269
|
}
|
|
11251
11270
|
async procWait(req, cancelled) {
|
|
11252
|
-
const
|
|
11271
|
+
const flags2 = {
|
|
11253
11272
|
untraced: req.untraced,
|
|
11254
11273
|
continued: req.continued,
|
|
11255
11274
|
inGroup: this.waitGroup(req.pid)
|
|
11256
11275
|
};
|
|
11257
11276
|
if (this.owed && !req.nohang) {
|
|
11258
|
-
const now = await this.children.wait(req.pid, true, this.interrupt.signal,
|
|
11277
|
+
const now = await this.children.wait(req.pid, true, this.interrupt.signal, flags2);
|
|
11259
11278
|
if (now[0] !== 0) return now;
|
|
11260
11279
|
}
|
|
11261
11280
|
const signal = req.nohang ? this.interrupt.signal : this.blockingSignal(cancelled);
|
|
11262
|
-
const waited = await this.children.wait(req.pid, req.nohang, signal,
|
|
11281
|
+
const waited = await this.children.wait(req.pid, req.nohang, signal, flags2);
|
|
11263
11282
|
return waited;
|
|
11264
11283
|
}
|
|
11265
11284
|
blockingSignal(cancelled, owing = true) {
|
|
@@ -11591,13 +11610,13 @@ var WasmProcess = class {
|
|
|
11591
11610
|
}
|
|
11592
11611
|
fdInfo(fd) {
|
|
11593
11612
|
const file = this.fds.get(fd).file;
|
|
11594
|
-
const
|
|
11613
|
+
const flags2 = this.fds.statusFlags(fd);
|
|
11595
11614
|
const terminal = (file.tty ?? file.pty?.slave)?.name;
|
|
11596
11615
|
return {
|
|
11597
11616
|
tty: file.tty !== void 0,
|
|
11598
11617
|
kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
|
|
11599
11618
|
...file.heldMeta ? { meta: file.heldMeta } : {},
|
|
11600
|
-
...
|
|
11619
|
+
...flags2 !== void 0 ? { flags: flags2 } : {},
|
|
11601
11620
|
...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
11602
11621
|
...file.read && !file.write ? { access: "read" } : {},
|
|
11603
11622
|
...file.write && !file.read ? { access: "write" } : {},
|
|
@@ -11785,7 +11804,27 @@ var WasmProcess = class {
|
|
|
11785
11804
|
return { ok: true, kind: "void" };
|
|
11786
11805
|
case "proc-captured":
|
|
11787
11806
|
return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
|
|
11788
|
-
|
|
11807
|
+
case "proc-sudo":
|
|
11808
|
+
return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
|
|
11809
|
+
}
|
|
11810
|
+
}
|
|
11811
|
+
async sudo(req, cancelled) {
|
|
11812
|
+
const { sudo } = this.options;
|
|
11813
|
+
if (!sudo) throw new KernelError("ENOSYS");
|
|
11814
|
+
const call = {
|
|
11815
|
+
op: "proc-sudo",
|
|
11816
|
+
args: words(req.args),
|
|
11817
|
+
cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
|
|
11818
|
+
...typeof req.term === "string" ? { term: req.term } : {}
|
|
11819
|
+
};
|
|
11820
|
+
const answer = await sudo(call, this.blockingSignal(cancelled));
|
|
11821
|
+
if (!("grant" in answer)) return answer;
|
|
11822
|
+
if (this.dying) throw new KernelError("EINTR");
|
|
11823
|
+
const { file, argv, env, cwd, cred, caller } = answer.grant;
|
|
11824
|
+
const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
|
|
11825
|
+
return {
|
|
11826
|
+
pid: await this.children.spawn({ file, argv, env, cwd }, stdio, [], { cred, caller })
|
|
11827
|
+
};
|
|
11789
11828
|
}
|
|
11790
11829
|
async mountSyscall(req, cancelled) {
|
|
11791
11830
|
const { mount, umount } = this.options;
|
|
@@ -11793,9 +11832,9 @@ var WasmProcess = class {
|
|
|
11793
11832
|
try {
|
|
11794
11833
|
if (req.op === "umount") await umount(req.target, req.flags);
|
|
11795
11834
|
else {
|
|
11796
|
-
const { source, target, type, flags, data } = req;
|
|
11835
|
+
const { source, target, type, flags: flags2, data } = req;
|
|
11797
11836
|
const signal = this.blockingSignal(cancelled);
|
|
11798
|
-
await Promise.race([mount({ source, target, type, flags, data }, signal), aborted(signal)]);
|
|
11837
|
+
await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted(signal)]);
|
|
11799
11838
|
}
|
|
11800
11839
|
} catch (err) {
|
|
11801
11840
|
const code = err?.code;
|
|
@@ -11938,14 +11977,14 @@ function descId(file) {
|
|
|
11938
11977
|
function inheritedFds(fds) {
|
|
11939
11978
|
return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
|
|
11940
11979
|
const open = fds.get(fd);
|
|
11941
|
-
const
|
|
11980
|
+
const flags2 = fds.statusFlags(fd);
|
|
11942
11981
|
const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
|
|
11943
11982
|
const meta = open.file.heldMeta;
|
|
11944
11983
|
return {
|
|
11945
11984
|
fd,
|
|
11946
11985
|
kind,
|
|
11947
11986
|
...kind === "device" && meta && "device" in meta ? { device: meta } : {},
|
|
11948
|
-
...
|
|
11987
|
+
...flags2 !== void 0 ? { flags: flags2 } : {},
|
|
11949
11988
|
...fds.closesOnExec(fd) ? { cloexec: true } : {},
|
|
11950
11989
|
...kind === "stream" ? { desc: descId(open) } : {}
|
|
11951
11990
|
};
|
|
@@ -12098,6 +12137,7 @@ function processOptions(opts, header4, hooks, created3) {
|
|
|
12098
12137
|
kill: opts.kill,
|
|
12099
12138
|
...opts.writesBack ? { writesBack: opts.writesBack } : {},
|
|
12100
12139
|
processes: opts.processes,
|
|
12140
|
+
...opts.sudo ? { sudo: opts.sudo } : {},
|
|
12101
12141
|
openFiles: opts.openFiles,
|
|
12102
12142
|
...opts.nodes ? { nodes: opts.nodes } : {},
|
|
12103
12143
|
mounts: opts.mounts,
|
|
@@ -12856,12 +12896,12 @@ function kernelFsHandlers(fs) {
|
|
|
12856
12896
|
const attr2 = attrOf(st);
|
|
12857
12897
|
return [{ name: name2, kind: attr2.kind, attr: attr2 }];
|
|
12858
12898
|
}),
|
|
12859
|
-
async open(path,
|
|
12899
|
+
async open(path, flags2) {
|
|
12860
12900
|
const st = await existing(path);
|
|
12861
|
-
if (st &&
|
|
12862
|
-
if (st?.isDirectory &&
|
|
12863
|
-
if (!st && !
|
|
12864
|
-
if (!st ||
|
|
12901
|
+
if (st && flags2.exclusive) throw fsError2("EEXIST", path);
|
|
12902
|
+
if (st?.isDirectory && flags2.write) throw fsError2("EISDIR", path);
|
|
12903
|
+
if (!st && !flags2.create) throw fsError2("ENOENT", path);
|
|
12904
|
+
if (!st || flags2.truncate) await fs.writeFile(path, new Uint8Array(0));
|
|
12865
12905
|
handles.set(++nextFh, { path });
|
|
12866
12906
|
return nextFh;
|
|
12867
12907
|
},
|
|
@@ -12991,24 +13031,24 @@ function absolute(target) {
|
|
|
12991
13031
|
return normalizePath(target);
|
|
12992
13032
|
}
|
|
12993
13033
|
function mountCall(call) {
|
|
12994
|
-
let
|
|
12995
|
-
if ((
|
|
12996
|
-
if (
|
|
12997
|
-
throw errnoError("EINVAL", `unsupported mount flags ${
|
|
13034
|
+
let flags2 = call.flags >>> 0;
|
|
13035
|
+
if ((flags2 & MS_MGC_MSK) >>> 0 === MS_MGC_VAL) flags2 &= ~MS_MGC_MSK;
|
|
13036
|
+
if (flags2 & ~(MS_RDONLY | IGNORED_FLAGS)) {
|
|
13037
|
+
throw errnoError("EINVAL", `unsupported mount flags ${flags2.toString(16)}`);
|
|
12998
13038
|
}
|
|
12999
13039
|
if (!call.type) throw errnoError("EINVAL", "no file system type");
|
|
13000
13040
|
const target = absolute(call.target);
|
|
13001
13041
|
refusedTarget(target);
|
|
13002
13042
|
const options = parseMountOptions(call.data);
|
|
13003
|
-
if (
|
|
13043
|
+
if (flags2 & MS_RDONLY) options.ro = "";
|
|
13004
13044
|
return { type: call.type, source: call.source || "none", target, options };
|
|
13005
13045
|
}
|
|
13006
|
-
function umountCall(target,
|
|
13046
|
+
function umountCall(target, flags2) {
|
|
13007
13047
|
const known = MNT_FORCE | MNT_DETACH | UMOUNT_NOFOLLOW;
|
|
13008
|
-
if (
|
|
13048
|
+
if (flags2 >>> 0 & ~known) throw errnoError("EINVAL", `unsupported umount flags ${flags2}`);
|
|
13009
13049
|
const at = absolute(target);
|
|
13010
13050
|
refusedTarget(at);
|
|
13011
|
-
return { target: at, detach: (
|
|
13051
|
+
return { target: at, detach: (flags2 & (MNT_FORCE | MNT_DETACH)) !== 0 };
|
|
13012
13052
|
}
|
|
13013
13053
|
|
|
13014
13054
|
// src/mount/fstab.ts
|
|
@@ -13066,22 +13106,22 @@ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discar
|
|
|
13066
13106
|
);
|
|
13067
13107
|
}
|
|
13068
13108
|
async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
|
|
13069
|
-
let
|
|
13109
|
+
let failed3 = invalid;
|
|
13070
13110
|
for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
|
|
13071
13111
|
try {
|
|
13072
13112
|
const entry = await mount(spec);
|
|
13073
13113
|
if (!signal.aborted) return { line, target: spec.target, entry };
|
|
13074
13114
|
discard(entry);
|
|
13075
|
-
|
|
13115
|
+
failed3 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
|
|
13076
13116
|
break;
|
|
13077
13117
|
} catch (err) {
|
|
13078
|
-
|
|
13118
|
+
failed3 = err;
|
|
13079
13119
|
if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
|
|
13080
13120
|
await wait(retries[attempt], signal);
|
|
13081
13121
|
}
|
|
13082
13122
|
}
|
|
13083
|
-
const code = codeOf3(
|
|
13084
|
-
return { line, target: spec.target, code, error: String(
|
|
13123
|
+
const code = codeOf3(failed3);
|
|
13124
|
+
return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
|
|
13085
13125
|
}
|
|
13086
13126
|
|
|
13087
13127
|
// src/mount/hostfs.ts
|
|
@@ -13258,13 +13298,13 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13258
13298
|
}, againMs);
|
|
13259
13299
|
timer.unref?.();
|
|
13260
13300
|
});
|
|
13261
|
-
async function writable2(client, path,
|
|
13262
|
-
const { ifMatch: _, ...asked } =
|
|
13301
|
+
async function writable2(client, path, flags2) {
|
|
13302
|
+
const { ifMatch: _, ...asked } = flags2;
|
|
13263
13303
|
const opening2 = () => client.call("open", { path: rel(path), ...asked });
|
|
13264
13304
|
try {
|
|
13265
13305
|
return { opened: await opening2(), restore: void 0 };
|
|
13266
13306
|
} catch (err) {
|
|
13267
|
-
if (!
|
|
13307
|
+
if (!flags2.write || codeOf4(err) !== "EACCES") throw err;
|
|
13268
13308
|
const was = await client.call("stat", { path: rel(path) });
|
|
13269
13309
|
const mode = was.mode ?? 0;
|
|
13270
13310
|
if (mode & 128) throw err;
|
|
@@ -13286,11 +13326,11 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13286
13326
|
);
|
|
13287
13327
|
return entries.map(({ name: name2, attr: attr2 }) => ({ name: name2, kind: attr2.kind, attr: attr2 }));
|
|
13288
13328
|
},
|
|
13289
|
-
async open(path,
|
|
13329
|
+
async open(path, flags2) {
|
|
13290
13330
|
const client = current();
|
|
13291
13331
|
const token = client.token;
|
|
13292
|
-
const { opened: opened2, restore } = await writable2(client, path,
|
|
13293
|
-
if (
|
|
13332
|
+
const { opened: opened2, restore } = await writable2(client, path, flags2);
|
|
13333
|
+
if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
|
|
13294
13334
|
await client.call("release", { fh: opened2.fh }, true);
|
|
13295
13335
|
throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
|
|
13296
13336
|
}
|
|
@@ -13298,7 +13338,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
13298
13338
|
client,
|
|
13299
13339
|
token,
|
|
13300
13340
|
fh: opened2.fh,
|
|
13301
|
-
etag:
|
|
13341
|
+
etag: flags2.write ? void 0 : opened2.attr?.etag,
|
|
13302
13342
|
restore
|
|
13303
13343
|
});
|
|
13304
13344
|
return nextFh;
|
|
@@ -13978,14 +14018,14 @@ function tmpfs() {
|
|
|
13978
14018
|
if (!found) throw fsError2("EBADF", `handle ${fh}`);
|
|
13979
14019
|
return found;
|
|
13980
14020
|
};
|
|
13981
|
-
function opened2(path,
|
|
14021
|
+
function opened2(path, flags2) {
|
|
13982
14022
|
const { dir, name: name2 } = parent(path);
|
|
13983
14023
|
const existing = dir.children.get(name2);
|
|
13984
|
-
if (existing &&
|
|
13985
|
-
if (existing?.kind === "directory" &&
|
|
14024
|
+
if (existing && flags2.exclusive) throw fsError2("EEXIST", path);
|
|
14025
|
+
if (existing?.kind === "directory" && flags2.write) throw fsError2("EISDIR", path);
|
|
13986
14026
|
if (existing) return existing;
|
|
13987
|
-
if (!
|
|
13988
|
-
const node = make("file", 33188, dir,
|
|
14027
|
+
if (!flags2.create) throw fsError2("ENOENT", path);
|
|
14028
|
+
const node = make("file", 33188, dir, flags2.owner);
|
|
13989
14029
|
place(dir, name2, node);
|
|
13990
14030
|
return node;
|
|
13991
14031
|
}
|
|
@@ -14005,13 +14045,13 @@ function tmpfs() {
|
|
|
14005
14045
|
([name2, node]) => ({ name: name2, kind: node.kind, attr: attr(node) })
|
|
14006
14046
|
);
|
|
14007
14047
|
},
|
|
14008
|
-
async open(path,
|
|
14009
|
-
const node = opened2(path,
|
|
14010
|
-
if (
|
|
14048
|
+
async open(path, flags2) {
|
|
14049
|
+
const node = opened2(path, flags2);
|
|
14050
|
+
if (flags2.truncate && node.kind === "file") {
|
|
14011
14051
|
node.data.truncate(0);
|
|
14012
14052
|
node.mtime = Date.now();
|
|
14013
14053
|
}
|
|
14014
|
-
handles.set(++nextFh, { node, write:
|
|
14054
|
+
handles.set(++nextFh, { node, write: flags2.write });
|
|
14015
14055
|
return nextFh;
|
|
14016
14056
|
},
|
|
14017
14057
|
async read(fh, offset, size) {
|
|
@@ -14127,10 +14167,10 @@ function importedMemory(bytes) {
|
|
|
14127
14167
|
return s;
|
|
14128
14168
|
};
|
|
14129
14169
|
const limits = () => {
|
|
14130
|
-
const
|
|
14170
|
+
const flags2 = bytes[at++];
|
|
14131
14171
|
const initial = u32();
|
|
14132
|
-
const maximum =
|
|
14133
|
-
return { initial, maximum, shared: (
|
|
14172
|
+
const maximum = flags2 & 1 ? u32() : void 0;
|
|
14173
|
+
return { initial, maximum, shared: (flags2 & 2) !== 0 };
|
|
14134
14174
|
};
|
|
14135
14175
|
while (at < bytes.length) {
|
|
14136
14176
|
const id2 = bytes[at++];
|
|
@@ -14209,9 +14249,9 @@ var Reader = class {
|
|
|
14209
14249
|
return code;
|
|
14210
14250
|
}
|
|
14211
14251
|
limits() {
|
|
14212
|
-
const
|
|
14252
|
+
const flags2 = this.byte();
|
|
14213
14253
|
this.u32();
|
|
14214
|
-
if (
|
|
14254
|
+
if (flags2 & 1) this.u32();
|
|
14215
14255
|
}
|
|
14216
14256
|
};
|
|
14217
14257
|
function readTypes(r) {
|
|
@@ -14266,6 +14306,412 @@ function foreignImports(bytes) {
|
|
|
14266
14306
|
return {};
|
|
14267
14307
|
}
|
|
14268
14308
|
|
|
14309
|
+
// src/sudo.ts
|
|
14310
|
+
var SUDOERS = "/etc/sudoers";
|
|
14311
|
+
var SUDOERS_D = "/etc/sudoers.d";
|
|
14312
|
+
var SECURE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
14313
|
+
var DEFAULT_SUDOERS = "%sudo Cmnd *\n";
|
|
14314
|
+
var SUDO_TIMEOUT_MS = 3e5;
|
|
14315
|
+
var NAME3 = /^[a-z_][a-z0-9_.-]*$/;
|
|
14316
|
+
var RULE = /^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?Cmnd(?:\s+(.*))?$/;
|
|
14317
|
+
var TERM = /^[A-Za-z0-9._+-]+$/;
|
|
14318
|
+
var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
14319
|
+
var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
|
|
14320
|
+
function failed2(errno, error) {
|
|
14321
|
+
return { error, errno };
|
|
14322
|
+
}
|
|
14323
|
+
function unescaped(double) {
|
|
14324
|
+
return double.replace(/\\(["\\$`])/g, "$1");
|
|
14325
|
+
}
|
|
14326
|
+
function splitWords(text) {
|
|
14327
|
+
const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
|
|
14328
|
+
const words2 = [];
|
|
14329
|
+
let word;
|
|
14330
|
+
while (piece.lastIndex < text.length) {
|
|
14331
|
+
const m = piece.exec(text);
|
|
14332
|
+
if (!m) return void 0;
|
|
14333
|
+
const [, single, double, escaped, plain, space] = m;
|
|
14334
|
+
if (space !== void 0) {
|
|
14335
|
+
if (word !== void 0) words2.push(word);
|
|
14336
|
+
word = void 0;
|
|
14337
|
+
continue;
|
|
14338
|
+
}
|
|
14339
|
+
word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain));
|
|
14340
|
+
}
|
|
14341
|
+
if (word !== void 0) words2.push(word);
|
|
14342
|
+
return words2;
|
|
14343
|
+
}
|
|
14344
|
+
function quoteWord(word) {
|
|
14345
|
+
return PLAIN.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
|
|
14346
|
+
}
|
|
14347
|
+
function validWho(who) {
|
|
14348
|
+
if (who === "ALL") return true;
|
|
14349
|
+
return NAME3.test(who.startsWith("%") ? who.slice(1) : who);
|
|
14350
|
+
}
|
|
14351
|
+
function parseRule(line) {
|
|
14352
|
+
const text = line.trim();
|
|
14353
|
+
if (text === "" || text.startsWith("#")) return null;
|
|
14354
|
+
const m = RULE.exec(text);
|
|
14355
|
+
if (!m) return void 0;
|
|
14356
|
+
const [, nopasswd, who = "", target = "root", rest2 = ""] = m;
|
|
14357
|
+
if (!validWho(who) || target !== "ALL" && !NAME3.test(target)) return void 0;
|
|
14358
|
+
if (rest2.trim() === "*") return { nopasswd: !!nopasswd, who, target, cmnd: "*" };
|
|
14359
|
+
const cmnd = splitWords(rest2);
|
|
14360
|
+
if (!cmnd?.[0]?.startsWith("/")) return void 0;
|
|
14361
|
+
return { nopasswd: !!nopasswd, who, target, cmnd };
|
|
14362
|
+
}
|
|
14363
|
+
function flags(parsed2, arg, next2) {
|
|
14364
|
+
for (let j = 1; j < arg.length; j++) {
|
|
14365
|
+
const flag = arg[j];
|
|
14366
|
+
if (flag === "n") parsed2.nonInteractive = true;
|
|
14367
|
+
else if (flag === "l") parsed2.list = true;
|
|
14368
|
+
else if (flag !== "u") return `sudo: invalid option -- '${flag}'
|
|
14369
|
+
${USAGE}`;
|
|
14370
|
+
else {
|
|
14371
|
+
const user = arg.slice(j + 1) || next2;
|
|
14372
|
+
if (!user) return `sudo: option requires an argument -- 'u'
|
|
14373
|
+
${USAGE}`;
|
|
14374
|
+
parsed2.user = user;
|
|
14375
|
+
return arg.slice(j + 1) ? 0 : 1;
|
|
14376
|
+
}
|
|
14377
|
+
}
|
|
14378
|
+
return 0;
|
|
14379
|
+
}
|
|
14380
|
+
function parseArgs(args) {
|
|
14381
|
+
const parsed2 = { list: false, nonInteractive: false, user: "root", command: [] };
|
|
14382
|
+
let i = 0;
|
|
14383
|
+
for (; i < args.length; i++) {
|
|
14384
|
+
const arg = args[i];
|
|
14385
|
+
if (arg === "--") {
|
|
14386
|
+
i++;
|
|
14387
|
+
break;
|
|
14388
|
+
}
|
|
14389
|
+
if (!arg.startsWith("-") || arg === "-") break;
|
|
14390
|
+
const used = flags(parsed2, arg, args[i + 1]);
|
|
14391
|
+
if (typeof used === "string") return used;
|
|
14392
|
+
i += used;
|
|
14393
|
+
}
|
|
14394
|
+
parsed2.command = args.slice(i);
|
|
14395
|
+
if (parsed2.list ? parsed2.command.length > 0 : parsed2.command.length === 0) return USAGE;
|
|
14396
|
+
return parsed2;
|
|
14397
|
+
}
|
|
14398
|
+
function described(rule) {
|
|
14399
|
+
const cmnd = rule.cmnd === "*" ? "ALL" : rule.cmnd.map(quoteWord).join(" ");
|
|
14400
|
+
return ` (${rule.target}) ${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}`;
|
|
14401
|
+
}
|
|
14402
|
+
var Sudo = class {
|
|
14403
|
+
o;
|
|
14404
|
+
warned = /* @__PURE__ */ new Set();
|
|
14405
|
+
pending = /* @__PURE__ */ new Set();
|
|
14406
|
+
next = 0;
|
|
14407
|
+
constructor(options) {
|
|
14408
|
+
this.o = options;
|
|
14409
|
+
}
|
|
14410
|
+
warn(message3) {
|
|
14411
|
+
if (this.warned.has(message3)) return;
|
|
14412
|
+
this.warned.add(message3);
|
|
14413
|
+
(this.o.warn ?? console.warn)(message3);
|
|
14414
|
+
}
|
|
14415
|
+
async provision() {
|
|
14416
|
+
const { fs } = this.o;
|
|
14417
|
+
if (!await fs.exists(SUDOERS)) {
|
|
14418
|
+
await fs.writeFile(SUDOERS, DEFAULT_SUDOERS);
|
|
14419
|
+
await fs.chown?.(SUDOERS, { uid: 0, gid: 0, mode: 288, follow: false });
|
|
14420
|
+
}
|
|
14421
|
+
if (!await fs.exists(SUDOERS_D)) {
|
|
14422
|
+
await fs.mkdir(SUDOERS_D, { recursive: true });
|
|
14423
|
+
await fs.chown?.(SUDOERS_D, { uid: 0, gid: 0, mode: 488, follow: false });
|
|
14424
|
+
}
|
|
14425
|
+
}
|
|
14426
|
+
async trusted(path) {
|
|
14427
|
+
const parts2 = normalizePath(path).split("/").filter(Boolean);
|
|
14428
|
+
for (let n = 0; n <= parts2.length; n++) {
|
|
14429
|
+
const at = `/${parts2.slice(0, n).join("/")}`;
|
|
14430
|
+
const st = await this.o.fs.lstat(at).catch(() => void 0);
|
|
14431
|
+
if (!st || st.isSymbolicLink || st.uid !== 0 || (st.mode & 18) !== 0) return false;
|
|
14432
|
+
}
|
|
14433
|
+
return true;
|
|
14434
|
+
}
|
|
14435
|
+
async policyFile(path) {
|
|
14436
|
+
if (!await this.trusted(path)) {
|
|
14437
|
+
this.warn(`sudo: ${path} skipped: it must be root's, no symlink, and writable by root only`);
|
|
14438
|
+
return [];
|
|
14439
|
+
}
|
|
14440
|
+
const text = await this.o.fs.readFile(path).catch(() => "");
|
|
14441
|
+
const rules = [];
|
|
14442
|
+
for (const [n, line] of text.split("\n").entries()) {
|
|
14443
|
+
const rule = parseRule(line);
|
|
14444
|
+
if (rule) rules.push(rule);
|
|
14445
|
+
else if (rule === void 0) this.warn(`sudo: ${path}:${n + 1}: bad line skipped: ${line}`);
|
|
14446
|
+
}
|
|
14447
|
+
return rules;
|
|
14448
|
+
}
|
|
14449
|
+
async policy() {
|
|
14450
|
+
const rules = await this.policyFile(SUDOERS);
|
|
14451
|
+
const names = await this.o.fs.readdir(SUDOERS_D).catch(() => []);
|
|
14452
|
+
if (names.length > 0 && !await this.trusted(SUDOERS_D)) {
|
|
14453
|
+
this.warn(`sudo: ${SUDOERS_D} skipped: it must be root's and writable by root only`);
|
|
14454
|
+
return rules;
|
|
14455
|
+
}
|
|
14456
|
+
for (const name2 of [...names].sort()) {
|
|
14457
|
+
if (name2.startsWith(".") || name2.endsWith("~")) continue;
|
|
14458
|
+
rules.push(...await this.policyFile(`${SUDOERS_D}/${name2}`));
|
|
14459
|
+
}
|
|
14460
|
+
return rules;
|
|
14461
|
+
}
|
|
14462
|
+
async groupIds() {
|
|
14463
|
+
return new Map((await this.o.users.groups()).map((g) => [g.name, g.gid]));
|
|
14464
|
+
}
|
|
14465
|
+
async applies(caller, cred) {
|
|
14466
|
+
const gids = await this.groupIds();
|
|
14467
|
+
const mine = /* @__PURE__ */ new Set([cred.rgid, cred.egid, ...cred.groups]);
|
|
14468
|
+
return (rule) => {
|
|
14469
|
+
if (rule.who === "ALL" || rule.who === caller.name) return true;
|
|
14470
|
+
const gid = rule.who.startsWith("%") ? gids.get(rule.who.slice(1)) : void 0;
|
|
14471
|
+
return gid !== void 0 && mine.has(gid);
|
|
14472
|
+
};
|
|
14473
|
+
}
|
|
14474
|
+
async lookup(user) {
|
|
14475
|
+
const key = /^#\d+$/.test(user) ? Number(user.slice(1)) : user;
|
|
14476
|
+
return this.o.users.lookup(key);
|
|
14477
|
+
}
|
|
14478
|
+
async canonical(cwd) {
|
|
14479
|
+
const { fs } = this.o;
|
|
14480
|
+
const path = normalizePath(cwd);
|
|
14481
|
+
return fs.realpath ? fs.realpath(path, true).catch(() => void 0) : path;
|
|
14482
|
+
}
|
|
14483
|
+
async reachable(cred, cwd) {
|
|
14484
|
+
const walked = await access(this.o.fs, cred, cwd, X_OK, { directory: true });
|
|
14485
|
+
if (walked.denied !== void 0 || walked.missing) return false;
|
|
14486
|
+
return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
|
|
14487
|
+
}
|
|
14488
|
+
async untrusted(path, cwd) {
|
|
14489
|
+
const files = await this.o.backing?.(path, cwd) ?? [];
|
|
14490
|
+
for (const file of [path, ...files]) if (!await this.trusted(file)) return file;
|
|
14491
|
+
return void 0;
|
|
14492
|
+
}
|
|
14493
|
+
async locate(word, cwd) {
|
|
14494
|
+
const { fs } = this.o;
|
|
14495
|
+
const candidates = word.includes("/") ? [fs.resolvePath(cwd, word)] : SECURE_PATH.split(":").map((dir) => `${dir}/${word}`);
|
|
14496
|
+
for (const path of candidates) {
|
|
14497
|
+
const st = await fs.stat(path).catch(() => void 0);
|
|
14498
|
+
if (!st?.isFile || (st.mode & 73) === 0) continue;
|
|
14499
|
+
return fs.realpath ? fs.realpath(path, true).catch(() => path) : path;
|
|
14500
|
+
}
|
|
14501
|
+
return void 0;
|
|
14502
|
+
}
|
|
14503
|
+
async request(caller, call, signal) {
|
|
14504
|
+
const parsed2 = parseArgs(call.args);
|
|
14505
|
+
if (typeof parsed2 === "string") return failed2("EINVAL", parsed2);
|
|
14506
|
+
if (this.o.mode !== "enforce") {
|
|
14507
|
+
const mode = this.o.mode;
|
|
14508
|
+
if (parsed2.list) return { list: `sudo: users are '${mode}': commands run as the caller
|
|
14509
|
+
` };
|
|
14510
|
+
return { run: parsed2.command };
|
|
14511
|
+
}
|
|
14512
|
+
const { cred } = caller;
|
|
14513
|
+
const self = await this.o.users.lookup(cred.ruid);
|
|
14514
|
+
if (!self) return failed2("EPERM", `sudo: uid ${cred.ruid} is not in the passwd database`);
|
|
14515
|
+
const target = await this.lookup(parsed2.user);
|
|
14516
|
+
if (!target) return failed2("ENOENT", `sudo: unknown user ${parsed2.user}`);
|
|
14517
|
+
const root = cred.euid === 0;
|
|
14518
|
+
const rules = root ? [] : await this.policy();
|
|
14519
|
+
const applies = await this.applies(self, cred);
|
|
14520
|
+
if (parsed2.list) return this.listed(self, root, rules.filter(applies));
|
|
14521
|
+
const [word = "", ...args] = parsed2.command;
|
|
14522
|
+
const cwd = await this.canonical(call.cwd);
|
|
14523
|
+
if (cwd === void 0 || !await this.reachable(cred, cwd)) {
|
|
14524
|
+
return failed2(
|
|
14525
|
+
"EACCES",
|
|
14526
|
+
`sudo: cannot run in ${call.cwd}: it isn't a directory you can enter`
|
|
14527
|
+
);
|
|
14528
|
+
}
|
|
14529
|
+
const path = await this.locate(word, cwd);
|
|
14530
|
+
if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
|
|
14531
|
+
const untrusted = await this.untrusted(path, cwd);
|
|
14532
|
+
if (untrusted) {
|
|
14533
|
+
return failed2("EPERM", `sudo: ${untrusted} must be root's, and writable by root only`);
|
|
14534
|
+
}
|
|
14535
|
+
const argv = [path, ...args];
|
|
14536
|
+
const matching = rules.filter(
|
|
14537
|
+
(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]))
|
|
14538
|
+
);
|
|
14539
|
+
const grant = () => this.grant(self, target, cred, parsed2.command, path, cwd, call.term);
|
|
14540
|
+
if (root || matching.some((rule) => rule.nopasswd)) return grant();
|
|
14541
|
+
const quoted = argv.map(quoteWord).join(" ");
|
|
14542
|
+
if (matching.length === 0) {
|
|
14543
|
+
return failed2(
|
|
14544
|
+
"EPERM",
|
|
14545
|
+
`sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
|
|
14546
|
+
);
|
|
14547
|
+
}
|
|
14548
|
+
if (parsed2.nonInteractive) return failed2("EPERM", "sudo: a password is required");
|
|
14549
|
+
if (this.pending.has(cred.ruid)) return failed2("EBUSY", "sudo: another request is pending");
|
|
14550
|
+
this.pending.add(cred.ruid);
|
|
14551
|
+
try {
|
|
14552
|
+
const answer = await this.ask(caller, self, target, parsed2.command, path, cwd, signal);
|
|
14553
|
+
if (typeof answer !== "string") return answer;
|
|
14554
|
+
if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
|
|
14555
|
+
return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
|
|
14556
|
+
}
|
|
14557
|
+
const changed = await this.untrusted(path, cwd);
|
|
14558
|
+
if (changed) {
|
|
14559
|
+
return failed2("EPERM", `sudo: ${changed} must be root's, and writable by root only`);
|
|
14560
|
+
}
|
|
14561
|
+
if (answer === "always" && !await this.remember(self, target, argv)) {
|
|
14562
|
+
return failed2("EIO", "sudo: the rule could not be recorded");
|
|
14563
|
+
}
|
|
14564
|
+
return grant();
|
|
14565
|
+
} finally {
|
|
14566
|
+
this.pending.delete(cred.ruid);
|
|
14567
|
+
}
|
|
14568
|
+
}
|
|
14569
|
+
listed(self, root, rules) {
|
|
14570
|
+
const host = this.o.hostname;
|
|
14571
|
+
if (root) {
|
|
14572
|
+
return {
|
|
14573
|
+
list: `User ${self.name} may run the following commands on ${host}:
|
|
14574
|
+
(ALL) NOPASSWD: ALL
|
|
14575
|
+
`
|
|
14576
|
+
};
|
|
14577
|
+
}
|
|
14578
|
+
if (rules.length === 0) {
|
|
14579
|
+
return failed2("EPERM", `User ${self.name} is not allowed to run sudo on ${host}.`);
|
|
14580
|
+
}
|
|
14581
|
+
const lines2 = rules.map(described).join("\n");
|
|
14582
|
+
return { list: `User ${self.name} may run the following commands on ${host}:
|
|
14583
|
+
${lines2}
|
|
14584
|
+
` };
|
|
14585
|
+
}
|
|
14586
|
+
async ask(caller, self, target, argv, path, cwd, requester) {
|
|
14587
|
+
const { approve } = this.o;
|
|
14588
|
+
if (!approve || requester.aborted) return failed2("EACCES", "sudo: approval denied");
|
|
14589
|
+
const createdAt = Date.now();
|
|
14590
|
+
const timeout = this.o.timeoutMs ?? SUDO_TIMEOUT_MS;
|
|
14591
|
+
const req = {
|
|
14592
|
+
id: `sudo-${++this.next}-${crypto.randomUUID()}`,
|
|
14593
|
+
kind: "sudo",
|
|
14594
|
+
user: self.name,
|
|
14595
|
+
uid: self.uid,
|
|
14596
|
+
target: target.name,
|
|
14597
|
+
argv: [...argv],
|
|
14598
|
+
path,
|
|
14599
|
+
cwd,
|
|
14600
|
+
pid: caller.pid,
|
|
14601
|
+
...caller.tag !== void 0 ? { tag: caller.tag } : {},
|
|
14602
|
+
...caller.tty ? { tty: caller.tty } : {},
|
|
14603
|
+
createdAt,
|
|
14604
|
+
expiresAt: createdAt + timeout,
|
|
14605
|
+
always: true
|
|
14606
|
+
};
|
|
14607
|
+
const control = new AbortController();
|
|
14608
|
+
const timer = setTimeout(() => control.abort("expired"), timeout);
|
|
14609
|
+
const withdraw = () => control.abort("withdrawn");
|
|
14610
|
+
requester.addEventListener("abort", withdraw, { once: true });
|
|
14611
|
+
const aborted2 = new Promise(
|
|
14612
|
+
(resolve) => control.signal.addEventListener("abort", () => resolve("aborted"), { once: true })
|
|
14613
|
+
);
|
|
14614
|
+
try {
|
|
14615
|
+
const answered = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
|
|
14616
|
+
const answer = await Promise.race([answered, aborted2]);
|
|
14617
|
+
if (answer === "aborted" || control.signal.aborted) {
|
|
14618
|
+
return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
|
|
14619
|
+
}
|
|
14620
|
+
return answer === "once" || answer === "always" ? answer : failed2("EACCES", "sudo: approval denied");
|
|
14621
|
+
} finally {
|
|
14622
|
+
clearTimeout(timer);
|
|
14623
|
+
requester.removeEventListener("abort", withdraw);
|
|
14624
|
+
}
|
|
14625
|
+
}
|
|
14626
|
+
async remember(self, target, argv) {
|
|
14627
|
+
const { fs } = this.o;
|
|
14628
|
+
const path = `${SUDOERS_D}/${self.name}`;
|
|
14629
|
+
if (!NAME3.test(self.name)) return false;
|
|
14630
|
+
try {
|
|
14631
|
+
await this.provision();
|
|
14632
|
+
if (!await this.trusted(SUDOERS_D)) return false;
|
|
14633
|
+
const st = await fs.lstat(path).catch(() => void 0);
|
|
14634
|
+
if (st && !await this.trusted(path)) return false;
|
|
14635
|
+
const old = st ? await fs.readFile(path) : "";
|
|
14636
|
+
const line = `NOPASSWD ${self.name} (${target.name}) Cmnd ${argv.map(quoteWord).join(" ")}
|
|
14637
|
+
`;
|
|
14638
|
+
const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
|
|
14639
|
+
${line}`;
|
|
14640
|
+
await fs.writeFile(path, text);
|
|
14641
|
+
await fs.chown?.(path, { uid: 0, gid: 0, mode: 288, follow: false });
|
|
14642
|
+
return true;
|
|
14643
|
+
} catch {
|
|
14644
|
+
return false;
|
|
14645
|
+
}
|
|
14646
|
+
}
|
|
14647
|
+
grant(self, target, cred, argv, file, cwd, term) {
|
|
14648
|
+
const env = {
|
|
14649
|
+
...this.o.env(),
|
|
14650
|
+
...userEnv(target, this.o.pnpmHome),
|
|
14651
|
+
SHELL: target.shell,
|
|
14652
|
+
PATH: SECURE_PATH,
|
|
14653
|
+
PWD: cwd,
|
|
14654
|
+
...term !== void 0 && TERM.test(term) ? { TERM: term } : {},
|
|
14655
|
+
SUDO_USER: self.name,
|
|
14656
|
+
SUDO_UID: String(cred.ruid),
|
|
14657
|
+
SUDO_GID: String(cred.rgid),
|
|
14658
|
+
SUDO_COMMAND: [file, ...argv.slice(1)].join(" ")
|
|
14659
|
+
};
|
|
14660
|
+
return { grant: { file, argv: [...argv], env, cwd, cred: credOf(target), caller: cred } };
|
|
14661
|
+
}
|
|
14662
|
+
};
|
|
14663
|
+
|
|
14664
|
+
// src/sudo-command.ts
|
|
14665
|
+
var SUDO_PATH = "/usr/bin/sudo";
|
|
14666
|
+
var SUDO_COMMAND = {
|
|
14667
|
+
name: "sudo",
|
|
14668
|
+
abi: "js",
|
|
14669
|
+
glue: SUDO_PATH,
|
|
14670
|
+
wasm: SUDO_PATH,
|
|
14671
|
+
argv0: "sudo"
|
|
14672
|
+
};
|
|
14673
|
+
var SUDO_SOURCE = [
|
|
14674
|
+
"export default async function sudo(ctx) {",
|
|
14675
|
+
" const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);",
|
|
14676
|
+
" let pid;",
|
|
14677
|
+
" let held;",
|
|
14678
|
+
" const relay = (sig) => {",
|
|
14679
|
+
" if (pid === undefined) held = sig;",
|
|
14680
|
+
" else return ctx.kill(pid, sig).catch(() => undefined);",
|
|
14681
|
+
" };",
|
|
14682
|
+
' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
|
|
14683
|
+
" const failed = () => (held === undefined ? 1 : 128 + held);",
|
|
14684
|
+
" let answer;",
|
|
14685
|
+
" try {",
|
|
14686
|
+
" answer = await ctx.sudo(ctx.argv.slice(1));",
|
|
14687
|
+
" } catch (err) {",
|
|
14688
|
+
' await say(2, "sudo: " + (err && err.code ? err.code : String(err)) + "\\n");',
|
|
14689
|
+
" return failed();",
|
|
14690
|
+
" }",
|
|
14691
|
+
" if (answer.error !== undefined) {",
|
|
14692
|
+
' await say(2, answer.error + "\\n");',
|
|
14693
|
+
" return failed();",
|
|
14694
|
+
" }",
|
|
14695
|
+
" if (answer.list !== undefined) {",
|
|
14696
|
+
" await say(1, answer.list);",
|
|
14697
|
+
" return 0;",
|
|
14698
|
+
" }",
|
|
14699
|
+
' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
|
|
14700
|
+
" if (answer.run !== undefined) {",
|
|
14701
|
+
" try {",
|
|
14702
|
+
" pid = (await ctx.spawn({ argv: answer.run })).pid;",
|
|
14703
|
+
" } catch (err) {",
|
|
14704
|
+
' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
|
|
14705
|
+
' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
|
|
14706
|
+
" return failed();",
|
|
14707
|
+
" }",
|
|
14708
|
+
" } else pid = answer.pid;",
|
|
14709
|
+
" if (held !== undefined) await relay(held);",
|
|
14710
|
+
" return (await ctx.wait(pid)).status;",
|
|
14711
|
+
"}",
|
|
14712
|
+
""
|
|
14713
|
+
].join("\n");
|
|
14714
|
+
|
|
14269
14715
|
// src/launcher.ts
|
|
14270
14716
|
var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
14271
14717
|
var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
|
|
@@ -14293,9 +14739,21 @@ function usersMode(mode = "off") {
|
|
|
14293
14739
|
if (USERS_MODES.includes(mode)) return mode;
|
|
14294
14740
|
throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
|
|
14295
14741
|
}
|
|
14742
|
+
function tagOption(tag) {
|
|
14743
|
+
if (tag !== void 0 && typeof tag !== "string") throw new TypeError("a tag must be a string");
|
|
14744
|
+
}
|
|
14296
14745
|
function baseName(path) {
|
|
14297
14746
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
14298
14747
|
}
|
|
14748
|
+
function packageRootOf(path, roots) {
|
|
14749
|
+
for (const root of roots) {
|
|
14750
|
+
const base = normalizePath(root);
|
|
14751
|
+
if (!path.startsWith(`${base}/`)) continue;
|
|
14752
|
+
const [first = "", second] = path.slice(base.length + 1).split("/");
|
|
14753
|
+
return first.startsWith("@") && second !== void 0 ? `${base}/${first}/${second}` : `${base}/${first}`;
|
|
14754
|
+
}
|
|
14755
|
+
return void 0;
|
|
14756
|
+
}
|
|
14299
14757
|
function modulePath(glue) {
|
|
14300
14758
|
return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
|
|
14301
14759
|
}
|
|
@@ -14307,11 +14765,11 @@ function shebang(head) {
|
|
|
14307
14765
|
return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
|
|
14308
14766
|
}
|
|
14309
14767
|
function interpreter(head) {
|
|
14310
|
-
const
|
|
14311
|
-
if (!
|
|
14312
|
-
if (baseName(
|
|
14313
|
-
if (
|
|
14314
|
-
return baseName(
|
|
14768
|
+
const words2 = shebang(head);
|
|
14769
|
+
if (!words2) return "node";
|
|
14770
|
+
if (baseName(words2[0]) === "env") words2.shift();
|
|
14771
|
+
if (words2[0] === "-S") words2.shift();
|
|
14772
|
+
return baseName(words2[0] ?? "");
|
|
14315
14773
|
}
|
|
14316
14774
|
function expandDefaults(defaults, env) {
|
|
14317
14775
|
const out = {};
|
|
@@ -14385,7 +14843,10 @@ var Launcher = class {
|
|
|
14385
14843
|
orphans = /* @__PURE__ */ new Set();
|
|
14386
14844
|
described = /* @__PURE__ */ new Map();
|
|
14387
14845
|
creds = /* @__PURE__ */ new Map();
|
|
14846
|
+
tags = /* @__PURE__ */ new Map();
|
|
14847
|
+
granted = /* @__PURE__ */ new Map();
|
|
14388
14848
|
users;
|
|
14849
|
+
sudoer;
|
|
14389
14850
|
jobs = new JobTable();
|
|
14390
14851
|
locks = new LockTable();
|
|
14391
14852
|
settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
|
|
@@ -14486,6 +14947,17 @@ var Launcher = class {
|
|
|
14486
14947
|
);
|
|
14487
14948
|
this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
|
|
14488
14949
|
this.users = new UserDb(this.fs, this.owners);
|
|
14950
|
+
this.sudoer = new Sudo({
|
|
14951
|
+
fs: this.fs,
|
|
14952
|
+
users: this.users,
|
|
14953
|
+
mode: this.usersMode,
|
|
14954
|
+
hostname,
|
|
14955
|
+
env: () => this.env,
|
|
14956
|
+
pnpmHome: this.pnpmHome,
|
|
14957
|
+
backing: (path, cwd) => this.backing(path, cwd),
|
|
14958
|
+
approve: options.approve,
|
|
14959
|
+
timeoutMs: options.sudoTimeoutMs
|
|
14960
|
+
});
|
|
14489
14961
|
void this.unlinked.sweep("/");
|
|
14490
14962
|
this.watchers.watch(
|
|
14491
14963
|
[this.modulesDir, this.pnpmHome],
|
|
@@ -14506,7 +14978,7 @@ var Launcher = class {
|
|
|
14506
14978
|
commands(scope = "") {
|
|
14507
14979
|
let found = this.catalogs.get(scope);
|
|
14508
14980
|
if (!found) {
|
|
14509
|
-
found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
|
|
14981
|
+
found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
|
|
14510
14982
|
this.catalogs.set(scope, found);
|
|
14511
14983
|
}
|
|
14512
14984
|
return found;
|
|
@@ -14629,10 +15101,10 @@ var Launcher = class {
|
|
|
14629
15101
|
const head = await this.head(script);
|
|
14630
15102
|
if (!head) return void 0;
|
|
14631
15103
|
const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
|
|
14632
|
-
const
|
|
14633
|
-
if (!
|
|
14634
|
-
if (baseName(
|
|
14635
|
-
const [interp, ...rest2] =
|
|
15104
|
+
const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
|
|
15105
|
+
if (!words2) return void 0;
|
|
15106
|
+
if (baseName(words2[0]) === "env") words2.shift();
|
|
15107
|
+
const [interp, ...rest2] = words2;
|
|
14636
15108
|
const arg = rest2.join(" ");
|
|
14637
15109
|
const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
|
|
14638
15110
|
const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
|
|
@@ -14669,6 +15141,41 @@ var Launcher = class {
|
|
|
14669
15141
|
await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
|
|
14670
15142
|
}
|
|
14671
15143
|
}
|
|
15144
|
+
async grantedCwd(caller, cwd) {
|
|
15145
|
+
const real = await physicalPathAsync(this.base, cwd).catch(() => void 0);
|
|
15146
|
+
return real === cwd && await this.sudoer.reachable(caller, cwd);
|
|
15147
|
+
}
|
|
15148
|
+
async binfmtManifests(scripts) {
|
|
15149
|
+
const sources = /* @__PURE__ */ new Map();
|
|
15150
|
+
const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
|
|
15151
|
+
const manifests = [];
|
|
15152
|
+
for (const script of scripts) {
|
|
15153
|
+
const head = await this.head(script);
|
|
15154
|
+
if (!head || isWasm(head) || shebang(head)) continue;
|
|
15155
|
+
const pkg = sources.get(binfmtSuffix(binfmts, script) ?? "");
|
|
15156
|
+
if (pkg !== void 0) manifests.push(`${pkg}/package.json`);
|
|
15157
|
+
}
|
|
15158
|
+
return manifests;
|
|
15159
|
+
}
|
|
15160
|
+
async backing(file, cwd) {
|
|
15161
|
+
const planned = await this.plan(file, [file], cwd, "");
|
|
15162
|
+
if (!planned) return [];
|
|
15163
|
+
const { glue, wasm, imports } = planned.target;
|
|
15164
|
+
const files = [glue, wasm, ...imports ? [imports] : []];
|
|
15165
|
+
const scripts = [];
|
|
15166
|
+
for (const name2 of [file, ...planned.via ?? []]) {
|
|
15167
|
+
if (name2.includes("/") && !COMMAND2.test(name2)) scripts.push(this.fs.resolvePath(cwd, name2));
|
|
15168
|
+
const script = (await this.scriptCommand(name2, cwd, ""))?.script;
|
|
15169
|
+
if (script) scripts.push(script);
|
|
15170
|
+
}
|
|
15171
|
+
files.push(...scripts, ...await this.binfmtManifests(scripts));
|
|
15172
|
+
const roots = await this.roots();
|
|
15173
|
+
for (const at of [...files]) {
|
|
15174
|
+
const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
|
|
15175
|
+
if (root !== void 0) files.push(`${root}/package.json`);
|
|
15176
|
+
}
|
|
15177
|
+
return [...new Set(files)].filter((path) => path !== "" && path !== SUDO_PATH);
|
|
15178
|
+
}
|
|
14672
15179
|
async unrunnable(file, cwd, scope) {
|
|
14673
15180
|
const path = this.fs.resolvePath(cwd, file);
|
|
14674
15181
|
const head = await this.base.readFileBuffer(path).catch(() => null);
|
|
@@ -14706,6 +15213,9 @@ var Launcher = class {
|
|
|
14706
15213
|
return await this.base.exists(optional) ? optional : void 0;
|
|
14707
15214
|
}
|
|
14708
15215
|
async program(target, env) {
|
|
15216
|
+
if (target.abi === "js" && target.wasm === SUDO_PATH) {
|
|
15217
|
+
return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
|
|
15218
|
+
}
|
|
14709
15219
|
if (target.abi === "js") {
|
|
14710
15220
|
return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
|
|
14711
15221
|
}
|
|
@@ -14792,6 +15302,7 @@ var Launcher = class {
|
|
|
14792
15302
|
kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
|
|
14793
15303
|
writesBack: (target) => this.processes.get(target)?.writesBack() === true,
|
|
14794
15304
|
processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
|
|
15305
|
+
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
14795
15306
|
openFiles: this.openFiles,
|
|
14796
15307
|
nodes: this.nodes,
|
|
14797
15308
|
held: this.held,
|
|
@@ -14799,7 +15310,7 @@ var Launcher = class {
|
|
|
14799
15310
|
statfs: (path) => this.mounts.statfs(path),
|
|
14800
15311
|
mounts: () => this.mounts.list(),
|
|
14801
15312
|
mount: (call, signal) => this.processMount(pid, call, signal),
|
|
14802
|
-
umount: (target,
|
|
15313
|
+
umount: (target, flags2) => this.processUmount(pid, target, flags2),
|
|
14803
15314
|
jobs: this.jobs,
|
|
14804
15315
|
ptys: this.ptys,
|
|
14805
15316
|
net: this.net,
|
|
@@ -14813,6 +15324,7 @@ var Launcher = class {
|
|
|
14813
15324
|
this.processes.set(pid, handle);
|
|
14814
15325
|
const { cred } = req;
|
|
14815
15326
|
this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
|
|
15327
|
+
if (req.tag !== void 0) this.tags.set(pid, req.tag);
|
|
14816
15328
|
if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
|
|
14817
15329
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
14818
15330
|
if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
|
|
@@ -14840,6 +15352,8 @@ var Launcher = class {
|
|
|
14840
15352
|
this.jobs.remove(pid);
|
|
14841
15353
|
this.described.delete(pid);
|
|
14842
15354
|
this.creds.delete(pid);
|
|
15355
|
+
this.tags.delete(pid);
|
|
15356
|
+
this.granted.delete(pid);
|
|
14843
15357
|
}
|
|
14844
15358
|
reaped(pid) {
|
|
14845
15359
|
if (this.zombies.delete(pid)) this.forget(pid);
|
|
@@ -14861,8 +15375,8 @@ var Launcher = class {
|
|
|
14861
15375
|
};
|
|
14862
15376
|
const listed = [];
|
|
14863
15377
|
for (const member of members.values()) {
|
|
14864
|
-
const
|
|
14865
|
-
if (member.execed || !
|
|
15378
|
+
const described2 = this.described.get(member.pid);
|
|
15379
|
+
if (member.execed || !described2) continue;
|
|
14866
15380
|
const cred = this.credOf(member.pid);
|
|
14867
15381
|
if (!maySee(viewer, cred)) continue;
|
|
14868
15382
|
const root = rootOf(member);
|
|
@@ -14872,8 +15386,8 @@ var Launcher = class {
|
|
|
14872
15386
|
ppid: shown(root.ppid),
|
|
14873
15387
|
pgid: member.pgid,
|
|
14874
15388
|
sid: member.sid,
|
|
14875
|
-
...
|
|
14876
|
-
started: (this.described.get(root.pid) ??
|
|
15389
|
+
...described2,
|
|
15390
|
+
started: (this.described.get(root.pid) ?? described2).started,
|
|
14877
15391
|
state: this.processes.has(member.pid) ? "S" : "Z",
|
|
14878
15392
|
memory: this.processes.get(member.pid)?.memory() ?? 0,
|
|
14879
15393
|
umask: this.umaskOf(member.pid),
|
|
@@ -14883,12 +15397,26 @@ var Launcher = class {
|
|
|
14883
15397
|
}
|
|
14884
15398
|
return listed;
|
|
14885
15399
|
}
|
|
15400
|
+
sudo(pid, call, signal) {
|
|
15401
|
+
const caller = {
|
|
15402
|
+
cred: this.credOf(pid),
|
|
15403
|
+
pid: this.jobs.shown(pid),
|
|
15404
|
+
tag: this.tagOf(pid),
|
|
15405
|
+
tty: this.described.get(pid)?.tty
|
|
15406
|
+
};
|
|
15407
|
+
return this.sudoer.request(caller, call, signal);
|
|
15408
|
+
}
|
|
14886
15409
|
spawner(ppid, report) {
|
|
14887
|
-
return async (req, fds) => {
|
|
14888
|
-
|
|
15410
|
+
return async (req, fds, grant) => {
|
|
15411
|
+
if (grant && !await this.grantedCwd(grant.caller, req.cwd)) {
|
|
15412
|
+
await fds.closeAll();
|
|
15413
|
+
throw new SpawnError("EACCES");
|
|
15414
|
+
}
|
|
15415
|
+
const cred = grant ? grant.cred : this.credOf(ppid);
|
|
15416
|
+
const scope = grant ? "" : await this.scopeOf(cred.euid);
|
|
14889
15417
|
const planned = await this.plan(req.file, req.argv, req.cwd, scope);
|
|
14890
15418
|
try {
|
|
14891
|
-
await this.mayExec(
|
|
15419
|
+
await this.mayExec(cred, { ...req, pid: ppid }, planned);
|
|
14892
15420
|
} catch (err) {
|
|
14893
15421
|
await fds.closeAll();
|
|
14894
15422
|
throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
|
|
@@ -14908,10 +15436,12 @@ var Launcher = class {
|
|
|
14908
15436
|
ppid,
|
|
14909
15437
|
ignored: this.ignoredBy(ppid),
|
|
14910
15438
|
umask: this.umaskOf(ppid),
|
|
14911
|
-
cred
|
|
15439
|
+
cred,
|
|
15440
|
+
tag: this.tagOf(ppid),
|
|
14912
15441
|
...exec
|
|
14913
15442
|
});
|
|
14914
15443
|
deciding?.attach();
|
|
15444
|
+
if (grant) this.granted.set(handle.pid, ppid);
|
|
14915
15445
|
return childHandle(handle);
|
|
14916
15446
|
};
|
|
14917
15447
|
}
|
|
@@ -14955,7 +15485,8 @@ var Launcher = class {
|
|
|
14955
15485
|
fork: state,
|
|
14956
15486
|
ignored: this.ignoredBy(ppid, true),
|
|
14957
15487
|
umask: this.umaskOf(ppid),
|
|
14958
|
-
cred: this.credOf(ppid)
|
|
15488
|
+
cred: this.credOf(ppid),
|
|
15489
|
+
tag: this.tagOf(ppid)
|
|
14959
15490
|
})
|
|
14960
15491
|
);
|
|
14961
15492
|
}
|
|
@@ -14980,6 +15511,9 @@ var Launcher = class {
|
|
|
14980
15511
|
credOf(pid) {
|
|
14981
15512
|
return this.creds.get(pid)?.() ?? copyCred(ROOT);
|
|
14982
15513
|
}
|
|
15514
|
+
tagOf(pid) {
|
|
15515
|
+
return this.tags.get(pid);
|
|
15516
|
+
}
|
|
14983
15517
|
released(path) {
|
|
14984
15518
|
const at = normalizePath(path);
|
|
14985
15519
|
this.unlinked.closed(at);
|
|
@@ -14998,7 +15532,7 @@ var Launcher = class {
|
|
|
14998
15532
|
}
|
|
14999
15533
|
kill(pid, sig, sender, from) {
|
|
15000
15534
|
const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
|
|
15001
|
-
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);
|
|
15535
|
+
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;
|
|
15002
15536
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
15003
15537
|
const handle = this.processes.get(pid);
|
|
15004
15538
|
if (!handle) return false;
|
|
@@ -15119,7 +15653,8 @@ var Launcher = class {
|
|
|
15119
15653
|
if (fresh) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
|
|
15120
15654
|
}
|
|
15121
15655
|
}
|
|
15122
|
-
async realLayoutPath(
|
|
15656
|
+
async realLayoutPath(given) {
|
|
15657
|
+
const path = normalizePath(given);
|
|
15123
15658
|
if (!await this.base.exists(path)) return void 0;
|
|
15124
15659
|
if (path === "/") return path;
|
|
15125
15660
|
return joinName(await realDir(this.base, parentOf(path)), baseName(path));
|
|
@@ -15130,7 +15665,8 @@ var Launcher = class {
|
|
|
15130
15665
|
mountedBy(spec, cred) {
|
|
15131
15666
|
if (!this.owners || cred.euid === 0 && cred.egid === 0) return spec;
|
|
15132
15667
|
const ids = { uid: String(cred.euid), gid: String(cred.egid) };
|
|
15133
|
-
|
|
15668
|
+
const options = cred.euid === 0 ? { ...ids, ...spec.options } : { ...spec.options, ...ids };
|
|
15669
|
+
return { ...spec, options };
|
|
15134
15670
|
}
|
|
15135
15671
|
mountRoot(path) {
|
|
15136
15672
|
let root = "/";
|
|
@@ -15175,13 +15711,36 @@ var Launcher = class {
|
|
|
15175
15711
|
async processMount(pid, call, signal) {
|
|
15176
15712
|
const spec = mountCall(call);
|
|
15177
15713
|
await this.permit({ op: "mount", pid, ...spec, options: { ...spec.options } });
|
|
15714
|
+
await this.mountsOwn(
|
|
15715
|
+
this.credOf(pid),
|
|
15716
|
+
spec.target,
|
|
15717
|
+
async () => (await this.fs.stat(spec.target)).uid
|
|
15718
|
+
);
|
|
15178
15719
|
const entry = await this.mount(this.mountedBy(spec, this.credOf(pid)));
|
|
15179
15720
|
if (signal.aborted) this.umount(entry.target, true);
|
|
15180
15721
|
}
|
|
15181
|
-
async
|
|
15182
|
-
const
|
|
15722
|
+
async mountAs(cred, spec) {
|
|
15723
|
+
const target = normalizePath(spec.target);
|
|
15724
|
+
await this.mountsOwn(cred, target, async () => (await this.fs.stat(target)).uid);
|
|
15725
|
+
return this.mount(this.mountedBy(spec, cred));
|
|
15726
|
+
}
|
|
15727
|
+
async umountAs(cred, target) {
|
|
15728
|
+
const at = normalizePath(target);
|
|
15729
|
+
await this.mountsOwn(cred, at, async () => Number(this.mounts.mounted(at)?.options.uid));
|
|
15730
|
+
this.umount(at);
|
|
15731
|
+
}
|
|
15732
|
+
async mountsOwn(cred, target, owner) {
|
|
15733
|
+
const { euid } = cred;
|
|
15734
|
+
if (this.usersMode !== "enforce" || euid === 0) return;
|
|
15735
|
+
if (await owner().catch(() => void 0) !== euid) {
|
|
15736
|
+
throw fsError("EPERM", `${target} is not the caller's to mount or unmount`);
|
|
15737
|
+
}
|
|
15738
|
+
}
|
|
15739
|
+
async processUmount(pid, target, flags2) {
|
|
15740
|
+
const call = umountCall(target, flags2);
|
|
15741
|
+
let mounted;
|
|
15183
15742
|
for (; ; ) {
|
|
15184
|
-
|
|
15743
|
+
mounted = this.mounts.mounted(call.target);
|
|
15185
15744
|
await this.permit({
|
|
15186
15745
|
op: "umount",
|
|
15187
15746
|
pid,
|
|
@@ -15190,6 +15749,7 @@ var Launcher = class {
|
|
|
15190
15749
|
});
|
|
15191
15750
|
if (this.mounts.mounted(call.target) === mounted) break;
|
|
15192
15751
|
}
|
|
15752
|
+
await this.mountsOwn(this.credOf(pid), call.target, async () => Number(mounted?.options.uid));
|
|
15193
15753
|
this.umount(call.target, call.detach);
|
|
15194
15754
|
}
|
|
15195
15755
|
async prepare() {
|
|
@@ -15198,6 +15758,7 @@ var Launcher = class {
|
|
|
15198
15758
|
await this.provisionPnpmHome();
|
|
15199
15759
|
await this.users.ensure();
|
|
15200
15760
|
if (this.owners) await this.stampLayout();
|
|
15761
|
+
if (this.owners) await this.sudoer.provision();
|
|
15201
15762
|
await writeCaFile(this.base, this.ca).catch(() => void 0);
|
|
15202
15763
|
this.fstab = this.mountFstab();
|
|
15203
15764
|
}
|
|
@@ -15235,8 +15796,9 @@ var Launcher = class {
|
|
|
15235
15796
|
if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
|
|
15236
15797
|
return found;
|
|
15237
15798
|
}
|
|
15238
|
-
identity(account) {
|
|
15799
|
+
identity(account, tag) {
|
|
15239
15800
|
return {
|
|
15801
|
+
tag,
|
|
15240
15802
|
cred: credOf(account),
|
|
15241
15803
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
15242
15804
|
};
|
|
@@ -15253,6 +15815,7 @@ var Launcher = class {
|
|
|
15253
15815
|
return { cwd, file, planned };
|
|
15254
15816
|
}
|
|
15255
15817
|
async openTerminal(argv, options) {
|
|
15818
|
+
tagOption(options.tag);
|
|
15256
15819
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
15257
15820
|
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
15258
15821
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
@@ -15281,7 +15844,13 @@ var Launcher = class {
|
|
|
15281
15844
|
);
|
|
15282
15845
|
const report = (message3) => options.onData(encoder3.encode(`${message3}\r
|
|
15283
15846
|
`));
|
|
15284
|
-
const handle = await this.launch(planned, {
|
|
15847
|
+
const handle = await this.launch(planned, {
|
|
15848
|
+
env,
|
|
15849
|
+
cwd,
|
|
15850
|
+
fds,
|
|
15851
|
+
report,
|
|
15852
|
+
...this.identity(account, options.tag)
|
|
15853
|
+
});
|
|
15285
15854
|
leader = handle.pid;
|
|
15286
15855
|
void handle.exited.then(() => tty.hangup());
|
|
15287
15856
|
return {
|
|
@@ -15297,6 +15866,7 @@ var Launcher = class {
|
|
|
15297
15866
|
};
|
|
15298
15867
|
}
|
|
15299
15868
|
async run(argv, options = {}) {
|
|
15869
|
+
tagOption(options.tag);
|
|
15300
15870
|
const account = await this.account(options.user);
|
|
15301
15871
|
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
15302
15872
|
const env = this.environment(cwd, options.env, account);
|
|
@@ -15326,7 +15896,7 @@ var Launcher = class {
|
|
|
15326
15896
|
fds,
|
|
15327
15897
|
report,
|
|
15328
15898
|
...group,
|
|
15329
|
-
...this.identity(account)
|
|
15899
|
+
...this.identity(account, options.tag)
|
|
15330
15900
|
});
|
|
15331
15901
|
options.onStarted?.(handle.pid);
|
|
15332
15902
|
const status = await handle.exited;
|
|
@@ -16478,7 +17048,8 @@ async function createNodeKernel(options = {}) {
|
|
|
16478
17048
|
...options.cdp ? { cdp: options.cdp } : {},
|
|
16479
17049
|
...options.hostname !== void 0 ? { hostname: options.hostname } : {},
|
|
16480
17050
|
...options.users !== void 0 ? { users: options.users } : {},
|
|
16481
|
-
...options.onDenial ? { onDenial: options.onDenial } : {}
|
|
17051
|
+
...options.onDenial ? { onDenial: options.onDenial } : {},
|
|
17052
|
+
...options.approve ? { approve: options.approve } : {}
|
|
16482
17053
|
});
|
|
16483
17054
|
await launcher.prepare();
|
|
16484
17055
|
const guard2 = (work) => new Promise((resolve, reject) => {
|
|
@@ -16554,8 +17125,9 @@ async function createNodeKernel(options = {}) {
|
|
|
16554
17125
|
addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
|
|
16555
17126
|
removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
|
|
16556
17127
|
},
|
|
16557
|
-
async connect({ user } = {}) {
|
|
17128
|
+
async connect({ user, tag } = {}) {
|
|
16558
17129
|
if (terminated) throw new Error(TERMINATED);
|
|
17130
|
+
tagOption(tag);
|
|
16559
17131
|
if (user !== void 0 && !await launcher.users.lookup(user)) {
|
|
16560
17132
|
throw fsError("ENOENT", `no user ${String(user)}`);
|
|
16561
17133
|
}
|
|
@@ -16563,7 +17135,8 @@ async function createNodeKernel(options = {}) {
|
|
|
16563
17135
|
const served = serveClient(port1, {
|
|
16564
17136
|
launcher: async () => launcher,
|
|
16565
17137
|
signal: signalNumber,
|
|
16566
|
-
...user !== void 0 ? { user } : {}
|
|
17138
|
+
...user !== void 0 ? { user } : {},
|
|
17139
|
+
...tag !== void 0 ? { tag } : {}
|
|
16567
17140
|
});
|
|
16568
17141
|
clients.add(served);
|
|
16569
17142
|
void served.closed.then(() => clients.delete(served));
|