@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.
@@ -6108,7 +6108,7 @@ function satisfies(version, range2) {
6108
6108
  }
6109
6109
 
6110
6110
  // src/version.ts
6111
- var KERNEL_VERSION = true ? "1.108.4" : "0.0.0-dev";
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/fs/unlinked.ts
6471
- var UNLINKED_DIR = ".slicc-unlinked";
6472
- function webLocks() {
6473
- return globalThis.navigator?.locks;
6474
- }
6475
- var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
6476
- var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6477
- var lockName = (instance) => `slicc-unlinked:${instance}`;
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 UnlinkedKeeper = class {
6502
- fs;
6503
- holders;
6504
- rootOf;
6505
- locks;
6506
- busy;
6507
- waiting = /* @__PURE__ */ new Set();
6508
- owner;
6509
- instance;
6510
- gate = new PathGate();
6511
- queue = Promise.resolve();
6512
- count = 0;
6513
- links = /* @__PURE__ */ new Map();
6514
- linked = /* @__PURE__ */ new Map();
6515
- dirOwner;
6516
- constructor(fs, holders, rootOf, options = {}) {
6517
- const { locks, owner } = options;
6518
- this.dirOwner = options.dirOwner;
6519
- this.fs = fs;
6520
- this.holders = holders;
6521
- this.rootOf = rootOf;
6522
- this.locks = locks;
6523
- this.busy = options.busy ?? (() => false);
6524
- this.owner = owner;
6525
- this.instance = owner ? `${owner.id}~${crypto.randomUUID()}` : crypto.randomUUID();
6526
- void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
6527
- }
6528
- serial(op) {
6529
- const next3 = this.queue.then(op);
6530
- this.queue = next3.catch(() => void 0);
6531
- return next3;
6532
- }
6533
- wrap(fs = this.fs) {
6534
- const keeper = this;
6535
- const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
6536
- const counted2 = (st) => {
6537
- const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
6538
- return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
6539
- };
6540
- return {
6541
- ...fs,
6542
- links: (path) => mayLink(fs, path),
6543
- stat: async (path) => counted2(await fs.stat(path)),
6544
- lstat: async (path) => counted2(await fs.lstat(path)),
6545
- async rm(path, options) {
6546
- const target = fs.resolvePath("/", path);
6547
- const pinned = await keeper.pin(target);
6548
- if (pinned.moved.has(target)) pinned.commit();
6549
- else await keeper.settle(pinned, () => fs.rm(path, options));
6550
- },
6551
- async rename(from, to) {
6552
- const target = fs.resolvePath("/", to);
6553
- if (target === fs.resolvePath("/", from)) return fs.rename(from, to);
6554
- const pinned = await keeper.pin(target);
6555
- await keeper.settle(pinned, () => fs.rename(from, to));
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
- async settle(pinned, op) {
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 op();
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 pinned.rollback();
6566
- throw err;
6553
+ await say(2, "crontab: " + tmp + ": " + (err && err.code ? err.code : String(err)) + "\\n");
6554
+ return 1;
6567
6555
  }
6568
- pinned.commit();
6569
- }
6570
- async pin(target) {
6571
- const held = /* @__PURE__ */ new Map();
6572
- for (const holder of this.holders) {
6573
- if (holder.owns(target)) continue;
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 paths = [...held.keys()].sort();
6580
- const releases = [];
6581
- for (const path of paths) releases.push(await this.gate.acquire(path));
6582
- const hidden = /* @__PURE__ */ new Map();
6583
- for (const path of paths) {
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 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);
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
- return {
6598
- moved: new Set(hidden.keys()),
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("~") || name2.endsWith(".new")) continue;
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
- status,
11291
- payload,
11292
- timer: setTimeout(() => drop(id2), PENDING_TTL_MS)
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((commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND));
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 !== SUDO_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),