@ai-ecoverse/slicc-kernel 1.71.1 → 1.72.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 +18 -3
- package/dist/index.js +29 -0
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +392 -36
- 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 +359 -34
- 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 = [] }) {
|
|
@@ -530,6 +537,121 @@ var ProtectedFs = class {
|
|
|
530
537
|
}
|
|
531
538
|
};
|
|
532
539
|
|
|
540
|
+
// src/fs/remote.ts
|
|
541
|
+
var ANNOUNCE_WAIT_MS = 500;
|
|
542
|
+
var PRUNE_EVERY_MS = 3e4;
|
|
543
|
+
function lockManager() {
|
|
544
|
+
return globalThis.navigator?.locks;
|
|
545
|
+
}
|
|
546
|
+
var RemoteWatch = class {
|
|
547
|
+
channel;
|
|
548
|
+
name;
|
|
549
|
+
tab = crypto.randomUUID();
|
|
550
|
+
peers = /* @__PURE__ */ new Set();
|
|
551
|
+
others = /* @__PURE__ */ new Map();
|
|
552
|
+
union = /* @__PURE__ */ new Set();
|
|
553
|
+
listeners = /* @__PURE__ */ new Set();
|
|
554
|
+
waiting = /* @__PURE__ */ new Map();
|
|
555
|
+
locks;
|
|
556
|
+
mine = [];
|
|
557
|
+
seq = 0;
|
|
558
|
+
pruned = 0;
|
|
559
|
+
constructor(name2, options = {}) {
|
|
560
|
+
this.name = name2;
|
|
561
|
+
this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
|
|
562
|
+
this.channel = new BroadcastChannel(name2);
|
|
563
|
+
this.channel.onmessage = ({ data }) => this.receive(data);
|
|
564
|
+
this.channel.unref?.();
|
|
565
|
+
void this.locks?.request(this.lockOf(this.tab), () => new Promise(() => {
|
|
566
|
+
}));
|
|
567
|
+
this.channel.postMessage({ hello: this.tab });
|
|
568
|
+
}
|
|
569
|
+
lockOf(tab) {
|
|
570
|
+
return `${this.name}:${tab}`;
|
|
571
|
+
}
|
|
572
|
+
receive(data) {
|
|
573
|
+
if (data.hello !== void 0) this.greeted(data.hello);
|
|
574
|
+
if (data.here !== void 0 && data.to === this.tab) this.peers.add(data.here);
|
|
575
|
+
if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
|
|
576
|
+
if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
|
|
577
|
+
this.acked(data.ack, data.from);
|
|
578
|
+
}
|
|
579
|
+
if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [] });
|
|
580
|
+
}
|
|
581
|
+
greeted(peer) {
|
|
582
|
+
this.peers.add(peer);
|
|
583
|
+
this.channel.postMessage({ here: this.tab, to: peer });
|
|
584
|
+
if (this.mine.length > 0) void this.announce(this.mine);
|
|
585
|
+
}
|
|
586
|
+
announced({ tab, paths, seq: seq2 }) {
|
|
587
|
+
const from = tab;
|
|
588
|
+
this.peers.add(from);
|
|
589
|
+
if (paths.length > 0) this.others.set(from, paths);
|
|
590
|
+
else this.others.delete(from);
|
|
591
|
+
this.rebuild();
|
|
592
|
+
this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
|
|
593
|
+
}
|
|
594
|
+
acked(seq2, from) {
|
|
595
|
+
const wait2 = this.waiting.get(seq2);
|
|
596
|
+
if (!wait2) return;
|
|
597
|
+
wait2.left.delete(from);
|
|
598
|
+
if (wait2.left.size === 0) wait2.done();
|
|
599
|
+
}
|
|
600
|
+
rebuild() {
|
|
601
|
+
this.union.clear();
|
|
602
|
+
for (const paths of this.others.values()) for (const path of paths) this.union.add(path);
|
|
603
|
+
}
|
|
604
|
+
async prune() {
|
|
605
|
+
if (!this.locks) return;
|
|
606
|
+
const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
|
|
607
|
+
for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
|
|
608
|
+
if (!held.has(this.lockOf(tab))) {
|
|
609
|
+
this.others.delete(tab);
|
|
610
|
+
this.peers.delete(tab);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
this.rebuild();
|
|
614
|
+
}
|
|
615
|
+
watched() {
|
|
616
|
+
return this.union.size > 0;
|
|
617
|
+
}
|
|
618
|
+
wrote(path) {
|
|
619
|
+
if (!this.union.has(path)) return;
|
|
620
|
+
this.channel.postMessage({ changed: [path] });
|
|
621
|
+
const now = Date.now();
|
|
622
|
+
if (now - this.pruned < PRUNE_EVERY_MS) return;
|
|
623
|
+
this.pruned = now;
|
|
624
|
+
void this.prune();
|
|
625
|
+
}
|
|
626
|
+
announce(paths) {
|
|
627
|
+
this.mine = [...paths];
|
|
628
|
+
const seq2 = ++this.seq;
|
|
629
|
+
this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
|
|
630
|
+
if (this.peers.size === 0) return Promise.resolve();
|
|
631
|
+
return new Promise((resolve) => {
|
|
632
|
+
const done4 = () => {
|
|
633
|
+
clearTimeout(timer);
|
|
634
|
+
this.waiting.delete(seq2);
|
|
635
|
+
resolve();
|
|
636
|
+
};
|
|
637
|
+
const timer = setTimeout(() => {
|
|
638
|
+
done4();
|
|
639
|
+
void this.prune();
|
|
640
|
+
}, ANNOUNCE_WAIT_MS);
|
|
641
|
+
this.waiting.set(seq2, { left: new Set(this.peers), done: done4 });
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
emit(change) {
|
|
645
|
+
for (const listener of this.listeners) listener(change);
|
|
646
|
+
}
|
|
647
|
+
listen(listener) {
|
|
648
|
+
this.listeners.add(listener);
|
|
649
|
+
return () => {
|
|
650
|
+
this.listeners.delete(listener);
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
533
655
|
// src/fs/opfs.ts
|
|
534
656
|
var S_IFDIR = 16384;
|
|
535
657
|
var S_IFREG = 32768;
|
|
@@ -657,6 +779,7 @@ var OpfsFs = class {
|
|
|
657
779
|
dirs = new PathMap();
|
|
658
780
|
checked = /* @__PURE__ */ new WeakMap();
|
|
659
781
|
channel;
|
|
782
|
+
remote;
|
|
660
783
|
ranged;
|
|
661
784
|
owners = false;
|
|
662
785
|
constructor(root, meta = new MemoryMeta(), channel, options = {}) {
|
|
@@ -664,9 +787,15 @@ var OpfsFs = class {
|
|
|
664
787
|
this.meta = sidecar(meta);
|
|
665
788
|
this.ranged = options.ranged === true;
|
|
666
789
|
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
790
|
+
this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
|
|
667
791
|
if (this.channel) {
|
|
792
|
+
const remote = this.remote;
|
|
793
|
+
meta.listen?.(
|
|
794
|
+
(c) => remote?.emit({ paths: c.paths ?? [], trees: c.trees ?? [] })
|
|
795
|
+
);
|
|
668
796
|
this.channel.onmessage = ({ data }) => {
|
|
669
797
|
for (const path of data) this.forget(path);
|
|
798
|
+
remote?.emit({ paths: data, trees: [] });
|
|
670
799
|
};
|
|
671
800
|
this.channel.unref?.();
|
|
672
801
|
}
|
|
@@ -675,6 +804,16 @@ var OpfsFs = class {
|
|
|
675
804
|
for (const path of paths) this.forget(path);
|
|
676
805
|
this.channel?.postMessage(paths);
|
|
677
806
|
}
|
|
807
|
+
remoteChanges(listener) {
|
|
808
|
+
return this.remote?.listen(listener) ?? (() => {
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
announceWatch(paths) {
|
|
812
|
+
return this.remote?.announce(paths) ?? Promise.resolve();
|
|
813
|
+
}
|
|
814
|
+
committed(target) {
|
|
815
|
+
if (this.remote?.watched()) this.remote.wrote(target);
|
|
816
|
+
}
|
|
678
817
|
resolvePath(base, path) {
|
|
679
818
|
return resolvePath(base, path);
|
|
680
819
|
}
|
|
@@ -1026,15 +1165,18 @@ var OpfsFs = class {
|
|
|
1026
1165
|
);
|
|
1027
1166
|
}
|
|
1028
1167
|
async pwrite(path, offset, bytes) {
|
|
1029
|
-
const handle =
|
|
1168
|
+
const [handle, , target] = await this.fileFor(path);
|
|
1030
1169
|
await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
|
|
1170
|
+
this.committed(target);
|
|
1031
1171
|
}
|
|
1032
1172
|
async truncate(path, size) {
|
|
1033
1173
|
const handle = await this.existing(path, true);
|
|
1174
|
+
const target = this.remote?.watched() ? (await this.locate(path, true)).path : path;
|
|
1034
1175
|
await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
|
|
1176
|
+
this.committed(target);
|
|
1035
1177
|
}
|
|
1036
1178
|
async writeSession(path, truncate) {
|
|
1037
|
-
const handle =
|
|
1179
|
+
const [handle, , target] = await this.fileFor(path);
|
|
1038
1180
|
const writable2 = await this.retrying(
|
|
1039
1181
|
path,
|
|
1040
1182
|
() => handle.createWritable({ keepExistingData: !truncate }),
|
|
@@ -1047,9 +1189,12 @@ var OpfsFs = class {
|
|
|
1047
1189
|
truncate: (size) => writable2.truncate(size).catch((err) => {
|
|
1048
1190
|
throw translate(err, path, "write", false);
|
|
1049
1191
|
}),
|
|
1050
|
-
close:
|
|
1051
|
-
|
|
1052
|
-
|
|
1192
|
+
close: async () => {
|
|
1193
|
+
await writable2.close().catch((err) => {
|
|
1194
|
+
throw translate(err, path, "commit", false);
|
|
1195
|
+
});
|
|
1196
|
+
this.committed(target);
|
|
1197
|
+
}
|
|
1053
1198
|
};
|
|
1054
1199
|
}
|
|
1055
1200
|
async readFile(path) {
|
|
@@ -1098,6 +1243,7 @@ var OpfsFs = class {
|
|
|
1098
1243
|
await writable2?.abort().catch(() => void 0);
|
|
1099
1244
|
throw translate(err, path, "commit", false);
|
|
1100
1245
|
}
|
|
1246
|
+
this.committed(target);
|
|
1101
1247
|
}
|
|
1102
1248
|
async listing(path) {
|
|
1103
1249
|
const found = await this.locate(path, true);
|
|
@@ -1119,12 +1265,12 @@ var OpfsFs = class {
|
|
|
1119
1265
|
const base = dir === "/" ? "" : dir;
|
|
1120
1266
|
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
1121
1267
|
const handed2 = this.owners ? await this.handedTo(dir) : void 0;
|
|
1122
|
-
const
|
|
1268
|
+
const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
|
|
1123
1269
|
return Promise.all(
|
|
1124
1270
|
children2.map(async ([name2, child], i) => {
|
|
1125
|
-
if (!isHandle(child)) return [name2,
|
|
1271
|
+
if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
|
|
1126
1272
|
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
1127
|
-
return [name2, stat &&
|
|
1273
|
+
return [name2, stat && owned4(stat, metas[i])];
|
|
1128
1274
|
})
|
|
1129
1275
|
);
|
|
1130
1276
|
}
|
|
@@ -1733,6 +1879,7 @@ var Dac = class {
|
|
|
1733
1879
|
log;
|
|
1734
1880
|
seen = /* @__PURE__ */ new Set();
|
|
1735
1881
|
suppressed = false;
|
|
1882
|
+
tagOf = () => void 0;
|
|
1736
1883
|
constructor(mode, hook, log = (line) => console.warn(line)) {
|
|
1737
1884
|
this.mode = mode;
|
|
1738
1885
|
this.hook = hook;
|
|
@@ -1741,7 +1888,9 @@ var Dac = class {
|
|
|
1741
1888
|
get enforcing() {
|
|
1742
1889
|
return this.mode === "enforce";
|
|
1743
1890
|
}
|
|
1744
|
-
denied(
|
|
1891
|
+
denied(given) {
|
|
1892
|
+
const tag = given.tag ?? (given.pid ? this.tagOf(given.pid) : void 0);
|
|
1893
|
+
const denial = tag === void 0 ? given : { ...given, tag };
|
|
1745
1894
|
try {
|
|
1746
1895
|
this.hook?.({ ...denial });
|
|
1747
1896
|
} catch {
|
|
@@ -1759,7 +1908,7 @@ var Dac = class {
|
|
|
1759
1908
|
}
|
|
1760
1909
|
this.seen.add(key);
|
|
1761
1910
|
this.log(
|
|
1762
|
-
`slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
|
|
1911
|
+
`slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid}${d.tag === void 0 ? "" : `, tag ${d.tag}`})`
|
|
1763
1912
|
);
|
|
1764
1913
|
}
|
|
1765
1914
|
};
|
|
@@ -1775,7 +1924,8 @@ var Created = class {
|
|
|
1775
1924
|
}
|
|
1776
1925
|
};
|
|
1777
1926
|
function deny(g, op, path, errno = "EACCES") {
|
|
1778
|
-
g.
|
|
1927
|
+
const tag = g.tag === void 0 ? {} : { tag: g.tag };
|
|
1928
|
+
g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
|
|
1779
1929
|
}
|
|
1780
1930
|
var TTY_MODE = 400;
|
|
1781
1931
|
function ttyOpenable(g, tty) {
|
|
@@ -3745,9 +3895,9 @@ function ownerFor(l, self) {
|
|
|
3745
3895
|
if (!l.owners || !self) return void 0;
|
|
3746
3896
|
return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
|
|
3747
3897
|
}
|
|
3748
|
-
function guardFor(l, self) {
|
|
3898
|
+
function guardFor(l, self, tag) {
|
|
3749
3899
|
if (!l.dac || !self) return void 0;
|
|
3750
|
-
return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
|
|
3900
|
+
return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac, tag };
|
|
3751
3901
|
}
|
|
3752
3902
|
async function accountOf(l, user) {
|
|
3753
3903
|
const found = await l.users.lookup(user);
|
|
@@ -3851,6 +4001,7 @@ async function watchOp(l, req, watches, send, g) {
|
|
|
3851
4001
|
function serveClient(port, host) {
|
|
3852
4002
|
let state = "new";
|
|
3853
4003
|
let self;
|
|
4004
|
+
const guard2 = (l) => guardFor(l, self, host.tag);
|
|
3854
4005
|
const terminals = /* @__PURE__ */ new Map();
|
|
3855
4006
|
const groups = /* @__PURE__ */ new Set();
|
|
3856
4007
|
const sessions = /* @__PURE__ */ new Set();
|
|
@@ -3960,10 +4111,10 @@ function serveClient(port, host) {
|
|
|
3960
4111
|
if (!FS_METHODS.includes(req.method)) {
|
|
3961
4112
|
throw new Error(`unknown file system call ${req.method}`);
|
|
3962
4113
|
}
|
|
3963
|
-
return fsCall(l.processFs, req.method, req.args, ownerFor(l, self),
|
|
4114
|
+
return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guard2(l));
|
|
3964
4115
|
case "watch":
|
|
3965
4116
|
case "unwatch":
|
|
3966
|
-
return watchOp(l, req, watches, send,
|
|
4117
|
+
return watchOp(l, req, watches, send, guard2(l));
|
|
3967
4118
|
case "mount":
|
|
3968
4119
|
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
3969
4120
|
case "umount":
|
|
@@ -6624,6 +6775,136 @@ function binfmtOf(binfmts, path) {
|
|
|
6624
6775
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
6625
6776
|
}
|
|
6626
6777
|
|
|
6778
|
+
// src/fs/attest.ts
|
|
6779
|
+
var ATTEST_MAX = 1024 * 1024;
|
|
6780
|
+
var UNOWNED = 1e3;
|
|
6781
|
+
function refuse(code, path, why) {
|
|
6782
|
+
throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
|
|
6783
|
+
}
|
|
6784
|
+
function chainOf(path) {
|
|
6785
|
+
const parts2 = path.split("/").filter(Boolean);
|
|
6786
|
+
return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
|
|
6787
|
+
}
|
|
6788
|
+
function under2(path, target) {
|
|
6789
|
+
return path === target || path.startsWith(`${target}/`);
|
|
6790
|
+
}
|
|
6791
|
+
function owned(path, st) {
|
|
6792
|
+
return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
|
|
6793
|
+
}
|
|
6794
|
+
function unchanged(a, b, bytes) {
|
|
6795
|
+
return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
|
|
6796
|
+
}
|
|
6797
|
+
async function sha256(bytes) {
|
|
6798
|
+
const digest = new Uint8Array(
|
|
6799
|
+
await crypto.subtle.digest("SHA-256", bytes)
|
|
6800
|
+
);
|
|
6801
|
+
return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6802
|
+
}
|
|
6803
|
+
function attested(path, st, bytes, digest, o, parents) {
|
|
6804
|
+
const { uid, gid, mode } = owned(path, st);
|
|
6805
|
+
return {
|
|
6806
|
+
path,
|
|
6807
|
+
bytes,
|
|
6808
|
+
uid,
|
|
6809
|
+
gid,
|
|
6810
|
+
mode,
|
|
6811
|
+
ino: st.ino,
|
|
6812
|
+
mtimeMs: st.mtime.getTime(),
|
|
6813
|
+
sha256: digest,
|
|
6814
|
+
users: o.users,
|
|
6815
|
+
onRootFs: true,
|
|
6816
|
+
parents
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
async function trustedParents(fs, path, o) {
|
|
6820
|
+
const parents = [];
|
|
6821
|
+
for (const dir of chainOf(path).slice(0, -1)) {
|
|
6822
|
+
const st = await fs.lstat(dir);
|
|
6823
|
+
if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
|
|
6824
|
+
if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
|
|
6825
|
+
const perm = owned(dir, st);
|
|
6826
|
+
if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
|
|
6827
|
+
refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
|
|
6828
|
+
}
|
|
6829
|
+
parents.push(perm);
|
|
6830
|
+
}
|
|
6831
|
+
return parents;
|
|
6832
|
+
}
|
|
6833
|
+
async function attest(fs, asked, o) {
|
|
6834
|
+
if (typeof asked !== "string" || !asked.startsWith("/")) {
|
|
6835
|
+
refuse("EINVAL", String(asked), "not an absolute path");
|
|
6836
|
+
}
|
|
6837
|
+
const path = normalizePath(asked);
|
|
6838
|
+
const mount = o.mounts.find((target) => {
|
|
6839
|
+
const at = normalizePath(target);
|
|
6840
|
+
return at === "/" || under2(path, at);
|
|
6841
|
+
});
|
|
6842
|
+
if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
|
|
6843
|
+
const parents = await trustedParents(fs, path, o);
|
|
6844
|
+
for (let tries = 0; ; tries++) {
|
|
6845
|
+
const before = await fs.lstat(path);
|
|
6846
|
+
if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
|
|
6847
|
+
if (!before.isFile)
|
|
6848
|
+
refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
|
|
6849
|
+
if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
|
|
6850
|
+
const bytes = (await fs.readFileBuffer(path)).slice();
|
|
6851
|
+
const after = await fs.lstat(path);
|
|
6852
|
+
if (unchanged(before, after, bytes)) {
|
|
6853
|
+
return attested(path, after, bytes, await sha256(bytes), o, parents);
|
|
6854
|
+
}
|
|
6855
|
+
if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
var WATCH_DEBOUNCE_MS = 50;
|
|
6859
|
+
var FileWatches = class {
|
|
6860
|
+
live = /* @__PURE__ */ new Set();
|
|
6861
|
+
local;
|
|
6862
|
+
remote;
|
|
6863
|
+
constructor(local, remote) {
|
|
6864
|
+
this.local = local;
|
|
6865
|
+
this.remote = remote;
|
|
6866
|
+
}
|
|
6867
|
+
announce() {
|
|
6868
|
+
return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
|
|
6869
|
+
}
|
|
6870
|
+
async watch(paths, notify) {
|
|
6871
|
+
const files = paths.map(normalizePath);
|
|
6872
|
+
const timers = /* @__PURE__ */ new Map();
|
|
6873
|
+
let active = true;
|
|
6874
|
+
const fire = (changed, trees) => {
|
|
6875
|
+
for (const file of files) {
|
|
6876
|
+
const hit = [...changed, ...trees].some((p) => under2(file, normalizePath(p)));
|
|
6877
|
+
if (!hit) continue;
|
|
6878
|
+
clearTimeout(timers.get(file));
|
|
6879
|
+
timers.set(
|
|
6880
|
+
file,
|
|
6881
|
+
setTimeout(() => {
|
|
6882
|
+
timers.delete(file);
|
|
6883
|
+
if (active) notify(file);
|
|
6884
|
+
}, WATCH_DEBOUNCE_MS)
|
|
6885
|
+
);
|
|
6886
|
+
}
|
|
6887
|
+
};
|
|
6888
|
+
const unwatch = this.local.watch(
|
|
6889
|
+
files,
|
|
6890
|
+
{ recursive: false },
|
|
6891
|
+
(change) => fire("overflow" in change ? files : change.paths, [])
|
|
6892
|
+
);
|
|
6893
|
+
const unlisten = this.remote.remoteChanges?.((change) => fire(change.paths, change.trees));
|
|
6894
|
+
this.live.add(files);
|
|
6895
|
+
await this.announce();
|
|
6896
|
+
return () => {
|
|
6897
|
+
if (!active) return;
|
|
6898
|
+
active = false;
|
|
6899
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
6900
|
+
unwatch();
|
|
6901
|
+
unlisten?.();
|
|
6902
|
+
this.live.delete(files);
|
|
6903
|
+
void this.announce();
|
|
6904
|
+
};
|
|
6905
|
+
}
|
|
6906
|
+
};
|
|
6907
|
+
|
|
6627
6908
|
// src/fs/commands.ts
|
|
6628
6909
|
var S_IFDIR2 = 16384;
|
|
6629
6910
|
var S_IFREG2 = 32768;
|
|
@@ -6662,7 +6943,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
6662
6943
|
if (st || path === "/") return st ?? {};
|
|
6663
6944
|
return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
|
|
6664
6945
|
}
|
|
6665
|
-
async function
|
|
6946
|
+
async function owned4(stat2, dir) {
|
|
6666
6947
|
if (!owners) return stat2;
|
|
6667
6948
|
const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
|
|
6668
6949
|
return { ...stat2, uid, gid };
|
|
@@ -6676,17 +6957,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
6676
6957
|
}
|
|
6677
6958
|
async function virtual(path) {
|
|
6678
6959
|
if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
|
|
6679
|
-
return
|
|
6960
|
+
return owned4(synthetic(true, path));
|
|
6680
6961
|
}
|
|
6681
6962
|
const name2 = COMMAND.exec(path)?.[1];
|
|
6682
6963
|
if (name2 !== void 0) {
|
|
6683
|
-
return (await names()).has(name2) ?
|
|
6964
|
+
return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
|
|
6684
6965
|
}
|
|
6685
|
-
if ((await userDir(path))?.size) return
|
|
6966
|
+
if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
|
|
6686
6967
|
const slash = path.lastIndexOf("/");
|
|
6687
6968
|
const dir = path.slice(0, slash);
|
|
6688
6969
|
const own = await userDir(dir);
|
|
6689
|
-
return own?.has(path.slice(slash + 1)) ?
|
|
6970
|
+
return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
|
|
6690
6971
|
}
|
|
6691
6972
|
async function linked(path) {
|
|
6692
6973
|
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
@@ -7035,11 +7316,11 @@ var UnlinkedKeeper = class {
|
|
|
7035
7316
|
|
|
7036
7317
|
// src/fs/watch.ts
|
|
7037
7318
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
7038
|
-
var
|
|
7319
|
+
var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
7039
7320
|
var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7040
7321
|
function touches(watcher, changed) {
|
|
7041
7322
|
return watcher.paths.some(
|
|
7042
|
-
(watched) => changed === watched ||
|
|
7323
|
+
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
|
|
7043
7324
|
);
|
|
7044
7325
|
}
|
|
7045
7326
|
var FsWatchers = class {
|
|
@@ -8758,7 +9039,7 @@ async function keptStat(entry, req) {
|
|
|
8758
9039
|
return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
|
|
8759
9040
|
return json2(statJson(await entry.fs.stat(hidden)));
|
|
8760
9041
|
}
|
|
8761
|
-
async function
|
|
9042
|
+
async function owned2(entry, own, req, path, target) {
|
|
8762
9043
|
const source = target === path ? void 0 : path;
|
|
8763
9044
|
if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
|
|
8764
9045
|
return run(entry, path, req);
|
|
@@ -8813,7 +9094,7 @@ async function dispatchOne(entry, req) {
|
|
|
8813
9094
|
}
|
|
8814
9095
|
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
8815
9096
|
const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
8816
|
-
return await
|
|
9097
|
+
return await owned2(entry, entry.own, req, path, target);
|
|
8817
9098
|
} catch (err) {
|
|
8818
9099
|
return toErrno(err);
|
|
8819
9100
|
}
|
|
@@ -11668,6 +11949,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11668
11949
|
"proc-sudo",
|
|
11669
11950
|
"proc-run",
|
|
11670
11951
|
"proc-exit",
|
|
11952
|
+
"proc-kill-self",
|
|
11671
11953
|
"proc-fork",
|
|
11672
11954
|
"proc-kill",
|
|
11673
11955
|
"thread-kill",
|
|
@@ -11795,6 +12077,20 @@ var WasmProcess = class {
|
|
|
11795
12077
|
else this.adoptExec(pid);
|
|
11796
12078
|
return pid;
|
|
11797
12079
|
}
|
|
12080
|
+
async killSelf(sig, target, probe) {
|
|
12081
|
+
if (!this.options.exitSignal) throw new KernelError("ENOSYS");
|
|
12082
|
+
if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
|
|
12083
|
+
const { caught, ignored } = this.signalMasks();
|
|
12084
|
+
const to = target === 0 ? -this.pgid() : target ?? this.pid;
|
|
12085
|
+
if (sig !== SIG.KILL && (caught | ignored) & sigbit(sig)) {
|
|
12086
|
+
if (!await this.options.kill?.(to, sig)) throw new KernelError("ESRCH");
|
|
12087
|
+
return { ok: true, kind: "void" };
|
|
12088
|
+
}
|
|
12089
|
+
if (probe) return { ok: true, kind: "json", json: "terminate" };
|
|
12090
|
+
if (to !== this.pid) await this.options.kill?.(to, sig);
|
|
12091
|
+
this.options.exitSignal(sig);
|
|
12092
|
+
return DYING;
|
|
12093
|
+
}
|
|
11798
12094
|
run(req) {
|
|
11799
12095
|
return runChild(req, {
|
|
11800
12096
|
children: this.children,
|
|
@@ -12392,6 +12688,8 @@ var WasmProcess = class {
|
|
|
12392
12688
|
if (!this.options.exitNow) throw new KernelError("ENOSYS");
|
|
12393
12689
|
this.options.exitNow(req.status & 255);
|
|
12394
12690
|
return DYING;
|
|
12691
|
+
case "proc-kill-self":
|
|
12692
|
+
return this.killSelf(req.sig, req.target, req.probe);
|
|
12395
12693
|
case "proc-identity":
|
|
12396
12694
|
return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
|
|
12397
12695
|
case "proc-exec": {
|
|
@@ -12826,6 +13124,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
12826
13124
|
raise: hooks.raise,
|
|
12827
13125
|
threadKill: hooks.threadKill,
|
|
12828
13126
|
...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
|
|
13127
|
+
...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
|
|
12829
13128
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
|
|
12830
13129
|
};
|
|
12831
13130
|
}
|
|
@@ -12859,7 +13158,10 @@ function spawnWasmProcess(opts) {
|
|
|
12859
13158
|
{
|
|
12860
13159
|
raise: (sig) => signal(sig),
|
|
12861
13160
|
threadKill: (tid, sig) => killThread(threadSignals.get(tid), process.signalMasks().caught, sig),
|
|
12862
|
-
...opts.program.abi === "js" ? {
|
|
13161
|
+
...opts.program.abi === "js" ? {
|
|
13162
|
+
exitNow: (code) => exitNow(code),
|
|
13163
|
+
exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
|
|
13164
|
+
} : {}
|
|
12863
13165
|
},
|
|
12864
13166
|
created3
|
|
12865
13167
|
)
|
|
@@ -14664,8 +14966,8 @@ var ChunkedBytes = class {
|
|
|
14664
14966
|
}
|
|
14665
14967
|
return out;
|
|
14666
14968
|
}
|
|
14667
|
-
write(offset, bytes,
|
|
14668
|
-
const whole =
|
|
14969
|
+
write(offset, bytes, owned4 = false) {
|
|
14970
|
+
const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
|
|
14669
14971
|
for (let done4 = 0; done4 < bytes.length; ) {
|
|
14670
14972
|
const at = offset + done4;
|
|
14671
14973
|
const index = Math.floor(at / CHUNK3);
|
|
@@ -14715,7 +15017,7 @@ function created2(ino, kind, mode, from, owner) {
|
|
|
14715
15017
|
gid: owner?.gid ?? from?.gid ?? 0
|
|
14716
15018
|
};
|
|
14717
15019
|
}
|
|
14718
|
-
function
|
|
15020
|
+
function owned3(node, change) {
|
|
14719
15021
|
if (change.uid !== void 0) node.uid = change.uid;
|
|
14720
15022
|
if (change.gid !== void 0) node.gid = change.gid;
|
|
14721
15023
|
}
|
|
@@ -14886,7 +15188,7 @@ function tmpfs() {
|
|
|
14886
15188
|
node.mtime = Date.now();
|
|
14887
15189
|
}
|
|
14888
15190
|
if (change.mtime !== void 0) node.mtime = change.mtime;
|
|
14889
|
-
|
|
15191
|
+
owned3(node, change);
|
|
14890
15192
|
},
|
|
14891
15193
|
async statfs() {
|
|
14892
15194
|
return { bsize: 4096, blocks: 262144, bfree: 262144 };
|
|
@@ -15635,12 +15937,16 @@ var Launcher = class {
|
|
|
15635
15937
|
usersMode;
|
|
15636
15938
|
dac;
|
|
15637
15939
|
sealed;
|
|
15940
|
+
rootFs;
|
|
15941
|
+
fileWatches;
|
|
15638
15942
|
nextPid = 1e3;
|
|
15639
15943
|
boot = Date.now();
|
|
15640
15944
|
terminals = 0;
|
|
15641
15945
|
constructor(options) {
|
|
15642
15946
|
this.usersMode = usersMode(options.users);
|
|
15947
|
+
this.rootFs = options.fs;
|
|
15643
15948
|
this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
|
|
15949
|
+
if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
|
|
15644
15950
|
this.sealed = new SealedBroker(
|
|
15645
15951
|
options.sealed,
|
|
15646
15952
|
this.usersMode,
|
|
@@ -15890,7 +16196,7 @@ var Launcher = class {
|
|
|
15890
16196
|
async mayExec(cred, req, planned) {
|
|
15891
16197
|
const { dac } = this;
|
|
15892
16198
|
if (!dac) return;
|
|
15893
|
-
const g = { fs: this.fs, cred, pid: req.pid, dac };
|
|
16199
|
+
const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
|
|
15894
16200
|
const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
|
|
15895
16201
|
if (byPath(req.file)) {
|
|
15896
16202
|
const path = this.fs.resolvePath(req.cwd, req.file);
|
|
@@ -16393,6 +16699,13 @@ var Launcher = class {
|
|
|
16393
16699
|
kill(pid, sig, sender, from) {
|
|
16394
16700
|
const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
|
|
16395
16701
|
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;
|
|
16702
|
+
if (pid === -1) {
|
|
16703
|
+
const sender2 = from === void 0 ? void 0 : this.imageOf(from);
|
|
16704
|
+
return this.killAll(
|
|
16705
|
+
sig,
|
|
16706
|
+
(member) => this.imageOf(member) !== sender2 && may(this.imageOf(member))
|
|
16707
|
+
);
|
|
16708
|
+
}
|
|
16396
16709
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
16397
16710
|
const handle = this.processes.get(pid);
|
|
16398
16711
|
if (!handle) return false;
|
|
@@ -16400,6 +16713,15 @@ var Launcher = class {
|
|
|
16400
16713
|
if (sig !== 0) handle.signal(sig);
|
|
16401
16714
|
return true;
|
|
16402
16715
|
}
|
|
16716
|
+
killAll(sig, may) {
|
|
16717
|
+
let any = false;
|
|
16718
|
+
for (const [pid, handle] of [...this.processes]) {
|
|
16719
|
+
if (pid === 1 || !may(pid)) continue;
|
|
16720
|
+
any = true;
|
|
16721
|
+
if (sig !== 0) handle.signal(sig);
|
|
16722
|
+
}
|
|
16723
|
+
return any;
|
|
16724
|
+
}
|
|
16403
16725
|
async driver(type, spec) {
|
|
16404
16726
|
if (type === "fsa") return this.fsaDriver(spec);
|
|
16405
16727
|
if (type === "hostfs") {
|
|
@@ -16489,6 +16811,16 @@ var Launcher = class {
|
|
|
16489
16811
|
}
|
|
16490
16812
|
};
|
|
16491
16813
|
}
|
|
16814
|
+
attest(path) {
|
|
16815
|
+
return attest(this.rootFs, path, {
|
|
16816
|
+
users: this.usersMode,
|
|
16817
|
+
mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
|
|
16818
|
+
});
|
|
16819
|
+
}
|
|
16820
|
+
watchFiles(paths, notify) {
|
|
16821
|
+
this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
|
|
16822
|
+
return this.fileWatches.watch(paths, notify);
|
|
16823
|
+
}
|
|
16492
16824
|
async mount(spec) {
|
|
16493
16825
|
this.guardMount(spec.target);
|
|
16494
16826
|
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
@@ -16669,7 +17001,7 @@ var Launcher = class {
|
|
|
16669
17001
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
16670
17002
|
};
|
|
16671
17003
|
}
|
|
16672
|
-
async starting(argv, dir, account) {
|
|
17004
|
+
async starting(argv, dir, account, tag) {
|
|
16673
17005
|
this.resetCatalogs();
|
|
16674
17006
|
const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
|
|
16675
17007
|
const start2 = dir ?? "/";
|
|
@@ -16677,13 +17009,13 @@ var Launcher = class {
|
|
|
16677
17009
|
const [file = ""] = argv;
|
|
16678
17010
|
if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
|
|
16679
17011
|
const planned = await this.plan(file, argv, cwd, scope);
|
|
16680
|
-
await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
|
|
17012
|
+
await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
|
|
16681
17013
|
return { cwd, file, planned };
|
|
16682
17014
|
}
|
|
16683
17015
|
async openTerminal(argv, options) {
|
|
16684
17016
|
tagOption(options.tag);
|
|
16685
17017
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
16686
|
-
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
17018
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
|
|
16687
17019
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
16688
17020
|
let leader = 0;
|
|
16689
17021
|
const tty = new KernelTty(
|
|
@@ -16734,7 +17066,7 @@ var Launcher = class {
|
|
|
16734
17066
|
async run(argv, options = {}) {
|
|
16735
17067
|
tagOption(options.tag);
|
|
16736
17068
|
const account = await this.account(options.user);
|
|
16737
|
-
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
17069
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
|
|
16738
17070
|
const env = this.environment(cwd, options.env, account);
|
|
16739
17071
|
const out = [];
|
|
16740
17072
|
const err = [];
|
|
@@ -16893,9 +17225,31 @@ var USERS_OPS = /* @__PURE__ */ new Set([
|
|
|
16893
17225
|
"groups-add-member",
|
|
16894
17226
|
"groups-remove-member"
|
|
16895
17227
|
]);
|
|
17228
|
+
var ATTEST_OPS = /* @__PURE__ */ new Set(["attest", "watch-files", "unwatch-files"]);
|
|
17229
|
+
function isAttestRequest(req) {
|
|
17230
|
+
return ATTEST_OPS.has(req.op);
|
|
17231
|
+
}
|
|
16896
17232
|
function isUsersRequest(req) {
|
|
16897
17233
|
return USERS_OPS.has(req.op);
|
|
16898
17234
|
}
|
|
17235
|
+
function transferOf2(req, result) {
|
|
17236
|
+
if (result instanceof MessagePort) return [result];
|
|
17237
|
+
if (req.op === "attest") return [result.bytes.buffer];
|
|
17238
|
+
return void 0;
|
|
17239
|
+
}
|
|
17240
|
+
function attestOps(port) {
|
|
17241
|
+
const watches = /* @__PURE__ */ new Map();
|
|
17242
|
+
return async (l, req) => {
|
|
17243
|
+
if (req.op === "attest") return l.attest(req.path);
|
|
17244
|
+
if (req.op === "unwatch-files") {
|
|
17245
|
+
watches.get(req.watch)?.();
|
|
17246
|
+
return watches.delete(req.watch);
|
|
17247
|
+
}
|
|
17248
|
+
const notify = (path) => port.postMessage({ fileChanged: { watch: req.id, path } });
|
|
17249
|
+
watches.set(req.id, await l.watchFiles(req.paths, notify));
|
|
17250
|
+
return req.id;
|
|
17251
|
+
};
|
|
17252
|
+
}
|
|
16899
17253
|
function failure(err) {
|
|
16900
17254
|
const code = err?.code;
|
|
16901
17255
|
return {
|
|
@@ -17034,8 +17388,10 @@ function serveKernel(port, deps) {
|
|
|
17034
17388
|
if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
|
|
17035
17389
|
return l.mounts.list();
|
|
17036
17390
|
}
|
|
17391
|
+
const attestOp = attestOps(port);
|
|
17037
17392
|
async function handle(req) {
|
|
17038
17393
|
if (req.op === "init") return init(req);
|
|
17394
|
+
if (isAttestRequest(req)) return attestOp(await ready2(), req);
|
|
17039
17395
|
if (req.op === "open-terminal") return terminal(req);
|
|
17040
17396
|
if (req.op === "connect") return connect(req.user, req.tag);
|
|
17041
17397
|
if (isUsersRequest(req)) return usersOp(await ready2(), req);
|
|
@@ -17084,7 +17440,7 @@ function serveKernel(port, deps) {
|
|
|
17084
17440
|
if (answered(event.data, remote, uplink, approvals)) return;
|
|
17085
17441
|
const req = event.data;
|
|
17086
17442
|
handle(req).then(
|
|
17087
|
-
(result) => reply(req.id, { result },
|
|
17443
|
+
(result) => reply(req.id, { result }, transferOf2(req, result)),
|
|
17088
17444
|
(err) => reply(req.id, failure(err))
|
|
17089
17445
|
);
|
|
17090
17446
|
});
|