@ai-ecoverse/slicc-kernel 1.71.1 → 1.73.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 +21 -6
- package/dist/index.js +42 -3
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +470 -51
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +37 -6
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +432 -48
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +37 -6
- package/dist/process-worker.js.map +2 -2
- package/package.json +4 -3
package/dist/kernel-worker.js
CHANGED
|
@@ -141,11 +141,18 @@ var IndexedDbMeta = class {
|
|
|
141
141
|
cache = new PathMap();
|
|
142
142
|
channel;
|
|
143
143
|
generation = 0;
|
|
144
|
+
listeners = /* @__PURE__ */ new Set();
|
|
145
|
+
listen(listener) {
|
|
146
|
+
this.listeners.add(listener);
|
|
147
|
+
}
|
|
144
148
|
constructor(db) {
|
|
145
149
|
this.db = db;
|
|
146
150
|
db.onversionchange = () => db.close();
|
|
147
151
|
this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);
|
|
148
|
-
this.channel.onmessage = ({ data }) =>
|
|
152
|
+
this.channel.onmessage = ({ data }) => {
|
|
153
|
+
this.drop(data);
|
|
154
|
+
for (const listener of this.listeners) listener(data);
|
|
155
|
+
};
|
|
149
156
|
this.channel.unref?.();
|
|
150
157
|
}
|
|
151
158
|
drop({ paths = [], trees = [] }) {
|
|
@@ -354,7 +361,17 @@ async function chownAs(fs, cred, path, change) {
|
|
|
354
361
|
}
|
|
355
362
|
|
|
356
363
|
// src/fs/protect.ts
|
|
357
|
-
|
|
364
|
+
function protectInit(protection) {
|
|
365
|
+
const writers = [...writerTags(protection)];
|
|
366
|
+
return { paths: [...protection.paths], ...writers.length > 0 ? { writers } : {} };
|
|
367
|
+
}
|
|
368
|
+
function writerTags(protection) {
|
|
369
|
+
const writers = protection?.writers ?? [];
|
|
370
|
+
if (!Array.isArray(writers) || writers.some((tag) => typeof tag !== "string" || tag === "")) {
|
|
371
|
+
throw new TypeError("protect.writers must be a list of process tags");
|
|
372
|
+
}
|
|
373
|
+
return new Set(writers);
|
|
374
|
+
}
|
|
358
375
|
function protectedPrefixes(paths) {
|
|
359
376
|
return [...new Set(paths.map((p) => normalizePath(p)))];
|
|
360
377
|
}
|
|
@@ -530,6 +547,121 @@ var ProtectedFs = class {
|
|
|
530
547
|
}
|
|
531
548
|
};
|
|
532
549
|
|
|
550
|
+
// src/fs/remote.ts
|
|
551
|
+
var ANNOUNCE_WAIT_MS = 500;
|
|
552
|
+
var PRUNE_EVERY_MS = 3e4;
|
|
553
|
+
function lockManager() {
|
|
554
|
+
return globalThis.navigator?.locks;
|
|
555
|
+
}
|
|
556
|
+
var RemoteWatch = class {
|
|
557
|
+
channel;
|
|
558
|
+
name;
|
|
559
|
+
tab = crypto.randomUUID();
|
|
560
|
+
peers = /* @__PURE__ */ new Set();
|
|
561
|
+
others = /* @__PURE__ */ new Map();
|
|
562
|
+
union = /* @__PURE__ */ new Set();
|
|
563
|
+
listeners = /* @__PURE__ */ new Set();
|
|
564
|
+
waiting = /* @__PURE__ */ new Map();
|
|
565
|
+
locks;
|
|
566
|
+
mine = [];
|
|
567
|
+
seq = 0;
|
|
568
|
+
pruned = 0;
|
|
569
|
+
constructor(name2, options = {}) {
|
|
570
|
+
this.name = name2;
|
|
571
|
+
this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
|
|
572
|
+
this.channel = new BroadcastChannel(name2);
|
|
573
|
+
this.channel.onmessage = ({ data }) => this.receive(data);
|
|
574
|
+
this.channel.unref?.();
|
|
575
|
+
void this.locks?.request(this.lockOf(this.tab), () => new Promise(() => {
|
|
576
|
+
}));
|
|
577
|
+
this.channel.postMessage({ hello: this.tab });
|
|
578
|
+
}
|
|
579
|
+
lockOf(tab) {
|
|
580
|
+
return `${this.name}:${tab}`;
|
|
581
|
+
}
|
|
582
|
+
receive(data) {
|
|
583
|
+
if (data.hello !== void 0) this.greeted(data.hello);
|
|
584
|
+
if (data.here !== void 0 && data.to === this.tab) this.peers.add(data.here);
|
|
585
|
+
if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
|
|
586
|
+
if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
|
|
587
|
+
this.acked(data.ack, data.from);
|
|
588
|
+
}
|
|
589
|
+
if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [] });
|
|
590
|
+
}
|
|
591
|
+
greeted(peer) {
|
|
592
|
+
this.peers.add(peer);
|
|
593
|
+
this.channel.postMessage({ here: this.tab, to: peer });
|
|
594
|
+
if (this.mine.length > 0) void this.announce(this.mine);
|
|
595
|
+
}
|
|
596
|
+
announced({ tab, paths, seq: seq2 }) {
|
|
597
|
+
const from = tab;
|
|
598
|
+
this.peers.add(from);
|
|
599
|
+
if (paths.length > 0) this.others.set(from, paths);
|
|
600
|
+
else this.others.delete(from);
|
|
601
|
+
this.rebuild();
|
|
602
|
+
this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
|
|
603
|
+
}
|
|
604
|
+
acked(seq2, from) {
|
|
605
|
+
const wait2 = this.waiting.get(seq2);
|
|
606
|
+
if (!wait2) return;
|
|
607
|
+
wait2.left.delete(from);
|
|
608
|
+
if (wait2.left.size === 0) wait2.done();
|
|
609
|
+
}
|
|
610
|
+
rebuild() {
|
|
611
|
+
this.union.clear();
|
|
612
|
+
for (const paths of this.others.values()) for (const path of paths) this.union.add(path);
|
|
613
|
+
}
|
|
614
|
+
async prune() {
|
|
615
|
+
if (!this.locks) return;
|
|
616
|
+
const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
|
|
617
|
+
for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
|
|
618
|
+
if (!held.has(this.lockOf(tab))) {
|
|
619
|
+
this.others.delete(tab);
|
|
620
|
+
this.peers.delete(tab);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
this.rebuild();
|
|
624
|
+
}
|
|
625
|
+
watched() {
|
|
626
|
+
return this.union.size > 0;
|
|
627
|
+
}
|
|
628
|
+
wrote(path) {
|
|
629
|
+
if (!this.union.has(path)) return;
|
|
630
|
+
this.channel.postMessage({ changed: [path] });
|
|
631
|
+
const now = Date.now();
|
|
632
|
+
if (now - this.pruned < PRUNE_EVERY_MS) return;
|
|
633
|
+
this.pruned = now;
|
|
634
|
+
void this.prune();
|
|
635
|
+
}
|
|
636
|
+
announce(paths) {
|
|
637
|
+
this.mine = [...paths];
|
|
638
|
+
const seq2 = ++this.seq;
|
|
639
|
+
this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
|
|
640
|
+
if (this.peers.size === 0) return Promise.resolve();
|
|
641
|
+
return new Promise((resolve) => {
|
|
642
|
+
const done4 = () => {
|
|
643
|
+
clearTimeout(timer);
|
|
644
|
+
this.waiting.delete(seq2);
|
|
645
|
+
resolve();
|
|
646
|
+
};
|
|
647
|
+
const timer = setTimeout(() => {
|
|
648
|
+
done4();
|
|
649
|
+
void this.prune();
|
|
650
|
+
}, ANNOUNCE_WAIT_MS);
|
|
651
|
+
this.waiting.set(seq2, { left: new Set(this.peers), done: done4 });
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
emit(change) {
|
|
655
|
+
for (const listener of this.listeners) listener(change);
|
|
656
|
+
}
|
|
657
|
+
listen(listener) {
|
|
658
|
+
this.listeners.add(listener);
|
|
659
|
+
return () => {
|
|
660
|
+
this.listeners.delete(listener);
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
533
665
|
// src/fs/opfs.ts
|
|
534
666
|
var S_IFDIR = 16384;
|
|
535
667
|
var S_IFREG = 32768;
|
|
@@ -657,6 +789,7 @@ var OpfsFs = class {
|
|
|
657
789
|
dirs = new PathMap();
|
|
658
790
|
checked = /* @__PURE__ */ new WeakMap();
|
|
659
791
|
channel;
|
|
792
|
+
remote;
|
|
660
793
|
ranged;
|
|
661
794
|
owners = false;
|
|
662
795
|
constructor(root, meta = new MemoryMeta(), channel, options = {}) {
|
|
@@ -664,9 +797,15 @@ var OpfsFs = class {
|
|
|
664
797
|
this.meta = sidecar(meta);
|
|
665
798
|
this.ranged = options.ranged === true;
|
|
666
799
|
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
800
|
+
this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
|
|
667
801
|
if (this.channel) {
|
|
802
|
+
const remote = this.remote;
|
|
803
|
+
meta.listen?.(
|
|
804
|
+
(c) => remote?.emit({ paths: c.paths ?? [], trees: c.trees ?? [] })
|
|
805
|
+
);
|
|
668
806
|
this.channel.onmessage = ({ data }) => {
|
|
669
807
|
for (const path of data) this.forget(path);
|
|
808
|
+
remote?.emit({ paths: data, trees: [] });
|
|
670
809
|
};
|
|
671
810
|
this.channel.unref?.();
|
|
672
811
|
}
|
|
@@ -675,6 +814,16 @@ var OpfsFs = class {
|
|
|
675
814
|
for (const path of paths) this.forget(path);
|
|
676
815
|
this.channel?.postMessage(paths);
|
|
677
816
|
}
|
|
817
|
+
remoteChanges(listener) {
|
|
818
|
+
return this.remote?.listen(listener) ?? (() => {
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
announceWatch(paths) {
|
|
822
|
+
return this.remote?.announce(paths) ?? Promise.resolve();
|
|
823
|
+
}
|
|
824
|
+
committed(target) {
|
|
825
|
+
if (this.remote?.watched()) this.remote.wrote(target);
|
|
826
|
+
}
|
|
678
827
|
resolvePath(base, path) {
|
|
679
828
|
return resolvePath(base, path);
|
|
680
829
|
}
|
|
@@ -734,6 +883,9 @@ var OpfsFs = class {
|
|
|
734
883
|
protect(prefixes2) {
|
|
735
884
|
this.guarded = prefixes2;
|
|
736
885
|
}
|
|
886
|
+
unguarded() {
|
|
887
|
+
return Object.create(this, { guarded: { value: [] } });
|
|
888
|
+
}
|
|
737
889
|
refuse(path, holding = false) {
|
|
738
890
|
if (isProtected(this.guarded, path) || holding && holdsProtected(this.guarded, path)) {
|
|
739
891
|
throw fsError("EROFS", path);
|
|
@@ -903,15 +1055,15 @@ var OpfsFs = class {
|
|
|
903
1055
|
}
|
|
904
1056
|
dirTimes = /* @__PURE__ */ new Map();
|
|
905
1057
|
dirDirty = /* @__PURE__ */ new Set();
|
|
906
|
-
|
|
1058
|
+
flushing = {};
|
|
907
1059
|
touch(...dirs) {
|
|
908
1060
|
const now = Date.now();
|
|
909
1061
|
for (const dir of dirs) {
|
|
910
1062
|
this.dirTimes.set(dir, now);
|
|
911
1063
|
this.dirDirty.add(dir);
|
|
912
1064
|
}
|
|
913
|
-
this.
|
|
914
|
-
this.
|
|
1065
|
+
this.flushing.dir ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
|
|
1066
|
+
this.flushing.dir = void 0;
|
|
915
1067
|
});
|
|
916
1068
|
}
|
|
917
1069
|
async flushDirTimes() {
|
|
@@ -1026,15 +1178,18 @@ var OpfsFs = class {
|
|
|
1026
1178
|
);
|
|
1027
1179
|
}
|
|
1028
1180
|
async pwrite(path, offset, bytes) {
|
|
1029
|
-
const handle =
|
|
1181
|
+
const [handle, , target] = await this.fileFor(path);
|
|
1030
1182
|
await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
|
|
1183
|
+
this.committed(target);
|
|
1031
1184
|
}
|
|
1032
1185
|
async truncate(path, size) {
|
|
1033
1186
|
const handle = await this.existing(path, true);
|
|
1187
|
+
const target = this.remote?.watched() ? (await this.locate(path, true)).path : path;
|
|
1034
1188
|
await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
|
|
1189
|
+
this.committed(target);
|
|
1035
1190
|
}
|
|
1036
1191
|
async writeSession(path, truncate) {
|
|
1037
|
-
const handle =
|
|
1192
|
+
const [handle, , target] = await this.fileFor(path);
|
|
1038
1193
|
const writable2 = await this.retrying(
|
|
1039
1194
|
path,
|
|
1040
1195
|
() => handle.createWritable({ keepExistingData: !truncate }),
|
|
@@ -1047,9 +1202,12 @@ var OpfsFs = class {
|
|
|
1047
1202
|
truncate: (size) => writable2.truncate(size).catch((err) => {
|
|
1048
1203
|
throw translate(err, path, "write", false);
|
|
1049
1204
|
}),
|
|
1050
|
-
close:
|
|
1051
|
-
|
|
1052
|
-
|
|
1205
|
+
close: async () => {
|
|
1206
|
+
await writable2.close().catch((err) => {
|
|
1207
|
+
throw translate(err, path, "commit", false);
|
|
1208
|
+
});
|
|
1209
|
+
this.committed(target);
|
|
1210
|
+
}
|
|
1053
1211
|
};
|
|
1054
1212
|
}
|
|
1055
1213
|
async readFile(path) {
|
|
@@ -1098,6 +1256,7 @@ var OpfsFs = class {
|
|
|
1098
1256
|
await writable2?.abort().catch(() => void 0);
|
|
1099
1257
|
throw translate(err, path, "commit", false);
|
|
1100
1258
|
}
|
|
1259
|
+
this.committed(target);
|
|
1101
1260
|
}
|
|
1102
1261
|
async listing(path) {
|
|
1103
1262
|
const found = await this.locate(path, true);
|
|
@@ -1119,12 +1278,12 @@ var OpfsFs = class {
|
|
|
1119
1278
|
const base = dir === "/" ? "" : dir;
|
|
1120
1279
|
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
1121
1280
|
const handed2 = this.owners ? await this.handedTo(dir) : void 0;
|
|
1122
|
-
const
|
|
1281
|
+
const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
|
|
1123
1282
|
return Promise.all(
|
|
1124
1283
|
children2.map(async ([name2, child], i) => {
|
|
1125
|
-
if (!isHandle(child)) return [name2,
|
|
1284
|
+
if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
|
|
1126
1285
|
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
1127
|
-
return [name2, stat &&
|
|
1286
|
+
return [name2, stat && owned4(stat, metas[i])];
|
|
1128
1287
|
})
|
|
1129
1288
|
);
|
|
1130
1289
|
}
|
|
@@ -1733,6 +1892,7 @@ var Dac = class {
|
|
|
1733
1892
|
log;
|
|
1734
1893
|
seen = /* @__PURE__ */ new Set();
|
|
1735
1894
|
suppressed = false;
|
|
1895
|
+
tagOf = () => void 0;
|
|
1736
1896
|
constructor(mode, hook, log = (line) => console.warn(line)) {
|
|
1737
1897
|
this.mode = mode;
|
|
1738
1898
|
this.hook = hook;
|
|
@@ -1741,7 +1901,9 @@ var Dac = class {
|
|
|
1741
1901
|
get enforcing() {
|
|
1742
1902
|
return this.mode === "enforce";
|
|
1743
1903
|
}
|
|
1744
|
-
denied(
|
|
1904
|
+
denied(given) {
|
|
1905
|
+
const tag = given.tag ?? (given.pid ? this.tagOf(given.pid) : void 0);
|
|
1906
|
+
const denial = tag === void 0 ? given : { ...given, tag };
|
|
1745
1907
|
try {
|
|
1746
1908
|
this.hook?.({ ...denial });
|
|
1747
1909
|
} catch {
|
|
@@ -1759,7 +1921,7 @@ var Dac = class {
|
|
|
1759
1921
|
}
|
|
1760
1922
|
this.seen.add(key);
|
|
1761
1923
|
this.log(
|
|
1762
|
-
`slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
|
|
1924
|
+
`slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid}${d.tag === void 0 ? "" : `, tag ${d.tag}`})`
|
|
1763
1925
|
);
|
|
1764
1926
|
}
|
|
1765
1927
|
};
|
|
@@ -1775,7 +1937,8 @@ var Created = class {
|
|
|
1775
1937
|
}
|
|
1776
1938
|
};
|
|
1777
1939
|
function deny(g, op, path, errno = "EACCES") {
|
|
1778
|
-
g.
|
|
1940
|
+
const tag = g.tag === void 0 ? {} : { tag: g.tag };
|
|
1941
|
+
g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
|
|
1779
1942
|
}
|
|
1780
1943
|
var TTY_MODE = 400;
|
|
1781
1944
|
function ttyOpenable(g, tty) {
|
|
@@ -3671,6 +3834,12 @@ var CLIENT_CHECKS = {
|
|
|
3671
3834
|
readlink: (g, at) => searchable(g, "readlink", at(0), false),
|
|
3672
3835
|
exists: (g, at) => searchable(g, "exists", at(0), true)
|
|
3673
3836
|
};
|
|
3837
|
+
function clientFs(l, tag, self, req) {
|
|
3838
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
3839
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
3840
|
+
}
|
|
3841
|
+
return fsCall(l.fsFor(tag), req.method, req.args, ownerFor(l, self), guardFor(l, self, tag));
|
|
3842
|
+
}
|
|
3674
3843
|
async function fsCall(fs, method, args, owner, g) {
|
|
3675
3844
|
const path = (i) => fs.resolvePath("/", String(args[i]));
|
|
3676
3845
|
if (g) {
|
|
@@ -3745,9 +3914,9 @@ function ownerFor(l, self) {
|
|
|
3745
3914
|
if (!l.owners || !self) return void 0;
|
|
3746
3915
|
return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
|
|
3747
3916
|
}
|
|
3748
|
-
function guardFor(l, self) {
|
|
3917
|
+
function guardFor(l, self, tag) {
|
|
3749
3918
|
if (!l.dac || !self) return void 0;
|
|
3750
|
-
return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
|
|
3919
|
+
return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac, tag };
|
|
3751
3920
|
}
|
|
3752
3921
|
async function accountOf(l, user) {
|
|
3753
3922
|
const found = await l.users.lookup(user);
|
|
@@ -3851,6 +4020,7 @@ async function watchOp(l, req, watches, send, g) {
|
|
|
3851
4020
|
function serveClient(port, host) {
|
|
3852
4021
|
let state = "new";
|
|
3853
4022
|
let self;
|
|
4023
|
+
const guard2 = (l) => guardFor(l, self, host.tag);
|
|
3854
4024
|
const terminals = /* @__PURE__ */ new Map();
|
|
3855
4025
|
const groups = /* @__PURE__ */ new Set();
|
|
3856
4026
|
const sessions = /* @__PURE__ */ new Set();
|
|
@@ -3957,13 +4127,10 @@ function serveClient(port, host) {
|
|
|
3957
4127
|
case "ps":
|
|
3958
4128
|
return { result: processes(l, self) };
|
|
3959
4129
|
case "fs":
|
|
3960
|
-
|
|
3961
|
-
throw new Error(`unknown file system call ${req.method}`);
|
|
3962
|
-
}
|
|
3963
|
-
return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
|
|
4130
|
+
return clientFs(l, host.tag, self, req);
|
|
3964
4131
|
case "watch":
|
|
3965
4132
|
case "unwatch":
|
|
3966
|
-
return watchOp(l, req, watches, send,
|
|
4133
|
+
return watchOp(l, req, watches, send, guard2(l));
|
|
3967
4134
|
case "mount":
|
|
3968
4135
|
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
3969
4136
|
case "umount":
|
|
@@ -6624,6 +6791,136 @@ function binfmtOf(binfmts, path) {
|
|
|
6624
6791
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
6625
6792
|
}
|
|
6626
6793
|
|
|
6794
|
+
// src/fs/attest.ts
|
|
6795
|
+
var ATTEST_MAX = 1024 * 1024;
|
|
6796
|
+
var UNOWNED = 1e3;
|
|
6797
|
+
function refuse(code, path, why) {
|
|
6798
|
+
throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
|
|
6799
|
+
}
|
|
6800
|
+
function chainOf(path) {
|
|
6801
|
+
const parts2 = path.split("/").filter(Boolean);
|
|
6802
|
+
return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
|
|
6803
|
+
}
|
|
6804
|
+
function under2(path, target) {
|
|
6805
|
+
return path === target || path.startsWith(`${target}/`);
|
|
6806
|
+
}
|
|
6807
|
+
function owned(path, st) {
|
|
6808
|
+
return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
|
|
6809
|
+
}
|
|
6810
|
+
function unchanged(a, b, bytes) {
|
|
6811
|
+
return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
|
|
6812
|
+
}
|
|
6813
|
+
async function sha256(bytes) {
|
|
6814
|
+
const digest = new Uint8Array(
|
|
6815
|
+
await crypto.subtle.digest("SHA-256", bytes)
|
|
6816
|
+
);
|
|
6817
|
+
return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6818
|
+
}
|
|
6819
|
+
function attested(path, st, bytes, digest, o, parents) {
|
|
6820
|
+
const { uid, gid, mode } = owned(path, st);
|
|
6821
|
+
return {
|
|
6822
|
+
path,
|
|
6823
|
+
bytes,
|
|
6824
|
+
uid,
|
|
6825
|
+
gid,
|
|
6826
|
+
mode,
|
|
6827
|
+
ino: st.ino,
|
|
6828
|
+
mtimeMs: st.mtime.getTime(),
|
|
6829
|
+
sha256: digest,
|
|
6830
|
+
users: o.users,
|
|
6831
|
+
onRootFs: true,
|
|
6832
|
+
parents
|
|
6833
|
+
};
|
|
6834
|
+
}
|
|
6835
|
+
async function trustedParents(fs, path, o) {
|
|
6836
|
+
const parents = [];
|
|
6837
|
+
for (const dir of chainOf(path).slice(0, -1)) {
|
|
6838
|
+
const st = await fs.lstat(dir);
|
|
6839
|
+
if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
|
|
6840
|
+
if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
|
|
6841
|
+
const perm = owned(dir, st);
|
|
6842
|
+
if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
|
|
6843
|
+
refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
|
|
6844
|
+
}
|
|
6845
|
+
parents.push(perm);
|
|
6846
|
+
}
|
|
6847
|
+
return parents;
|
|
6848
|
+
}
|
|
6849
|
+
async function attest(fs, asked, o) {
|
|
6850
|
+
if (typeof asked !== "string" || !asked.startsWith("/")) {
|
|
6851
|
+
refuse("EINVAL", String(asked), "not an absolute path");
|
|
6852
|
+
}
|
|
6853
|
+
const path = normalizePath(asked);
|
|
6854
|
+
const mount = o.mounts.find((target) => {
|
|
6855
|
+
const at = normalizePath(target);
|
|
6856
|
+
return at === "/" || under2(path, at);
|
|
6857
|
+
});
|
|
6858
|
+
if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
|
|
6859
|
+
const parents = await trustedParents(fs, path, o);
|
|
6860
|
+
for (let tries = 0; ; tries++) {
|
|
6861
|
+
const before = await fs.lstat(path);
|
|
6862
|
+
if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
|
|
6863
|
+
if (!before.isFile)
|
|
6864
|
+
refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
|
|
6865
|
+
if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
|
|
6866
|
+
const bytes = (await fs.readFileBuffer(path)).slice();
|
|
6867
|
+
const after = await fs.lstat(path);
|
|
6868
|
+
if (unchanged(before, after, bytes)) {
|
|
6869
|
+
return attested(path, after, bytes, await sha256(bytes), o, parents);
|
|
6870
|
+
}
|
|
6871
|
+
if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
var WATCH_DEBOUNCE_MS = 50;
|
|
6875
|
+
var FileWatches = class {
|
|
6876
|
+
live = /* @__PURE__ */ new Set();
|
|
6877
|
+
local;
|
|
6878
|
+
remote;
|
|
6879
|
+
constructor(local, remote) {
|
|
6880
|
+
this.local = local;
|
|
6881
|
+
this.remote = remote;
|
|
6882
|
+
}
|
|
6883
|
+
announce() {
|
|
6884
|
+
return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
|
|
6885
|
+
}
|
|
6886
|
+
async watch(paths, notify) {
|
|
6887
|
+
const files = paths.map(normalizePath);
|
|
6888
|
+
const timers = /* @__PURE__ */ new Map();
|
|
6889
|
+
let active = true;
|
|
6890
|
+
const fire = (changed, trees) => {
|
|
6891
|
+
for (const file of files) {
|
|
6892
|
+
const hit = [...changed, ...trees].some((p) => under2(file, normalizePath(p)));
|
|
6893
|
+
if (!hit) continue;
|
|
6894
|
+
clearTimeout(timers.get(file));
|
|
6895
|
+
timers.set(
|
|
6896
|
+
file,
|
|
6897
|
+
setTimeout(() => {
|
|
6898
|
+
timers.delete(file);
|
|
6899
|
+
if (active) notify(file);
|
|
6900
|
+
}, WATCH_DEBOUNCE_MS)
|
|
6901
|
+
);
|
|
6902
|
+
}
|
|
6903
|
+
};
|
|
6904
|
+
const unwatch = this.local.watch(
|
|
6905
|
+
files,
|
|
6906
|
+
{ recursive: false },
|
|
6907
|
+
(change) => fire("overflow" in change ? files : change.paths, [])
|
|
6908
|
+
);
|
|
6909
|
+
const unlisten = this.remote.remoteChanges?.((change) => fire(change.paths, change.trees));
|
|
6910
|
+
this.live.add(files);
|
|
6911
|
+
await this.announce();
|
|
6912
|
+
return () => {
|
|
6913
|
+
if (!active) return;
|
|
6914
|
+
active = false;
|
|
6915
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
6916
|
+
unwatch();
|
|
6917
|
+
unlisten?.();
|
|
6918
|
+
this.live.delete(files);
|
|
6919
|
+
void this.announce();
|
|
6920
|
+
};
|
|
6921
|
+
}
|
|
6922
|
+
};
|
|
6923
|
+
|
|
6627
6924
|
// src/fs/commands.ts
|
|
6628
6925
|
var S_IFDIR2 = 16384;
|
|
6629
6926
|
var S_IFREG2 = 32768;
|
|
@@ -6662,7 +6959,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
6662
6959
|
if (st || path === "/") return st ?? {};
|
|
6663
6960
|
return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
|
|
6664
6961
|
}
|
|
6665
|
-
async function
|
|
6962
|
+
async function owned4(stat2, dir) {
|
|
6666
6963
|
if (!owners) return stat2;
|
|
6667
6964
|
const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
|
|
6668
6965
|
return { ...stat2, uid, gid };
|
|
@@ -6676,17 +6973,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
6676
6973
|
}
|
|
6677
6974
|
async function virtual(path) {
|
|
6678
6975
|
if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
|
|
6679
|
-
return
|
|
6976
|
+
return owned4(synthetic(true, path));
|
|
6680
6977
|
}
|
|
6681
6978
|
const name2 = COMMAND.exec(path)?.[1];
|
|
6682
6979
|
if (name2 !== void 0) {
|
|
6683
|
-
return (await names()).has(name2) ?
|
|
6980
|
+
return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
|
|
6684
6981
|
}
|
|
6685
|
-
if ((await userDir(path))?.size) return
|
|
6982
|
+
if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
|
|
6686
6983
|
const slash = path.lastIndexOf("/");
|
|
6687
6984
|
const dir = path.slice(0, slash);
|
|
6688
6985
|
const own = await userDir(dir);
|
|
6689
|
-
return own?.has(path.slice(slash + 1)) ?
|
|
6986
|
+
return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
|
|
6690
6987
|
}
|
|
6691
6988
|
async function linked(path) {
|
|
6692
6989
|
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
@@ -7035,11 +7332,11 @@ var UnlinkedKeeper = class {
|
|
|
7035
7332
|
|
|
7036
7333
|
// src/fs/watch.ts
|
|
7037
7334
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
7038
|
-
var
|
|
7335
|
+
var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
7039
7336
|
var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7040
7337
|
function touches(watcher, changed) {
|
|
7041
7338
|
return watcher.paths.some(
|
|
7042
|
-
(watched) => changed === watched ||
|
|
7339
|
+
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
|
|
7043
7340
|
);
|
|
7044
7341
|
}
|
|
7045
7342
|
var FsWatchers = class {
|
|
@@ -8758,7 +9055,7 @@ async function keptStat(entry, req) {
|
|
|
8758
9055
|
return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
|
|
8759
9056
|
return json2(statJson(await entry.fs.stat(hidden)));
|
|
8760
9057
|
}
|
|
8761
|
-
async function
|
|
9058
|
+
async function owned2(entry, own, req, path, target) {
|
|
8762
9059
|
const source = target === path ? void 0 : path;
|
|
8763
9060
|
if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
|
|
8764
9061
|
return run(entry, path, req);
|
|
@@ -8813,7 +9110,7 @@ async function dispatchOne(entry, req) {
|
|
|
8813
9110
|
}
|
|
8814
9111
|
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
8815
9112
|
const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
8816
|
-
return await
|
|
9113
|
+
return await owned2(entry, entry.own, req, path, target);
|
|
8817
9114
|
} catch (err) {
|
|
8818
9115
|
return toErrno(err);
|
|
8819
9116
|
}
|
|
@@ -11668,6 +11965,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11668
11965
|
"proc-sudo",
|
|
11669
11966
|
"proc-run",
|
|
11670
11967
|
"proc-exit",
|
|
11968
|
+
"proc-kill-self",
|
|
11671
11969
|
"proc-fork",
|
|
11672
11970
|
"proc-kill",
|
|
11673
11971
|
"thread-kill",
|
|
@@ -11795,6 +12093,20 @@ var WasmProcess = class {
|
|
|
11795
12093
|
else this.adoptExec(pid);
|
|
11796
12094
|
return pid;
|
|
11797
12095
|
}
|
|
12096
|
+
async killSelf(sig, target, probe) {
|
|
12097
|
+
if (!this.options.exitSignal) throw new KernelError("ENOSYS");
|
|
12098
|
+
if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
|
|
12099
|
+
const { caught, ignored } = this.signalMasks();
|
|
12100
|
+
const to = target === 0 ? -this.pgid() : target ?? this.pid;
|
|
12101
|
+
if (sig !== SIG.KILL && (caught | ignored) & sigbit(sig)) {
|
|
12102
|
+
if (!await this.options.kill?.(to, sig)) throw new KernelError("ESRCH");
|
|
12103
|
+
return { ok: true, kind: "void" };
|
|
12104
|
+
}
|
|
12105
|
+
if (probe) return { ok: true, kind: "json", json: "terminate" };
|
|
12106
|
+
if (to !== this.pid) await this.options.kill?.(to, sig);
|
|
12107
|
+
this.options.exitSignal(sig);
|
|
12108
|
+
return DYING;
|
|
12109
|
+
}
|
|
11798
12110
|
run(req) {
|
|
11799
12111
|
return runChild(req, {
|
|
11800
12112
|
children: this.children,
|
|
@@ -12392,6 +12704,8 @@ var WasmProcess = class {
|
|
|
12392
12704
|
if (!this.options.exitNow) throw new KernelError("ENOSYS");
|
|
12393
12705
|
this.options.exitNow(req.status & 255);
|
|
12394
12706
|
return DYING;
|
|
12707
|
+
case "proc-kill-self":
|
|
12708
|
+
return this.killSelf(req.sig, req.target, req.probe);
|
|
12395
12709
|
case "proc-identity":
|
|
12396
12710
|
return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
|
|
12397
12711
|
case "proc-exec": {
|
|
@@ -12826,6 +13140,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
12826
13140
|
raise: hooks.raise,
|
|
12827
13141
|
threadKill: hooks.threadKill,
|
|
12828
13142
|
...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
|
|
13143
|
+
...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
|
|
12829
13144
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
|
|
12830
13145
|
};
|
|
12831
13146
|
}
|
|
@@ -12859,7 +13174,10 @@ function spawnWasmProcess(opts) {
|
|
|
12859
13174
|
{
|
|
12860
13175
|
raise: (sig) => signal(sig),
|
|
12861
13176
|
threadKill: (tid, sig) => killThread(threadSignals.get(tid), process.signalMasks().caught, sig),
|
|
12862
|
-
...opts.program.abi === "js" ? {
|
|
13177
|
+
...opts.program.abi === "js" ? {
|
|
13178
|
+
exitNow: (code) => exitNow(code),
|
|
13179
|
+
exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
|
|
13180
|
+
} : {}
|
|
12863
13181
|
},
|
|
12864
13182
|
created3
|
|
12865
13183
|
)
|
|
@@ -14664,8 +14982,8 @@ var ChunkedBytes = class {
|
|
|
14664
14982
|
}
|
|
14665
14983
|
return out;
|
|
14666
14984
|
}
|
|
14667
|
-
write(offset, bytes,
|
|
14668
|
-
const whole =
|
|
14985
|
+
write(offset, bytes, owned4 = false) {
|
|
14986
|
+
const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
|
|
14669
14987
|
for (let done4 = 0; done4 < bytes.length; ) {
|
|
14670
14988
|
const at = offset + done4;
|
|
14671
14989
|
const index = Math.floor(at / CHUNK3);
|
|
@@ -14715,7 +15033,7 @@ function created2(ino, kind, mode, from, owner) {
|
|
|
14715
15033
|
gid: owner?.gid ?? from?.gid ?? 0
|
|
14716
15034
|
};
|
|
14717
15035
|
}
|
|
14718
|
-
function
|
|
15036
|
+
function owned3(node, change) {
|
|
14719
15037
|
if (change.uid !== void 0) node.uid = change.uid;
|
|
14720
15038
|
if (change.gid !== void 0) node.gid = change.gid;
|
|
14721
15039
|
}
|
|
@@ -14886,7 +15204,7 @@ function tmpfs() {
|
|
|
14886
15204
|
node.mtime = Date.now();
|
|
14887
15205
|
}
|
|
14888
15206
|
if (change.mtime !== void 0) node.mtime = change.mtime;
|
|
14889
|
-
|
|
15207
|
+
owned3(node, change);
|
|
14890
15208
|
},
|
|
14891
15209
|
async statfs() {
|
|
14892
15210
|
return { bsize: 4096, blocks: 262144, bfree: 262144 };
|
|
@@ -15578,6 +15896,10 @@ var Launcher = class {
|
|
|
15578
15896
|
fs;
|
|
15579
15897
|
processFs;
|
|
15580
15898
|
prefixes;
|
|
15899
|
+
writerTags;
|
|
15900
|
+
unguardedRoot;
|
|
15901
|
+
writers;
|
|
15902
|
+
unlinkOwnerOption;
|
|
15581
15903
|
base;
|
|
15582
15904
|
createWorker;
|
|
15583
15905
|
modulesDir;
|
|
@@ -15635,12 +15957,16 @@ var Launcher = class {
|
|
|
15635
15957
|
usersMode;
|
|
15636
15958
|
dac;
|
|
15637
15959
|
sealed;
|
|
15960
|
+
rootFs;
|
|
15961
|
+
fileWatches;
|
|
15638
15962
|
nextPid = 1e3;
|
|
15639
15963
|
boot = Date.now();
|
|
15640
15964
|
terminals = 0;
|
|
15641
15965
|
constructor(options) {
|
|
15642
15966
|
this.usersMode = usersMode(options.users);
|
|
15967
|
+
this.rootFs = options.fs;
|
|
15643
15968
|
this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
|
|
15969
|
+
if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
|
|
15644
15970
|
this.sealed = new SealedBroker(
|
|
15645
15971
|
options.sealed,
|
|
15646
15972
|
this.usersMode,
|
|
@@ -15691,7 +16017,7 @@ var Launcher = class {
|
|
|
15691
16017
|
this.nodes = new VfsNodes(fs, (path) => this.released(path));
|
|
15692
16018
|
this.nodes.keepsOnDisk = true;
|
|
15693
16019
|
this.openFiles.add(this.nodes);
|
|
15694
|
-
const holders = [this.held,
|
|
16020
|
+
const holders = [this.held, this.openFiles];
|
|
15695
16021
|
this.unlinked = new UnlinkedKeeper(
|
|
15696
16022
|
fs,
|
|
15697
16023
|
{ [Symbol.iterator]: () => holders.flatMap((set2) => [...set2]).values() },
|
|
@@ -15704,7 +16030,12 @@ var Launcher = class {
|
|
|
15704
16030
|
}
|
|
15705
16031
|
);
|
|
15706
16032
|
this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
|
|
15707
|
-
|
|
16033
|
+
this.writerTags = writerTags(options.protect);
|
|
16034
|
+
this.unlinkOwnerOption = options.unlinkOwner;
|
|
16035
|
+
if (this.writerTags.size > 0 && options.fs.protect && !options.fs.unguarded) {
|
|
16036
|
+
throw new TypeError("protect.writers needs a root file system with an unguarded view");
|
|
16037
|
+
}
|
|
16038
|
+
this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
|
|
15708
16039
|
this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
|
|
15709
16040
|
this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
|
|
15710
16041
|
options.fs.protect?.(this.prefixes);
|
|
@@ -15890,7 +16221,7 @@ var Launcher = class {
|
|
|
15890
16221
|
async mayExec(cred, req, planned) {
|
|
15891
16222
|
const { dac } = this;
|
|
15892
16223
|
if (!dac) return;
|
|
15893
|
-
const g = { fs: this.fs, cred, pid: req.pid, dac };
|
|
16224
|
+
const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
|
|
15894
16225
|
const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
|
|
15895
16226
|
if (byPath(req.file)) {
|
|
15896
16227
|
const path = this.fs.resolvePath(req.cwd, req.file);
|
|
@@ -16028,6 +16359,7 @@ var Launcher = class {
|
|
|
16028
16359
|
throw fsError("ESRCH", `no process group ${req.pgid}`);
|
|
16029
16360
|
}
|
|
16030
16361
|
const pid = this.nextPid++;
|
|
16362
|
+
const stack = this.stackFor(req.tag);
|
|
16031
16363
|
const terminal = req.fds.stdioTerminal();
|
|
16032
16364
|
const decided = req.decided ?? Promise.resolve(true);
|
|
16033
16365
|
const count = (byParent) => {
|
|
@@ -16058,8 +16390,8 @@ var Launcher = class {
|
|
|
16058
16390
|
env: req.env,
|
|
16059
16391
|
cwd: req.cwd,
|
|
16060
16392
|
fds: req.fds,
|
|
16061
|
-
fs:
|
|
16062
|
-
...this.prefixes.length > 0 ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
|
|
16393
|
+
fs: stack.fs,
|
|
16394
|
+
...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
|
|
16063
16395
|
createWorker: this.createWorker,
|
|
16064
16396
|
onError: req.report,
|
|
16065
16397
|
spawner: this.spawner(pid, req.report),
|
|
@@ -16074,9 +16406,9 @@ var Launcher = class {
|
|
|
16074
16406
|
}),
|
|
16075
16407
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
16076
16408
|
openFiles: this.openFiles,
|
|
16077
|
-
nodes:
|
|
16409
|
+
nodes: stack.nodes,
|
|
16078
16410
|
held: this.held,
|
|
16079
|
-
unlinkGate:
|
|
16411
|
+
unlinkGate: stack.unlinked.gate,
|
|
16080
16412
|
statfs: (path) => this.mounts.statfs(path),
|
|
16081
16413
|
mounts: () => this.mounts.list(),
|
|
16082
16414
|
mount: (call, signal) => this.processMount(pid, call, signal),
|
|
@@ -16377,6 +16709,7 @@ var Launcher = class {
|
|
|
16377
16709
|
released(path) {
|
|
16378
16710
|
const at = normalizePath(path);
|
|
16379
16711
|
this.unlinked.closed(at);
|
|
16712
|
+
this.writers?.unlinked.closed(at);
|
|
16380
16713
|
const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
|
|
16381
16714
|
if (!open) void this.mounts.commit(at).catch(() => void 0);
|
|
16382
16715
|
}
|
|
@@ -16393,6 +16726,13 @@ var Launcher = class {
|
|
|
16393
16726
|
kill(pid, sig, sender, from) {
|
|
16394
16727
|
const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
|
|
16395
16728
|
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;
|
|
16729
|
+
if (pid === -1) {
|
|
16730
|
+
const sender2 = from === void 0 ? void 0 : this.imageOf(from);
|
|
16731
|
+
return this.killAll(
|
|
16732
|
+
sig,
|
|
16733
|
+
(member) => this.imageOf(member) !== sender2 && may(this.imageOf(member))
|
|
16734
|
+
);
|
|
16735
|
+
}
|
|
16396
16736
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
16397
16737
|
const handle = this.processes.get(pid);
|
|
16398
16738
|
if (!handle) return false;
|
|
@@ -16400,6 +16740,15 @@ var Launcher = class {
|
|
|
16400
16740
|
if (sig !== 0) handle.signal(sig);
|
|
16401
16741
|
return true;
|
|
16402
16742
|
}
|
|
16743
|
+
killAll(sig, may) {
|
|
16744
|
+
let any = false;
|
|
16745
|
+
for (const [pid, handle] of [...this.processes]) {
|
|
16746
|
+
if (pid === 1 || !may(pid)) continue;
|
|
16747
|
+
any = true;
|
|
16748
|
+
if (sig !== 0) handle.signal(sig);
|
|
16749
|
+
}
|
|
16750
|
+
return any;
|
|
16751
|
+
}
|
|
16403
16752
|
async driver(type, spec) {
|
|
16404
16753
|
if (type === "fsa") return this.fsaDriver(spec);
|
|
16405
16754
|
if (type === "hostfs") {
|
|
@@ -16489,6 +16838,16 @@ var Launcher = class {
|
|
|
16489
16838
|
}
|
|
16490
16839
|
};
|
|
16491
16840
|
}
|
|
16841
|
+
attest(path) {
|
|
16842
|
+
return attest(this.rootFs, path, {
|
|
16843
|
+
users: this.usersMode,
|
|
16844
|
+
mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
|
|
16845
|
+
});
|
|
16846
|
+
}
|
|
16847
|
+
watchFiles(paths, notify) {
|
|
16848
|
+
this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
|
|
16849
|
+
return this.fileWatches.watch(paths, notify);
|
|
16850
|
+
}
|
|
16492
16851
|
async mount(spec) {
|
|
16493
16852
|
this.guardMount(spec.target);
|
|
16494
16853
|
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
@@ -16669,21 +17028,57 @@ var Launcher = class {
|
|
|
16669
17028
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
16670
17029
|
};
|
|
16671
17030
|
}
|
|
16672
|
-
|
|
17031
|
+
fsFor(tag) {
|
|
17032
|
+
return this.stackFor(tag).fs;
|
|
17033
|
+
}
|
|
17034
|
+
isWriter(tag) {
|
|
17035
|
+
return tag !== void 0 && this.prefixes.length > 0 && this.writerTags.has(tag);
|
|
17036
|
+
}
|
|
17037
|
+
stackFor(tag) {
|
|
17038
|
+
if (!this.isWriter(tag)) {
|
|
17039
|
+
return { fs: this.processFs, nodes: this.nodes, unlinked: this.unlinked, writer: false };
|
|
17040
|
+
}
|
|
17041
|
+
this.writers ??= this.writerStack();
|
|
17042
|
+
return this.writers;
|
|
17043
|
+
}
|
|
17044
|
+
writerStack() {
|
|
17045
|
+
const fs = withCommandDirs(
|
|
17046
|
+
this.watchers.wrap(this.mounts.wrap(this.unguardedRoot)),
|
|
17047
|
+
async () => new Set((await this.commands()).keys()),
|
|
17048
|
+
(dir) => this.userCommandDir(dir),
|
|
17049
|
+
this.owners
|
|
17050
|
+
);
|
|
17051
|
+
const nodes = new VfsNodes(fs, (path) => this.released(path));
|
|
17052
|
+
nodes.keepsOnDisk = true;
|
|
17053
|
+
this.openFiles.add(nodes);
|
|
17054
|
+
const unlinked = new UnlinkedKeeper(
|
|
17055
|
+
fs,
|
|
17056
|
+
{ [Symbol.iterator]: () => [this.held, this.openFiles].flatMap((set2) => [...set2]).values() },
|
|
17057
|
+
(path) => this.mountRoot(path),
|
|
17058
|
+
{
|
|
17059
|
+
locks: webLocks(),
|
|
17060
|
+
busy: (path) => [...this.openFiles].some((open) => open.holds(path)),
|
|
17061
|
+
owner: this.unlinkOwnerOption,
|
|
17062
|
+
...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
|
|
17063
|
+
}
|
|
17064
|
+
);
|
|
17065
|
+
return { fs: unlinked.wrap(keepingOpen(fs, nodes)), nodes, unlinked, writer: true };
|
|
17066
|
+
}
|
|
17067
|
+
async starting(argv, dir, account, tag) {
|
|
16673
17068
|
this.resetCatalogs();
|
|
16674
17069
|
const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
|
|
16675
17070
|
const start2 = dir ?? "/";
|
|
16676
17071
|
const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
|
|
16677
17072
|
const [file = ""] = argv;
|
|
16678
|
-
if (!await this.base.exists(cwd)) await this.
|
|
17073
|
+
if (!await this.base.exists(cwd)) await this.stackFor(tag).fs.mkdir(cwd, { recursive: true });
|
|
16679
17074
|
const planned = await this.plan(file, argv, cwd, scope);
|
|
16680
|
-
await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
|
|
17075
|
+
await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
|
|
16681
17076
|
return { cwd, file, planned };
|
|
16682
17077
|
}
|
|
16683
17078
|
async openTerminal(argv, options) {
|
|
16684
17079
|
tagOption(options.tag);
|
|
16685
17080
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
16686
|
-
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
17081
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
|
|
16687
17082
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
16688
17083
|
let leader = 0;
|
|
16689
17084
|
const tty = new KernelTty(
|
|
@@ -16734,7 +17129,7 @@ var Launcher = class {
|
|
|
16734
17129
|
async run(argv, options = {}) {
|
|
16735
17130
|
tagOption(options.tag);
|
|
16736
17131
|
const account = await this.account(options.user);
|
|
16737
|
-
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
17132
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
|
|
16738
17133
|
const env = this.environment(cwd, options.env, account);
|
|
16739
17134
|
const out = [];
|
|
16740
17135
|
const err = [];
|
|
@@ -16893,9 +17288,31 @@ var USERS_OPS = /* @__PURE__ */ new Set([
|
|
|
16893
17288
|
"groups-add-member",
|
|
16894
17289
|
"groups-remove-member"
|
|
16895
17290
|
]);
|
|
17291
|
+
var ATTEST_OPS = /* @__PURE__ */ new Set(["attest", "watch-files", "unwatch-files"]);
|
|
17292
|
+
function isAttestRequest(req) {
|
|
17293
|
+
return ATTEST_OPS.has(req.op);
|
|
17294
|
+
}
|
|
16896
17295
|
function isUsersRequest(req) {
|
|
16897
17296
|
return USERS_OPS.has(req.op);
|
|
16898
17297
|
}
|
|
17298
|
+
function transferOf2(req, result) {
|
|
17299
|
+
if (result instanceof MessagePort) return [result];
|
|
17300
|
+
if (req.op === "attest") return [result.bytes.buffer];
|
|
17301
|
+
return void 0;
|
|
17302
|
+
}
|
|
17303
|
+
function attestOps(port) {
|
|
17304
|
+
const watches = /* @__PURE__ */ new Map();
|
|
17305
|
+
return async (l, req) => {
|
|
17306
|
+
if (req.op === "attest") return l.attest(req.path);
|
|
17307
|
+
if (req.op === "unwatch-files") {
|
|
17308
|
+
watches.get(req.watch)?.();
|
|
17309
|
+
return watches.delete(req.watch);
|
|
17310
|
+
}
|
|
17311
|
+
const notify = (path) => port.postMessage({ fileChanged: { watch: req.id, path } });
|
|
17312
|
+
watches.set(req.id, await l.watchFiles(req.paths, notify));
|
|
17313
|
+
return req.id;
|
|
17314
|
+
};
|
|
17315
|
+
}
|
|
16899
17316
|
function failure(err) {
|
|
16900
17317
|
const code = err?.code;
|
|
16901
17318
|
return {
|
|
@@ -16916,7 +17333,7 @@ function hostOptions(req, port, sealed) {
|
|
|
16916
17333
|
...req.hostname !== void 0 ? { hostname: req.hostname } : {},
|
|
16917
17334
|
...req.users !== void 0 ? { users: req.users } : {},
|
|
16918
17335
|
...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
|
|
16919
|
-
...req.protect ? { protect:
|
|
17336
|
+
...req.protect ? { protect: protectInit(req.protect) } : {},
|
|
16920
17337
|
...req.sealed ? { sealed } : {}
|
|
16921
17338
|
};
|
|
16922
17339
|
}
|
|
@@ -17034,8 +17451,10 @@ function serveKernel(port, deps) {
|
|
|
17034
17451
|
if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
|
|
17035
17452
|
return l.mounts.list();
|
|
17036
17453
|
}
|
|
17454
|
+
const attestOp = attestOps(port);
|
|
17037
17455
|
async function handle(req) {
|
|
17038
17456
|
if (req.op === "init") return init(req);
|
|
17457
|
+
if (isAttestRequest(req)) return attestOp(await ready2(), req);
|
|
17039
17458
|
if (req.op === "open-terminal") return terminal(req);
|
|
17040
17459
|
if (req.op === "connect") return connect(req.user, req.tag);
|
|
17041
17460
|
if (isUsersRequest(req)) return usersOp(await ready2(), req);
|
|
@@ -17084,7 +17503,7 @@ function serveKernel(port, deps) {
|
|
|
17084
17503
|
if (answered(event.data, remote, uplink, approvals)) return;
|
|
17085
17504
|
const req = event.data;
|
|
17086
17505
|
handle(req).then(
|
|
17087
|
-
(result) => reply(req.id, { result },
|
|
17506
|
+
(result) => reply(req.id, { result }, transferOf2(req, result)),
|
|
17088
17507
|
(err) => reply(req.id, failure(err))
|
|
17089
17508
|
);
|
|
17090
17509
|
});
|