@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/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
|
}
|
|
@@ -5273,6 +5417,136 @@ function binfmtOf(binfmts, path) {
|
|
|
5273
5417
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
5274
5418
|
}
|
|
5275
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
|
+
|
|
5276
5550
|
// src/fs/commands.ts
|
|
5277
5551
|
var S_IFDIR2 = 16384;
|
|
5278
5552
|
var S_IFREG2 = 32768;
|
|
@@ -5311,7 +5585,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
5311
5585
|
if (st || path === "/") return st ?? {};
|
|
5312
5586
|
return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
|
|
5313
5587
|
}
|
|
5314
|
-
async function
|
|
5588
|
+
async function owned4(stat2, dir) {
|
|
5315
5589
|
if (!owners) return stat2;
|
|
5316
5590
|
const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
|
|
5317
5591
|
return { ...stat2, uid, gid };
|
|
@@ -5325,17 +5599,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
5325
5599
|
}
|
|
5326
5600
|
async function virtual(path) {
|
|
5327
5601
|
if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
|
|
5328
|
-
return
|
|
5602
|
+
return owned4(synthetic(true, path));
|
|
5329
5603
|
}
|
|
5330
5604
|
const name2 = COMMAND.exec(path)?.[1];
|
|
5331
5605
|
if (name2 !== void 0) {
|
|
5332
|
-
return (await names()).has(name2) ?
|
|
5606
|
+
return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
|
|
5333
5607
|
}
|
|
5334
|
-
if ((await userDir(path))?.size) return
|
|
5608
|
+
if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
|
|
5335
5609
|
const slash = path.lastIndexOf("/");
|
|
5336
5610
|
const dir = path.slice(0, slash);
|
|
5337
5611
|
const own = await userDir(dir);
|
|
5338
|
-
return own?.has(path.slice(slash + 1)) ?
|
|
5612
|
+
return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
|
|
5339
5613
|
}
|
|
5340
5614
|
async function linked(path) {
|
|
5341
5615
|
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
@@ -5462,11 +5736,11 @@ async function physicalPathAsync(fs, path) {
|
|
|
5462
5736
|
|
|
5463
5737
|
// src/fs/watch.ts
|
|
5464
5738
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
5465
|
-
var
|
|
5739
|
+
var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
5466
5740
|
var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
5467
5741
|
function touches(watcher, changed) {
|
|
5468
5742
|
return watcher.paths.some(
|
|
5469
|
-
(watched) => changed === watched ||
|
|
5743
|
+
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
|
|
5470
5744
|
);
|
|
5471
5745
|
}
|
|
5472
5746
|
var FsWatchers = class {
|
|
@@ -7185,7 +7459,7 @@ async function keptStat(entry, req) {
|
|
|
7185
7459
|
return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
|
|
7186
7460
|
return json2(statJson(await entry.fs.stat(hidden)));
|
|
7187
7461
|
}
|
|
7188
|
-
async function
|
|
7462
|
+
async function owned2(entry, own, req, path, target) {
|
|
7189
7463
|
const source = target === path ? void 0 : path;
|
|
7190
7464
|
if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
|
|
7191
7465
|
return run(entry, path, req);
|
|
@@ -7240,7 +7514,7 @@ async function dispatchOne(entry, req) {
|
|
|
7240
7514
|
}
|
|
7241
7515
|
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
7242
7516
|
const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
7243
|
-
return await
|
|
7517
|
+
return await owned2(entry, entry.own, req, path, target);
|
|
7244
7518
|
} catch (err) {
|
|
7245
7519
|
return toErrno(err);
|
|
7246
7520
|
}
|
|
@@ -11443,6 +11717,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
11443
11717
|
"proc-sudo",
|
|
11444
11718
|
"proc-run",
|
|
11445
11719
|
"proc-exit",
|
|
11720
|
+
"proc-kill-self",
|
|
11446
11721
|
"proc-fork",
|
|
11447
11722
|
"proc-kill",
|
|
11448
11723
|
"thread-kill",
|
|
@@ -11570,6 +11845,20 @@ var WasmProcess = class {
|
|
|
11570
11845
|
else this.adoptExec(pid);
|
|
11571
11846
|
return pid;
|
|
11572
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
|
+
}
|
|
11573
11862
|
run(req) {
|
|
11574
11863
|
return runChild(req, {
|
|
11575
11864
|
children: this.children,
|
|
@@ -12167,6 +12456,8 @@ var WasmProcess = class {
|
|
|
12167
12456
|
if (!this.options.exitNow) throw new KernelError("ENOSYS");
|
|
12168
12457
|
this.options.exitNow(req.status & 255);
|
|
12169
12458
|
return DYING;
|
|
12459
|
+
case "proc-kill-self":
|
|
12460
|
+
return this.killSelf(req.sig, req.target, req.probe);
|
|
12170
12461
|
case "proc-identity":
|
|
12171
12462
|
return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
|
|
12172
12463
|
case "proc-exec": {
|
|
@@ -12601,6 +12892,7 @@ function processOptions(opts, header4, hooks, created3) {
|
|
|
12601
12892
|
raise: hooks.raise,
|
|
12602
12893
|
threadKill: hooks.threadKill,
|
|
12603
12894
|
...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
|
|
12895
|
+
...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
|
|
12604
12896
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {}
|
|
12605
12897
|
};
|
|
12606
12898
|
}
|
|
@@ -12634,7 +12926,10 @@ function spawnWasmProcess(opts) {
|
|
|
12634
12926
|
{
|
|
12635
12927
|
raise: (sig) => signal(sig),
|
|
12636
12928
|
threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig),
|
|
12637
|
-
...opts.program.abi === "js" ? {
|
|
12929
|
+
...opts.program.abi === "js" ? {
|
|
12930
|
+
exitNow: (code) => exitNow(code),
|
|
12931
|
+
exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
|
|
12932
|
+
} : {}
|
|
12638
12933
|
},
|
|
12639
12934
|
created3
|
|
12640
12935
|
)
|
|
@@ -14542,8 +14837,8 @@ var ChunkedBytes = class {
|
|
|
14542
14837
|
}
|
|
14543
14838
|
return out;
|
|
14544
14839
|
}
|
|
14545
|
-
write(offset, bytes,
|
|
14546
|
-
const whole =
|
|
14840
|
+
write(offset, bytes, owned4 = false) {
|
|
14841
|
+
const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
|
|
14547
14842
|
for (let done4 = 0; done4 < bytes.length; ) {
|
|
14548
14843
|
const at = offset + done4;
|
|
14549
14844
|
const index = Math.floor(at / CHUNK3);
|
|
@@ -14593,7 +14888,7 @@ function created2(ino, kind, mode, from, owner) {
|
|
|
14593
14888
|
gid: owner?.gid ?? from?.gid ?? 0
|
|
14594
14889
|
};
|
|
14595
14890
|
}
|
|
14596
|
-
function
|
|
14891
|
+
function owned3(node, change) {
|
|
14597
14892
|
if (change.uid !== void 0) node.uid = change.uid;
|
|
14598
14893
|
if (change.gid !== void 0) node.gid = change.gid;
|
|
14599
14894
|
}
|
|
@@ -14764,7 +15059,7 @@ function tmpfs() {
|
|
|
14764
15059
|
node.mtime = Date.now();
|
|
14765
15060
|
}
|
|
14766
15061
|
if (change.mtime !== void 0) node.mtime = change.mtime;
|
|
14767
|
-
|
|
15062
|
+
owned3(node, change);
|
|
14768
15063
|
},
|
|
14769
15064
|
async statfs() {
|
|
14770
15065
|
return { bsize: 4096, blocks: 262144, bfree: 262144 };
|
|
@@ -15513,12 +15808,16 @@ var Launcher = class {
|
|
|
15513
15808
|
usersMode;
|
|
15514
15809
|
dac;
|
|
15515
15810
|
sealed;
|
|
15811
|
+
rootFs;
|
|
15812
|
+
fileWatches;
|
|
15516
15813
|
nextPid = 1e3;
|
|
15517
15814
|
boot = Date.now();
|
|
15518
15815
|
terminals = 0;
|
|
15519
15816
|
constructor(options) {
|
|
15520
15817
|
this.usersMode = usersMode(options.users);
|
|
15818
|
+
this.rootFs = options.fs;
|
|
15521
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);
|
|
15522
15821
|
this.sealed = new SealedBroker(
|
|
15523
15822
|
options.sealed,
|
|
15524
15823
|
this.usersMode,
|
|
@@ -15768,7 +16067,7 @@ var Launcher = class {
|
|
|
15768
16067
|
async mayExec(cred, req, planned) {
|
|
15769
16068
|
const { dac } = this;
|
|
15770
16069
|
if (!dac) return;
|
|
15771
|
-
const g = { fs: this.fs, cred, pid: req.pid, dac };
|
|
16070
|
+
const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
|
|
15772
16071
|
const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
|
|
15773
16072
|
if (byPath(req.file)) {
|
|
15774
16073
|
const path = this.fs.resolvePath(req.cwd, req.file);
|
|
@@ -16271,6 +16570,13 @@ var Launcher = class {
|
|
|
16271
16570
|
kill(pid, sig, sender, from) {
|
|
16272
16571
|
const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
|
|
16273
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
|
+
}
|
|
16274
16580
|
if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
|
|
16275
16581
|
const handle = this.processes.get(pid);
|
|
16276
16582
|
if (!handle) return false;
|
|
@@ -16278,6 +16584,15 @@ var Launcher = class {
|
|
|
16278
16584
|
if (sig !== 0) handle.signal(sig);
|
|
16279
16585
|
return true;
|
|
16280
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
|
+
}
|
|
16281
16596
|
async driver(type, spec) {
|
|
16282
16597
|
if (type === "fsa") return this.fsaDriver(spec);
|
|
16283
16598
|
if (type === "hostfs") {
|
|
@@ -16367,6 +16682,16 @@ var Launcher = class {
|
|
|
16367
16682
|
}
|
|
16368
16683
|
};
|
|
16369
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
|
+
}
|
|
16370
16695
|
async mount(spec) {
|
|
16371
16696
|
this.guardMount(spec.target);
|
|
16372
16697
|
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
@@ -16547,7 +16872,7 @@ var Launcher = class {
|
|
|
16547
16872
|
...account.umask !== void 0 ? { umask: account.umask } : {}
|
|
16548
16873
|
};
|
|
16549
16874
|
}
|
|
16550
|
-
async starting(argv, dir, account) {
|
|
16875
|
+
async starting(argv, dir, account, tag) {
|
|
16551
16876
|
this.resetCatalogs();
|
|
16552
16877
|
const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
|
|
16553
16878
|
const start2 = dir ?? "/";
|
|
@@ -16555,13 +16880,13 @@ var Launcher = class {
|
|
|
16555
16880
|
const [file = ""] = argv;
|
|
16556
16881
|
if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
|
|
16557
16882
|
const planned = await this.plan(file, argv, cwd, scope);
|
|
16558
|
-
await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
|
|
16883
|
+
await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
|
|
16559
16884
|
return { cwd, file, planned };
|
|
16560
16885
|
}
|
|
16561
16886
|
async openTerminal(argv, options) {
|
|
16562
16887
|
tagOption(options.tag);
|
|
16563
16888
|
const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
|
|
16564
|
-
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);
|
|
16565
16890
|
if (!planned) throw new Error(`${file}: command not found`);
|
|
16566
16891
|
let leader = 0;
|
|
16567
16892
|
const tty = new KernelTty(
|
|
@@ -16612,7 +16937,7 @@ var Launcher = class {
|
|
|
16612
16937
|
async run(argv, options = {}) {
|
|
16613
16938
|
tagOption(options.tag);
|
|
16614
16939
|
const account = await this.account(options.user);
|
|
16615
|
-
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);
|
|
16616
16941
|
const env = this.environment(cwd, options.env, account);
|
|
16617
16942
|
const out = [];
|
|
16618
16943
|
const err = [];
|