@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.
@@ -0,0 +1,4 @@
1
+ import type { Command } from '../commands.js';
2
+ export declare const CRONTAB_PATH = "/usr/bin/crontab";
3
+ export declare const CRONTAB_COMMAND: Command;
4
+ export declare const CRONTAB_SOURCE = "export default async function crontab(ctx) {\n const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);\n const usage = async () => {\n await say(2, \"usage: crontab [-u user] { -l | -r | -e | file | - }\\n\");\n return 1;\n };\n const args = ctx.argv.slice(1);\n let user;\n let action;\n let file;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"-u\") {\n user = args[++i];\n if (user === undefined) return usage();\n } else if (arg === \"-l\" || arg === \"-r\" || arg === \"-e\") {\n if (action !== undefined) return usage();\n action = arg.slice(1);\n } else if (arg === \"-\" || !arg.startsWith(\"-\")) {\n if (file !== undefined) return usage();\n file = arg;\n } else return usage();\n }\n if ((action === undefined) === (file === undefined)) return usage();\n const call = (c) => ctx.crontab(user === undefined ? c : { ...c, user });\n const failed = async (answer) => {\n await say(2, answer.error + \"\\n\");\n return 1;\n };\n const decode = (bytes) => new TextDecoder().decode(bytes);\n if (action === \"l\") {\n const answer = await call({ action: \"list\" });\n if (answer.error !== undefined) return failed(answer);\n await say(1, answer.text);\n return 0;\n }\n if (action === \"r\") {\n const answer = await call({ action: \"remove\" });\n return answer.error !== undefined ? failed(answer) : 0;\n }\n if (action === \"e\") return edit();\n let text;\n try {\n if (file === \"-\") {\n const parts = [];\n for (;;) {\n const chunk = await ctx.read(0);\n if (chunk.length === 0) break;\n parts.push(decode(chunk));\n }\n text = parts.join(\"\");\n } else text = decode(await ctx.fs.readFile(ctx.resolve(file)));\n } catch (err) {\n await say(2, \"crontab: \" + file + \": \" + (err && err.code ? err.code : String(err)) + \"\\n\");\n return 1;\n }\n const answer = await call({ action: \"install\", text });\n return answer.error !== undefined ? failed(answer) : 0;\n\n async function edit() {\n const editor = ctx.env.VISUAL || ctx.env.EDITOR;\n if (!editor) {\n await say(2, \"crontab: set VISUAL or EDITOR to edit, or install a file with crontab <file>\\n\");\n return 1;\n }\n const current = await call({ action: \"list\" });\n const before = current.error === undefined ? current.text : \"\";\n if (current.error !== undefined && !current.error.startsWith(\"no crontab for \")) return failed(current);\n const tmp = \"/tmp/crontab.\" + ctx.pid + \".\" + crypto.randomUUID();\n try {\n const file = await ctx.open(tmp, { write: true, create: true, exclusive: true, mode: 0o600 });\n await file.write(0, new TextEncoder().encode(before));\n await file.close();\n } catch (err) {\n await say(2, \"crontab: \" + tmp + \": \" + (err && err.code ? err.code : String(err)) + \"\\n\");\n return 1;\n }\n const child = await ctx.spawn({ argv: [\"/bin/bash\", \"-c\", editor + ' \"$1\"', \"crontab\", tmp] });\n const ended = await ctx.wait(child.pid);\n if (ended.status !== 0) {\n await say(2, \"crontab: the editor exited with status \" + ended.status + \", crontab not changed\\n\");\n await ctx.fs.rm(tmp).catch(() => undefined);\n return 1;\n }\n const after = decode(await ctx.fs.readFile(tmp));\n if (after === before) {\n await say(2, \"crontab: no changes made to crontab\\n\");\n await ctx.fs.rm(tmp).catch(() => undefined);\n return 0;\n }\n const answer = await call({ action: \"install\", text: after });\n if (answer.error !== undefined) {\n await say(2, answer.error + \"\\ncrontab: your edit is kept in \" + tmp + \"\\n\");\n return 1;\n }\n await ctx.fs.rm(tmp).catch(() => undefined);\n return 0;\n }\n}\n";
@@ -0,0 +1,27 @@
1
+ import type { KernelFs } from '../fs/types.js';
2
+ import type { Cred } from '../kernel/cred.js';
3
+ import type { Account } from '../users.js';
4
+ export declare const CRONTAB_MAX: number;
5
+ export interface CrontabCall {
6
+ op: 'proc-crontab';
7
+ action: 'list' | 'remove' | 'install';
8
+ user?: string;
9
+ text?: string;
10
+ }
11
+ export type CrontabResult = {
12
+ text: string;
13
+ } | {
14
+ error: string;
15
+ };
16
+ export interface CrontabsOptions {
17
+ fs: KernelFs;
18
+ owners: boolean;
19
+ lookup(user: string | number): Promise<Account | undefined>;
20
+ }
21
+ export declare function crontabCall(req: Partial<CrontabCall>): CrontabCall | undefined;
22
+ export declare class Crontabs {
23
+ private readonly o;
24
+ constructor(options: CrontabsOptions);
25
+ request(cred: Readonly<Cred>, call: CrontabCall): Promise<CrontabResult>;
26
+ private install;
27
+ }
@@ -34,7 +34,7 @@ export { serveHostfsPort } from './mount/hostfs.js';
34
34
  export type { MountEntry, MountSpec } from './mount/mount-fs.js';
