@ai-ecoverse/slicc-kernel 1.108.5 → 1.110.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 +22 -2
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +554 -311
- 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 +272 -29
- 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/commands.d.ts +6 -0
- 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 +4 -1
- package/dist/types/process/js/js-context.d.ts +13 -0
- package/package.json +1 -6
package/dist/kernel-worker.js
CHANGED
|
@@ -2645,14 +2645,14 @@ var WsReader = class {
|
|
|
2645
2645
|
}
|
|
2646
2646
|
};
|
|
2647
2647
|
function switchingHead(accept2, protocol = "") {
|
|
2648
|
-
const
|
|
2648
|
+
const chosen2 = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
|
|
2649
2649
|
` : "";
|
|
2650
2650
|
return latin1Bytes(
|
|
2651
2651
|
`HTTP/1.1 101 Switching Protocols\r
|
|
2652
2652
|
Upgrade: websocket\r
|
|
2653
2653
|
Connection: Upgrade\r
|
|
2654
2654
|
Sec-WebSocket-Accept: ${accept2}\r
|
|
2655
|
-
${
|
|
2655
|
+
${chosen2}\r
|
|
2656
2656
|
`
|
|
2657
2657
|
);
|
|
2658
2658
|
}
|
|
@@ -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.110.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
|
|
@@ -6156,6 +6156,37 @@ function argsOf(pkg, raw) {
|
|
|
6156
6156
|
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
6157
6157
|
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
6158
6158
|
}
|
|
6159
|
+
var warnedVariants = /* @__PURE__ */ new Set();
|
|
6160
|
+
function variantOf(pkg, raw) {
|
|
6161
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
6162
|
+
const { env, wasm } = raw;
|
|
6163
|
+
const path = inside(pkg, wasm);
|
|
6164
|
+
if (!path || !env || typeof env !== "object" || Array.isArray(env)) return void 0;
|
|
6165
|
+
const pairs = Object.entries(env);
|
|
6166
|
+
if (pairs.length === 0) return void 0;
|
|
6167
|
+
if (!pairs.every(([key, value2]) => ENV_KEY.test(key) && typeof value2 === "string")) {
|
|
6168
|
+
return void 0;
|
|
6169
|
+
}
|
|
6170
|
+
return { env: Object.fromEntries(pairs), wasm: path };
|
|
6171
|
+
}
|
|
6172
|
+
function variantsOf(pkg, name2, raw) {
|
|
6173
|
+
if (raw === void 0) return void 0;
|
|
6174
|
+
const variants = Array.isArray(raw) ? raw.map((one) => variantOf(pkg, one)) : [void 0];
|
|
6175
|
+
if (variants.every((one) => one !== void 0)) {
|
|
6176
|
+
return variants.length > 0 ? variants : void 0;
|
|
6177
|
+
}
|
|
6178
|
+
const key = `${pkg}:${name2}:${JSON.stringify(raw)}`;
|
|
6179
|
+
if (!warnedVariants.has(key)) {
|
|
6180
|
+
warnedVariants.add(key);
|
|
6181
|
+
console.warn(`slicc-kernel: ${pkg}: ${name2}: ignoring malformed "variants", running its wasm`);
|
|
6182
|
+
}
|
|
6183
|
+
return void 0;
|
|
6184
|
+
}
|
|
6185
|
+
function variantFor(variants, env) {
|
|
6186
|
+
return variants?.find(
|
|
6187
|
+
(one) => Object.entries(one.env).every(([key, value2]) => env[key] === value2)
|
|
6188
|
+
)?.wasm;
|
|
6189
|
+
}
|
|
6159
6190
|
function abiOf(raw, fallback) {
|
|
6160
6191
|
const abi = raw ?? fallback;
|
|
6161
6192
|
return abi === "emscripten" || abi === "wasi" || abi === "js" ? abi : void 0;
|
|
@@ -6189,6 +6220,7 @@ function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
|
6189
6220
|
const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
|
|
6190
6221
|
if (!abi || !glue || !wasm) return void 0;
|
|
6191
6222
|
const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
|
|
6223
|
+
const variants = variantsOf(pkg, name2, raw.variants);
|
|
6192
6224
|
return {
|
|
6193
6225
|
name: name2,
|
|
6194
6226
|
abi,
|
|
@@ -6200,7 +6232,8 @@ function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
|
6200
6232
|
...abi === "wasi" && raw.preopenRoot === true ? { preopenRoot: true } : {},
|
|
6201
6233
|
...args ? { args } : {},
|
|
6202
6234
|
...withEnv,
|
|
6203
|
-
...imports ? { imports } : {}
|
|
6235
|
+
...imports ? { imports } : {},
|
|
6236
|
+
...variants ? { variants } : {}
|
|
6204
6237
|
};
|
|
6205
6238
|
}
|
|
6206
6239
|
function commandsOf(pkg, manifest) {
|
|
@@ -6467,245 +6500,116 @@ function binfmtOf(binfmts, path) {
|
|
|
6467
6500
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
6468
6501
|
}
|
|
6469
6502
|
|
|
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
|
-
}
|
|
6503
|
+
// src/cron/command.ts
|
|
6504
|
+
var CRONTAB_PATH = "/usr/bin/crontab";
|
|
6505
|
+
var CRONTAB_COMMAND = {
|
|
6506
|
+
name: "crontab",
|
|
6507
|
+
abi: "js",
|
|
6508
|
+
glue: CRONTAB_PATH,
|
|
6509
|
+
wasm: CRONTAB_PATH,
|
|
6510
|
+
argv0: "crontab"
|
|
6500
6511
|
};
|
|
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
|
-
|
|
6548
|
-
|
|
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
|
-
};
|
|
6512
|
+
var CRONTAB_SOURCE = `export default async function crontab(ctx) {
|
|
6513
|
+
const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);
|
|
6514
|
+
const usage = async () => {
|
|
6515
|
+
await say(2, "usage: crontab [-u user] { -l | -r | -e | file | - }\\n");
|
|
6516
|
+
return 1;
|
|
6517
|
+
};
|
|
6518
|
+
const args = ctx.argv.slice(1);
|
|
6519
|
+
let user;
|
|
6520
|
+
let action;
|
|
6521
|
+
let file;
|
|
6522
|
+
for (let i = 0; i < args.length; i++) {
|
|
6523
|
+
const arg = args[i];
|
|
6524
|
+
if (arg === "-u") {
|
|
6525
|
+
user = args[++i];
|
|
6526
|
+
if (user === undefined) return usage();
|
|
6527
|
+
} else if (arg === "-l" || arg === "-r" || arg === "-e") {
|
|
6528
|
+
if (action !== undefined) return usage();
|
|
6529
|
+
action = arg.slice(1);
|
|
6530
|
+
} else if (arg === "-" || !arg.startsWith("-")) {
|
|
6531
|
+
if (file !== undefined) return usage();
|
|
6532
|
+
file = arg;
|
|
6533
|
+
} else return usage();
|
|
6534
|
+
}
|
|
6535
|
+
if ((action === undefined) === (file === undefined)) return usage();
|
|
6536
|
+
const call = (c) => ctx.crontab(user === undefined ? c : { ...c, user });
|
|
6537
|
+
const failed = async (answer) => {
|
|
6538
|
+
await say(2, answer.error + "\\n");
|
|
6539
|
+
return 1;
|
|
6540
|
+
};
|
|
6541
|
+
const decode = (bytes) => new TextDecoder().decode(bytes);
|
|
6542
|
+
if (action === "l") {
|
|
6543
|
+
const answer = await call({ action: "list" });
|
|
6544
|
+
if (answer.error !== undefined) return failed(answer);
|
|
6545
|
+
await say(1, answer.text);
|
|
6546
|
+
return 0;
|
|
6547
|
+
}
|
|
6548
|
+
if (action === "r") {
|
|
6549
|
+
const answer = await call({ action: "remove" });
|
|
6550
|
+
return answer.error !== undefined ? failed(answer) : 0;
|
|
6551
|
+
}
|
|
6552
|
+
if (action === "e") return edit();
|
|
6553
|
+
let text;
|
|
6554
|
+
try {
|
|
6555
|
+
if (file === "-") {
|
|
6556
|
+
const stream = new TextDecoder();
|
|
6557
|
+
const parts = [];
|
|
6558
|
+
for (;;) {
|
|
6559
|
+
const chunk = await ctx.read(0);
|
|
6560
|
+
if (chunk.length === 0) break;
|
|
6561
|
+
parts.push(stream.decode(chunk, { stream: true }));
|
|
6562
|
+
}
|
|
6563
|
+
text = parts.join("") + stream.decode();
|
|
6564
|
+
} else text = decode(await ctx.fs.readFile(ctx.resolve(file)));
|
|
6565
|
+
} catch (err) {
|
|
6566
|
+
await say(2, "crontab: " + file + ": " + (err && err.code ? err.code : String(err)) + "\\n");
|
|
6567
|
+
return 1;
|
|
6560
6568
|
}
|
|
6561
|
-
|
|
6569
|
+
const answer = await call({ action: "install", text });
|
|
6570
|
+
return answer.error !== undefined ? failed(answer) : 0;
|
|
6571
|
+
|
|
6572
|
+
async function edit() {
|
|
6573
|
+
const editor = ctx.env.VISUAL || ctx.env.EDITOR;
|
|
6574
|
+
if (!editor) {
|
|
6575
|
+
await say(2, "crontab: set VISUAL or EDITOR to edit, or install a file with crontab <file>\\n");
|
|
6576
|
+
return 1;
|
|
6577
|
+
}
|
|
6578
|
+
const current = await call({ action: "list" });
|
|
6579
|
+
const before = current.error === undefined ? current.text : "";
|
|
6580
|
+
if (current.error !== undefined && !current.error.startsWith("no crontab for ")) return failed(current);
|
|
6581
|
+
const tmp = "/tmp/crontab." + ctx.pid + "." + crypto.randomUUID();
|
|
6562
6582
|
try {
|
|
6563
|
-
await
|
|
6583
|
+
const file = await ctx.open(tmp, { write: true, create: true, exclusive: true, mode: 0o600 });
|
|
6584
|
+
await file.write(0, new TextEncoder().encode(before));
|
|
6585
|
+
await file.close();
|
|
6564
6586
|
} catch (err) {
|
|
6565
|
-
await
|
|
6566
|
-
|
|
6587
|
+
await say(2, "crontab: " + tmp + ": " + (err && err.code ? err.code : String(err)) + "\\n");
|
|
6588
|
+
return 1;
|
|
6567
6589
|
}
|
|
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
|
-
}
|
|
6590
|
+
const child = await ctx.spawn({ argv: ["/bin/bash", "-c", editor + ' "$1"', "crontab", tmp] });
|
|
6591
|
+
const ended = await ctx.wait(child.pid);
|
|
6592
|
+
if (ended.status !== 0) {
|
|
6593
|
+
await say(2, "crontab: the editor exited with status " + ended.status + ", crontab not changed\\n");
|
|
6594
|
+
await ctx.fs.rm(tmp).catch(() => undefined);
|
|
6595
|
+
return 1;
|
|
6578
6596
|
}
|
|
6579
|
-
const
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
const at = await this.hide(path);
|
|
6585
|
-
if (at !== void 0) hidden.set(path, at);
|
|
6586
|
-
}
|
|
6587
|
-
const done4 = () => {
|
|
6588
|
-
for (const release of releases) release();
|
|
6589
|
-
};
|
|
6590
|
-
const files = /* @__PURE__ */ new Map();
|
|
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);
|
|
6597
|
+
const after = decode(await ctx.fs.readFile(tmp));
|
|
6598
|
+
if (after === before) {
|
|
6599
|
+
await say(2, "crontab: no changes made to crontab\\n");
|
|
6600
|
+
await ctx.fs.rm(tmp).catch(() => undefined);
|
|
6601
|
+
return 0;
|
|
6596
6602
|
}
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
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;
|
|
6603
|
+
const answer = await call({ action: "install", text: after });
|
|
6604
|
+
if (answer.error !== undefined) {
|
|
6605
|
+
await say(2, answer.error + "\\ncrontab: your edit is kept in " + tmp + "\\n");
|
|
6606
|
+
return 1;
|
|
6647
6607
|
}
|
|
6648
|
-
await
|
|
6649
|
-
|
|
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);
|
|
6608
|
+
await ctx.fs.rm(tmp).catch(() => undefined);
|
|
6609
|
+
return 0;
|
|
6659
6610
|
}
|
|
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
|
-
};
|
|
6611
|
+
}
|
|
6612
|
+
`;
|
|
6709
6613
|
|
|
6710
6614
|
// src/cron/schedule.ts
|
|
6711
6615
|
var MISSED_CAP = 1e5;
|
|
@@ -6877,79 +6781,378 @@ function hashId(text) {
|
|
|
6877
6781
|
a = Math.imul(a ^ c, 16777619);
|
|
6878
6782
|
b = Math.imul(b ^ c, 1540483477);
|
|
6879
6783
|
}
|
|
6880
|
-
const hex3 = (n) => (n >>> 0).toString(16).padStart(8, "0");
|
|
6881
|
-
return hex3(a) + hex3(b);
|
|
6882
|
-
}
|
|
6883
|
-
function split2(text, count2) {
|
|
6884
|
-
const words2 = [];
|
|
6885
|
-
let rest2 = text;
|
|
6886
|
-
for (let i = 0; i < count2; i++) {
|
|
6887
|
-
const match = /^(\S+)\s+/.exec(rest2);
|
|
6888
|
-
if (!match) return void 0;
|
|
6889
|
-
words2.push(match[1]);
|
|
6890
|
-
rest2 = rest2.slice(match[0].length);
|
|
6784
|
+
const hex3 = (n) => (n >>> 0).toString(16).padStart(8, "0");
|
|
6785
|
+
return hex3(a) + hex3(b);
|
|
6786
|
+
}
|
|
6787
|
+
function split2(text, count2) {
|
|
6788
|
+
const words2 = [];
|
|
6789
|
+
let rest2 = text;
|
|
6790
|
+
for (let i = 0; i < count2; i++) {
|
|
6791
|
+
const match = /^(\S+)\s+/.exec(rest2);
|
|
6792
|
+
if (!match) return void 0;
|
|
6793
|
+
words2.push(match[1]);
|
|
6794
|
+
rest2 = rest2.slice(match[0].length);
|
|
6795
|
+
}
|
|
6796
|
+
return [words2, rest2.trim()];
|
|
6797
|
+
}
|
|
6798
|
+
function jobOf(content, pending, ctx) {
|
|
6799
|
+
const fields = content.startsWith("@") ? 1 : 5;
|
|
6800
|
+
const parts2 = split2(content, fields + (ctx.system ? 1 : 0));
|
|
6801
|
+
if (!parts2 || parts2[1] === "") throw new Error("no command");
|
|
6802
|
+
const [words2, command] = parts2;
|
|
6803
|
+
const schedule = words2.slice(0, fields).join(" ");
|
|
6804
|
+
const when = parseWhen(schedule);
|
|
6805
|
+
const user = ctx.system ? words2[fields] : void 0;
|
|
6806
|
+
if (user !== void 0 && !USER_NAME.test(user)) throw new Error(`bad user name ${user}`);
|
|
6807
|
+
if (pending !== void 0 && !JOB_NAME.test(pending))
|
|
6808
|
+
throw new Error(`bad job name "${pending}"`);
|
|
6809
|
+
if (pending !== void 0 && ctx.names.has(pending))
|
|
6810
|
+
throw new Error(`job name ${pending} is taken`);
|
|
6811
|
+
const hashed = hashId(`${ctx.path}
|
|
6812
|
+
${user ?? ""}
|
|
6813
|
+
${schedule}
|
|
6814
|
+
${command}`);
|
|
6815
|
+
let id2 = pending ?? hashed;
|
|
6816
|
+
for (let n = 2; pending === void 0 && ctx.names.has(id2); n++) id2 = `${hashed}.${n}`;
|
|
6817
|
+
ctx.names.add(id2);
|
|
6818
|
+
return {
|
|
6819
|
+
id: id2,
|
|
6820
|
+
...pending !== void 0 ? { name: pending } : {},
|
|
6821
|
+
schedule,
|
|
6822
|
+
when,
|
|
6823
|
+
...user !== void 0 ? { user } : {},
|
|
6824
|
+
command
|
|
6825
|
+
};
|
|
6826
|
+
}
|
|
6827
|
+
function parseCrontab(text, path, system = false) {
|
|
6828
|
+
const jobs = [];
|
|
6829
|
+
const errors = [];
|
|
6830
|
+
const env = {};
|
|
6831
|
+
const ctx = { path, system, names: /* @__PURE__ */ new Set() };
|
|
6832
|
+
let name2;
|
|
6833
|
+
for (const [index, raw] of text.split("\n").entries()) {
|
|
6834
|
+
const line = index + 1;
|
|
6835
|
+
const content = raw.trim();
|
|
6836
|
+
const named = NAME_LINE.exec(content);
|
|
6837
|
+
if (named) {
|
|
6838
|
+
name2 = named[1];
|
|
6839
|
+
continue;
|
|
6840
|
+
}
|
|
6841
|
+
const pending = name2;
|
|
6842
|
+
name2 = void 0;
|
|
6843
|
+
if (content === "" || content.startsWith("#")) continue;
|
|
6844
|
+
const assign = ENV_LINE.exec(content);
|
|
6845
|
+
if (assign) {
|
|
6846
|
+
env[assign[1]] = unquote(assign[2]);
|
|
6847
|
+
continue;
|
|
6848
|
+
}
|
|
6849
|
+
try {
|
|
6850
|
+
jobs.push({ line, ...jobOf(content, pending, ctx), env: { ...env } });
|
|
6851
|
+
} catch (err) {
|
|
6852
|
+
errors.push({ line, message: err.message });
|
|
6853
|
+
}
|
|
6854
|
+
}
|
|
6855
|
+
return { jobs, errors };
|
|
6856
|
+
}
|
|
6857
|
+
|
|
6858
|
+
// src/cron/crontab.ts
|
|
6859
|
+
var CRONTAB_MAX = 256 * 1024;
|
|
6860
|
+
var ACTIONS = /* @__PURE__ */ new Set(["list", "remove", "install"]);
|
|
6861
|
+
function crontabCall(req) {
|
|
6862
|
+
const { action, user, text } = req;
|
|
6863
|
+
if (typeof action !== "string" || !ACTIONS.has(action)) return void 0;
|
|
6864
|
+
if (user !== void 0 && typeof user !== "string") return void 0;
|
|
6865
|
+
if (action === "install" && typeof text !== "string") return void 0;
|
|
6866
|
+
return {
|
|
6867
|
+
op: "proc-crontab",
|
|
6868
|
+
action,
|
|
6869
|
+
...user !== void 0 ? { user } : {},
|
|
6870
|
+
...action === "install" ? { text } : {}
|
|
6871
|
+
};
|
|
6872
|
+
}
|
|
6873
|
+
var Crontabs = class {
|
|
6874
|
+
o;
|
|
6875
|
+
constructor(options) {
|
|
6876
|
+
this.o = options;
|
|
6877
|
+
}
|
|
6878
|
+
async request(cred, call) {
|
|
6879
|
+
const caller = await this.o.lookup(cred.ruid);
|
|
6880
|
+
if (!caller) return { error: `crontab: your uid ${cred.ruid} is not in /etc/passwd` };
|
|
6881
|
+
let target = caller;
|
|
6882
|
+
if (call.user !== void 0 && call.user !== caller.name) {
|
|
6883
|
+
if (cred.euid !== 0) return { error: "crontab: must be privileged to use -u" };
|
|
6884
|
+
const found = await this.o.lookup(call.user);
|
|
6885
|
+
if (!found) return { error: `crontab: user ${call.user} unknown` };
|
|
6886
|
+
target = found;
|
|
6887
|
+
}
|
|
6888
|
+
const path = `${SPOOL_DIR}/${target.name}`;
|
|
6889
|
+
const none = { error: `no crontab for ${target.name}` };
|
|
6890
|
+
const { fs } = this.o;
|
|
6891
|
+
if (call.action === "install") return this.install(path, target, call.text);
|
|
6892
|
+
if (!await fs.exists(path)) return none;
|
|
6893
|
+
if (call.action === "list") return { text: await fs.readFile(path) };
|
|
6894
|
+
await fs.rm(path);
|
|
6895
|
+
return { text: "" };
|
|
6896
|
+
}
|
|
6897
|
+
async install(path, target, text) {
|
|
6898
|
+
if (text.length > CRONTAB_MAX) return { error: "crontab: the table is too long" };
|
|
6899
|
+
const { errors } = parseCrontab(text, path);
|
|
6900
|
+
if (errors.length > 0) {
|
|
6901
|
+
const lines2 = errors.map(({ line, message: message3 }) => `"-":${line}: ${message3}`);
|
|
6902
|
+
return { error: ["crontab: errors in crontab file, cannot install", ...lines2].join("\n") };
|
|
6903
|
+
}
|
|
6904
|
+
const { fs, owners } = this.o;
|
|
6905
|
+
if (!await fs.exists(SPOOL_DIR)) {
|
|
6906
|
+
await fs.mkdir(SPOOL_DIR, { recursive: true });
|
|
6907
|
+
if (owners) await fs.chown?.(SPOOL_DIR, { uid: 0, gid: 0, mode: 448, follow: false });
|
|
6908
|
+
}
|
|
6909
|
+
const fresh2 = `${SPOOL_DIR}/.${target.name}.${crypto.randomUUID()}.new`;
|
|
6910
|
+
const owner = owners ? { uid: target.uid, gid: target.gid, mode: 384 } : void 0;
|
|
6911
|
+
await fs.writeFile(fresh2, text, owner);
|
|
6912
|
+
await fs.rename(fresh2, path);
|
|
6913
|
+
return { text: "" };
|
|
6914
|
+
}
|
|
6915
|
+
};
|
|
6916
|
+
|
|
6917
|
+
// src/fs/unlinked.ts
|
|
6918
|
+
var UNLINKED_DIR = ".slicc-unlinked";
|
|
6919
|
+
function webLocks() {
|
|
6920
|
+
return globalThis.navigator?.locks;
|
|
6921
|
+
}
|
|
6922
|
+
var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
6923
|
+
var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
6924
|
+
var lockName = (instance) => `slicc-unlinked:${instance}`;
|
|
6925
|
+
var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
|
|
6926
|
+
var PathGate = class {
|
|
6927
|
+
tails = /* @__PURE__ */ new Map();
|
|
6928
|
+
async acquire(path) {
|
|
6929
|
+
const prev = this.tails.get(path) ?? Promise.resolve();
|
|
6930
|
+
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
6931
|
+
const tail2 = prev.then(() => promise);
|
|
6932
|
+
this.tails.set(path, tail2);
|
|
6933
|
+
await prev;
|
|
6934
|
+
return () => {
|
|
6935
|
+
resolve2();
|
|
6936
|
+
if (this.tails.get(path) === tail2) this.tails.delete(path);
|
|
6937
|
+
};
|
|
6938
|
+
}
|
|
6939
|
+
async run(path, op) {
|
|
6940
|
+
const release = await this.acquire(path);
|
|
6941
|
+
try {
|
|
6942
|
+
return await op();
|
|
6943
|
+
} finally {
|
|
6944
|
+
release();
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
};
|
|
6948
|
+
var UnlinkedKeeper = class {
|
|
6949
|
+
fs;
|
|
6950
|
+
holders;
|
|
6951
|
+
rootOf;
|
|
6952
|
+
locks;
|
|
6953
|
+
busy;
|
|
6954
|
+
waiting = /* @__PURE__ */ new Set();
|
|
6955
|
+
owner;
|
|
6956
|
+
instance;
|
|
6957
|
+
gate = new PathGate();
|
|
6958
|
+
queue = Promise.resolve();
|
|
6959
|
+
count = 0;
|
|
6960
|
+
links = /* @__PURE__ */ new Map();
|
|
6961
|
+
linked = /* @__PURE__ */ new Map();
|
|
6962
|
+
dirOwner;
|
|
6963
|
+
constructor(fs, holders, rootOf, options = {}) {
|
|
6964
|
+
const { locks, owner } = options;
|
|
6965
|
+
this.dirOwner = options.dirOwner;
|
|
6966
|
+
this.fs = fs;
|
|
6967
|
+
this.holders = holders;
|
|
6968
|
+
this.rootOf = rootOf;
|
|
6969
|
+
this.locks = locks;
|
|
6970
|
+
this.busy = options.busy ?? (() => false);
|
|
6971
|
+
this.owner = owner;
|
|
6972
|
+
this.instance = owner ? `${owner.id}~${crypto.randomUUID()}` : crypto.randomUUID();
|
|
6973
|
+
void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
|
|
6974
|
+
}
|
|
6975
|
+
serial(op) {
|
|
6976
|
+
const next3 = this.queue.then(op);
|
|
6977
|
+
this.queue = next3.catch(() => void 0);
|
|
6978
|
+
return next3;
|
|
6979
|
+
}
|
|
6980
|
+
wrap(fs = this.fs) {
|
|
6981
|
+
const keeper = this;
|
|
6982
|
+
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
6983
|
+
const counted2 = (st) => {
|
|
6984
|
+
const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
|
|
6985
|
+
return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
|
|
6986
|
+
};
|
|
6987
|
+
return {
|
|
6988
|
+
...fs,
|
|
6989
|
+
links: (path) => mayLink(fs, path),
|
|
6990
|
+
stat: async (path) => counted2(await fs.stat(path)),
|
|
6991
|
+
lstat: async (path) => counted2(await fs.lstat(path)),
|
|
6992
|
+
async rm(path, options) {
|
|
6993
|
+
const target = fs.resolvePath("/", path);
|
|
6994
|
+
const pinned = await keeper.pin(target);
|
|
6995
|
+
if (pinned.moved.has(target)) pinned.commit();
|
|
6996
|
+
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
6997
|
+
},
|
|
6998
|
+
async rename(from, to) {
|
|
6999
|
+
const target = fs.resolvePath("/", to);
|
|
7000
|
+
if (target === fs.resolvePath("/", from)) return fs.rename(from, to);
|
|
7001
|
+
const pinned = await keeper.pin(target);
|
|
7002
|
+
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
7003
|
+
},
|
|
7004
|
+
readdir: async (path) => visible(await fs.readdir(path)),
|
|
7005
|
+
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
|
|
7006
|
+
};
|
|
6891
7007
|
}
|
|
6892
|
-
|
|
6893
|
-
}
|
|
6894
|
-
function jobOf(content, pending, ctx) {
|
|
6895
|
-
const fields = content.startsWith("@") ? 1 : 5;
|
|
6896
|
-
const parts2 = split2(content, fields + (ctx.system ? 1 : 0));
|
|
6897
|
-
if (!parts2 || parts2[1] === "") throw new Error("no command");
|
|
6898
|
-
const [words2, command] = parts2;
|
|
6899
|
-
const schedule = words2.slice(0, fields).join(" ");
|
|
6900
|
-
const when = parseWhen(schedule);
|
|
6901
|
-
const user = ctx.system ? words2[fields] : void 0;
|
|
6902
|
-
if (user !== void 0 && !USER_NAME.test(user)) throw new Error(`bad user name ${user}`);
|
|
6903
|
-
if (pending !== void 0 && !JOB_NAME.test(pending))
|
|
6904
|
-
throw new Error(`bad job name "${pending}"`);
|
|
6905
|
-
if (pending !== void 0 && ctx.names.has(pending))
|
|
6906
|
-
throw new Error(`job name ${pending} is taken`);
|
|
6907
|
-
const hashed = hashId(`${ctx.path}
|
|
6908
|
-
${user ?? ""}
|
|
6909
|
-
${schedule}
|
|
6910
|
-
${command}`);
|
|
6911
|
-
let id2 = pending ?? hashed;
|
|
6912
|
-
for (let n = 2; pending === void 0 && ctx.names.has(id2); n++) id2 = `${hashed}.${n}`;
|
|
6913
|
-
ctx.names.add(id2);
|
|
6914
|
-
return {
|
|
6915
|
-
id: id2,
|
|
6916
|
-
...pending !== void 0 ? { name: pending } : {},
|
|
6917
|
-
schedule,
|
|
6918
|
-
when,
|
|
6919
|
-
...user !== void 0 ? { user } : {},
|
|
6920
|
-
command
|
|
6921
|
-
};
|
|
6922
|
-
}
|
|
6923
|
-
function parseCrontab(text, path, system = false) {
|
|
6924
|
-
const jobs = [];
|
|
6925
|
-
const errors = [];
|
|
6926
|
-
const env = {};
|
|
6927
|
-
const ctx = { path, system, names: /* @__PURE__ */ new Set() };
|
|
6928
|
-
let name2;
|
|
6929
|
-
for (const [index, raw] of text.split("\n").entries()) {
|
|
6930
|
-
const line = index + 1;
|
|
6931
|
-
const content = raw.trim();
|
|
6932
|
-
const named = NAME_LINE.exec(content);
|
|
6933
|
-
if (named) {
|
|
6934
|
-
name2 = named[1];
|
|
6935
|
-
continue;
|
|
6936
|
-
}
|
|
6937
|
-
const pending = name2;
|
|
6938
|
-
name2 = void 0;
|
|
6939
|
-
if (content === "" || content.startsWith("#")) continue;
|
|
6940
|
-
const assign = ENV_LINE.exec(content);
|
|
6941
|
-
if (assign) {
|
|
6942
|
-
env[assign[1]] = unquote(assign[2]);
|
|
6943
|
-
continue;
|
|
6944
|
-
}
|
|
7008
|
+
async settle(pinned, op) {
|
|
6945
7009
|
try {
|
|
6946
|
-
|
|
7010
|
+
await op();
|
|
6947
7011
|
} catch (err) {
|
|
6948
|
-
|
|
7012
|
+
await pinned.rollback();
|
|
7013
|
+
throw err;
|
|
6949
7014
|
}
|
|
7015
|
+
pinned.commit();
|
|
6950
7016
|
}
|
|
6951
|
-
|
|
6952
|
-
|
|
7017
|
+
async pin(target) {
|
|
7018
|
+
const held = /* @__PURE__ */ new Map();
|
|
7019
|
+
for (const holder of this.holders) {
|
|
7020
|
+
if (holder.owns(target)) continue;
|
|
7021
|
+
for (const path of holder.held()) {
|
|
7022
|
+
if (!within4(path, target) || holder.isKept(path)) continue;
|
|
7023
|
+
held.set(path, [...held.get(path) ?? [], holder]);
|
|
7024
|
+
}
|
|
7025
|
+
}
|
|
7026
|
+
const paths = [...held.keys()].sort();
|
|
7027
|
+
const releases = [];
|
|
7028
|
+
for (const path of paths) releases.push(await this.gate.acquire(path));
|
|
7029
|
+
const hidden = /* @__PURE__ */ new Map();
|
|
7030
|
+
for (const path of paths) {
|
|
7031
|
+
const at = await this.hide(path);
|
|
7032
|
+
if (at !== void 0) hidden.set(path, at);
|
|
7033
|
+
}
|
|
7034
|
+
const done4 = () => {
|
|
7035
|
+
for (const release of releases) release();
|
|
7036
|
+
};
|
|
7037
|
+
const files = /* @__PURE__ */ new Map();
|
|
7038
|
+
for (const [path, at] of hidden) {
|
|
7039
|
+
const holders = held.get(path);
|
|
7040
|
+
const file = this.keptFile(at, { refs: holders.length + 1 });
|
|
7041
|
+
files.set(path, file);
|
|
7042
|
+
for (const holder of holders) holder.keep(path, file);
|
|
7043
|
+
}
|
|
7044
|
+
return {
|
|
7045
|
+
moved: new Set(hidden.keys()),
|
|
7046
|
+
commit: () => {
|
|
7047
|
+
for (const file of files.values()) file.release();
|
|
7048
|
+
done4();
|
|
7049
|
+
},
|
|
7050
|
+
rollback: async () => {
|
|
7051
|
+
for (const [path, file] of files) {
|
|
7052
|
+
for (const holder of held.get(path)) holder.unkeep(path, file);
|
|
7053
|
+
await this.serial(async () => {
|
|
7054
|
+
await Promise.allSettled([this.fs.rename(file.hidden, path)]);
|
|
7055
|
+
this.unlinkedName(file.hidden);
|
|
7056
|
+
await this.tidy(parentOf5(file.hidden));
|
|
7057
|
+
});
|
|
7058
|
+
}
|
|
7059
|
+
done4();
|
|
7060
|
+
}
|
|
7061
|
+
};
|
|
7062
|
+
}
|
|
7063
|
+
keptFile(hidden, entry) {
|
|
7064
|
+
return {
|
|
7065
|
+
hidden,
|
|
7066
|
+
release: () => {
|
|
7067
|
+
if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
|
|
7068
|
+
}
|
|
7069
|
+
};
|
|
7070
|
+
}
|
|
7071
|
+
hide(path) {
|
|
7072
|
+
return this.serial(async () => {
|
|
7073
|
+
const dir = dirOf(this.rootOf(path));
|
|
7074
|
+
const hidden = `${dir}/${this.instance}.${++this.count}`;
|
|
7075
|
+
try {
|
|
7076
|
+
const owner = this.dirOwner;
|
|
7077
|
+
await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
|
|
7078
|
+
await this.fs.rename(path, hidden);
|
|
7079
|
+
await this.linkedName(hidden);
|
|
7080
|
+
return hidden;
|
|
7081
|
+
} catch {
|
|
7082
|
+
await this.tidy(dir);
|
|
7083
|
+
return void 0;
|
|
7084
|
+
}
|
|
7085
|
+
});
|
|
7086
|
+
}
|
|
7087
|
+
closed(path) {
|
|
7088
|
+
if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
|
|
7089
|
+
}
|
|
7090
|
+
async drop(hidden) {
|
|
7091
|
+
if (this.busy(hidden)) {
|
|
7092
|
+
this.waiting.add(hidden);
|
|
7093
|
+
return;
|
|
7094
|
+
}
|
|
7095
|
+
await this.fs.rm(hidden).catch(() => void 0);
|
|
7096
|
+
this.unlinkedName(hidden);
|
|
7097
|
+
await this.tidy(parentOf5(hidden));
|
|
7098
|
+
}
|
|
7099
|
+
async linkedName(hidden) {
|
|
7100
|
+
if (!mayLink(this.fs, hidden)) return;
|
|
7101
|
+
const st = await this.fs.lstat(hidden).catch(() => void 0);
|
|
7102
|
+
if (st?.nlink === void 0) return;
|
|
7103
|
+
const id2 = `${st.dev ?? 0}:${st.ino}`;
|
|
7104
|
+
this.linked.set(hidden, id2);
|
|
7105
|
+
this.links.set(id2, (this.links.get(id2) ?? 0) + 1);
|
|
7106
|
+
}
|
|
7107
|
+
unlinkedName(hidden) {
|
|
7108
|
+
const id2 = this.linked.get(hidden);
|
|
7109
|
+
if (id2 === void 0) return;
|
|
7110
|
+
this.linked.delete(hidden);
|
|
7111
|
+
const left = (this.links.get(id2) ?? 1) - 1;
|
|
7112
|
+
if (left > 0) this.links.set(id2, left);
|
|
7113
|
+
else this.links.delete(id2);
|
|
7114
|
+
}
|
|
7115
|
+
async tidy(dir) {
|
|
7116
|
+
const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
|
|
7117
|
+
if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
|
|
7118
|
+
}
|
|
7119
|
+
sweep(root) {
|
|
7120
|
+
const query = this.locks?.query?.bind(this.locks);
|
|
7121
|
+
const owner = this.owner;
|
|
7122
|
+
if (!query && !owner) return Promise.resolve();
|
|
7123
|
+
return this.serial(async () => {
|
|
7124
|
+
const dir = dirOf(root);
|
|
7125
|
+
const names = await this.fs.readdir(dir).catch(() => void 0);
|
|
7126
|
+
if (!names) return;
|
|
7127
|
+
const held = query && new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
7128
|
+
for (const name2 of names) {
|
|
7129
|
+
const id2 = name2.slice(0, name2.lastIndexOf("."));
|
|
7130
|
+
const at = id2.indexOf("~");
|
|
7131
|
+
const dead = id2 !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id2)) : owner?.alive(id2.slice(0, at)) === false);
|
|
7132
|
+
if (dead) await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
|
|
7133
|
+
}
|
|
7134
|
+
await this.tidy(dir);
|
|
7135
|
+
});
|
|
7136
|
+
}
|
|
7137
|
+
};
|
|
7138
|
+
|
|
7139
|
+
// src/cron/clock.ts
|
|
7140
|
+
var systemCronClock = {
|
|
7141
|
+
now: () => Date.now(),
|
|
7142
|
+
sleep(until, signal) {
|
|
7143
|
+
if (signal.aborted) return Promise.resolve();
|
|
7144
|
+
return new Promise((resolve2) => {
|
|
7145
|
+
const done4 = () => {
|
|
7146
|
+
clearTimeout(timer);
|
|
7147
|
+
signal.removeEventListener("abort", done4);
|
|
7148
|
+
resolve2();
|
|
7149
|
+
};
|
|
7150
|
+
const timer = setTimeout(done4, Math.max(0, until - Date.now()));
|
|
7151
|
+
timer.unref?.();
|
|
7152
|
+
signal.addEventListener("abort", done4, { once: true });
|
|
7153
|
+
});
|
|
7154
|
+
}
|
|
7155
|
+
};
|
|
6953
7156
|
|
|
6954
7157
|
// src/cron/service.ts
|
|
6955
7158
|
var CRON_DIR = "/var/spool/cron";
|
|
@@ -7125,9 +7328,12 @@ var CronService = class {
|
|
|
7125
7328
|
const account = await this.host.lookup(job.user);
|
|
7126
7329
|
if (account) out.push({ source: "system", account, job });
|
|
7127
7330
|
}
|
|
7128
|
-
const names = await fs.readdir(SPOOL_DIR).catch(() =>
|
|
7331
|
+
const names = await fs.readdir(SPOOL_DIR).catch((err) => {
|
|
7332
|
+
if (err?.code === "ENOENT") return [];
|
|
7333
|
+
throw err;
|
|
7334
|
+
});
|
|
7129
7335
|
for (const name2 of names.sort()) {
|
|
7130
|
-
if (name2.startsWith(".") || name2.endsWith("~")
|
|
7336
|
+
if (name2.startsWith(".") || name2.endsWith("~")) continue;
|
|
7131
7337
|
const path = `${SPOOL_DIR}/${name2}`;
|
|
7132
7338
|
const account = await this.host.lookup(name2);
|
|
7133
7339
|
if (!account) {
|
|
@@ -16637,6 +16843,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
16637
16843
|
"proc-wait",
|
|
16638
16844
|
"proc-captured",
|
|
16639
16845
|
"proc-sudo",
|
|
16846
|
+
"proc-crontab",
|
|
16640
16847
|
"proc-run",
|
|
16641
16848
|
"proc-exit",
|
|
16642
16849
|
"proc-kill-self",
|
|
@@ -17826,8 +18033,17 @@ var WasmProcess = class {
|
|
|
17826
18033
|
return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
|
|
17827
18034
|
case "proc-sudo":
|
|
17828
18035
|
return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
|
|
18036
|
+
case "proc-crontab":
|
|
18037
|
+
return { ok: true, kind: "json", json: await this.crontab(req) };
|
|
17829
18038
|
}
|
|
17830
18039
|
}
|
|
18040
|
+
async crontab(req) {
|
|
18041
|
+
const { crontab } = this.options;
|
|
18042
|
+
if (!crontab) throw new KernelError("ENOSYS");
|
|
18043
|
+
const call = crontabCall(req);
|
|
18044
|
+
if (!call) throw new KernelError("EINVAL");
|
|
18045
|
+
return crontab(call);
|
|
18046
|
+
}
|
|
17831
18047
|
signalSyscall(req) {
|
|
17832
18048
|
if (req.op === "sig-inherited") {
|
|
17833
18049
|
return { ok: true, kind: "json", json: this.inherited & ~this.caught };
|
|
@@ -18230,6 +18446,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
18230
18446
|
...opts.writesBack ? { writesBack: opts.writesBack } : {},
|
|
18231
18447
|
processes: opts.processes,
|
|
18232
18448
|
...opts.sudo ? { sudo: opts.sudo } : {},
|
|
18449
|
+
...opts.crontab ? { crontab: opts.crontab } : {},
|
|
18233
18450
|
openFiles: opts.openFiles,
|
|
18234
18451
|
...opts.nodes ? { nodes: opts.nodes } : {},
|
|
18235
18452
|
mounts: opts.mounts,
|
|
@@ -21697,6 +21914,7 @@ var LAYOUT = [
|
|
|
21697
21914
|
["/usr", 493],
|
|
21698
21915
|
["/usr/local", 493]
|
|
21699
21916
|
];
|
|
21917
|
+
var IN_TREE = /* @__PURE__ */ new Set([SUDO_PATH, CRONTAB_PATH]);
|
|
21700
21918
|
var LOAD_SAMPLE_MS = 5e3;
|
|
21701
21919
|
var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
|
|
21702
21920
|
var encoder8 = new TextEncoder();
|
|
@@ -21795,9 +22013,18 @@ function targetOf(command) {
|
|
|
21795
22013
|
...command.env ? { env: command.env } : {},
|
|
21796
22014
|
...command.unset ? { unset: command.unset } : {},
|
|
21797
22015
|
...command.imports ? { imports: command.imports } : {},
|
|
21798
|
-
...command.preopenRoot ? { preopenRoot: true } : {}
|
|
22016
|
+
...command.preopenRoot ? { preopenRoot: true } : {},
|
|
22017
|
+
...command.variants ? { variants: command.variants } : {}
|
|
21799
22018
|
};
|
|
21800
22019
|
}
|
|
22020
|
+
function chosen(planned, env) {
|
|
22021
|
+
const { target } = planned;
|
|
22022
|
+
const wasm = variantFor(target.variants, env);
|
|
22023
|
+
if (wasm === void 0) return planned;
|
|
22024
|
+
const { asked: _asked, ...rest2 } = target;
|
|
22025
|
+
const glue = target.abi === "wasi" ? wasm : target.glue;
|
|
22026
|
+
return { ...planned, target: { ...rest2, wasm, glue } };
|
|
22027
|
+
}
|
|
21801
22028
|
function withScriptEnv(target, command) {
|
|
21802
22029
|
if (!command) return target;
|
|
21803
22030
|
return {
|
|
@@ -21927,6 +22154,7 @@ var Launcher = class {
|
|
|
21927
22154
|
boot = Date.now();
|
|
21928
22155
|
bootId = crypto.randomUUID();
|
|
21929
22156
|
cron;
|
|
22157
|
+
crontabs;
|
|
21930
22158
|
terminals = 0;
|
|
21931
22159
|
constructor(options) {
|
|
21932
22160
|
this.usersMode = usersMode(options.users);
|
|
@@ -22127,7 +22355,9 @@ var Launcher = class {
|
|
|
22127
22355
|
found = this.trustedAppended().then(async (appended2) => {
|
|
22128
22356
|
const tiers = await this.tiers(scope, appended2);
|
|
22129
22357
|
return scanCommands(this.scanned, tiers, tiers.length - appended2.length);
|
|
22130
|
-
}).then(
|
|
22358
|
+
}).then(
|
|
22359
|
+
(commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND).set(CRONTAB_COMMAND.name, CRONTAB_COMMAND)
|
|
22360
|
+
);
|
|
22131
22361
|
this.catalogs.set(scope, found);
|
|
22132
22362
|
}
|
|
22133
22363
|
return found;
|
|
@@ -22532,7 +22762,7 @@ var Launcher = class {
|
|
|
22532
22762
|
const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
|
|
22533
22763
|
if (root !== void 0) files.push(`${root}/package.json`);
|
|
22534
22764
|
}
|
|
22535
|
-
return [...new Set(files)].filter((path) => path !== "" && path
|
|
22765
|
+
return [...new Set(files)].filter((path) => path !== "" && !IN_TREE.has(path));
|
|
22536
22766
|
}
|
|
22537
22767
|
async unrunnable(file, cwd, scope) {
|
|
22538
22768
|
const path = this.fs.resolvePath(cwd, file);
|
|
@@ -22672,6 +22902,9 @@ var Launcher = class {
|
|
|
22672
22902
|
if (target.abi === "js" && target.wasm === SUDO_PATH) {
|
|
22673
22903
|
return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
|
|
22674
22904
|
}
|
|
22905
|
+
if (target.abi === "js" && target.wasm === CRONTAB_PATH) {
|
|
22906
|
+
return { abi: "js", glue: CRONTAB_SOURCE, path: CRONTAB_PATH };
|
|
22907
|
+
}
|
|
22675
22908
|
if (target.abi === "js" && target.wasm === REFUSED_PATH) {
|
|
22676
22909
|
return { abi: "js", glue: REFUSED_SOURCE, path: REFUSED_PATH };
|
|
22677
22910
|
}
|
|
@@ -22702,14 +22935,15 @@ var Launcher = class {
|
|
|
22702
22935
|
...target.preopenRoot ? { preopenRoot: true } : {}
|
|
22703
22936
|
};
|
|
22704
22937
|
}
|
|
22705
|
-
async launch(
|
|
22706
|
-
const { target, args } = planned;
|
|
22938
|
+
async launch(asked, req) {
|
|
22707
22939
|
const cwd = await physicalPathAsync(this.base, req.cwd);
|
|
22708
22940
|
const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
|
|
22709
|
-
const env = { ...expandDefaults(target.env, seen), ...req.env };
|
|
22710
|
-
for (const key of target.unset ?? []) delete env[key];
|
|
22941
|
+
const env = { ...expandDefaults(asked.target.env, seen), ...req.env };
|
|
22942
|
+
for (const key of asked.target.unset ?? []) delete env[key];
|
|
22711
22943
|
delete env[TICKET_ENV];
|
|
22712
22944
|
env.HOSTNAME = this.hostname;
|
|
22945
|
+
const planned = chosen(asked, env);
|
|
22946
|
+
const { target, args } = planned;
|
|
22713
22947
|
let program;
|
|
22714
22948
|
await this.pnpmReady();
|
|
22715
22949
|
try {
|
|
@@ -22827,6 +23061,7 @@ var Launcher = class {
|
|
|
22827
23061
|
cpu: this.cpuTotal()
|
|
22828
23062
|
}),
|
|
22829
23063
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
23064
|
+
crontab: (call) => this.crontab(pid, call),
|
|
22830
23065
|
openFiles: this.openFiles,
|
|
22831
23066
|
nodes: stack.nodes,
|
|
22832
23067
|
held: this.held,
|
|
@@ -23045,6 +23280,14 @@ var Launcher = class {
|
|
|
23045
23280
|
const threads = this.stateOf(pid) === "S" ? this.processes.get(pid)?.threads?.() : void 0;
|
|
23046
23281
|
return threads ? { threads } : {};
|
|
23047
23282
|
}
|
|
23283
|
+
crontab(pid, call) {
|
|
23284
|
+
this.crontabs ??= new Crontabs({
|
|
23285
|
+
fs: this.fs,
|
|
23286
|
+
owners: this.owners,
|
|
23287
|
+
lookup: (user) => this.users.lookup(user)
|
|
23288
|
+
});
|
|
23289
|
+
return this.crontabs.request(this.credOf(pid), call);
|
|
23290
|
+
}
|
|
23048
23291
|
sudo(pid, call, signal) {
|
|
23049
23292
|
const caller = {
|
|
23050
23293
|
cred: this.credOf(pid),
|