@ai-ecoverse/slicc-kernel 1.108.4 → 1.109.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 +2 -0
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +437 -240
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +28 -3
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +223 -26
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +28 -3
- package/dist/process-worker.js.map +3 -3
- package/dist/types/cron/command.d.ts +4 -0
- package/dist/types/cron/crontab.d.ts +27 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/kernel/host.d.ts +1 -0
- package/dist/types/kernel/process.d.ts +4 -1
- package/dist/types/launcher.d.ts +2 -0
- package/dist/types/process/js/js-context.d.ts +13 -0
- package/package.json +1 -1
package/dist/kernel-worker.js
CHANGED
|
@@ -6108,7 +6108,7 @@ function satisfies(version, range2) {
|
|
|
6108
6108
|
}
|
|
6109
6109
|
|
|
6110
6110
|
// src/version.ts
|
|
6111
|
-
var KERNEL_VERSION = true ? "1.
|
|
6111
|
+
var KERNEL_VERSION = true ? "1.109.0" : "0.0.0-dev";
|
|
6112
6112
|
var OS_RELEASE = "6.0.0-slicc";
|
|
6113
6113
|
function procVersion() {
|
|
6114
6114
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -6467,245 +6467,115 @@ function binfmtOf(binfmts, path) {
|
|
|
6467
6467
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
6468
6468
|
}
|
|
6469
6469
|
|
|
6470
|
-
// src/
|
|
6471
|
-
var
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
|
|
6479
|
-
var PathGate = class {
|
|
6480
|
-
tails = /* @__PURE__ */ new Map();
|
|
6481
|
-
async acquire(path) {
|
|
6482
|
-
const prev = this.tails.get(path) ?? Promise.resolve();
|
|
6483
|
-
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
6484
|
-
const tail2 = prev.then(() => promise);
|
|
6485
|
-
this.tails.set(path, tail2);
|
|
6486
|
-
await prev;
|
|
6487
|
-
return () => {
|
|
6488
|
-
resolve2();
|
|
6489
|
-
if (this.tails.get(path) === tail2) this.tails.delete(path);
|
|
6490
|
-
};
|
|
6491
|
-
}
|
|
6492
|
-
async run(path, op) {
|
|
6493
|
-
const release = await this.acquire(path);
|
|
6494
|
-
try {
|
|
6495
|
-
return await op();
|
|
6496
|
-
} finally {
|
|
6497
|
-
release();
|
|
6498
|
-
}
|
|
6499
|
-
}
|
|
6470
|
+
// src/cron/command.ts
|
|
6471
|
+
var CRONTAB_PATH = "/usr/bin/crontab";
|
|
6472
|
+
var CRONTAB_COMMAND = {
|
|
6473
|
+
name: "crontab",
|
|
6474
|
+
abi: "js",
|
|
6475
|
+
glue: CRONTAB_PATH,
|
|
6476
|
+
wasm: CRONTAB_PATH,
|
|
6477
|
+
argv0: "crontab"
|
|
6500
6478
|
};
|
|
6501
|
-
var
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
const
|
|
6548
|
-
if (
|
|
6549
|
-
|
|
6550
|
-
}
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
},
|
|
6557
|
-
readdir: async (path) => visible(await fs.readdir(path)),
|
|
6558
|
-
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
|
|
6559
|
-
};
|
|
6479
|
+
var CRONTAB_SOURCE = `export default async function crontab(ctx) {
|
|
6480
|
+
const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);
|
|
6481
|
+
const usage = async () => {
|
|
6482
|
+
await say(2, "usage: crontab [-u user] { -l | -r | -e | file | - }\\n");
|
|
6483
|
+
return 1;
|
|
6484
|
+
};
|
|
6485
|
+
const args = ctx.argv.slice(1);
|
|
6486
|
+
let user;
|
|
6487
|
+
let action;
|
|
6488
|
+
let file;
|
|
6489
|
+
for (let i = 0; i < args.length; i++) {
|
|
6490
|
+
const arg = args[i];
|
|
6491
|
+
if (arg === "-u") {
|
|
6492
|
+
user = args[++i];
|
|
6493
|
+
if (user === undefined) return usage();
|
|
6494
|
+
} else if (arg === "-l" || arg === "-r" || arg === "-e") {
|
|
6495
|
+
if (action !== undefined) return usage();
|
|
6496
|
+
action = arg.slice(1);
|
|
6497
|
+
} else if (arg === "-" || !arg.startsWith("-")) {
|
|
6498
|
+
if (file !== undefined) return usage();
|
|
6499
|
+
file = arg;
|
|
6500
|
+
} else return usage();
|
|
6501
|
+
}
|
|
6502
|
+
if ((action === undefined) === (file === undefined)) return usage();
|
|
6503
|
+
const call = (c) => ctx.crontab(user === undefined ? c : { ...c, user });
|
|
6504
|
+
const failed = async (answer) => {
|
|
6505
|
+
await say(2, answer.error + "\\n");
|
|
6506
|
+
return 1;
|
|
6507
|
+
};
|
|
6508
|
+
const decode = (bytes) => new TextDecoder().decode(bytes);
|
|
6509
|
+
if (action === "l") {
|
|
6510
|
+
const answer = await call({ action: "list" });
|
|
6511
|
+
if (answer.error !== undefined) return failed(answer);
|
|
6512
|
+
await say(1, answer.text);
|
|
6513
|
+
return 0;
|
|
6514
|
+
}
|
|
6515
|
+
if (action === "r") {
|
|
6516
|
+
const answer = await call({ action: "remove" });
|
|
6517
|
+
return answer.error !== undefined ? failed(answer) : 0;
|
|
6518
|
+
}
|
|
6519
|
+
if (action === "e") return edit();
|
|
6520
|
+
let text;
|
|
6521
|
+
try {
|
|
6522
|
+
if (file === "-") {
|
|
6523
|
+
const parts = [];
|
|
6524
|
+
for (;;) {
|
|
6525
|
+
const chunk = await ctx.read(0);
|
|
6526
|
+
if (chunk.length === 0) break;
|
|
6527
|
+
parts.push(decode(chunk));
|
|
6528
|
+
}
|
|
6529
|
+
text = parts.join("");
|
|
6530
|
+
} else text = decode(await ctx.fs.readFile(ctx.resolve(file)));
|
|
6531
|
+
} catch (err) {
|
|
6532
|
+
await say(2, "crontab: " + file + ": " + (err && err.code ? err.code : String(err)) + "\\n");
|
|
6533
|
+
return 1;
|
|
6560
6534
|
}
|
|
6561
|
-
|
|
6535
|
+
const answer = await call({ action: "install", text });
|
|
6536
|
+
return answer.error !== undefined ? failed(answer) : 0;
|
|
6537
|
+
|
|
6538
|
+
async function edit() {
|
|
6539
|
+
const editor = ctx.env.VISUAL || ctx.env.EDITOR;
|
|
6540
|
+
if (!editor) {
|
|
6541
|
+
await say(2, "crontab: set VISUAL or EDITOR to edit, or install a file with crontab <file>\\n");
|
|
6542
|
+
return 1;
|
|
6543
|
+
}
|
|
6544
|
+
const current = await call({ action: "list" });
|
|
6545
|
+
const before = current.error === undefined ? current.text : "";
|
|
6546
|
+
if (current.error !== undefined && !current.error.startsWith("no crontab for ")) return failed(current);
|
|
6547
|
+
const tmp = "/tmp/crontab." + ctx.pid + "." + crypto.randomUUID();
|
|
6562
6548
|
try {
|
|
6563
|
-
await
|
|
6549
|
+
const file = await ctx.open(tmp, { write: true, create: true, exclusive: true, mode: 0o600 });
|
|
6550
|
+
await file.write(0, new TextEncoder().encode(before));
|
|
6551
|
+
await file.close();
|
|
6564
6552
|
} catch (err) {
|
|
6565
|
-
await
|
|
6566
|
-
|
|
6553
|
+
await say(2, "crontab: " + tmp + ": " + (err && err.code ? err.code : String(err)) + "\\n");
|
|
6554
|
+
return 1;
|
|
6567
6555
|
}
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
for (const path of holder.held()) {
|
|
6575
|
-
if (!within4(path, target) || holder.isKept(path)) continue;
|
|
6576
|
-
held.set(path, [...held.get(path) ?? [], holder]);
|
|
6577
|
-
}
|
|
6556
|
+
const child = await ctx.spawn({ argv: ["/bin/bash", "-c", editor + ' "$1"', "crontab", tmp] });
|
|
6557
|
+
const ended = await ctx.wait(child.pid);
|
|
6558
|
+
if (ended.status !== 0) {
|
|
6559
|
+
await say(2, "crontab: the editor exited with status " + ended.status + ", crontab not changed\\n");
|
|
6560
|
+
await ctx.fs.rm(tmp).catch(() => undefined);
|
|
6561
|
+
return 1;
|
|
6578
6562
|
}
|
|
6579
|
-
const
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
const at = await this.hide(path);
|
|
6585
|
-
if (at !== void 0) hidden.set(path, at);
|
|
6563
|
+
const after = decode(await ctx.fs.readFile(tmp));
|
|
6564
|
+
if (after === before) {
|
|
6565
|
+
await say(2, "crontab: no changes made to crontab\\n");
|
|
6566
|
+
await ctx.fs.rm(tmp).catch(() => undefined);
|
|
6567
|
+
return 0;
|
|
6586
6568
|
}
|
|
6587
|
-
const
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
for (const [path, at] of hidden) {
|
|
6592
|
-
const holders = held.get(path);
|
|
6593
|
-
const file = this.keptFile(at, { refs: holders.length + 1 });
|
|
6594
|
-
files.set(path, file);
|
|
6595
|
-
for (const holder of holders) holder.keep(path, file);
|
|
6569
|
+
const answer = await call({ action: "install", text: after });
|
|
6570
|
+
if (answer.error !== undefined) {
|
|
6571
|
+
await say(2, answer.error + "\\ncrontab: your edit is kept in " + tmp + "\\n");
|
|
6572
|
+
return 1;
|
|
6596
6573
|
}
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
commit: () => {
|
|
6600
|
-
for (const file of files.values()) file.release();
|
|
6601
|
-
done4();
|
|
6602
|
-
},
|
|
6603
|
-
rollback: async () => {
|
|
6604
|
-
for (const [path, file] of files) {
|
|
6605
|
-
for (const holder of held.get(path)) holder.unkeep(path, file);
|
|
6606
|
-
await this.serial(async () => {
|
|
6607
|
-
await Promise.allSettled([this.fs.rename(file.hidden, path)]);
|
|
6608
|
-
this.unlinkedName(file.hidden);
|
|
6609
|
-
await this.tidy(parentOf5(file.hidden));
|
|
6610
|
-
});
|
|
6611
|
-
}
|
|
6612
|
-
done4();
|
|
6613
|
-
}
|
|
6614
|
-
};
|
|
6615
|
-
}
|
|
6616
|
-
keptFile(hidden, entry) {
|
|
6617
|
-
return {
|
|
6618
|
-
hidden,
|
|
6619
|
-
release: () => {
|
|
6620
|
-
if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
|
|
6621
|
-
}
|
|
6622
|
-
};
|
|
6623
|
-
}
|
|
6624
|
-
hide(path) {
|
|
6625
|
-
return this.serial(async () => {
|
|
6626
|
-
const dir = dirOf(this.rootOf(path));
|
|
6627
|
-
const hidden = `${dir}/${this.instance}.${++this.count}`;
|
|
6628
|
-
try {
|
|
6629
|
-
const owner = this.dirOwner;
|
|
6630
|
-
await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
|
|
6631
|
-
await this.fs.rename(path, hidden);
|
|
6632
|
-
await this.linkedName(hidden);
|
|
6633
|
-
return hidden;
|
|
6634
|
-
} catch {
|
|
6635
|
-
await this.tidy(dir);
|
|
6636
|
-
return void 0;
|
|
6637
|
-
}
|
|
6638
|
-
});
|
|
6639
|
-
}
|
|
6640
|
-
closed(path) {
|
|
6641
|
-
if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
|
|
6642
|
-
}
|
|
6643
|
-
async drop(hidden) {
|
|
6644
|
-
if (this.busy(hidden)) {
|
|
6645
|
-
this.waiting.add(hidden);
|
|
6646
|
-
return;
|
|
6647
|
-
}
|
|
6648
|
-
await this.fs.rm(hidden).catch(() => void 0);
|
|
6649
|
-
this.unlinkedName(hidden);
|
|
6650
|
-
await this.tidy(parentOf5(hidden));
|
|
6651
|
-
}
|
|
6652
|
-
async linkedName(hidden) {
|
|
6653
|
-
if (!mayLink(this.fs, hidden)) return;
|
|
6654
|
-
const st = await this.fs.lstat(hidden).catch(() => void 0);
|
|
6655
|
-
if (st?.nlink === void 0) return;
|
|
6656
|
-
const id2 = `${st.dev ?? 0}:${st.ino}`;
|
|
6657
|
-
this.linked.set(hidden, id2);
|
|
6658
|
-
this.links.set(id2, (this.links.get(id2) ?? 0) + 1);
|
|
6659
|
-
}
|
|
6660
|
-
unlinkedName(hidden) {
|
|
6661
|
-
const id2 = this.linked.get(hidden);
|
|
6662
|
-
if (id2 === void 0) return;
|
|
6663
|
-
this.linked.delete(hidden);
|
|
6664
|
-
const left = (this.links.get(id2) ?? 1) - 1;
|
|
6665
|
-
if (left > 0) this.links.set(id2, left);
|
|
6666
|
-
else this.links.delete(id2);
|
|
6667
|
-
}
|
|
6668
|
-
async tidy(dir) {
|
|
6669
|
-
const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
|
|
6670
|
-
if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
|
|
6671
|
-
}
|
|
6672
|
-
sweep(root) {
|
|
6673
|
-
const query = this.locks?.query?.bind(this.locks);
|
|
6674
|
-
const owner = this.owner;
|
|
6675
|
-
if (!query && !owner) return Promise.resolve();
|
|
6676
|
-
return this.serial(async () => {
|
|
6677
|
-
const dir = dirOf(root);
|
|
6678
|
-
const names = await this.fs.readdir(dir).catch(() => void 0);
|
|
6679
|
-
if (!names) return;
|
|
6680
|
-
const held = query && new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
6681
|
-
for (const name2 of names) {
|
|
6682
|
-
const id2 = name2.slice(0, name2.lastIndexOf("."));
|
|
6683
|
-
const at = id2.indexOf("~");
|
|
6684
|
-
const dead = id2 !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id2)) : owner?.alive(id2.slice(0, at)) === false);
|
|
6685
|
-
if (dead) await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
|
|
6686
|
-
}
|
|
6687
|
-
await this.tidy(dir);
|
|
6688
|
-
});
|
|
6574
|
+
await ctx.fs.rm(tmp).catch(() => undefined);
|
|
6575
|
+
return 0;
|
|
6689
6576
|
}
|
|
6690
|
-
}
|
|
6691
|
-
|
|
6692
|
-
// src/cron/clock.ts
|
|
6693
|
-
var systemCronClock = {
|
|
6694
|
-
now: () => Date.now(),
|
|
6695
|
-
sleep(until, signal) {
|
|
6696
|
-
if (signal.aborted) return Promise.resolve();
|
|
6697
|
-
return new Promise((resolve2) => {
|
|
6698
|
-
const done4 = () => {
|
|
6699
|
-
clearTimeout(timer);
|
|
6700
|
-
signal.removeEventListener("abort", done4);
|
|
6701
|
-
resolve2();
|
|
6702
|
-
};
|
|
6703
|
-
const timer = setTimeout(done4, Math.max(0, until - Date.now()));
|
|
6704
|
-
timer.unref?.();
|
|
6705
|
-
signal.addEventListener("abort", done4, { once: true });
|
|
6706
|
-
});
|
|
6707
|
-
}
|
|
6708
|
-
};
|
|
6577
|
+
}
|
|
6578
|
+
`;
|
|
6709
6579
|
|
|
6710
6580
|
// src/cron/schedule.ts
|
|
6711
6581
|
var MISSED_CAP = 1e5;
|
|
@@ -6951,6 +6821,305 @@ function parseCrontab(text, path, system = false) {
|
|
|
6951
6821
|
return { jobs, errors };
|
|
6952
6822
|
}
|
|
6953
6823
|
|
|
6824
|
+
// src/cron/crontab.ts
|
|
6825
|
+
var CRONTAB_MAX = 256 * 1024;
|
|
6826
|
+
var ACTIONS = /* @__PURE__ */ new Set(["list", "remove", "install"]);
|
|
6827
|
+
function crontabCall(req) {
|
|
6828
|
+
const { action, user, text } = req;
|
|
6829
|
+
if (typeof action !== "string" || !ACTIONS.has(action)) return void 0;
|
|
6830
|
+
if (user !== void 0 && typeof user !== "string") return void 0;
|
|
6831
|
+
if (action === "install" && typeof text !== "string") return void 0;
|
|
6832
|
+
return {
|
|
6833
|
+
op: "proc-crontab",
|
|
6834
|
+
action,
|
|
6835
|
+
...user !== void 0 ? { user } : {},
|
|
6836
|
+
...action === "install" ? { text } : {}
|
|
6837
|
+
};
|
|
6838
|
+
}
|
|
6839
|
+
var Crontabs = class {
|
|
6840
|
+
o;
|
|
6841
|
+
constructor(options) {
|
|
6842
|
+
this.o = options;
|
|
6843
|
+
}
|
|
6844
|
+
async request(cred, call) {
|
|
6845
|
+
const caller = await this.o.lookup(cred.ruid);
|
|
6846
|
+
if (!caller) return { error: `crontab: your uid ${cred.ruid} is not in /etc/passwd` };
|
|
6847
|
+
let target = caller;
|
|
6848
|
+
if (call.user !== void 0 && call.user !== caller.name) {
|
|
6849
|
+
if (cred.euid !== 0) return { error: "crontab: must be privileged to use -u" };
|
|
6850
|
+
const found = await this.o.lookup(call.user);
|
|
6851
|
+
if (!found) return { error: `crontab: user ${call.user} unknown` };
|
|
6852
|
+
target = found;
|
|
6853
|
+
}
|
|
6854
|
+
const path = `${SPOOL_DIR}/${target.name}`;
|
|
6855
|
+
const none = { error: `no crontab for ${target.name}` };
|
|
6856
|
+
const { fs } = this.o;
|
|
6857
|
+
if (call.action === "install") return this.install(path, target, call.text);
|
|
6858
|
+
if (!await fs.exists(path)) return none;
|
|
6859
|
+
if (call.action === "list") return { text: await fs.readFile(path) };
|
|
6860
|
+
await fs.rm(path);
|
|
6861
|
+
return { text: "" };
|
|
6862
|
+
}
|
|
6863
|
+
async install(path, target, text) {
|
|
6864
|
+
if (text.length > CRONTAB_MAX) return { error: "crontab: the table is too long" };
|
|
6865
|
+
const { errors } = parseCrontab(text, path);
|
|
6866
|
+
if (errors.length > 0) {
|
|
6867
|
+
const lines2 = errors.map(({ line, message: message3 }) => `"-":${line}: ${message3}`);
|
|
6868
|
+
return { error: ["crontab: errors in crontab file, cannot install", ...lines2].join("\n") };
|
|
6869
|
+
}
|
|
6870
|
+
const { fs, owners } = this.o;
|
|
6871
|
+
if (!await fs.exists(SPOOL_DIR)) {
|
|
6872
|
+
await fs.mkdir(SPOOL_DIR, { recursive: true });
|
|
6873
|
+
if (owners) await fs.chown?.(SPOOL_DIR, { uid: 0, gid: 0, mode: 448, follow: false });
|
|
6874
|
+
}
|
|
6875
|
+
const fresh2 = `${SPOOL_DIR}/.${target.name}.${crypto.randomUUID()}.new`;
|
|
6876
|
+
const owner = owners ? { uid: target.uid, gid: target.gid, mode: 384 } : void 0;
|
|
6877
|
+
await fs.writeFile(fresh2, text, owner);
|
|
6878
|
+
await fs.rename(fresh2, path);
|
|
6879
|
+
return { text: "" };
|
|
6880
|
+
}
|
|
6881
|
+
};
|
|
6882
|
+
|
|
6883
|
+
// src/fs/unlinked.ts
|
|
6884
|
+
var UNLINKED_DIR = ".slicc-unlinked";
|
|
6885
|
+
function webLocks() {
|
|
6886
|
+
return globalThis.navigator?.locks;
|
|
6887
|
+
}
|
|
6888
|
+
var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
6889
|
+
var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
6890
|
+
var lockName = (instance) => `slicc-unlinked:${instance}`;
|
|
6891
|
+
var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
|
|
6892
|
+
var PathGate = class {
|
|
6893
|
+
tails = /* @__PURE__ */ new Map();
|
|
6894
|
+
async acquire(path) {
|
|
6895
|
+
const prev = this.tails.get(path) ?? Promise.resolve();
|
|
6896
|
+
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
6897
|
+
const tail2 = prev.then(() => promise);
|
|
6898
|
+
this.tails.set(path, tail2);
|
|
6899
|
+
await prev;
|
|
6900
|
+
return () => {
|
|
6901
|
+
resolve2();
|
|
6902
|
+
if (this.tails.get(path) === tail2) this.tails.delete(path);
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
async run(path, op) {
|
|
6906
|
+
const release = await this.acquire(path);
|
|
6907
|
+
try {
|
|
6908
|
+
return await op();
|
|
6909
|
+
} finally {
|
|
6910
|
+
release();
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6913
|
+
};
|
|
6914
|
+
var UnlinkedKeeper = class {
|
|
6915
|
+
fs;
|
|
6916
|
+
holders;
|
|
6917
|
+
rootOf;
|
|
6918
|
+
locks;
|
|
6919
|
+
busy;
|
|
6920
|
+
waiting = /* @__PURE__ */ new Set();
|
|
6921
|
+
owner;
|
|
6922
|
+
instance;
|
|
6923
|
+
gate = new PathGate();
|
|
6924
|
+
queue = Promise.resolve();
|
|
6925
|
+
count = 0;
|
|
6926
|
+
links = /* @__PURE__ */ new Map();
|
|
6927
|
+
linked = /* @__PURE__ */ new Map();
|
|
6928
|
+
dirOwner;
|
|
6929
|
+
constructor(fs, holders, rootOf, options = {}) {
|
|
6930
|
+
const { locks, owner } = options;
|
|
6931
|
+
this.dirOwner = options.dirOwner;
|
|
6932
|
+
this.fs = fs;
|
|
6933
|
+
this.holders = holders;
|
|
6934
|
+
this.rootOf = rootOf;
|
|
6935
|
+
this.locks = locks;
|
|
6936
|
+
this.busy = options.busy ?? (() => false);
|
|
6937
|
+
this.owner = owner;
|
|
6938
|
+
this.instance = owner ? `${owner.id}~${crypto.randomUUID()}` : crypto.randomUUID();
|
|
6939
|
+
void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
|
|
6940
|
+
}
|
|
6941
|
+
serial(op) {
|
|
6942
|
+
const next3 = this.queue.then(op);
|
|
6943
|
+
this.queue = next3.catch(() => void 0);
|
|
6944
|
+
return next3;
|
|
6945
|
+
}
|
|
6946
|
+
wrap(fs = this.fs) {
|
|
6947
|
+
const keeper = this;
|
|
6948
|
+
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
6949
|
+
const counted2 = (st) => {
|
|
6950
|
+
const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
|
|
6951
|
+
return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
|
|
6952
|
+
};
|
|
6953
|
+
return {
|
|
6954
|
+
...fs,
|
|
6955
|
+
links: (path) => mayLink(fs, path),
|
|
6956
|
+
stat: async (path) => counted2(await fs.stat(path)),
|
|
6957
|
+
lstat: async (path) => counted2(await fs.lstat(path)),
|
|
6958
|
+
async rm(path, options) {
|
|
6959
|
+
const target = fs.resolvePath("/", path);
|
|
6960
|
+
const pinned = await keeper.pin(target);
|
|
6961
|
+
if (pinned.moved.has(target)) pinned.commit();
|
|
6962
|
+
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
6963
|
+
},
|
|
6964
|
+
async rename(from, to) {
|
|
6965
|
+
const target = fs.resolvePath("/", to);
|
|
6966
|
+
if (target === fs.resolvePath("/", from)) return fs.rename(from, to);
|
|
6967
|
+
const pinned = await keeper.pin(target);
|
|
6968
|
+
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
6969
|
+
},
|
|
6970
|
+
readdir: async (path) => visible(await fs.readdir(path)),
|
|
6971
|
+
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
|
|
6972
|
+
};
|
|
6973
|
+
}
|
|
6974
|
+
async settle(pinned, op) {
|
|
6975
|
+
try {
|
|
6976
|
+
await op();
|
|
6977
|
+
} catch (err) {
|
|
6978
|
+
await pinned.rollback();
|
|
6979
|
+
throw err;
|
|
6980
|
+
}
|
|
6981
|
+
pinned.commit();
|
|
6982
|
+
}
|
|
6983
|
+
async pin(target) {
|
|
6984
|
+
const held = /* @__PURE__ */ new Map();
|
|
6985
|
+
for (const holder of this.holders) {
|
|
6986
|
+
if (holder.owns(target)) continue;
|
|
6987
|
+
for (const path of holder.held()) {
|
|
6988
|
+
if (!within4(path, target) || holder.isKept(path)) continue;
|
|
6989
|
+
held.set(path, [...held.get(path) ?? [], holder]);
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
const paths = [...held.keys()].sort();
|
|
6993
|
+
const releases = [];
|
|
6994
|
+
for (const path of paths) releases.push(await this.gate.acquire(path));
|
|
6995
|
+
const hidden = /* @__PURE__ */ new Map();
|
|
6996
|
+
for (const path of paths) {
|
|
6997
|
+
const at = await this.hide(path);
|
|
6998
|
+
if (at !== void 0) hidden.set(path, at);
|
|
6999
|
+
}
|
|
7000
|
+
const done4 = () => {
|
|
7001
|
+
for (const release of releases) release();
|
|
7002
|
+
};
|
|
7003
|
+
const files = /* @__PURE__ */ new Map();
|
|
7004
|
+
for (const [path, at] of hidden) {
|
|
7005
|
+
const holders = held.get(path);
|
|
7006
|
+
const file = this.keptFile(at, { refs: holders.length + 1 });
|
|
7007
|
+
files.set(path, file);
|
|
7008
|
+
for (const holder of holders) holder.keep(path, file);
|
|
7009
|
+
}
|
|
7010
|
+
return {
|
|
7011
|
+
moved: new Set(hidden.keys()),
|
|
7012
|
+
commit: () => {
|
|
7013
|
+
for (const file of files.values()) file.release();
|
|
7014
|
+
done4();
|
|
7015
|
+
},
|
|
7016
|
+
rollback: async () => {
|
|
7017
|
+
for (const [path, file] of files) {
|
|
7018
|
+
for (const holder of held.get(path)) holder.unkeep(path, file);
|
|
7019
|
+
await this.serial(async () => {
|
|
7020
|
+
await Promise.allSettled([this.fs.rename(file.hidden, path)]);
|
|
7021
|
+
this.unlinkedName(file.hidden);
|
|
7022
|
+
await this.tidy(parentOf5(file.hidden));
|
|
7023
|
+
});
|
|
7024
|
+
}
|
|
7025
|
+
done4();
|
|
7026
|
+
}
|
|
7027
|
+
};
|
|
7028
|
+
}
|
|
7029
|
+
keptFile(hidden, entry) {
|
|
7030
|
+
return {
|
|
7031
|
+
hidden,
|
|
7032
|
+
release: () => {
|
|
7033
|
+
if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
|
|
7034
|
+
}
|
|
7035
|
+
};
|
|
7036
|
+
}
|
|
7037
|
+
hide(path) {
|
|
7038
|
+
return this.serial(async () => {
|
|
7039
|
+
const dir = dirOf(this.rootOf(path));
|
|
7040
|
+
const hidden = `${dir}/${this.instance}.${++this.count}`;
|
|
7041
|
+
try {
|
|
7042
|
+
const owner = this.dirOwner;
|
|
7043
|
+
await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
|
|
7044
|
+
await this.fs.rename(path, hidden);
|
|
7045
|
+
await this.linkedName(hidden);
|
|
7046
|
+
return hidden;
|
|
7047
|
+
} catch {
|
|
7048
|
+
await this.tidy(dir);
|
|
7049
|
+
return void 0;
|
|
7050
|
+
}
|
|
7051
|
+
});
|
|
7052
|
+
}
|
|
7053
|
+
closed(path) {
|
|
7054
|
+
if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
|
|
7055
|
+
}
|
|
7056
|
+
async drop(hidden) {
|
|
7057
|
+
if (this.busy(hidden)) {
|
|
7058
|
+
this.waiting.add(hidden);
|
|
7059
|
+
return;
|
|
7060
|
+
}
|
|
7061
|
+
await this.fs.rm(hidden).catch(() => void 0);
|
|
7062
|
+
this.unlinkedName(hidden);
|
|
7063
|
+
await this.tidy(parentOf5(hidden));
|
|
7064
|
+
}
|
|
7065
|
+
async linkedName(hidden) {
|
|
7066
|
+
if (!mayLink(this.fs, hidden)) return;
|
|
7067
|
+
const st = await this.fs.lstat(hidden).catch(() => void 0);
|
|
7068
|
+
if (st?.nlink === void 0) return;
|
|
7069
|
+
const id2 = `${st.dev ?? 0}:${st.ino}`;
|
|
7070
|
+
this.linked.set(hidden, id2);
|
|
7071
|
+
this.links.set(id2, (this.links.get(id2) ?? 0) + 1);
|
|
7072
|
+
}
|
|
7073
|
+
unlinkedName(hidden) {
|
|
7074
|
+
const id2 = this.linked.get(hidden);
|
|
7075
|
+
if (id2 === void 0) return;
|
|
7076
|
+
this.linked.delete(hidden);
|
|
7077
|
+
const left = (this.links.get(id2) ?? 1) - 1;
|
|
7078
|
+
if (left > 0) this.links.set(id2, left);
|
|
7079
|
+
else this.links.delete(id2);
|
|
7080
|
+
}
|
|
7081
|
+
async tidy(dir) {
|
|
7082
|
+
const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
|
|
7083
|
+
if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
|
|
7084
|
+
}
|
|
7085
|
+
sweep(root) {
|
|
7086
|
+
const query = this.locks?.query?.bind(this.locks);
|
|
7087
|
+
const owner = this.owner;
|
|
7088
|
+
if (!query && !owner) return Promise.resolve();
|
|
7089
|
+
return this.serial(async () => {
|
|
7090
|
+
const dir = dirOf(root);
|
|
7091
|
+
const names = await this.fs.readdir(dir).catch(() => void 0);
|
|
7092
|
+
if (!names) return;
|
|
7093
|
+
const held = query && new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
7094
|
+
for (const name2 of names) {
|
|
7095
|
+
const id2 = name2.slice(0, name2.lastIndexOf("."));
|
|
7096
|
+
const at = id2.indexOf("~");
|
|
7097
|
+
const dead = id2 !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id2)) : owner?.alive(id2.slice(0, at)) === false);
|
|
7098
|
+
if (dead) await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
|
|
7099
|
+
}
|
|
7100
|
+
await this.tidy(dir);
|
|
7101
|
+
});
|
|
7102
|
+
}
|
|
7103
|
+
};
|
|
7104
|
+
|
|
7105
|
+
// src/cron/clock.ts
|
|
7106
|
+
var systemCronClock = {
|
|
7107
|
+
now: () => Date.now(),
|
|
7108
|
+
sleep(until, signal) {
|
|
7109
|
+
if (signal.aborted) return Promise.resolve();
|
|
7110
|
+
return new Promise((resolve2) => {
|
|
7111
|
+
const done4 = () => {
|
|
7112
|
+
clearTimeout(timer);
|
|
7113
|
+
signal.removeEventListener("abort", done4);
|
|
7114
|
+
resolve2();
|
|
7115
|
+
};
|
|
7116
|
+
const timer = setTimeout(done4, Math.max(0, until - Date.now()));
|
|
7117
|
+
timer.unref?.();
|
|
7118
|
+
signal.addEventListener("abort", done4, { once: true });
|
|
7119
|
+
});
|
|
7120
|
+
}
|
|
7121
|
+
};
|
|
7122
|
+
|
|
6954
7123
|
// src/cron/service.ts
|
|
6955
7124
|
var CRON_DIR = "/var/spool/cron";
|
|
6956
7125
|
var STAMP_PATH = `${CRON_DIR}/stamp`;
|
|
@@ -7127,7 +7296,7 @@ var CronService = class {
|
|
|
7127
7296
|
}
|
|
7128
7297
|
const names = await fs.readdir(SPOOL_DIR).catch(() => []);
|
|
7129
7298
|
for (const name2 of names.sort()) {
|
|
7130
|
-
if (name2.startsWith(".") || name2.endsWith("~")
|
|
7299
|
+
if (name2.startsWith(".") || name2.endsWith("~")) continue;
|
|
7131
7300
|
const path = `${SPOOL_DIR}/${name2}`;
|
|
7132
7301
|
const account = await this.host.lookup(name2);
|
|
7133
7302
|
if (!account) {
|
|
@@ -11286,12 +11455,11 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
11286
11455
|
if (disposed) return;
|
|
11287
11456
|
const { status, payload } = encodeSabResult(result);
|
|
11288
11457
|
drop(id2);
|
|
11289
|
-
const entry = {
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
}
|
|
11294
|
-
pending.set(id2, entry);
|
|
11458
|
+
const entry = { status, payload };
|
|
11459
|
+
if (payload.byteLength > window.byteLength) {
|
|
11460
|
+
entry.timer = setTimeout(() => drop(id2), PENDING_TTL_MS);
|
|
11461
|
+
pending.set(id2, entry);
|
|
11462
|
+
}
|
|
11295
11463
|
publish(id2, entry, 0);
|
|
11296
11464
|
}
|
|
11297
11465
|
const handler = (event) => {
|
|
@@ -12111,6 +12279,8 @@ var AsyncRangedFile = class {
|
|
|
12111
12279
|
|
|
12112
12280
|
// src/realm/sync-sab-bridge.ts
|
|
12113
12281
|
var PATH_OPS = new Set(SYNC_FS_OPS);
|
|
12282
|
+
var SPIN_CORES = 4;
|
|
12283
|
+
var spinning = (globalThis.navigator?.hardwareConcurrency ?? 0) >= SPIN_CORES;
|
|
12114
12284
|
|
|
12115
12285
|
// src/realm/live-vfs-fs.ts
|
|
12116
12286
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
@@ -16636,6 +16806,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
16636
16806
|
"proc-wait",
|
|
16637
16807
|
"proc-captured",
|
|
16638
16808
|
"proc-sudo",
|
|
16809
|
+
"proc-crontab",
|
|
16639
16810
|
"proc-run",
|
|
16640
16811
|
"proc-exit",
|
|
16641
16812
|
"proc-kill-self",
|
|
@@ -17825,8 +17996,17 @@ var WasmProcess = class {
|
|
|
17825
17996
|
return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
|
|
17826
17997
|
case "proc-sudo":
|
|
17827
17998
|
return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
|
|
17999
|
+
case "proc-crontab":
|
|
18000
|
+
return { ok: true, kind: "json", json: await this.crontab(req) };
|
|
17828
18001
|
}
|
|
17829
18002
|
}
|
|
18003
|
+
async crontab(req) {
|
|
18004
|
+
const { crontab } = this.options;
|
|
18005
|
+
if (!crontab) throw new KernelError("ENOSYS");
|
|
18006
|
+
const call = crontabCall(req);
|
|
18007
|
+
if (!call) throw new KernelError("EINVAL");
|
|
18008
|
+
return crontab(call);
|
|
18009
|
+
}
|
|
17830
18010
|
signalSyscall(req) {
|
|
17831
18011
|
if (req.op === "sig-inherited") {
|
|
17832
18012
|
return { ok: true, kind: "json", json: this.inherited & ~this.caught };
|
|
@@ -18229,6 +18409,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
18229
18409
|
...opts.writesBack ? { writesBack: opts.writesBack } : {},
|
|
18230
18410
|
processes: opts.processes,
|
|
18231
18411
|
...opts.sudo ? { sudo: opts.sudo } : {},
|
|
18412
|
+
...opts.crontab ? { crontab: opts.crontab } : {},
|
|
18232
18413
|
openFiles: opts.openFiles,
|
|
18233
18414
|
...opts.nodes ? { nodes: opts.nodes } : {},
|
|
18234
18415
|
mounts: opts.mounts,
|
|
@@ -21696,6 +21877,7 @@ var LAYOUT = [
|
|
|
21696
21877
|
["/usr", 493],
|
|
21697
21878
|
["/usr/local", 493]
|
|
21698
21879
|
];
|
|
21880
|
+
var IN_TREE = /* @__PURE__ */ new Set([SUDO_PATH, CRONTAB_PATH]);
|
|
21699
21881
|
var LOAD_SAMPLE_MS = 5e3;
|
|
21700
21882
|
var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
|
|
21701
21883
|
var encoder8 = new TextEncoder();
|
|
@@ -21926,6 +22108,7 @@ var Launcher = class {
|
|
|
21926
22108
|
boot = Date.now();
|
|
21927
22109
|
bootId = crypto.randomUUID();
|
|
21928
22110
|
cron;
|
|
22111
|
+
crontabs;
|
|
21929
22112
|
terminals = 0;
|
|
21930
22113
|
constructor(options) {
|
|
21931
22114
|
this.usersMode = usersMode(options.users);
|
|
@@ -22126,7 +22309,9 @@ var Launcher = class {
|
|
|
22126
22309
|
found = this.trustedAppended().then(async (appended2) => {
|
|
22127
22310
|
const tiers = await this.tiers(scope, appended2);
|
|
22128
22311
|
return scanCommands(this.scanned, tiers, tiers.length - appended2.length);
|
|
22129
|
-
}).then(
|
|
22312
|
+
}).then(
|
|
22313
|
+
(commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND).set(CRONTAB_COMMAND.name, CRONTAB_COMMAND)
|
|
22314
|
+
);
|
|
22130
22315
|
this.catalogs.set(scope, found);
|
|
22131
22316
|
}
|
|
22132
22317
|
return found;
|
|
@@ -22531,7 +22716,7 @@ var Launcher = class {
|
|
|
22531
22716
|
const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
|
|
22532
22717
|
if (root !== void 0) files.push(`${root}/package.json`);
|
|
22533
22718
|
}
|
|
22534
|
-
return [...new Set(files)].filter((path) => path !== "" && path
|
|
22719
|
+
return [...new Set(files)].filter((path) => path !== "" && !IN_TREE.has(path));
|
|
22535
22720
|
}
|
|
22536
22721
|
async unrunnable(file, cwd, scope) {
|
|
22537
22722
|
const path = this.fs.resolvePath(cwd, file);
|
|
@@ -22671,6 +22856,9 @@ var Launcher = class {
|
|
|
22671
22856
|
if (target.abi === "js" && target.wasm === SUDO_PATH) {
|
|
22672
22857
|
return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
|
|
22673
22858
|
}
|
|
22859
|
+
if (target.abi === "js" && target.wasm === CRONTAB_PATH) {
|
|
22860
|
+
return { abi: "js", glue: CRONTAB_SOURCE, path: CRONTAB_PATH };
|
|
22861
|
+
}
|
|
22674
22862
|
if (target.abi === "js" && target.wasm === REFUSED_PATH) {
|
|
22675
22863
|
return { abi: "js", glue: REFUSED_SOURCE, path: REFUSED_PATH };
|
|
22676
22864
|
}
|
|
@@ -22826,6 +23014,7 @@ var Launcher = class {
|
|
|
22826
23014
|
cpu: this.cpuTotal()
|
|
22827
23015
|
}),
|
|
22828
23016
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
23017
|
+
crontab: (call) => this.crontab(pid, call),
|
|
22829
23018
|
openFiles: this.openFiles,
|
|
22830
23019
|
nodes: stack.nodes,
|
|
22831
23020
|
held: this.held,
|
|
@@ -23044,6 +23233,14 @@ var Launcher = class {
|
|
|
23044
23233
|
const threads = this.stateOf(pid) === "S" ? this.processes.get(pid)?.threads?.() : void 0;
|
|
23045
23234
|
return threads ? { threads } : {};
|
|
23046
23235
|
}
|
|
23236
|
+
crontab(pid, call) {
|
|
23237
|
+
this.crontabs ??= new Crontabs({
|
|
23238
|
+
fs: this.fs,
|
|
23239
|
+
owners: this.owners,
|
|
23240
|
+
lookup: (user) => this.users.lookup(user)
|
|
23241
|
+
});
|
|
23242
|
+
return this.crontabs.request(this.credOf(pid), call);
|
|
23243
|
+
}
|
|
23047
23244
|
sudo(pid, call, signal) {
|
|
23048
23245
|
const caller = {
|
|
23049
23246
|
cred: this.credOf(pid),
|