@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/node.js
CHANGED
|
@@ -371,6 +371,7 @@ var Dac = class {
|
|
|
371
371
|
log;
|
|
372
372
|
seen = /* @__PURE__ */ new Set();
|
|
373
373
|
suppressed = false;
|
|
374
|
+
tagOf = () => void 0;
|
|
374
375
|
constructor(mode, hook, log = (line) => console.warn(line)) {
|
|
375
376
|
this.mode = mode;
|
|
376
377
|
this.hook = hook;
|
|
@@ -379,7 +380,9 @@ var Dac = class {
|
|
|
379
380
|
get enforcing() {
|
|
380
381
|
return this.mode === "enforce";
|
|
381
382
|
}
|
|
382
|
-
denied(
|
|
383
|
+
denied(given) {
|
|
384
|
+
const tag = given.tag ?? (given.pid ? this.tagOf(given.pid) : void 0);
|
|
385
|
+
const denial = tag === void 0 ? given : { ...given, tag };
|
|
383
386
|
try {
|
|
384
387
|
this.hook?.({ ...denial });
|
|
385
388
|
} catch {
|
|
@@ -397,7 +400,7 @@ var Dac = class {
|
|
|
397
400
|
}
|
|
398
401
|
this.seen.add(key);
|
|
399
402
|
this.log(
|
|
400
|
-
`slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
|
|
403
|
+
`slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid}${d.tag === void 0 ? "" : `, tag ${d.tag}`})`
|
|
401
404
|
);
|
|
402
405
|
}
|
|
403
406
|
};
|
|
@@ -413,7 +416,8 @@ var Created = class {
|
|
|
413
416
|
}
|
|
414
417
|
};
|
|
415
418
|
function deny(g, op, path, errno = "EACCES") {
|
|
416
|
-
g.
|
|
419
|
+
const tag = g.tag === void 0 ? {} : { tag: g.tag };
|
|
420
|
+
g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
|
|
417
421
|
}
|
|
418
422
|
var TTY_MODE = 400;
|
|
419
423
|
function ttyOpenable(g, tty) {
|
|
@@ -2595,9 +2599,9 @@ function ownerFor(l, self) {
|
|
|
2595
2599
|
if (!l.owners || !self) return void 0;
|
|
2596
2600
|
return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
|
|
2597
2601
|
}
|
|
2598
|
-
function guardFor(l, self) {
|
|
2602
|
+
function guardFor(l, self, tag) {
|
|
2599
2603
|
if (!l.dac || !self) return void 0;
|
|
2600
|
-
return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
|
|
2604
|
+
return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac, tag };
|
|
2601
2605
|
}
|
|
2602
2606
|
async function accountOf(l, user) {
|
|
2603
2607
|
const found = await l.users.lookup(user);
|
|
@@ -2701,6 +2705,7 @@ async function watchOp(l, req, watches, send, g) {
|
|
|
2701
2705
|
function serveClient(port, host) {
|
|
2702
2706
|
let state = "new";
|
|
2703
2707
|
let self;
|
|
2708
|
+
const guard2 = (l) => guardFor(l, self, host.tag);
|
|
2704
2709
|
const terminals = /* @__PURE__ */ new Map();
|
|
2705
2710
|
const groups = /* @__PURE__ */ new Set();
|
|
2706
2711
|
const sessions = /* @__PURE__ */ new Set();
|
|
@@ -2810,10 +2815,10 @@ function serveClient(port, host) {
|
|
|
2810
2815
|
if (!FS_METHODS.includes(req.method)) {
|
|
2811
2816
|
throw new Error(`unknown file system call ${req.method}`);
|
|
2812
2817
|
}
|
|
2813
|
-
return fsCall(l.processFs, req.method, req.args, ownerFor(l, self),
|
|
2818
|
+
return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guard2(l));
|
|
2814
2819
|
case "watch":
|
|
2815
2820
|
case "unwatch":
|
|
2816
|
-
return watchOp(l, req, watches, send,
|
|
2821
|
+
return watchOp(l, req, watches, send, guard2(l));
|
|
2817
2822
|
case "mount":
|
|
2818
2823
|
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
2819
2824
|
case "umount":
|
|
@@ -3174,6 +3179,121 @@ var ProtectedFs = class {
|
|
|
3174
3179
|
}
|
|
3175
3180
|
};
|
|
3176
3181
|
|
|
3182
|
+
// src/fs/remote.ts
|
|
3183
|
+
var ANNOUNCE_WAIT_MS = 500;
|
|
3184
|
+
var PRUNE_EVERY_MS = 3e4;
|
|
3185
|
+
function lockManager() {
|
|
3186
|
+
return globalThis.navigator?.locks;
|
|
3187
|
+
}
|
|
3188
|
+
var RemoteWatch = class {
|
|
3189
|
+
channel;
|
|
3190
|
+
name;
|
|
3191
|
+
tab = crypto.randomUUID();
|
|
3192
|
+
peers = /* @__PURE__ */ new Set();
|
|
3193
|
+
others = /* @__PURE__ */ new Map();
|
|
3194
|
+
union = /* @__PURE__ */ new Set();
|
|
3195
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3196
|
+
waiting = /* @__PURE__ */ new Map();
|
|
3197
|
+
locks;
|
|
3198
|
+
mine = [];
|
|
3199
|
+
seq = 0;
|
|
3200
|
+
pruned = 0;
|
|
3201
|
+
constructor(name2, options = {}) {
|
|
3202
|
+
this.name = name2;
|
|
3203
|
+
this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
|
|
3204
|
+
this.channel = new BroadcastChannel(name2);
|
|
3205
|
+
this.channel.onmessage = ({ data }) => this.receive(data);
|
|
3206
|
+
this.channel.unref?.();
|
|
3207
|
+
void this.locks?.request(this.lockOf(this.tab), () => new Promise(() => {
|
|
3208
|
+
}));
|
|
3209
|
+
this.channel.postMessage({ hello: this.tab });
|
|
3210
|
+
}
|
|
3211
|
+
lockOf(tab) {
|
|
3212
|
+
return `${this.name}:${tab}`;
|
|
3213
|
+
}
|
|
3214
|
+
receive(data) {
|
|
3215
|
+
if (data.hello !== void 0) this.greeted(data.hello);
|
|
3216
|
+
if (data.here !== void 0 && data.to === this.tab) this.peers.add(data.here);
|
|
3217
|
+
if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
|
|
3218
|
+
if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
|
|
3219
|
+
this.acked(data.ack, data.from);
|
|
3220
|
+
}
|
|
3221
|
+
if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [] });
|
|
3222
|
+
}
|
|
3223
|
+
greeted(peer) {
|
|
3224
|
+
this.peers.add(peer);
|
|
3225
|
+
this.channel.postMessage({ here: this.tab, to: peer });
|
|
3226
|
+
if (this.mine.length > 0) void this.announce(this.mine);
|
|
3227
|
+
}
|
|
3228
|
+
announced({ tab, paths, seq: seq2 }) {
|
|
3229
|
+
const from = tab;
|
|
3230
|
+
this.peers.add(from);
|
|
3231
|
+
if (paths.length > 0) this.others.set(from, paths);
|
|
3232
|
+
else this.others.delete(from);
|
|
3233
|
+
this.rebuild();
|
|
3234
|
+
this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
|
|
3235
|
+
}
|
|
3236
|
+
acked(seq2, from) {
|
|
3237
|
+
const wait2 = this.waiting.get(seq2);
|
|
3238
|
+
if (!wait2) return;
|
|
3239
|
+
wait2.left.delete(from);
|
|
3240
|
+
if (wait2.left.size === 0) wait2.done();
|
|
3241
|
+
}
|
|
3242
|
+
rebuild() {
|
|
3243
|
+
this.union.clear();
|
|
3244
|
+
for (const paths of this.others.values()) for (const path of paths) this.union.add(path);
|
|
3245
|
+
}
|
|
3246
|
+
async prune() {
|
|
3247
|
+
if (!this.locks) return;
|
|
3248
|
+
const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
|
|
3249
|
+
for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
|
|
3250
|
+
if (!held.has(this.lockOf(tab))) {
|
|
3251
|
+
this.others.delete(tab);
|
|
3252
|
+
this.peers.delete(tab);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
this.rebuild();
|
|
3256
|
+
}
|
|
3257
|
+
watched() {
|
|
3258
|
+
return this.union.size > 0;
|
|
3259
|
+
}
|
|
3260
|
+
wrote(path) {
|
|
3261
|
+
if (!this.union.has(path)) return;
|
|
3262
|
+
this.channel.postMessage({ changed: [path] });
|
|
3263
|
+
const now = Date.now();
|
|
3264
|
+
if (now - this.pruned < PRUNE_EVERY_MS) return;
|
|
3265
|
+
this.pruned = now;
|
|
3266
|
+
void this.prune();
|
|
3267
|
+
}
|
|
3268
|
+
announce(paths) {
|
|
3269
|
+
this.mine = [...paths];
|
|
3270
|
+
const seq2 = ++this.seq;
|
|
3271
|
+
this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
|
|
3272
|
+
if (this.peers.size === 0) return Promise.resolve();
|
|
3273
|
+
return new Promise((resolve) => {
|
|
3274
|
+
const done4 = () => {
|
|
3275
|
+
clearTimeout(timer);
|
|
3276
|
+
this.waiting.delete(seq2);
|
|
3277
|
+
resolve();
|
|
3278
|
+
};
|
|
3279
|
+
const timer = setTimeout(() => {
|
|
3280
|
+
done4();
|
|
3281
|
+
void this.prune();
|
|
3282
|
+
}, ANNOUNCE_WAIT_MS);
|
|
3283
|
+
this.waiting.set(seq2, { left: new Set(this.peers), done: done4 });
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
emit(change) {
|
|
3287
|
+
for (const listener of this.listeners) listener(change);
|
|
3288
|
+
}
|
|
3289
|
+
listen(listener) {
|
|
3290
|
+
this.listeners.add(listener);
|
|
3291
|
+
return () => {
|
|
3292
|
+
this.listeners.delete(listener);
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
};
|
|
3296
|
+
|
|
3177
3297
|
// src/fs/opfs.ts
|
|
3178
3298
|
var S_IFDIR = 16384;
|
|
3179
3299
|
var S_IFREG = 32768;
|
|
@@ -3297,6 +3417,7 @@ var OpfsFs = class {
|
|
|
3297
3417
|
dirs = new PathMap();
|
|
3298
3418
|
checked = /* @__PURE__ */ new WeakMap();
|
|
3299
3419
|
channel;
|
|
3420
|
+
remote;
|
|
3300
3421
|
ranged;
|
|
3301
3422
|
owners = false;
|
|
3302
3423
|
constructor(root, meta = new MemoryMeta(), channel, options = {}) {
|
|
@@ -3304,9 +3425,15 @@ var OpfsFs = class {
|
|
|
3304
3425
|
this.meta = sidecar(meta);
|
|
3305
3426
|
this.ranged = options.ranged === true;
|
|
3306
3427
|
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
3428
|
+
this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
|
|
3307
3429
|
if (this.channel) {
|
|
3430
|
+
const remote = this.remote;
|
|
3431
|
+
meta.listen?.(
|
|
3432
|
+
(c) => remote?.emit({ paths: c.paths ?? [], trees: c.trees ?? [] })
|
|
3433
|
+
);
|
|
3308
3434
|
this.channel.onmessage = ({ data }) => {
|
|
3309
3435
|
for (const path of data) this.forget(path);
|
|
3436
|
+
remote?.emit({ paths: data, trees: [] });
|
|
3310
3437
|
};
|
|
3311
3438
|
this.channel.unref?.();
|
|
3312
3439
|
}
|
|
@@ -3315,6 +3442,16 @@ var OpfsFs = class {
|
|
|
3315
3442
|
for (const path of paths) this.forget(path);
|
|
3316
3443
|
this.channel?.postMessage(paths);
|
|
3317
3444
|
}
|
|
3445
|
+
remoteChanges(listener) {
|
|
3446
|
+
return this.remote?.listen(listener) ?? (() => {
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
announceWatch(paths) {
|
|
3450
|
+
return this.remote?.announce(paths) ?? Promise.resolve();
|
|
3451
|
+
}
|
|
3452
|
+
committed(target) {
|
|
3453
|
+
if (this.remote?.watched()) this.remote.wrote(target);
|
|
3454
|
+
}
|
|
3318
3455
|
resolvePath(base, path) {
|
|
3319
3456
|
return resolvePath(base, path);
|
|
3320
3457
|
}
|
|
@@ -3666,15 +3803,18 @@ var OpfsFs = class {
|
|
|
3666
3803
|
);
|
|
3667
3804
|
}
|
|
3668
3805
|
async pwrite(path, offset, bytes) {
|
|
3669
|
-
const handle =
|
|
3806
|
+
const [handle, , target] = await this.fileFor(path);
|
|
3670
3807
|
await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
|
|
3808
|
+
this.committed(target);
|
|
3671
3809
|
}
|
|
3672
3810
|
async truncate(path, size) {
|
|
3673
3811
|
const handle = await this.existing(path, true);
|
|
3812
|
+
const target = this.remote?.watched() ? (await this.locate(path, true)).path : path;
|
|
3674
3813
|
await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
|
|
3814
|
+
this.committed(target);
|
|
3675
3815
|
}
|
|
3676
3816
|
async writeSession(path, truncate) {
|
|
3677
|
-
const handle =
|
|
3817
|
+
const [handle, , target] = await this.fileFor(path);
|
|
3678
3818
|
const writable2 = await this.retrying(
|
|
3679
3819
|
path,
|
|
3680
3820
|
() => handle.createWritable({ keepExistingData: !truncate }),
|
|
@@ -3687,9 +3827,12 @@ var OpfsFs = class {
|
|
|
3687
3827
|
truncate: (size) => writable2.truncate(size).catch((err) => {
|
|
3688
3828
|
throw translate(err, path, "write", false);
|
|
3689
3829
|
}),
|
|
3690
|
-
close:
|
|
3691
|
-
|
|
3692
|
-
|
|
3830
|
+
close: async () => {
|
|
3831
|
+
await writable2.close().catch((err) => {
|
|
3832
|
+
throw translate(err, path, "commit", false);
|
|
3833
|
+
});
|
|
3834
|
+
this.committed(target);
|
|
3835
|
+
}
|
|
3693
3836
|
};
|
|
3694
3837
|
}
|
|
3695
3838
|
async readFile(path) {
|
|
@@ -3738,6 +3881,7 @@ var OpfsFs = class {
|
|
|
3738
3881
|
await writable2?.abort().catch(() => void 0);
|
|
3739
3882
|
throw translate(err, path, "commit", false);
|
|
3740
3883
|
}
|
|
3884
|
+
this.committed(target);
|
|
3741
3885
|
}
|
|
3742
3886
|
async listing(path) {
|
|
3743
3887
|
const found = await this.locate(path, true);
|
|
@@ -3759,12 +3903,12 @@ var OpfsFs = class {
|
|
|
3759
3903
|
const base = dir === "/" ? "" : dir;
|
|
3760
3904
|
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
3761
3905
|
const handed2 = this.owners ? await this.handedTo(dir) : void 0;
|
|
3762
|
-
const
|
|
3906
|
+
const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
|
|
3763
3907
|
return Promise.all(
|
|
3764
3908
|
children2.map(async ([name2, child], i) => {
|
|
3765
|
-
if (!isHandle(child)) return [name2,
|
|
3909
|
+
if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
|
|
3766
3910
|
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
3767
|
-
return [name2, stat &&
|
|
3911
|
+
return [name2, stat && owned4(stat, metas[i])];
|
|
3768
3912
|
})
|
|
3769
3913
|
);
|
|
3770
3914
|
}
|
|
@@ -3828,7 +3972,14 @@ var OpfsFs = class {
|
|
|
3828
3972
|
links() {
|
|
3829
3973
|
return false;
|
|
3830
3974
|
}
|
|
3831
|
-
async link(from) {
|
|
3975
|
+
async link(from, to, follow2) {
|
|
3976
|
+
await (follow2 ? this.stat(from) : this.lstat(from));
|
|
3977
|
+
if (!(await this.stat(parentOf4(to))).isDirectory) throw fsError("ENOTDIR", to);
|
|
3978
|
+
if (await this.lstat(to).then(
|
|
3979
|
+
() => true,
|
|
3980
|
+
() => false
|
|
3981
|
+
))
|
|
3982
|
+
throw fsError("EEXIST", to);
|
|
3832
3983
|
throw fsError("EPERM", `${from}: this file system has no hard links`);
|
|
3833
3984
|
}
|
|
3834
3985
|
async rename(from, to) {
|
|
@@ -5266,6 +5417,136 @@ function binfmtOf(binfmts, path) {
|
|
|
5266
5417
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
5267
5418
|
}
|
|
5268
5419
|
|
|
5420
|
+
// src/fs/attest.ts
|
|
5421
|
+
var ATTEST_MAX = 1024 * 1024;
|
|
5422
|
+
var UNOWNED = 1e3;
|
|
5423
|
+
function refuse(code, path, why) {
|
|
5424
|
+
throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
|
|
5425
|
+
}
|
|
5426
|
+
function chainOf(path) {
|
|
5427
|
+
const parts2 = path.split("/").filter(Boolean);
|
|
5428
|
+
return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
|
|
5429
|
+
}
|
|
5430
|
+
function under2(path, target) {
|
|
5431
|
+
return path === target || path.startsWith(`${target}/`);
|
|
5432
|
+
}
|
|
5433
|
+
function owned(path, st) {
|
|
5434
|
+
return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
|
|
5435
|
+
}
|
|
5436
|
+
function unchanged(a, b, bytes) {
|
|
5437
|
+
return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
|
|
5438
|
+
}
|
|
5439
|
+
async function sha256(bytes) {
|
|
5440
|
+
const digest = new Uint8Array(
|
|
5441
|
+
await crypto.subtle.digest("SHA-256", bytes)
|
|
5442
|
+
);
|
|
5443
|
+
return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
5444
|
+
}
|
|
5445
|
+
function attested(path, st, bytes, digest, o, parents) {
|
|
5446
|
+
const { uid, gid, mode } = owned(path, st);
|
|
5447
|
+
return {
|
|
5448
|
+
path,
|
|
5449
|
+
bytes,
|
|
5450
|
+
uid,
|
|
5451
|
+
gid,
|
|
5452
|
+
mode,
|
|
5453
|
+
ino: st.ino,
|
|
5454
|
+
mtimeMs: st.mtime.getTime(),
|
|
5455
|
+
sha256: digest,
|
|
5456
|
+
users: o.users,
|
|
5457
|
+
onRootFs: true,
|
|
5458
|
+
parents
|
|
5459
|
+
};
|
|
5460
|
+
}
|
|
5461
|
+
async function trustedParents(fs, path, o) {
|
|
5462
|
+
const parents = [];
|
|
5463
|
+
for (const dir of chainOf(path).slice(0, -1)) {
|
|
5464
|
+
const st = await fs.lstat(dir);
|
|
5465
|
+
if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
|
|
5466
|
+
if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
|
|
5467
|
+
const perm = owned(dir, st);
|
|
5468
|
+
if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
|
|
5469
|
+
refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
|
|
5470
|
+
}
|
|
5471
|
+
parents.push(perm);
|
|
5472
|
+
}
|
|
5473
|
+
return parents;
|
|
5474
|
+
}
|
|
5475
|
+
async function attest(fs, asked, o) {
|
|
5476
|
+
if (typeof asked !== "string" || !asked.startsWith("/")) {
|
|
5477
|
+
refuse("EINVAL", String(asked), "not an absolute path");
|
|
5478
|
+
}
|
|
5479
|
+
const path = normalizePath(asked);
|
|
5480
|
+
const mount = o.mounts.find((target) => {
|
|
5481
|
+
const at = normalizePath(target);
|
|
5482
|
+
return at === "/" || under2(path, at);
|
|
5483
|
+
});
|
|
5484
|
+
if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
|
|
5485
|
+
const parents = await trustedParents(fs, path, o);
|
|
5486
|
+
for (let tries = 0; ; tries++) {
|
|
5487
|
+
const before = await fs.lstat(path);
|
|
5488
|
+
if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
|
|
5489
|
+
if (!before.isFile)
|
|
5490
|
+
refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
|
|
5491
|
+
if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
|
|
5492
|
+
const bytes = (await fs.readFileBuffer(path)).slice();
|
|
5493
|
+
const after = await fs.lstat(path);
|
|
5494
|
+
if (unchanged(before, after, bytes)) {
|
|
5495
|
+
return attested(path, after, bytes, await sha256(bytes), o, parents);
|
|
5496
|
+
}
|
|
5497
|
+
if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
var WATCH_DEBOUNCE_MS = 50;
|
|
5501
|
+
var FileWatches = class {
|
|
5502
|
+
live = /* @__PURE__ */ new Set();
|
|
5503
|
+
local;
|
|
5504
|
+
remote;
|
|
5505
|
+
constructor(local, remote) {
|
|
5506
|
+
this.local = local;
|
|
5507
|
+
this.remote = remote;
|
|
5508
|
+
}
|
|
5509
|
+
announce() {
|
|
5510
|
+
return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
|
|
5511
|
+
}
|
|
5512
|
+
async watch(paths, notify) {
|
|
5513
|
+
const files = paths.map(normalizePath);
|
|
5514
|
+
const timers = /* @__PURE__ */ new Map();
|
|
5515
|
+
let active = true;
|
|
5516
|
+
const fire = (changed, trees) => {
|
|
5517
|
+
for (const file of files) {
|
|
5518
|
+
const hit = [...changed, ...trees].some((p) => under2(file, normalizePath(p)));
|
|
5519
|
+
if (!hit) continue;
|
|
5520
|
+
clearTimeout(timers.get(file));
|
|
5521
|
+
timers.set(
|
|
5522
|
+
file,
|
|
5523
|
+
setTimeout(() => {
|
|
5524
|
+
timers.delete(file);
|
|
5525
|
+
if (active) notify(file);
|
|
5526
|
+
}, WATCH_DEBOUNCE_MS)
|
|
5527
|
+
);
|
|
5528
|
+
}
|
|
5529
|
+
};
|
|
5530
|
+
const unwatch = this.local.watch(
|
|
5531
|
+
files,
|
|
5532
|
+
{ recursive: false },
|
|
5533
|
+
(change) => fire("overflow" in change ? files : change.paths, [])
|
|
5534
|
+
);
|
|
5535
|
+
const unlisten = this.remote.remoteChanges?.((change) => fire(change.paths, change.trees));
|
|
5536
|
+
this.live.add(files);
|
|
5537
|
+
await this.announce();
|
|
5538
|
+
return () => {
|
|
5539
|
+
if (!active) return;
|
|
5540
|
+
active = false;
|
|
5541
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
5542
|
+
unwatch();
|
|
5543
|
+
unlisten?.();
|
|
5544
|
+
this.live.delete(files);
|
|
5545
|
+
void this.announce();
|
|
5546
|
+
};
|
|
5547
|
+
}
|
|
5548
|
+
};
|
|
5549
|
+
|
|
5269
5550
|
// src/fs/commands.ts
|
|
5270
5551
|
var S_IFDIR2 = 16384;
|
|
5271
5552
|
var S_IFREG2 = 32768;
|
|
@@ -5304,7 +5585,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
5304
5585
|
if (st || path === "/") return st ?? {};
|
|
5305
5586
|
return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
|
|
5306
5587
|
}
|
|
5307
|
-
async function
|
|
5588
|
+
async function owned4(stat2, dir) {
|
|
5308
5589
|
if (!owners) return stat2;
|
|
5309
5590
|
const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
|
|
5310
5591
|
return { ...stat2, uid, gid };
|
|
@@ -5318,17 +5599,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
5318
5599
|
}
|
|
5319
5600
|
async function virtual(path) {
|
|
5320
5601
|
if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
|
|
5321
|
-
return
|
|
5602
|
+
return owned4(synthetic(true, path));
|
|
5322
5603
|
}
|
|
5323
5604
|
const name2 = COMMAND.exec(path)?.[1];
|
|
5324
5605
|
if (name2 !== void 0) {
|
|
5325
|
-
return (await names()).has(name2) ?
|
|
5606
|
+
return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
|
|
5326
5607
|
}
|
|
5327
|
-
if ((await userDir(path))?.size) return
|
|
5608
|
+
if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
|
|
5328
5609
|
const slash = path.lastIndexOf("/");
|
|
5329
5610
|
const dir = path.slice(0, slash);
|
|
5330
5611
|
const own = await userDir(dir);
|
|
5331
|
-
return own?.has(path.slice(slash + 1)) ?
|
|
5612
|
+
return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
|
|
5332
5613
|
}
|
|
5333
5614
|
async function linked(path) {
|
|
5334
5615
|
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
@@ -5455,11 +5736,11 @@ async function physicalPathAsync(fs, path) {
|
|
|
5455
5736
|
|
|
5456
5737
|
// src/fs/watch.ts
|
|
5457
5738
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
5458
|
-
var
|
|
5739
|
+
var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
5459
5740
|
var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
5460
5741
|
function touches(watcher, changed) {
|
|
5461
5742
|
return watcher.paths.some(
|
|
5462
|
-
(watched) => changed === watched ||
|
|
5743
|
+
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
|
|
5463
5744
|
);
|
|
5464
5745
|
}
|
|
5465
5746
|
var FsWatchers = class {
|
|
@@ -7178,7 +7459,7 @@ async function keptStat(entry, req) {
|
|
|
7178
7459
|
return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
|
|
7179
7460
|
return json2(statJson(await entry.fs.stat(hidden)));
|
|
7180
7461
|
}
|
|
7181
|
-
async function
|
|
7462
|
+
async function owned2(entry, own, req, path, target) {
|
|
7182
7463
|
const source = target === path ? void 0 : path;
|
|
7183
7464
|
if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
|
|
7184
7465
|
return run(entry, path, req);
|
|
@@ -7233,7 +7514,7 @@ async function dispatchOne(entry, req) {
|
|
|
7233
7514
|
}
|
|
7234
7515
|
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
7235
7516
|
const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
7236
|
-
return await
|
|
7517
|
+
return await owned2(entry, entry.own, req, path, target);
|
|
7237
7518
|
} catch (err) {
|
|
7238
7519
|
return toErrno(err);
|
|
7239
7520
|
}
|
|
@@ -11436,6 +11717,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11436
11717
|
"proc-sudo",
|
|
11437
11718
|
"proc-run",
|
|
11438
11719
|
"proc-exit",
|
|
11720
|
+
"proc-kill-self",
|
|
11439
11721
|
"proc-fork",
|
|
11440
11722
|
"proc-kill",
|
|
11441
11723
|
"thread-kill",
|
|
@@ -11563,6 +11845,20 @@ var WasmProcess = class {
|
|
|
11563
11845
|
else this.adoptExec(pid);
|
|
11564
11846
|
return pid;
|
|
11565
11847
|
}
|
|
11848
|
+
async killSelf(sig, target, probe2) {
|
|
11849
|
+
if (!this.options.exitSignal) throw new KernelError("ENOSYS");
|
|
11850
|
+
if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
|
|
11851
|
+
const { caught, ignored } = this.signalMasks();
|
|
11852
|
+
const to = target === 0 ? -this.pgid() : target ?? this.pid;
|
|
11853
|
+
if (sig !== SIG.KILL && (caught | ignored) & sigbit(sig)) {
|
|
11854
|
+
if (!await this.options.kill?.(to, sig)) throw new KernelError("ESRCH");
|
|
11855
|
+
return { ok: true, kind: "void" };
|
|
11856
|
+
}
|
|
11857
|
+
if (probe2) return { ok: true, kind: "json", json: "terminate" };
|
|
11858
|
+
if (to !== this.pid) await this.options.kill?.(to, sig);
|
|
11859
|
+
this.options.exitSignal(sig);
|
|
11860
|
+
return DYING;
|
|
11861
|
+
}
|
|
11566
11862
|
run(req) {
|
|
11567
11863
|
return runChild(req, {
|
|
11568
11864
|
children: this.children,
|
|
@@ -12160,6 +12456,8 @@ var WasmProcess = class {
|
|
|
12160
12456
|
if (!this.options.exitNow) throw new KernelError("ENOSYS");
|
|
12161
12457
|
this.options.exitNow(req.status & 255);
|
|
12162
12458
|
return DYING;
|
|
12459
|
+
case "proc-kill-self":
|
|
12460
|
+
return this.killSelf(req.sig, req.target, req.probe);
|
|
12163
12461
|
case "proc-identity":
|
|
12164
12462
|
return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
|
|
12165
12463
|
case "proc-exec": {
|
|
@@ -12594,6 +12892,7 @@ function processOptions(opts, header4, hooks, created3) {
|
|
|
12594
12892
|
raise: hooks.raise,
|
|
12595
12893
|
threadKill: hooks.threadKill,
|
|
12596
12894
|
...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
|
|
12895
|
+
...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
|
|
12597
12896
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {}
|
|
12598
12897
|
};
|
|
12599
12898
|
}
|
|
@@ -12627,7 +12926,10 @@ function spawnWasmProcess(opts) {
|
|
|
12627
12926
|
{
|
|
12628
12927
|
raise: (sig) => signal(sig),
|
|
12629
12928
|
threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig),
|
|
12630
|
-
...opts.program.abi === "js" ? {
|
|
12929
|
+
...opts.program.abi === "js" ? {
|
|
12930
|
+
exitNow: (code) => exitNow(code),
|
|
12931
|
+
exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
|
|
12932
|
+
} : {}
|
|
12631
12933
|
},
|
|
12632
12934
|
created3
|
|
12633
12935
|
)
|
|
@@ -14535,8 +14837,8 @@ var ChunkedBytes = class {
|
|
|
14535
14837
|
}
|
|
14536
14838
|
return out;
|
|
14537
14839
|
}
|
|
14538
|
-
write(offset, bytes,
|
|
14539
|
-
const whole =
|
|
14840
|
+
write(offset, bytes, owned4 = false) {
|
|
14841
|
+
const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
|
|
14540
14842
|
for (let done4 = 0; done4 < bytes.length; ) {
|
|
14541
14843
|
const at = offset + done4;
|
|
14542
14844
|
const index = Math.floor(at / CHUNK3);
|
|
@@ -14586,7 +14888,7 @@ function created2(ino, kind, mode, from, owner) {
|
|
|
14586
14888
|
gid: owner?.gid ?? from?.gid ?? 0
|
|
14587
14889
|
};
|
|
14588
14890
|
}
|
|
14589
|
-
function
|
|
14891
|
+
function owned3(node, change) {
|
|
14590
14892
|
if (change.uid !== void 0) node.uid = change.uid;
|
|
14591
14893
|
if (change.gid !== void 0) node.gid = change.gid;
|
|
14592
14894
|
}
|
|
@@ -14757,7 +15059,7 @@ function tmpfs() {
|
|
|
14757
15059
|
node.mtime = Date.now();
|
|
14758
15060
|
}
|
|
14759
15061
|
if (change.mtime !== void 0) node.mtime = change.mtime;
|
|
14760
|
-
|
|
15062
|
+
owned3(node, change);
|
|
14761
15063
|
},
|
|
14762
15064
|
async statfs() {
|
|
14763
15065
|
return { bsize: 4096, blocks: 262144, bfree: 262144 };
|
|
@@ -15506,12 +15808,16 @@ var Launcher = class {
|
|
|
15506
15808
|
usersMode;
|
|
15507
15809
|
dac;
|
|
15508
15810
|
sealed;
|
|
15811
|
+
rootFs;
|
|
15812
|
+
fileWatches;
|
|
15509
15813
|
nextPid = 1e3;
|
|
15510
15814
|
boot = Date.now();
|
|
15511
15815
|
terminals = 0;
|
|
15512
15816
|
constructor(options) {
|
|
15513
15817
|
this.usersMode = usersMode(options.users);
|
|
15818
|
+
this.rootFs = options.fs;
|
|
15514
15819
|
this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
|
|
15820
|
+
if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
|
|
15515
15821
|
this.sealed = new SealedBroker(
|
|
15516
15822
|
options.sealed,
|
|
15517
15823
|
this.usersMode,
|
|
@@ -15761,7 +16067,7 @@ var Launcher = class {
|
|
|
15761
16067
|
async mayExec(cred, req, planned) {
|
|
15762
16068
|
const { dac } = this;
|
|
15763
16069
|
if (!dac) return;
|
|
15764
|
-
const g = { fs: this.fs, cred, pid: req.pid, dac };
|
|
16070
|
+
const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
|
|
15765
16071
|
const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
|
|
15766
16072
|
if (byPath(req.file)) {
|
|
15767
16073
|
const path = this.fs.resolvePath(req.cwd, req.file);
|
|
@@ -16264,6 +16570,13 @@ var Launcher = class {
|
|
|
16264
16570
|
kill(pid, sig, sender, from) {
|
|
16265
16571
|
const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
|
|
16266
16572
|
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;
|
|
16573
|
+
if (pid === -1) {
|
|
16574
|
+
const sender2 = from === void 0 ? void 0 : this.imageOf(from);
|
|
16575
|
+
return this.killAll(
|
|
16576
|
+
sig,
|
|
16577
|
+
(member) => this.imageOf(member) !== sender2 && may(this.imageOf(member))
|
|
16578
|
+
);
|
|
16579
|
+
}
|
|
16267
16580
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
16268
16581
|
const handle = this.processes.get(pid);
|
|
16269
16582
|
if (!handle) return false;
|
|
@@ -16271,6 +16584,15 @@ var Launcher = class {
|
|
|
16271
16584
|
if (sig !== 0) handle.signal(sig);
|
|
16272
16585
|
return true;
|
|
16273
16586
|
}
|
|
16587
|
+
killAll(sig, may) {
|
|
16588
|
+
let any = false;
|
|
16589
|
+
for (const [pid, handle] of [...this.processes]) {
|
|
16590
|
+
if (pid === 1 || !may(pid)) continue;
|
|
16591
|
+
any = true;
|
|
16592
|
+
if (sig !== 0) handle.signal(sig);
|
|
16593
|
+
}
|
|
16594
|
+
return any;
|
|
16595
|
+
}
|
|
16274
16596
|
async driver(type, spec) {
|
|
16275
16597
|
if (type === "fsa") return this.fsaDriver(spec);
|
|
16276
16598
|
if (type === "hostfs") {
|
|
@@ -16360,6 +16682,16 @@ var Launcher = class {
|
|
|
16360
16682
|
}
|
|
16361
16683
|
};
|
|
16362
16684
|
}
|
|
16685
|
+
attest(path) {
|
|
16686
|
+
return attest(this.rootFs, path, {
|
|
16687
|
+
users: this.usersMode,
|
|
16688
|
+
mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
|
|
16689
|
+
});
|
|
16690
|
+
}
|
|
16691
|
+
watchFiles(paths, notify) {
|
|
16692
|
+
this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
|
|
16693
|
+
return this.fileWatches.watch(paths, notify);
|
|
16694
|
+
}
|
|
16363
16695
|
async mount(spec) {
|
|
16364
16696
|
this.guardMount(spec.target);
|
|
16365
16697
|
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
@@ -16540,7 +16872,7 @@ var Launcher = class {
|
|
|
16540
16872
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
16541
16873
|
};
|
|
16542
16874
|
}
|
|
16543
|
-
async starting(argv, dir, account) {
|
|
16875
|
+
async starting(argv, dir, account, tag) {
|
|
16544
16876
|
this.resetCatalogs();
|
|
16545
16877
|
const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
|
|
16546
16878
|
const start2 = dir ?? "/";
|
|
@@ -16548,13 +16880,13 @@ var Launcher = class {
|
|
|
16548
16880
|
const [file = ""] = argv;
|
|
16549
16881
|
if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
|
|
16550
16882
|
const planned = await this.plan(file, argv, cwd, scope);
|
|
16551
|
-
await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
|
|
16883
|
+
await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
|
|
16552
16884
|
return { cwd, file, planned };
|
|
16553
16885
|
}
|
|
16554
16886
|
async openTerminal(argv, options) {
|
|
16555
16887
|
tagOption(options.tag);
|
|
16556
16888
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
16557
|
-
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
16889
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
|
|
16558
16890
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
16559
16891
|
let leader = 0;
|
|
16560
16892
|
const tty = new KernelTty(
|
|
@@ -16605,7 +16937,7 @@ var Launcher = class {
|
|
|
16605
16937
|
async run(argv, options = {}) {
|
|
16606
16938
|
tagOption(options.tag);
|
|
16607
16939
|
const account = await this.account(options.user);
|
|
16608
|
-
const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
|
|
16940
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
|
|
16609
16941
|
const env = this.environment(cwd, options.env, account);
|
|
16610
16942
|
const out = [];
|
|
16611
16943
|
const err = [];
|