@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/dist/node.js CHANGED
@@ -5975,14 +5975,14 @@ var WsReader = class {
5975
5975
  }
5976
5976
  };
5977
5977
  function switchingHead(accept2, protocol = "") {
5978
- const chosen = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
5978
+ const chosen2 = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
5979
5979
  ` : "";
5980
5980
  return latin1Bytes(
5981
5981
  `HTTP/1.1 101 Switching Protocols\r
5982
5982
  Upgrade: websocket\r
5983
5983
  Connection: Upgrade\r
5984
5984
  Sec-WebSocket-Accept: ${accept2}\r
5985
- ${chosen}\r
5985
+ ${chosen2}\r
5986
5986
  `
5987
5987
  );
5988
5988
  }
@@ -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.110.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
@@ -6483,6 +6483,37 @@ function argsOf(pkg, raw) {
6483
6483
  if (!raw.every((arg) => typeof arg === "string")) return void 0;
6484
6484
  return raw.map((arg) => arg.replace(PACKAGE, pkg));
6485
6485
  }
6486
+ var warnedVariants = /* @__PURE__ */ new Set();
6487
+ function variantOf(pkg, raw) {
6488
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
6489
+ const { env, wasm } = raw;
6490
+ const path = inside(pkg, wasm);
6491
+ if (!path || !env || typeof env !== "object" || Array.isArray(env)) return void 0;
6492
+ const pairs = Object.entries(env);
6493
+ if (pairs.length === 0) return void 0;
6494
+ if (!pairs.every(([key, value2]) => ENV_KEY.test(key) && typeof value2 === "string")) {
6495
+ return void 0;
6496
+ }
6497
+ return { env: Object.fromEntries(pairs), wasm: path };
6498
+ }
6499
+ function variantsOf(pkg, name2, raw) {
6500
+ if (raw === void 0) return void 0;
6501
+ const variants = Array.isArray(raw) ? raw.map((one) => variantOf(pkg, one)) : [void 0];
6502
+ if (variants.every((one) => one !== void 0)) {
6503
+ return variants.length > 0 ? variants : void 0;
6504
+ }
6505
+ const key = `${pkg}:${name2}:${JSON.stringify(raw)}`;
6506
+ if (!warnedVariants.has(key)) {
6507
+ warnedVariants.add(key);
6508
+ console.warn(`slicc-kernel: ${pkg}: ${name2}: ignoring malformed "variants", running its wasm`);
6509
+ }
6510
+ return void 0;
6511
+ }
6512
+ function variantFor(variants, env) {
6513
+ return variants?.find(
6514
+ (one) => Object.entries(one.env).every(([key, value2]) => env[key] === value2)
6515
+ )?.wasm;
6516
+ }
6486
6517
  function abiOf(raw, fallback) {
6487
6518
  const abi = raw ?? fallback;
6488
6519
  return abi === "emscripten" || abi === "wasi" || abi === "js" ? abi : void 0;
@@ -6516,6 +6547,7 @@ function commandOf(pkg, name2, raw, packageAbi, shared) {
6516
6547
  const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
6517
6548
  if (!abi || !glue || !wasm) return void 0;
6518
6549
  const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
6550
+ const variants = variantsOf(pkg, name2, raw.variants);
6519
6551
  return {
6520
6552
  name: name2,
6521
6553
  abi,
@@ -6527,7 +6559,8 @@ function commandOf(pkg, name2, raw, packageAbi, shared) {
6527
6559
  ...abi === "wasi" && raw.preopenRoot === true ? { preopenRoot: true } : {},
6528
6560
  ...args ? { args } : {},
6529
6561
  ...withEnv,
6530
- ...imports ? { imports } : {}
6562
+ ...imports ? { imports } : {},
6563
+ ...variants ? { variants } : {}
6531
6564
  };
6532
6565
  }
6533
6566
  function commandsOf(pkg, manifest) {
@@ -6794,23 +6827,116 @@ function binfmtOf(binfmts, path) {
6794
6827
  return best === void 0 ? void 0 : binfmts.get(best);
6795
6828
  }
6796
6829
 
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
- }
6830
+ // src/cron/command.ts
6831
+ var CRONTAB_PATH = "/usr/bin/crontab";
6832
+ var CRONTAB_COMMAND = {
6833
+ name: "crontab",
6834
+ abi: "js",
6835
+ glue: CRONTAB_PATH,
6836
+ wasm: CRONTAB_PATH,
6837
+ argv0: "crontab"
6813
6838
  };
6839
+ var CRONTAB_SOURCE = `export default async function crontab(ctx) {
6840
+ const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);
6841
+ const usage = async () => {
6842
+ await say(2, "usage: crontab [-u user] { -l | -r | -e | file | - }\\n");
6843
+ return 1;
6844
+ };
6845
+ const args = ctx.argv.slice(1);
6846
+ let user;
6847
+ let action;
6848
+ let file;
6849
+ for (let i = 0; i < args.length; i++) {
6850
+ const arg = args[i];
6851
+ if (arg === "-u") {
6852
+ user = args[++i];
6853
+ if (user === undefined) return usage();
6854
+ } else if (arg === "-l" || arg === "-r" || arg === "-e") {
6855
+ if (action !== undefined) return usage();
6856
+ action = arg.slice(1);
6857
+ } else if (arg === "-" || !arg.startsWith("-")) {
6858
+ if (file !== undefined) return usage();
6859
+ file = arg;
6860
+ } else return usage();
6861
+ }
6862
+ if ((action === undefined) === (file === undefined)) return usage();
6863
+ const call = (c) => ctx.crontab(user === undefined ? c : { ...c, user });
6864
+ const failed = async (answer) => {
6865
+ await say(2, answer.error + "\\n");
6866
+ return 1;
6867
+ };
6868
+ const decode = (bytes) => new TextDecoder().decode(bytes);
6869
+ if (action === "l") {
6870
+ const answer = await call({ action: "list" });
6871
+ if (answer.error !== undefined) return failed(answer);
6872
+ await say(1, answer.text);
6873
+ return 0;
6874
+ }
6875
+ if (action === "r") {
6876
+ const answer = await call({ action: "remove" });
6877
+ return answer.error !== undefined ? failed(answer) : 0;
6878
+ }
6879
+ if (action === "e") return edit();
6880
+ let text;
6881
+ try {
6882
+ if (file === "-") {
6883
+ const stream = new TextDecoder();
6884
+ const parts = [];
6885
+ for (;;) {
6886
+ const chunk = await ctx.read(0);
6887
+ if (chunk.length === 0) break;
6888
+ parts.push(stream.decode(chunk, { stream: true }));
6889
+ }
6890
+ text = parts.join("") + stream.decode();
6891
+ } else text = decode(await ctx.fs.readFile(ctx.resolve(file)));
6892
+ } catch (err) {
6893
+ await say(2, "crontab: " + file + ": " + (err && err.code ? err.code : String(err)) + "\\n");
6894
+ return 1;
6895
+ }
6896
+ const answer = await call({ action: "install", text });
6897
+ return answer.error !== undefined ? failed(answer) : 0;
6898
+
6899
+ async function edit() {
6900
+ const editor = ctx.env.VISUAL || ctx.env.EDITOR;
6901
+ if (!editor) {
6902
+ await say(2, "crontab: set VISUAL or EDITOR to edit, or install a file with crontab <file>\\n");
6903
+ return 1;
6904
+ }
6905
+ const current = await call({ action: "list" });
6906
+ const before = current.error === undefined ? current.text : "";
6907
+ if (current.error !== undefined && !current.error.startsWith("no crontab for ")) return failed(current);
6908
+ const tmp = "/tmp/crontab." + ctx.pid + "." + crypto.randomUUID();
6909
+ try {
6910
+ const file = await ctx.open(tmp, { write: true, create: true, exclusive: true, mode: 0o600 });
6911
+ await file.write(0, new TextEncoder().encode(before));
6912
+ await file.close();
6913
+ } catch (err) {
6914
+ await say(2, "crontab: " + tmp + ": " + (err && err.code ? err.code : String(err)) + "\\n");
6915
+ return 1;
6916
+ }
6917
+ const child = await ctx.spawn({ argv: ["/bin/bash", "-c", editor + ' "$1"', "crontab", tmp] });
6918
+ const ended = await ctx.wait(child.pid);
6919
+ if (ended.status !== 0) {
6920
+ await say(2, "crontab: the editor exited with status " + ended.status + ", crontab not changed\\n");
6921
+ await ctx.fs.rm(tmp).catch(() => undefined);
6922
+ return 1;
6923
+ }
6924
+ const after = decode(await ctx.fs.readFile(tmp));
6925
+ if (after === before) {
6926
+ await say(2, "crontab: no changes made to crontab\\n");
6927
+ await ctx.fs.rm(tmp).catch(() => undefined);
6928
+ return 0;
6929
+ }
6930
+ const answer = await call({ action: "install", text: after });
6931
+ if (answer.error !== undefined) {
6932
+ await say(2, answer.error + "\\ncrontab: your edit is kept in " + tmp + "\\n");
6933
+ return 1;
6934
+ }
6935
+ await ctx.fs.rm(tmp).catch(() => undefined);
6936
+ return 0;
6937
+ }
6938
+ }
6939
+ `;
6814
6940
 
6815
6941
  // src/cron/schedule.ts
6816
6942
  var MISSED_CAP = 1e5;
@@ -7056,6 +7182,83 @@ function parseCrontab(text, path, system = false) {
7056
7182
  return { jobs, errors };
7057
7183
  }
7058
7184
 
7185
+ // src/cron/crontab.ts
7186
+ var CRONTAB_MAX = 256 * 1024;
7187
+ var ACTIONS = /* @__PURE__ */ new Set(["list", "remove", "install"]);
7188
+ function crontabCall(req) {
7189
+ const { action, user, text } = req;
7190
+ if (typeof action !== "string" || !ACTIONS.has(action)) return void 0;
7191
+ if (user !== void 0 && typeof user !== "string") return void 0;
7192
+ if (action === "install" && typeof text !== "string") return void 0;
7193
+ return {
7194
+ op: "proc-crontab",
7195
+ action,
7196
+ ...user !== void 0 ? { user } : {},
7197
+ ...action === "install" ? { text } : {}
7198
+ };
7199
+ }
7200
+ var Crontabs = class {
7201
+ o;
7202
+ constructor(options) {
7203
+ this.o = options;
7204
+ }
7205
+ async request(cred, call) {
7206
+ const caller = await this.o.lookup(cred.ruid);
7207
+ if (!caller) return { error: `crontab: your uid ${cred.ruid} is not in /etc/passwd` };
7208
+ let target = caller;
7209
+ if (call.user !== void 0 && call.user !== caller.name) {
7210
+ if (cred.euid !== 0) return { error: "crontab: must be privileged to use -u" };
7211
+ const found = await this.o.lookup(call.user);
7212
+ if (!found) return { error: `crontab: user ${call.user} unknown` };
7213
+ target = found;
7214
+ }
7215
+ const path = `${SPOOL_DIR}/${target.name}`;
7216
+ const none = { error: `no crontab for ${target.name}` };
7217
+ const { fs } = this.o;
7218
+ if (call.action === "install") return this.install(path, target, call.text);
7219
+ if (!await fs.exists(path)) return none;
7220
+ if (call.action === "list") return { text: await fs.readFile(path) };
7221
+ await fs.rm(path);
7222
+ return { text: "" };
7223
+ }
7224
+ async install(path, target, text) {
7225
+ if (text.length > CRONTAB_MAX) return { error: "crontab: the table is too long" };
7226
+ const { errors } = parseCrontab(text, path);
7227
+ if (errors.length > 0) {
7228
+ const lines2 = errors.map(({ line, message: message3 }) => `"-":${line}: ${message3}`);
7229
+ return { error: ["crontab: errors in crontab file, cannot install", ...lines2].join("\n") };
7230
+ }
7231
+ const { fs, owners } = this.o;
7232
+ if (!await fs.exists(SPOOL_DIR)) {
7233
+ await fs.mkdir(SPOOL_DIR, { recursive: true });
7234
+ if (owners) await fs.chown?.(SPOOL_DIR, { uid: 0, gid: 0, mode: 448, follow: false });
7235
+ }
7236
+ const fresh2 = `${SPOOL_DIR}/.${target.name}.${crypto.randomUUID()}.new`;
7237
+ const owner = owners ? { uid: target.uid, gid: target.gid, mode: 384 } : void 0;
7238
+ await fs.writeFile(fresh2, text, owner);
7239
+ await fs.rename(fresh2, path);
7240
+ return { text: "" };
7241
+ }
7242
+ };
7243
+
7244
+ // src/cron/clock.ts
7245
+ var systemCronClock = {
7246
+ now: () => Date.now(),
7247
+ sleep(until, signal) {
7248
+ if (signal.aborted) return Promise.resolve();
7249
+ return new Promise((resolve2) => {
7250
+ const done4 = () => {
7251
+ clearTimeout(timer);
7252
+ signal.removeEventListener("abort", done4);
7253
+ resolve2();
7254
+ };
7255
+ const timer = setTimeout(done4, Math.max(0, until - Date.now()));
7256
+ timer.unref?.();
7257
+ signal.addEventListener("abort", done4, { once: true });
7258
+ });
7259
+ }
7260
+ };
7261
+
7059
7262
  // src/cron/service.ts
7060
7263
  var CRON_DIR = "/var/spool/cron";
7061
7264
  var STAMP_PATH = `${CRON_DIR}/stamp`;
@@ -7230,9 +7433,12 @@ var CronService = class {
7230
7433
  const account = await this.host.lookup(job.user);
7231
7434
  if (account) out.push({ source: "system", account, job });
7232
7435
  }
7233
- const names = await fs.readdir(SPOOL_DIR).catch(() => []);
7436
+ const names = await fs.readdir(SPOOL_DIR).catch((err) => {
7437
+ if (err?.code === "ENOENT") return [];
7438
+ throw err;
7439
+ });
7234
7440
  for (const name2 of names.sort()) {
7235
- if (name2.startsWith(".") || name2.endsWith("~") || name2.endsWith(".new")) continue;
7441
+ if (name2.startsWith(".") || name2.endsWith("~")) continue;
7236
7442
  const path = `${SPOOL_DIR}/${name2}`;
7237
7443
  const account = await this.host.lookup(name2);
7238
7444
  if (!account) {
@@ -16584,6 +16790,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
16584
16790
  "proc-wait",
16585
16791
  "proc-captured",
16586
16792
  "proc-sudo",
16793
+ "proc-crontab",
16587
16794
  "proc-run",
16588
16795
  "proc-exit",
16589
16796
  "proc-kill-self",
@@ -17773,8 +17980,17 @@ var WasmProcess = class {
17773
17980
  return { ok: true, kind: "bytes", bytes: this.children.captured(req.pid, req.slot) };
17774
17981
  case "proc-sudo":
17775
17982
  return { ok: true, kind: "json", json: await this.sudo(req, cancelled) };
17983
+ case "proc-crontab":
17984
+ return { ok: true, kind: "json", json: await this.crontab(req) };
17776
17985
  }
17777
17986
  }
17987
+ async crontab(req) {
17988
+ const { crontab } = this.options;
17989
+ if (!crontab) throw new KernelError("ENOSYS");
17990
+ const call = crontabCall(req);
17991
+ if (!call) throw new KernelError("EINVAL");
17992
+ return crontab(call);
17993
+ }
17778
17994
  signalSyscall(req) {
17779
17995
  if (req.op === "sig-inherited") {
17780
17996
  return { ok: true, kind: "json", json: this.inherited & ~this.caught };
@@ -18177,6 +18393,7 @@ function processOptions(opts, header4, hooks, created3) {
18177
18393
  ...opts.writesBack ? { writesBack: opts.writesBack } : {},
18178
18394
  processes: opts.processes,
18179
18395
  ...opts.sudo ? { sudo: opts.sudo } : {},
18396
+ ...opts.crontab ? { crontab: opts.crontab } : {},
18180
18397
  openFiles: opts.openFiles,
18181
18398
  ...opts.nodes ? { nodes: opts.nodes } : {},
18182
18399
  mounts: opts.mounts,
@@ -21712,6 +21929,7 @@ var LAYOUT = [
21712
21929
  ["/usr", 493],
21713
21930
  ["/usr/local", 493]
21714
21931
  ];
21932
+ var IN_TREE = /* @__PURE__ */ new Set([SUDO_PATH, CRONTAB_PATH]);
21715
21933
  var LOAD_SAMPLE_MS = 5e3;
21716
21934
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
21717
21935
  var encoder8 = new TextEncoder();
@@ -21810,9 +22028,18 @@ function targetOf(command) {
21810
22028
  ...command.env ? { env: command.env } : {},
21811
22029
  ...command.unset ? { unset: command.unset } : {},
21812
22030
  ...command.imports ? { imports: command.imports } : {},
21813
- ...command.preopenRoot ? { preopenRoot: true } : {}
22031
+ ...command.preopenRoot ? { preopenRoot: true } : {},
22032
+ ...command.variants ? { variants: command.variants } : {}
21814
22033
  };
21815
22034
  }
22035
+ function chosen(planned, env) {
22036
+ const { target } = planned;
22037
+ const wasm = variantFor(target.variants, env);
22038
+ if (wasm === void 0) return planned;
22039
+ const { asked: _asked, ...rest2 } = target;
22040
+ const glue = target.abi === "wasi" ? wasm : target.glue;
22041
+ return { ...planned, target: { ...rest2, wasm, glue } };
22042
+ }
21816
22043
  function withScriptEnv(target, command) {
21817
22044
  if (!command) return target;
21818
22045
  return {
@@ -21942,6 +22169,7 @@ var Launcher = class {
21942
22169
  boot = Date.now();
21943
22170
  bootId = crypto.randomUUID();
21944
22171
  cron;
22172
+ crontabs;
21945
22173
  terminals = 0;
21946
22174
  constructor(options) {
21947
22175
  this.usersMode = usersMode(options.users);
@@ -22142,7 +22370,9 @@ var Launcher = class {
22142
22370
  found = this.trustedAppended().then(async (appended2) => {
22143
22371
  const tiers = await this.tiers(scope, appended2);
22144
22372
  return scanCommands(this.scanned, tiers, tiers.length - appended2.length);
22145
- }).then((commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND));
22373
+ }).then(
22374
+ (commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND).set(CRONTAB_COMMAND.name, CRONTAB_COMMAND)
22375
+ );
22146
22376
  this.catalogs.set(scope, found);
22147
22377
  }
22148
22378
  return found;
@@ -22547,7 +22777,7 @@ var Launcher = class {
22547
22777
  const root = packageRootOf(at, roots) ?? PACKAGE_ROOT.exec(at)?.[1];
22548
22778
  if (root !== void 0) files.push(`${root}/package.json`);
22549
22779
  }
22550
- return [...new Set(files)].filter((path) => path !== "" && path !== SUDO_PATH);
22780
+ return [...new Set(files)].filter((path) => path !== "" && !IN_TREE.has(path));
22551
22781
  }
22552
22782
  async unrunnable(file, cwd, scope) {
22553
22783
  const path = this.fs.resolvePath(cwd, file);
@@ -22687,6 +22917,9 @@ var Launcher = class {
22687
22917
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
22688
22918
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
22689
22919
  }
22920
+ if (target.abi === "js" && target.wasm === CRONTAB_PATH) {
22921
+ return { abi: "js", glue: CRONTAB_SOURCE, path: CRONTAB_PATH };
22922
+ }
22690
22923
  if (target.abi === "js" && target.wasm === REFUSED_PATH) {
22691
22924
  return { abi: "js", glue: REFUSED_SOURCE, path: REFUSED_PATH };
22692
22925
  }
@@ -22717,14 +22950,15 @@ var Launcher = class {
22717
22950
  ...target.preopenRoot ? { preopenRoot: true } : {}
22718
22951
  };
22719
22952
  }
22720
- async launch(planned, req) {
22721
- const { target, args } = planned;
22953
+ async launch(asked, req) {
22722
22954
  const cwd = await physicalPathAsync(this.base, req.cwd);
22723
22955
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
22724
- const env = { ...expandDefaults(target.env, seen), ...req.env };
22725
- for (const key of target.unset ?? []) delete env[key];
22956
+ const env = { ...expandDefaults(asked.target.env, seen), ...req.env };
22957
+ for (const key of asked.target.unset ?? []) delete env[key];
22726
22958
  delete env[TICKET_ENV];
22727
22959
  env.HOSTNAME = this.hostname;
22960
+ const planned = chosen(asked, env);
22961
+ const { target, args } = planned;
22728
22962
  let program;
22729
22963
  await this.pnpmReady();
22730
22964
  try {
@@ -22842,6 +23076,7 @@ var Launcher = class {
22842
23076
  cpu: this.cpuTotal()
22843
23077
  }),
22844
23078
  sudo: (call, signal) => this.sudo(pid, call, signal),
23079
+ crontab: (call) => this.crontab(pid, call),
22845
23080
  openFiles: this.openFiles,
22846
23081
  nodes: stack.nodes,
22847
23082
  held: this.held,
@@ -23060,6 +23295,14 @@ var Launcher = class {
23060
23295
  const threads = this.stateOf(pid) === "S" ? this.processes.get(pid)?.threads?.() : void 0;
23061
23296
  return threads ? { threads } : {};
23062
23297
  }
23298
+ crontab(pid, call) {
23299
+ this.crontabs ??= new Crontabs({
23300
+ fs: this.fs,
23301
+ owners: this.owners,
23302
+ lookup: (user) => this.users.lookup(user)
23303
+ });
23304
+ return this.crontabs.request(this.credOf(pid), call);
23305
+ }
23063
23306
  sudo(pid, call, signal) {
23064
23307
  const caller = {
23065
23308
  cred: this.credOf(pid),