@ai-ecoverse/slicc-kernel 1.32.3 → 1.34.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 CHANGED
@@ -65,7 +65,7 @@ Starts `argv` as the session leader on a new terminal and resolves once it is ru
65
65
  | --- | --- |
66
66
  | `pid` | the session leader's pid |
67
67
  | `onData` | the output listener; output that arrives while it is `null` is kept and delivered when one is set |
68
- | `write(data)` | types a string or bytes into the terminal; with `ISIG` on, `^C`, `^Z` and `^\` signal the foreground process group. A key typed while a child of that group is still setting up is held until the child has finished, then sent to the group in the foreground at that point. Setting up means the child's calls before its first other one: `setpgid`, `tcsetpgrp` and signal masks. The hold ends 250 ms after the child's first call at the latest. So `^Z` right after a command line stops the new job, as it does on Linux, where that setup takes microseconds rather than a worker start. |
68
+ | `write(data)` | types a string or bytes into the terminal; with `ISIG` on, `^C`, `^Z` and `^\` signal the foreground process group. A key typed while a child of that group is still setting up is held until the child has finished, then sent to the group in the foreground at that point. Setting up means the child's calls before its first other one: `setpgid`, `tcsetpgrp`, signal masks, and starting the program it execs (which then sets itself up too). The hold ends 2 s after the child's first call at the latest. A key typed while the foreground group is busy, not reading the terminal (a shell between reading a command line and starting it), goes to that group and also to the job the group starts next, once that job has taken the terminal; the second copy is dropped when the group reads the terminal again. So `^C` or `^Z` right after a command line reaches the new job, as it does on Linux, where starting a job takes microseconds rather than a worker start. |
69
69
  | `resize(cols, rows)` | sets the window size (`TIOCGWINSZ`) and sends `SIGWINCH` to the foreground process group |
70
70
  | `signal(name)` | sends a signal (`'SIGINT'`, `'SIGTSTP'`, `'SIGQUIT'`, `'SIGHUP'`, …) to the foreground process group, regardless of termios |
71
71
  | `exited` | resolves with the session leader's exit status (`128 + n` if killed by signal `n`) |
@@ -188,6 +188,28 @@ A WASI command can name an ES module of its package that provides imports of its
188
188
  - `async`: operations of the process that any of its threads can wait for: `submit(req)` runs a kernel syscall without blocking the caller, `resolve(value)` completes at once, `hold()` never completes, and each returns an id; `wait(timeoutMs?)` returns the id of a completed one (0 after the timeout, -1 once `close()` was called), `take(id)` its `{ value }` or `{ error }` (`undefined` while it is pending), and `cancel(id)` drops one;
189
189
  - `errno(err)`: the WASI errno of an error.
190
190
 
191
+ ### JS programs
192
+
193
+ A command can be an ES module instead of a wasm program: `"abi": "js"` and `module` name the module in the package, which must be one self-contained file (no relative or bare imports), and `args`, `argv0` and `env` work as for other commands.
194
+
195
+ ```json
196
+ { "slicc": { "commands": { "jstool": { "abi": "js", "module": "dist/jstool.mjs" } } } }
197
+ ```
198
+
199
+ The module's default export (or its `main` export) is called with a context and returns the exit status, or nothing for 0. It runs as an ordinary process in a process worker of its own: it has a pid and a process group, shows in `ps` and `/proc`, takes part in pipes, redirects and job control, and `kill`, `^C` and `^Z` reach it. A stopped program freezes at its next turn of the event loop (an `await`, a timer, a callback) and stays frozen, its timers and callbacks included, until `SIGCONT`; a synchronous loop that never yields runs on until it does, as a WASI program runs on until its next system call, since nothing can preempt a worker's running code short of ending it. A process worker is a dedicated worker, so a JS program can use what browsers offer there, such as WebCodecs (`VideoEncoder`, `VideoDecoder`, `AudioEncoder`, `AudioDecoder`, `ImageDecoder`) and `OffscreenCanvas`, without the page's main thread. The context (`JsProgramContext`, exported as a type) has:
200
+
201
+ - `argv` (with `argv[0]`), `env`, `pid` (the pid `ps` shows, also after an `exec`), `ppid()` (asked each time, so an orphan sees 1) and `cwd`, and `resolve(path)` to make a path absolute against `cwd`;
202
+ - `stdin`, `stdout` and `stderr` as web streams, and `read(fd, max?)` (empty at end of file), `write(fd, bytes | string)` and `close(fd)` on any descriptor it inherited. A write waits until the pipe has room, so a slow reader slows the program down;
203
+ - `fdStatus(fd)` (`{ type, tty, seekable }`, `type` being `file`, `pipe`, `tty`, `socket`, `device` or `directory`) and `isatty(fd)`, to tell `< file` from a pipe;
204
+ - `open(path, { read, write, append, create, exclusive, truncate })`, which returns a file with `read(position, length)`, `write(position, bytes)`, `size()`, `truncate(size)`, `sync()` and `close()`. Reads and writes go through the kernel's paged files at those positions, so a large file is never read whole;
205
+ - `fs`: `stat`, `lstat`, `exists`, `readdir`, `mkdir` (with parents), `rm` (recursive), `unlink`, `rename`, `symlink`, `readlink`, and `readFile`/`writeFile` for small files;
206
+ - `signals`: `on(signal, handler)` catches a signal (by number or as `'SIGINT'`), `ignore(signal)` ignores it and `reset(signal)` restores its default action. A call blocked in a read or write is restarted after a handler runs; signals without a handler keep their default actions, so an uncaught `SIGINT` ends the program with 130 and `SIGKILL` always does;
207
+ - `exit(status)`, which ends the program once its pending writes are done.
208
+
209
+ A program that throws, or leaves an error or a rejected promise unhandled, ends with status 1 and the message on stderr; one whose module cannot be loaded, or exports no function, ends with 126. A write to a pipe whose reader is gone ends the program with 141, unless it ignores or catches `SIGPIPE` (an ignore inherited from its parent counts), in which case the write fails with `EPIPE`. Errors carry a POSIX `code`. Reads and writes of pipes, terminals and sockets go through the kernel's asynchronous operations, so a program blocked reading stdin can still write and handle signals; other calls of one process are made one at a time. A JS program cannot `fork`, and threads it starts itself are not the kernel's.
210
+
211
+ The module has the trust of an Emscripten program's glue or a WASI program's imports module: it comes from a package that was installed, and it runs in the worker of its own process. The kernel withholds the worker's own ways out before it loads the module: `fetch`, `XMLHttpRequest`, `WebSocket`, `WebTransport`, `EventSource`, `importScripts`, `indexedDB`, `caches`, `cookieStore`, `Worker`, `SharedWorker`, `BroadcastChannel`, `postMessage`, `close`, and `navigator.storage`, `navigator.locks` and `navigator.serviceWorker` are `undefined`, and a worker where one of them cannot be removed refuses to run the program (126). A program's files are the kernel's, through the context; it reaches the kernel only through its own process's system calls, as the context makes them, and no other process's. This is a guard against programs going around the kernel, not a sandbox: `import()` of a URL is a request the kernel cannot withhold. Under the Node entry, a JS program also has Node's own modules.
212
+
191
213
  ## Network
192
214
 
193
215
  Programs talk to each other over sockets, and to the outside world through a proxy that hands every request to a transport the page passes in.
@@ -312,6 +334,8 @@ The proxy answers `200` with `Content-Type: application/vnd.slicc.raw-fetch`: a
312
334
 
313
335
  Directories are renamed with `FileSystemHandle.move()` where available, else by copy and delete.
314
336
 
337
+ `mmap` of a regular file works on every file system, for Emscripten programs built with `mmap` (whose glue exports `emscripten_builtin_memalign`) as well as for WASIX ones: the kernel reads the mapped range into fresh memory of the process, as Emscripten's MEMFS does. A `MAP_PRIVATE` mapping may be written, and its changes stay in the process. A writable `MAP_SHARED` mapping of such a file is `ENODEV`; a file in the process's own in-memory file system (MEMFS, as under `/dev`) keeps Emscripten's own mapping, which writes back. A read-only `MAP_SHARED` mapping is a copy taken at `mmap`, so later changes to the file do not show in it.
338
+
315
339
  ### Mounts
316
340
 
317
341
  `kernel.mount({ type, source, target, options })` mounts a file system on an existing directory. Every process sees it, Emscripten and WASI alike, and so does every attached client. `kernel.umount(target)` unmounts it, and fails with `EBUSY` while a process has a file open under it. `kernel.mounts()` lists the table, as does `/proc/mounts`. The Node entry and attached clients have the same three calls (client protocol 1.2).