@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/dist/node.js CHANGED
@@ -6435,7 +6435,7 @@ function satisfies(version, range2) {
6435
6435
  }
6436
6436
 
6437
6437
  // src/version.ts
6438
- var KERNEL_VERSION = true ? "1.108.5" : "0.0.0-dev";
6438
+ var KERNEL_VERSION = true ? "1.109.0" : "0.0.0-dev";
6439
6439
  var OS_RELEASE = "6.0.0-slicc";
6440
6440
  function procVersion() {
6441
6441
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -6794,23 +6794,115 @@ function binfmtOf(binfmts, path) {
6794
6794
  return best === void 0 ? void 0 : binfmts.get(best);
6795
6795
  }
6796
6796
 
6797
- // src/cron/clock.ts
6798
- var systemCronClock = {
6799
- now: () => Date.now(),
6800
- sleep(until, signal) {
6801
- if (signal.aborted) return Promise.resolve();
6802
- return new Promise((resolve2) => {
6803
- const done4 = () => {
6804
- clearTimeout(timer);
6805
- signal.removeEventListener("abort", done4);
6806
- resolve2();
6807
- };
6808
- const timer = setTimeout(done4, Math.max(0, until - Date.now()));
6809
- timer.unref?.();
6810
- signal.addEventListener("abort", done4, { once: true });
6811
- });
6812
- }
6797
+ // src/cron/command.ts
6798
+ var CRONTAB_PATH = "/usr/bin/crontab";
6799
+ var CRONTAB_COMMAND = {
6800
+ name: "crontab",
6801
+ abi: "js",
6802
+ glue: CRONTAB_PATH,
6803
+ wasm: CRONTAB_PATH,
6804
+ argv0: "crontab"
6813
6805
  };
6806
+ var CRONTAB_SOURCE = `export default async function crontab(ctx) {
6807
+ const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);
6808
+ const usage = async () => {
6809
+ await say(2, "usage: crontab [-u user] { -l | -r | -e | file | - }\\n");
6810
+ return 1;
6811
+ };
6812
+ const args = ctx.argv.slice(1);
6813
+ let user;
6814
+ let action;
6815
+ let file;
6816
+ for (let i = 0; i < args.length; i++) {
6817
+ const arg = args[i];
6818
+ if (arg === "-u") {
6819
+ user = args[++i];
6820
+ if (user === undefined) return usage();
6821
+ } else if (arg === "-l" || arg === "-r" || arg === "-e") {
6822
+ if (action !== undefined) return usage();
6823
+ action = arg.slice(1);
6824
+ } else if (arg === "-" || !arg.startsWith("-")) {
6825
+ if (file !== undefined) return usage();
6826
+ file = arg;
6827
+ } else return usage();
6828
+ }
6829
+ if ((action === undefined) === (file === undefined)) return usage();
6830
+ const call = (c) => ctx.crontab(user === undefined ? c : { ...c, user });
6831
+ const failed = async (answer) => {
6832
+ await say(2, answer.error + "\\n");
6833
+ return 1;
6834
+ };
6835
+ const decode = (bytes) => new TextDecoder().decode(bytes);
6836
+ if (action === "l") {
6837
+ const answer = await call({ action: "list" });
6838
+ if (answer.error !== undefined) return failed(answer);
6839
+ await say(1, answer.text);
6840
+ return 0;
6841
+ }
6842
+ if (action === "r") {
6843
+ const answer = await call({ action: "remove" });
6844
+ return answer.error !== undefined ? failed(answer) : 0;
6845
+ }
6846
+ if (action === "e") return edit();
6847
+ let text;
6848
+ try {
6849
+ if (file === "-") {
6850
+ const parts = [];
6851
+ for (;;) {
6852
+ const chunk = await ctx.read(0);
6853
+ if (chunk.length === 0) break;
6854
+ parts.push(decode(chunk));
6855
+ }
6856
+ text = parts.join("");
6857
+ } else text = decode(await ctx.fs.readFile(ctx.resolve(file)));
6858
+ } catch (err) {
6859
+ await say(2, "crontab: " + file + ": " + (err && err.code ? err.code : String(err)) + "\\n");
6860
+ return 1;
6861
+ }
6862
+ const answer = await call({ action: "install", text });
6863
+ return answer.error !== undefined ? failed(answer) : 0;
6864
+
6865
+ async function edit() {
6866
+ const editor = ctx.env.VISUAL || ctx.env.EDITOR;
6867
+ if (!editor) {
6868
+ await say(2, "crontab: set VISUAL or EDITOR to edit, or install a file with crontab <file>\\n");
6869
+ return 1;
6870
+ }
6871
+ const current = await call({ action: "list" });
6872
+ const before = current.error === undefined ? current.text : "";
6873
+ if (current.error !== undefined && !current.error.startsWith("no crontab for ")) return failed(current);
6874
+ const tmp = "/tmp/crontab." + ctx.pid + "." + crypto.randomUUID();
6875
+ try {
6876
+ const file = await ctx.open(tmp, { write: true, create: true, exclusive: true, mode: 0o600 });
6877
+ await file.write(0, new TextEncoder().encode(before));
6878
+ await file.close();
6879
+ } catch (err) {
6880
+ await say(2, "crontab: " + tmp + ": " + (err && err.code ? err.code : String(err)) + "\\n");
6881
+ return 1;
6882
+ }
6883
+ const child = await ctx.spawn({ argv: ["/bin/bash", "-c", editor + ' "$1"', "crontab", tmp] });
6884
+ const ended = await ctx.wait(child.pid);
6885
+ if (ended.status !== 0) {
6886
+ await say(2, "crontab: the editor exited with status " + ended.status + ", crontab not changed\\n");
6887
+ await ctx.fs.rm(tmp).catch(() => undefined);
6888
+ return 1;
6889
+ }
6890
+ const after = decode(await ctx.fs.readFile(tmp));
6891
+ if (after === before) {
6892
+ await say(2, "crontab: no changes made to crontab\\n");
6893
+ await ctx.fs.rm(tmp).catch(() => undefined);
6894
+ return 0;
6895
+ }
6896
+ const answer = await call({ action: "install", text: after });
6897
+ if (answer.error !== undefined) {
6898
+ await say(2, answer.error + "\\ncrontab: your edit is kept in " + tmp + "\\n");
6899
+ return 1;
6900
+ }
6901
+ await ctx.fs.rm(tmp).catch(() => undefined);
6902
+ return 0;
6903
+ }
6904
+ }
6905
+ `;
6814
6906
 
6815
6907
  // src/cron/schedule.ts
6816
6908
  var MISSED_CAP = 1e5;
@@ -7056,6 +7148,83 @@ function parseCrontab(text, path, system = false) {
7056
7148
  return { jobs, errors };
7057
7149
  }
7058
7150
 
7151
+ // src/cron/crontab.ts
7152
+ var CRONTAB_MAX = 256 * 1024;
7153
+ var ACTIONS = /* @__PURE__ */ new Set(["list", "remove", "install"]);
7154
+ function crontabCall(req) {
7155
+ const { action, user, text } = req;
7156
+ if (typeof action !== "string" || !ACTIONS.has(action)) return void 0;
7157
+ if (user !== void 0 && typeof user !== "string") return void 0;
7158
+ if (action === "install" && typeof text !== "string") return void 0;
7159
+ return {
7160
+ op: "proc-crontab",
7161
+ action,
7162
+ ...user !== void 0 ? { user } : {},
7163
+ ...action === "install" ? { text } : {}
7164
+ };
7165
+ }
7166
+ var Crontabs = class {
7167
+ o;
7168
+ constructor(options) {
7169
+ this.o = options;
7170
+ }
7171
+ async request(cred, call) {
7172
+ const caller = await this.o.lookup(cred.ruid);
7173
+ if (!caller) return { error: `crontab: your uid ${cred.ruid} is not in /etc/passwd` };
7174
+ let target = caller;
7175
+ if (call.user !== void 0 && call.user !== caller.name) {
7176
+ if (cred.euid !== 0) return { error: "crontab: must be privileged to use -u" };
7177
+ const found = await this.o.lookup(call.user);
7178
+ if (!found) return { error: `crontab: user ${call.user} unknown` };
7179
+ target = found;
7180
+ }
7181
+ const path = `${SPOOL_DIR}/${target.name}`;
7182
+ const none = { error: `no crontab for ${target.name}` };
7183
+ const { fs } = this.o;
7184
+ if (call.action === "install") return this.install(path, target, call.text);
7185
+ if (!await fs.exists(path)) return none;
7186
+ if (call.action === "list") return { text: await fs.readFile(path) };
7187
+ await fs.rm(path);
7188
+ return { text: "" };
7189
+ }
7190
+ async install(path, target, text) {
7191
+ if (text.length > CRONTAB_MAX) return { error: "crontab: the table is too long" };
7192
+ const { errors } = parseCrontab(text, path);
7193
+ if (errors.length > 0) {
7194
+ const lines2 = errors.map(({ line, message: message3 }) => `"-":${line}: ${message3}`);
7195
+ return { error: ["crontab: errors in crontab file, cannot install", ...lines2].join("\n") };
7196
+ }
7197
+ const { fs, owners } = this.o;
7198
+ if (!await fs.exists(SPOOL_DIR)) {
7199
+ await fs.mkdir(SPOOL_DIR, { recursive: true });
7200
+ if (owners) await fs.chown?.(SPOOL_DIR, { uid: 0, gid: 0, mode: 448, follow: false });
7201
+ }
7202
+ const fresh2 = `${SPOOL_DIR}/.${target.name}.${crypto.randomUUID()}.new`;
7203
+ const owner = owners ? { uid: target.uid, gid: target.gid, mode: 384 } : void 0;
7204
+ await fs.writeFile(fresh2, text, owner);
7205
+ await fs.rename(fresh2, path);
7206
+ return { text: "" };
7207
+ }
7208
+ };
7209
+
7210
+ // src/cron/clock.ts
7211
+ var systemCronClock = {
7212
+ now: () => Date.now(),
7213
+ sleep(until, signal) {
7214
+ if (signal.aborted) return Promise.resolve();
7215
+ return new Promise((resolve2) => {
7216
+ const done4 = () => {
7217
+ clearTimeout(timer);
7218
+ signal.removeEventListener("abort", done4);
7219
+ resolve2();
7220
+ };
7221
+ const timer = setTimeout(done4, Math.max(0, until - Date.now()));
7222
+ timer.unref?.();
7223
+ signal.addEventListener("abort", done4, { once: true });
7224
+ });
7225
+ }
7226
+ };
7227
+
7059
7228
  // src/cron/service.ts
7060
7229
  var CRON_DIR = "/var/spool/cron";
7061
7230
  var STAMP_PATH = `${CRON_DIR}/stamp`;
@@ -7232,7 +7401,7 @@ var CronService = class {
7232
7401
  }
7233
7402
  const names = await fs.readdir(SPOOL_DIR).catch(() => []);
7234
7403
  for (const name2 of names.sort()) {
7235
- if (name2.startsWith(".") || name2.endsWith("~") || name2.endsWith(".new")) continue;
7404
+ if (name2.startsWith(".") || name2.endsWith("~")) continue;
7236
7405
  const path = `${SPOOL_DIR}/${name2}`;
7237
7406
  const account = await this.host.lookup(name2);
7238
7407
  if (!account) {
@@ -16584,6 +16753,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
16584
16753
  "proc-wait",
16585
16754
  "proc-captured",
16586
16755
  "proc-sudo",
16756
+ "proc-crontab",
16587
16757
  "proc-run",
16588
16758
  "proc-exit",
16589
16759
  "proc-kill-self",
@@ -17773,8 +17943,17 @@ var WasmProcess = class {
17773
17943
  return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
17774
17944
  case "proc-sudo":
17775
17945
  return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
17946
+ case "proc-crontab":
17947
+ return { ok: true, kind: "json", json: await this.crontab(req) };
17776
17948
  }
17777
17949
  }
17950
+ async crontab(req) {
17951
+ const { crontab } = this.options;
17952
+ if (!crontab) throw new KernelError("ENOSYS");
17953
+ const call = crontabCall(req);
17954
+ if (!call) throw new KernelError("EINVAL");
17955
+ return crontab(call);
17956
+ }
17778
17957
  signalSyscall(req) {
17779
17958
  if (req.op === "sig-inherited") {
17780
17959
  return { ok: true, kind: "json", json: this.inherited & ~this.caught };
@@ -18177,6 +18356,7 @@ function processOptions(opts, header4, hooks, created3) {
18177
18356
  ...opts.writesBack ? { writesBack: opts.writesBack } : {},
18178
18357
  processes: opts.processes,
18179
18358
  ...opts.sudo ? { sudo: opts.sudo } : {},
18359
+ ...opts.crontab ? { crontab: opts.crontab } : {},
18180
18360
  openFiles: opts.openFiles,
18181
18361
  ...opts.nodes ? { nodes: opts.nodes } : {},
18182
18362
  mounts: opts.mounts,
@@ -21712,6 +21892,7 @@ var LAYOUT = [
21712
21892
  ["/usr", 493],
21713
21893
  ["/usr/local", 493]
21714
21894
  ];
21895
+ var IN_TREE = /* @__PURE__ */ new Set([SUDO_PATH, CRONTAB_PATH]);
21715
21896
  var LOAD_SAMPLE_MS = 5e3;
21716
21897
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
21717
21898
  var encoder8 = new TextEncoder();
@@ -21942,6 +22123,7 @@ var Launcher = class {
21942
22123
  boot = Date.now();
21943
22124
  bootId = crypto.randomUUID();
21944
22125
  cron;
22126
+ crontabs;
21945
22127
  terminals = 0;
21946
22128
  constructor(options) {
21947
22129
  this.usersMode = usersMode(options.users);
@@ -22142,7 +22324,9 @@ var Launcher = class {
22142
22324
  found = this.trustedAppended().then(async (appended2) => {
22143
22325
  const tiers = await this.tiers(scope, appended2);
22144
22326
  return scanCommands(this.scanned, tiers, tiers.length - appended2.length);
22145
- }).then((commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND));
22327
+ }).then(
22328
+ (commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND).set(CRONTAB_COMMAND.name, CRONTAB_COMMAND)
22329
+ );
22146
22330
  this.catalogs.set(scope, found);
22147
22331
  }
22148
22332
  return found;
@@ -22547,7 +22731,7 @@ var Launcher = class {
22547
22731
  const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
22548
22732
  if (root !== void 0) files.push(`${root}/package.json`);
22549
22733
  }
22550
- return [...new Set(files)].filter((path) => path !== "" && path !== SUDO_PATH);
22734
+ return [...new Set(files)].filter((path) => path !== "" && !IN_TREE.has(path));
22551
22735
  }
22552
22736
  async unrunnable(file, cwd, scope) {
22553
22737
  const path = this.fs.resolvePath(cwd, file);
@@ -22687,6 +22871,9 @@ var Launcher = class {
22687
22871
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
22688
22872
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
22689
22873
  }
22874
+ if (target.abi === "js" && target.wasm === CRONTAB_PATH) {
22875
+ return { abi: "js", glue: CRONTAB_SOURCE, path: CRONTAB_PATH };
22876
+ }
22690
22877
  if (target.abi === "js" && target.wasm === REFUSED_PATH) {
22691
22878
  return { abi: "js", glue: REFUSED_SOURCE, path: REFUSED_PATH };
22692
22879
  }
@@ -22842,6 +23029,7 @@ var Launcher = class {
22842
23029
  cpu: this.cpuTotal()
22843
23030
  }),
22844
23031
  sudo: (call, signal) => this.sudo(pid, call, signal),
23032
+ crontab: (call) => this.crontab(pid, call),
22845
23033
  openFiles: this.openFiles,
22846
23034
  nodes: stack.nodes,
22847
23035
  held: this.held,
@@ -23060,6 +23248,14 @@ var Launcher = class {
23060
23248
  const threads = this.stateOf(pid) === "S" ? this.processes.get(pid)?.threads?.() : void 0;
23061
23249
  return threads ? { threads } : {};
23062
23250
  }
23251
+ crontab(pid, call) {
23252
+ this.crontabs ??= new Crontabs({
23253
+ fs: this.fs,
23254
+ owners: this.owners,
23255
+ lookup: (user) => this.users.lookup(user)
23256
+ });
23257
+ return this.crontabs.request(this.credOf(pid), call);
23258
+ }
23063
23259
  sudo(pid, call, signal) {
23064
23260
  const caller = {
23065
23261
  cred: this.credOf(pid),