@ai-ecoverse/slicc-kernel 1.71.0 → 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 +21 -4
- package/dist/index.js +29 -0
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +400 -37
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +43 -7
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +367 -35
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +43 -7
- package/dist/process-worker.js.map +2 -2
- package/package.json +5 -4
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
|
}
|
|
@@ -1188,7 +1334,14 @@ var OpfsFs = class {
|
|
|
1188
1334
|
links() {
|
|
1189
1335
|
return false;
|
|
1190
1336
|
}
|
|
1191
|
-
async link(from) {
|
|
1337
|
+
async link(from, to, follow2) {
|
|
1338
|
+
await (follow2 ? this.stat(from) : this.lstat(from));
|
|
1339
|
+
if (!(await this.stat(parentOf3(to))).isDirectory) throw fsError("ENOTDIR", to);
|
|
1340
|
+
if (await this.lstat(to).then(
|
|
1341
|
+
() => true,
|
|
1342
|
+
() => false
|
|
1343
|
+
))
|
|
1344
|
+
throw fsError("EEXIST", to);
|
|
1192
1345
|
throw fsError("EPERM", `${from}: this file system has no hard links`);
|
|
1193
1346
|
}
|
|
1194
1347
|
async rename(from, to) {
|
|
@@ -1726,6 +1879,7 @@ var Dac = class {
|
|
|
1726
1879
|
log;
|
|
1727
1880
|
seen = /* @__PURE__ */ new Set();
|
|
1728
1881
|
suppressed = false;
|
|
1882
|
+
tagOf = () => void 0;
|
|
1729
1883
|
constructor(mode, hook, log = (line) => console.warn(line)) {
|
|
1730
1884
|
this.mode = mode;
|
|
1731
1885
|
this.hook = hook;
|
|
@@ -1734,7 +1888,9 @@ var Dac = class {
|
|
|
1734
1888
|
get enforcing() {
|
|
1735
1889
|
return this.mode === "enforce";
|
|
1736
1890
|
}
|
|
1737
|
-
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 };
|
|
1738
1894
|
try {
|
|
1739
1895
|
this.hook?.({ ...denial });
|
|
1740
1896
|
} catch {
|
|
@@ -1752,7 +1908,7 @@ var Dac = class {
|
|
|
1752
1908
|
}
|
|
1753
1909
|
this.seen.add(key);
|
|
1754
1910
|
this.log(
|
|
1755
|
-
`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}`})`
|
|
1756
1912
|
);
|
|
1757
1913
|
}
|
|
1758
1914
|
};
|
|
@@ -1768,7 +1924,8 @@ var Created = class {
|
|
|
1768
1924
|
}
|
|
1769
1925
|
};
|
|
1770
1926
|
function deny(g, op, path, errno = "EACCES") {
|
|
1771
|
-
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 });
|
|
1772
1929
|
}
|
|
1773
1930
|
var TTY_MODE = 400;
|
|
1774
1931
|
function ttyOpenable(g, tty) {
|
|
@@ -3738,9 +3895,9 @@ function ownerFor(l, self) {
|
|
|
3738
3895
|
if (!l.owners || !self) return void 0;
|
|
3739
3896
|
return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
|
|
3740
3897
|
}
|
|
3741
|
-
function guardFor(l, self) {
|
|
3898
|
+
function guardFor(l, self, tag) {
|
|
3742
3899
|
if (!l.dac || !self) return void 0;
|
|
3743
|
-
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 };
|
|
3744
3901
|
}
|
|
3745
3902
|
async function accountOf(l, user) {
|
|
3746
3903
|
const found = await l.users.lookup(user);
|
|
@@ -3844,6 +4001,7 @@ async function watchOp(l, req, watches, send, g) {
|
|
|
3844
4001
|
function serveClient(port, host) {
|
|
3845
4002
|
let state = "new";
|
|
3846
4003
|
let self;
|
|
4004
|
+
const guard2 = (l) => guardFor(l, self, host.tag);
|
|
3847
4005
|
const terminals = /* @__PURE__ */ new Map();
|
|
3848
4006
|
const groups = /* @__PURE__ */ new Set();
|
|
3849
4007
|
const sessions = /* @__PURE__ */ new Set();
|
|
@@ -3953,10 +4111,10 @@ function serveClient(port, host) {
|
|
|
3953
4111
|
if (!FS_METHODS.includes(req.method)) {
|
|
3954
4112
|
throw new Error(`unknown file system call ${req.method}`);
|
|
3955
4113
|
}
|
|
3956
|
-
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));
|
|
3957
4115
|
case "watch":
|
|
3958
4116
|
case "unwatch":
|
|
3959
|
-
return watchOp(l, req, watches, send,
|
|
4117
|
+
return watchOp(l, req, watches, send, guard2(l));
|
|
3960
4118
|
case "mount":
|
|
3961
4119
|
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
3962
4120
|
case "umount":
|
|
@@ -6617,6 +6775,136 @@ function binfmtOf(binfmts, path) {
|
|
|
6617
6775
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
6618
6776
|
}
|
|
6619
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
|
+
|
|
6620
6908
|
// src/fs/commands.ts
|
|
6621
6909
|
var S_IFDIR2 = 16384;
|
|
6622
6910
|
var S_IFREG2 = 32768;
|
|
@@ -6655,7 +6943,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
6655
6943
|
if (st || path === "/") return st ?? {};
|
|
6656
6944
|
return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
|
|
6657
6945
|
}
|
|
6658
|
-
async function
|
|
6946
|
+
async function owned4(stat2, dir) {
|
|
6659
6947
|
if (!owners) return stat2;
|
|
6660
6948
|
const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
|
|
6661
6949
|
return { ...stat2, uid, gid };
|
|
@@ -6669,17 +6957,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
6669
6957
|
}
|
|
6670
6958
|
async function virtual(path) {
|
|
6671
6959
|
if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
|
|
6672
|
-
return
|
|
6960
|
+
return owned4(synthetic(true, path));
|
|
6673
6961
|
}
|
|
6674
6962
|
const name2 = COMMAND.exec(path)?.[1];
|
|
6675
6963
|
if (name2 !== void 0) {
|
|
6676
|
-
return (await names()).has(name2) ?
|
|
6964
|
+
return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
|
|
6677
6965
|
}
|
|
6678
|
-
if ((await userDir(path))?.size) return
|
|
6966
|
+
if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
|
|
6679
6967
|
const slash = path.lastIndexOf("/");
|
|
6680
6968
|
const dir = path.slice(0, slash);
|
|
6681
6969
|
const own = await userDir(dir);
|
|
6682
|
-
return own?.has(path.slice(slash + 1)) ?
|
|
6970
|
+
return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
|
|
6683
6971
|
}
|
|
6684
6972
|
async function linked(path) {
|
|
6685
6973
|
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
@@ -7028,11 +7316,11 @@ var UnlinkedKeeper = class {
|
|
|
7028
7316
|
|
|
7029
7317
|
// src/fs/watch.ts
|
|
7030
7318
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
7031
|
-
var
|
|
7319
|
+
var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
7032
7320
|
var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7033
7321
|
function touches(watcher, changed) {
|
|
7034
7322
|
return watcher.paths.some(
|
|
7035
|
-
(watched) => changed === watched ||
|
|
7323
|
+
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
|
|
7036
7324
|
);
|
|
7037
7325
|
}
|
|
7038
7326
|
var FsWatchers = class {
|
|
@@ -8751,7 +9039,7 @@ async function keptStat(entry, req) {
|
|
|
8751
9039
|
return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
|
|
8752
9040
|
return json2(statJson(await entry.fs.stat(hidden)));
|
|
8753
9041
|
}
|
|
8754
|
-
async function
|
|
9042
|
+
async function owned2(entry, own, req, path, target) {
|
|
8755
9043
|
const source = target === path ? void 0 : path;
|
|
8756
9044
|
if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
|
|
8757
9045
|
return run(entry, path, req);
|
|
@@ -8806,7 +9094,7 @@ async function dispatchOne(entry, req) {
|
|
|
8806
9094
|
}
|
|
8807
9095
|
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
8808
9096
|
const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
8809
|
-
return await
|
|
9097
|
+
return await owned2(entry, entry.own, req, path, target);
|
|
8810
9098
|
} catch (err) {
|
|
8811
9099
|
return toErrno(err);
|
|
8812
9100
|
}
|
|
@@ -11661,6 +11949,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11661
11949
|
"proc-sudo",
|
|
11662
11950
|
"proc-run",
|
|
11663
11951
|
"proc-exit",
|
|
11952
|
+
"proc-kill-self",
|
|
11664
11953
|
"proc-fork",
|
|
11665
11954
|
"proc-kill",
|
|
11666
11955
|
"thread-kill",
|
|
@@ -11788,6 +12077,20 @@ var WasmProcess = class {
|
|
|
11788
12077
|
else this.adoptExec(pid);
|
|
11789
12078
|
return pid;
|
|
11790
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
|
+
}
|
|
11791
12094
|
run(req) {
|
|
11792
12095
|
return runChild(req, {
|
|
11793
12096
|
children: this.children,
|
|
@@ -12385,6 +12688,8 @@ var WasmProcess = class {
|
|
|
12385
12688
|
if (!this.options.exitNow) throw new KernelError("ENOSYS");
|
|
12386
12689
|
this.options.exitNow(req.status & 255);
|
|
12387
12690
|
return DYING;
|
|
12691
|
+
case "proc-kill-self":
|
|
12692
|
+
return this.killSelf(req.sig, req.target, req.probe);
|
|
12388
12693
|
case "proc-identity":
|
|
12389
12694
|
return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
|
|
12390
12695
|
case "proc-exec": {
|
|
@@ -12819,6 +13124,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
12819
13124
|
raise: hooks.raise,
|
|
12820
13125
|
threadKill: hooks.threadKill,
|
|
12821
13126
|
...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
|
|
13127
|
+
...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
|
|
12822
13128
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
|
|
12823
13129
|
};
|
|
12824
13130
|
}
|
|
@@ -12852,7 +13158,10 @@ function spawnWasmProcess(opts) {
|
|
|
12852
13158
|
{
|
|
12853
13159
|
raise: (sig) => signal(sig),
|
|
12854
13160
|
threadKill: (tid, sig) => killThread(threadSignals.get(tid), process.signalMasks().caught, sig),
|
|
12855
|
-
...opts.program.abi === "js" ? {
|
|
13161
|
+
...opts.program.abi === "js" ? {
|
|
13162
|
+
exitNow: (code) => exitNow(code),
|
|
13163
|
+
exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
|
|
13164
|
+
} : {}
|
|
12856
13165
|
},
|
|
12857
13166
|
created3
|
|
12858
13167
|
)
|
|
@@ -14657,8 +14966,8 @@ var ChunkedBytes = class {
|
|
|
14657
14966
|
}
|
|
14658
14967
|
return out;
|
|
14659
14968
|
}
|
|
14660
|
-
write(offset, bytes,
|
|
14661
|
-
const whole =
|
|
14969
|
+
write(offset, bytes, owned4 = false) {
|
|
14970
|
+
const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
|
|
14662
14971
|
for (let done4 = 0; done4 < bytes.length; ) {
|
|
14663
14972
|
const at = offset + done4;
|
|
14664
14973
|
const index = Math.floor(at / CHUNK3);
|
|
@@ -14708,7 +15017,7 @@ function created2(ino, kind, mode, from, owner) {
|
|
|
14708
15017
|
gid: owner?.gid ?? from?.gid ?? 0
|
|
14709
15018
|
};
|
|
14710
15019
|
}
|
|
14711
|
-
function
|
|
15020
|
+
function owned3(node, change) {
|
|
14712
15021
|
if (change.uid !== void 0) node.uid = change.uid;
|
|
14713
15022
|
if (change.gid !== void 0) node.gid = change.gid;
|
|
14714
15023
|
}
|
|
@@ -14879,7 +15188,7 @@ function tmpfs() {
|
|
|
14879
15188
|
node.mtime = Date.now();
|
|
14880
15189
|
}
|
|
14881
15190
|
if (change.mtime !== void 0) node.mtime = change.mtime;
|
|
14882
|
-
|
|
15191
|
+
owned3(node, change);
|
|
14883
15192
|
},
|
|
14884
15193
|
async statfs() {
|
|
14885
15194
|
return { bsize: 4096, blocks: 262144, bfree: 262144 };
|
|
@@ -15628,12 +15937,16 @@ var Launcher = class {
|
|
|
15628
15937
|
usersMode;
|
|
15629
15938
|
dac;
|
|
15630
15939
|
sealed;
|
|
15940
|
+
rootFs;
|
|
15941
|
+
fileWatches;
|
|
15631
15942
|
nextPid = 1e3;
|
|
15632
15943
|
boot = Date.now();
|
|
15633
15944
|
terminals = 0;
|
|
15634
15945
|
constructor(options) {
|
|
15635
15946
|
this.usersMode = usersMode(options.users);
|
|
15947
|
+
this.rootFs = options.fs;
|
|
15636
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);
|
|
15637
15950
|
this.sealed = new SealedBroker(
|
|
15638
15951
|
options.sealed,
|
|
15639
15952
|
this.usersMode,
|
|
@@ -15883,7 +16196,7 @@ var Launcher = class {
|
|
|
15883
16196
|
async mayExec(cred, req, planned) {
|
|
15884
16197
|
const { dac } = this;
|
|
15885
16198
|
if (!dac) return;
|
|
15886
|
-
const g = { fs: this.fs, cred, pid: req.pid, dac };
|
|
16199
|
+
const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
|
|
15887
16200
|
const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
|
|
15888
16201
|
if (byPath(req.file)) {
|
|
15889
16202
|
const path = this.fs.resolvePath(req.cwd, req.file);
|
|
@@ -16386,6 +16699,13 @@ var Launcher = class {
|
|
|
16386
16699
|
kill(pid, sig, sender, from) {
|
|
16387
16700
|
const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
|
|
16388
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
|
+
}
|
|
16389
16709
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
16390
16710
|
const handle = this.processes.get(pid);
|
|
16391
16711
|
if (!handle) return false;
|
|
@@ -16393,6 +16713,15 @@ var Launcher = class {
|
|
|
16393
16713
|
if (sig !== 0) handle.signal(sig);
|
|
16394
16714
|
return true;
|
|
16395
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
|
+
}
|
|
16396
16725
|
async driver(type, spec) {
|
|
16397
16726
|
if (type === "fsa") return this.fsaDriver(spec);
|
|
16398
16727
|
if (type === "hostfs") {
|
|
@@ -16482,6 +16811,16 @@ var Launcher = class {
|
|
|
16482
16811
|
}
|
|
16483
16812
|
};
|
|
16484
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
|
+
}
|
|
16485
16824
|
async mount(spec) {
|
|
16486
16825
|
this.guardMount(spec.target);
|
|
16487
16826
|
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
@@ -16662,7 +17001,7 @@ var Launcher = class {
|
|
|
16662
17001
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
16663
17002
|
};
|
|
16664
17003
|
}
|
|
16665
|
-
async starting(argv, dir, account) {
|
|
17004
|
+
async starting(argv, dir, account, tag) {
|
|
16666
17005
|
this.resetCatalogs();
|
|
16667
17006
|
const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
|
|
16668
17007
|
const start2 = dir ?? "/";
|
|
@@ -16670,13 +17009,13 @@ var Launcher = class {
|
|
|
16670
17009
|
const [file = ""] = argv;
|
|
16671
17010
|
if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
|
|
16672
17011
|
const planned = await this.plan(file, argv, cwd, scope);
|
|
16673
|
-
await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
|
|
17012
|
+
await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
|
|
16674
17013
|
return { cwd, file, planned };
|
|
16675
17014
|
}
|
|
16676
17015
|
async openTerminal(argv, options) {
|
|
16677
17016
|
tagOption(options.tag);
|
|
16678
17017
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
16679
|
-
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);
|
|
16680
17019
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
16681
17020
|
let leader = 0;
|
|
16682
17021
|
const tty = new KernelTty(
|
|
@@ -16727,7 +17066,7 @@ var Launcher = class {
|
|
|
16727
17066
|
async run(argv, options = {}) {
|
|
16728
17067
|
tagOption(options.tag);
|
|
16729
17068
|
const account = await this.account(options.user);
|
|
16730
|
-
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);
|
|
16731
17070
|
const env = this.environment(cwd, options.env, account);
|
|
16732
17071
|
const out = [];
|
|
16733
17072
|
const err = [];
|
|
@@ -16886,9 +17225,31 @@ var USERS_OPS = /* @__PURE__ */ new Set([
|
|
|
16886
17225
|
"groups-add-member",
|
|
16887
17226
|
"groups-remove-member"
|
|
16888
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
|
+
}
|
|
16889
17232
|
function isUsersRequest(req) {
|
|
16890
17233
|
return USERS_OPS.has(req.op);
|
|
16891
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
|
+
}
|
|
16892
17253
|
function failure(err) {
|
|
16893
17254
|
const code = err?.code;
|
|
16894
17255
|
return {
|
|
@@ -17027,8 +17388,10 @@ function serveKernel(port, deps) {
|
|
|
17027
17388
|
if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
|
|
17028
17389
|
return l.mounts.list();
|
|
17029
17390
|
}
|
|
17391
|
+
const attestOp = attestOps(port);
|
|
17030
17392
|
async function handle(req) {
|
|
17031
17393
|
if (req.op === "init") return init(req);
|
|
17394
|
+
if (isAttestRequest(req)) return attestOp(await ready2(), req);
|
|
17032
17395
|
if (req.op === "open-terminal") return terminal(req);
|
|
17033
17396
|
if (req.op === "connect") return connect(req.user, req.tag);
|
|
17034
17397
|
if (isUsersRequest(req)) return usersOp(await ready2(), req);
|
|
@@ -17077,7 +17440,7 @@ function serveKernel(port, deps) {
|
|
|
17077
17440
|
if (answered(event.data, remote, uplink, approvals)) return;
|
|
17078
17441
|
const req = event.data;
|
|
17079
17442
|
handle(req).then(
|
|
17080
|
-
(result) => reply(req.id, { result },
|
|
17443
|
+
(result) => reply(req.id, { result }, transferOf2(req, result)),
|
|
17081
17444
|
(err) => reply(req.id, failure(err))
|
|
17082
17445
|
);
|
|
17083
17446
|
});
|