@ai-ecoverse/slicc-kernel 1.108.5 → 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 +430 -234
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +2 -1
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +216 -20
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +2 -1
- 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);
|
|
6574
|
+
await ctx.fs.rm(tmp).catch(() => undefined);
|
|
6575
|
+
return 0;
|
|
6659
6576
|
}
|
|
6660
|
-
|
|
6661
|
-
|
|
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
|
-
});
|
|
6689
|
-
}
|
|
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) {
|
|
@@ -16637,6 +16806,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
16637
16806
|
"proc-wait",
|
|
16638
16807
|
"proc-captured",
|
|
16639
16808
|
"proc-sudo",
|
|
16809
|
+
"proc-crontab",
|
|
16640
16810
|
"proc-run",
|
|
16641
16811
|
"proc-exit",
|
|
16642
16812
|
"proc-kill-self",
|
|
@@ -17826,8 +17996,17 @@ var WasmProcess = class {
|
|
|
17826
17996
|
return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
|
|
17827
17997
|
case "proc-sudo":
|
|
17828
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) };
|
|
17829
18001
|
}
|
|
17830
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
|
+
}
|
|
17831
18010
|
signalSyscall(req) {
|
|
17832
18011
|
if (req.op === "sig-inherited") {
|
|
17833
18012
|
return { ok: true, kind: "json", json: this.inherited & ~this.caught };
|
|
@@ -18230,6 +18409,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
18230
18409
|
...opts.writesBack ? { writesBack: opts.writesBack } : {},
|
|
18231
18410
|
processes: opts.processes,
|
|
18232
18411
|
...opts.sudo ? { sudo: opts.sudo } : {},
|
|
18412
|
+
...opts.crontab ? { crontab: opts.crontab } : {},
|
|
18233
18413
|
openFiles: opts.openFiles,
|
|
18234
18414
|
...opts.nodes ? { nodes: opts.nodes } : {},
|
|
18235
18415
|
mounts: opts.mounts,
|
|
@@ -21697,6 +21877,7 @@ var LAYOUT = [
|
|
|
21697
21877
|
["/usr", 493],
|
|
21698
21878
|
["/usr/local", 493]
|
|
21699
21879
|
];
|
|
21880
|
+
var IN_TREE = /* @__PURE__ */ new Set([SUDO_PATH, CRONTAB_PATH]);
|
|
21700
21881
|
var LOAD_SAMPLE_MS = 5e3;
|
|
21701
21882
|
var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
|
|
21702
21883
|
var encoder8 = new TextEncoder();
|
|
@@ -21927,6 +22108,7 @@ var Launcher = class {
|
|
|
21927
22108
|
boot = Date.now();
|
|
21928
22109
|
bootId = crypto.randomUUID();
|
|
21929
22110
|
cron;
|
|
22111
|
+
crontabs;
|
|
21930
22112
|
terminals = 0;
|
|
21931
22113
|
constructor(options) {
|
|
21932
22114
|
this.usersMode = usersMode(options.users);
|
|
@@ -22127,7 +22309,9 @@ var Launcher = class {
|
|
|
22127
22309
|
found = this.trustedAppended().then(async (appended2) => {
|
|
22128
22310
|
const tiers = await this.tiers(scope, appended2);
|
|
22129
22311
|
return scanCommands(this.scanned, tiers, tiers.length - appended2.length);
|
|
22130
|
-
}).then(
|
|
22312
|
+
}).then(
|
|
22313
|
+
(commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND).set(CRONTAB_COMMAND.name, CRONTAB_COMMAND)
|
|
22314
|
+
);
|
|
22131
22315
|
this.catalogs.set(scope, found);
|
|
22132
22316
|
}
|
|
22133
22317
|
return found;
|
|
@@ -22532,7 +22716,7 @@ var Launcher = class {
|
|
|
22532
22716
|
const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
|
|
22533
22717
|
if (root !== void 0) files.push(`${root}/package.json`);
|
|
22534
22718
|
}
|
|
22535
|
-
return [...new Set(files)].filter((path) => path !== "" && path
|
|
22719
|
+
return [...new Set(files)].filter((path) => path !== "" && !IN_TREE.has(path));
|
|
22536
22720
|
}
|
|
22537
22721
|
async unrunnable(file, cwd, scope) {
|
|
22538
22722
|
const path = this.fs.resolvePath(cwd, file);
|
|
@@ -22672,6 +22856,9 @@ var Launcher = class {
|
|
|
22672
22856
|
if (target.abi === "js" && target.wasm === SUDO_PATH) {
|
|
22673
22857
|
return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
|
|
22674
22858
|
}
|
|
22859
|
+
if (target.abi === "js" && target.wasm === CRONTAB_PATH) {
|
|
22860
|
+
return { abi: "js", glue: CRONTAB_SOURCE, path: CRONTAB_PATH };
|
|
22861
|
+
}
|
|
22675
22862
|
if (target.abi === "js" && target.wasm === REFUSED_PATH) {
|
|
22676
22863
|
return { abi: "js", glue: REFUSED_SOURCE, path: REFUSED_PATH };
|
|
22677
22864
|
}
|
|
@@ -22827,6 +23014,7 @@ var Launcher = class {
|
|
|
22827
23014
|
cpu: this.cpuTotal()
|
|
22828
23015
|
}),
|
|
22829
23016
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
23017
|
+
crontab: (call) => this.crontab(pid, call),
|
|
22830
23018
|
openFiles: this.openFiles,
|
|
22831
23019
|
nodes: stack.nodes,
|
|
22832
23020
|
held: this.held,
|
|
@@ -23045,6 +23233,14 @@ var Launcher = class {
|
|
|
23045
23233
|
const threads = this.stateOf(pid) === "S" ? this.processes.get(pid)?.threads?.() : void 0;
|
|
23046
23234
|
return threads ? { threads } : {};
|
|
23047
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
|
+
}
|
|
23048
23244
|
sudo(pid, call, signal) {
|
|
23049
23245
|
const caller = {
|
|
23050
23246
|
cred: this.credOf(pid),
|