35
35
  export type { ProcessMountPolicy, ProcessMountRequest } from './mount/syscall.js';
36
36
  export type { JsChild, JsExited, JsSpawnOptions, JsStdio } from './process/js/js-children.js';
37
- export type { JsFdStatus, JsFdType, JsFile, JsOpenOptions, JsProgramContext, JsSignal, JsSudoAnswer, } from './process/js/js-context.js';
37
+ export type { JsCrontabAnswer, JsCrontabCall, JsFdStatus, JsFdType, JsFile, JsOpenOptions, JsProgramContext, JsSignal, JsSudoAnswer, } from './process/js/js-context.js';
38
38
  export type { JsStatFs } from './process/js/js-io.js';
39
39
  export type { JsAddress, JsConnection, JsConnectOptions, JsListener, JsListenOptions, JsNet, } from './process/js/js-net.js';
40
40
  export type { JsRunOptions, JsRunResult, JsRunStdio } from './process/js/js-run.js';
@@ -67,6 +67,7 @@ export interface SpawnWasmOptions {
67
67
  writesBack?: (pid: number) => boolean;
68
68
  processes?: () => ProcessListing;
69
69
  sudo?: WasmProcessOptions['sudo'];
70
+ crontab?: WasmProcessOptions['crontab'];
70
71
  openFiles?: Set<VfsNodes>;
71
72
  nodes?: VfsNodes;
72
73
  statfs?: (path: string) => Promise<{
@@ -1,3 +1,4 @@
1
+ import { type CrontabCall, type CrontabResult } from '../cron/crontab.js';
1
2
  import { type Created, type Dac } from '../fs/dac.js';
2
3
  import type { KernelFs } from '../fs/types.js';
3
4
  import type { MountCall } from '../mount/syscall.js';
@@ -255,7 +256,7 @@ export type WasmSyscall = {
255
256
  op: 'proc-captured';
256
257
  pid: number;
257
258
  slot: number;
258
- } | SudoCall | RunRequest | {
259
+ } | SudoCall | CrontabCall | RunRequest | {
259
260
  op: 'proc-exit';
260
261
  status: number;
261
262
  } | {
@@ -368,6 +369,7 @@ export interface WasmProcessOptions {
368
369
  writesBack?: (pid: number) => boolean;
369
370
  processes?: () => ProcessListing;
370
371
  sudo?: (call: SudoCall, signal: AbortSignal) => Promise<SudoResult>;
372
+ crontab?: (call: CrontabCall) => Promise<CrontabResult>;
371
373
  openFiles?: Set<VfsNodes>;
372
374
  nodes?: VfsNodes;
373
375
  mounts?: () => MountLine[];
@@ -517,6 +519,7 @@ export declare class WasmProcess {
517
519
  private socketSyscall;
518
520
  private tty;
519
521
  private procSyscall;
522
+ private crontab;
520
523
  private signalSyscall;
521
524
  private reapsChildren;
522
525
  private sudo;
@@ -226,6 +226,7 @@ export declare class Launcher {
226
226
  readonly boot: number;
227
227
  readonly bootId: `${string}-${string}-${string}-${string}-${string}`;
228
228
  readonly cron: CronService;
229
+ private crontabs;
229
230
  private terminals;
230
231
  constructor(options: LauncherOptions);
231
232
  private cronService;
@@ -318,6 +319,7 @@ export declare class Launcher {
318
319
  private timesOf;
319
320
  list(viewer?: Readonly<Cred>): ProcessInfo[];
320
321
  private threadsOf;
322
+ private crontab;
321
323
  private sudo;
322
324
  private spawner;
323
325
  private childGroup;
@@ -76,6 +76,7 @@ export interface JsProgramContext {
76
76
  wait(pid: number, options?: JsWaitOptions): Promise<JsExited>;
77
77
  kill(pid: number, signal?: JsSignal): Promise<void>;
78
78
  sudo(args: readonly string[]): Promise<JsSudoAnswer>;
79
+ crontab(call: JsCrontabCall): Promise<JsCrontabAnswer>;
79
80
  umask(mask?: number): Promise<number>;
80
81
  net: JsNet;
81
82
  websocket(url: string | URL, options?: JsWebSocketOptions): Promise<JsWebSocket>;
@@ -85,6 +86,18 @@ export interface JsProgramContext {
85
86
  onUncaught(listener: JsStrayListener): () => void;
86
87
  onUnhandledRejection(listener: JsStrayListener): () => void;
87
88
  }
89
+ export interface JsCrontabCall {
90
+ action: 'list' | 'remove' | 'install';
91
+ user?: string;
92
+ text?: string;
93
+ }
94
+ export type JsCrontabAnswer = {
95
+ text: string;
96
+ error?: undefined;
97
+ } | {
98
+ error: string;
99
+ text?: undefined;
100
+ };
88
101
  export type JsSudoAnswer = {
89
102
  error: string;
90
103
  errno: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-kernel",
3
- "version": "1.108.4",
3
+ "version": "1.109.0",
4
4
  "description": "SLICC's wasm-realm kernel: run Emscripten programs in the browser against OPFS, with fork, spawn, pipes and waitpid",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",