@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.
- package/README.md +2 -0
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +437 -240
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +28 -3
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +223 -26
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +28 -3
- package/dist/process-worker.js.map +3 -3
- package/dist/types/cron/command.d.ts +4 -0
- package/dist/types/cron/crontab.d.ts +27 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/kernel/host.d.ts +1 -0
- package/dist/types/kernel/process.d.ts +4 -1
- package/dist/types/launcher.d.ts +2 -0
- package/dist/types/process/js/js-context.d.ts +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -364,6 +364,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
364
364
|
- `spawn({ argv, argv0, env, cwd, pgid, session, parentDeathSignal, stdin, stdout, stderr })` starts a command (found as `kernel.run` finds it) as a child process and resolves with `{ pid, stdin, stdout, stderr, wait(), kill(signal) }`. Each stdio is `'inherit'` (the default: the program's own), `'pipe'` (a web stream on a kernel pipe), `'null'`, or one of the program's descriptors. `env` is the child's whole environment (the program's by default), and `cwd` defaults to the program's. The command run is `argv[0]`; `argv0`, when given, is the `argv[0]` the child sees instead, as given (no basename; an empty string too), as with Node's `options.argv0`: `spawn({ argv: ['/bin/sh', '-c', 'echo $0'], argv0: '/bin/sh' })` prints `/bin/sh`, where without it the child sees the command's own name (`sh`). A command whose package manifest sets its `argv0` (a multi-call program's applets, such as coreutils') keeps that one. An `argv0` that isn't a string is `EINVAL`. The child is in the program's process group and session, as after `fork` (Node's `detached: false`), so the terminal's `^C` and `^Z` reach it along with the program. `pgid: 0` makes it lead a new group in the program's session, set before it runs, so a `kill(-pid)` right after the call reaches it and everything it starts (`npx`, then `node`); on the program's terminal that group is in the background, so a read from the terminal stops it with `SIGTTIN` (`EIO` when it ignores `SIGTTIN`), as on Linux. `pgid: N` joins group N, which must be in the program's session: `EPERM` for another session's group, `ESRCH` when there is none. `session: true` makes it lead a new session and a new group (`setsid`, what Node's `detached: true` does on POSIX): its pid is its `pgid` and its `sid`, it has no controlling terminal (`/dev/tty` is `ENXIO`) and the terminal's keys don't signal it. `session` together with `pgid`, or a `pgid` that isn't a non-negative integer, is `EINVAL`. A command that does not exist rejects with `ENOENT`. `parentDeathSignal` (a number or `'SIGTERM'`, as Linux's `PR_SET_PDEATHSIG`) is sent to the child when the program ends, killed or not: to the child's whole group when it leads one (`pgid: 0` or `session: true`), else to the child alone. It's sent with the program's credentials, so a child the program may no longer signal (one that gained privilege) doesn't get it. If the program has already ended by the time the spawn completes, it's sent at once. A child of its own (not a group leader) that has been waited for gets nothing, and `sync.run` has no such option. A bad signal is `EINVAL`;
|
|
365
365
|
- `wait(pid, { untraced })` resolves with `{ status }` once a child ends, or `{ status: 128 + signal, signal }` when a signal ended it, without blocking the program; with `untraced: true` it also resolves when the child stops, with `{ status: 128 + signal, signal, stopped: true }`, as `waitpid` with `WUNTRACED` (a child's `wait({ untraced })` is the same), and options that aren't an object are `EINVAL`; `kill(pid, signal)` signals any process, by number or as `'SIGTERM'` (the default);
|
|
366
366
|
- `sudo(args)`, what the [`sudo`](#sudo) command calls: `args` are its arguments after `sudo`, and it resolves with `{ pid }`, the command the kernel started as the program's child, to `wait` for; `{ run }`, an argv to run as the caller (with `users: 'off'`); `{ list }`, the text of `sudo -l`; or `{ error, errno }`, the message to print;
|
|
367
|
+
- `crontab({ action, user?, text? })`, what the [`crontab`](#cron) command calls: `action` is `'list'`, `'remove'` or `'install'` (with the table as `text`), on the caller's own table or, for an effective uid of 0, on `user`'s. It resolves with `{ text }` (the table for `list`, `''` otherwise) or `{ error }`, the message to print. A malformed call is `EINVAL`;
|
|
367
368
|
- `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), and `close()`. The loopback is IPv4 (`127.0.0.1` by default, or `0.0.0.0`), so an IPv6 host such as `::1` fails with `EADDRNOTAVAIL` for both `listen` and `connect`, and binding and connecting fail as they would for a WASIX program (`EADDRINUSE`, `ECONNREFUSED`, `EADDRNOTAVAIL`). A blocked accept, read or write waits without holding anything in the kernel, so aborting it or closing the socket leaves nothing behind. A write to a connection the peer closed fails with `EPIPE` and no `SIGPIPE`;
|
|
368
369
|
- `websocket(url, { protocols, headers, signal })`, a WebSocket over the kernel's transport (its `websocket`, so `fetchTransport({ webSocket: true })` in a browser), shaped like `WebSocketStream`. It resolves once the socket is open, with `url`, the `protocol` the far end chose (always one the program offered: a transport that hands back a socket with another fails the open with `EPROTO`, while browsers and Node's `WebSocket`, which `fetchTransport()` uses, fail such a handshake themselves without saying why, so the open fails with `ECONNREFUSED`), a `readable` of messages (`string` for text, `Uint8Array` for binary), a `writable` for sending them, `closed` (the `{ code, reason }` of the close, from either side, whether or not the program reads) and `close({ code, reason })`. An `http(s):` URL is taken as `ws(s):`. Sending waits while more than 1 MiB is buffered. A signal aborts the open, and after it the socket. A transport without WebSockets rejects with a `TypeError` whose `cause.code` is `ENOTSUP`. One that cannot connect rejects with the `code` its error carries (an errno, such as `EAI_NONAME`, `ECONNREFUSED`, `ETIMEDOUT` or `EPROTO`), else `ECONNREFUSED`. `ws://host.slicc.internal:port` reaches the real machine's loopback, as it does through the proxy;
|
|
369
370
|
- `fetch(input, init)`, the web's `fetch` over the kernel's transport, the one the proxy uses, so the embedder's transport, its WebSocket and proxy settings and the kernel's routes apply. The response body streams as the program reads it. On a transport with `traits.streamingBodies` (below), a `ReadableStream` body (with `duplex: 'half'`, which `Request` requires) streams to it as the program produces it, with no size limit of the kernel's, and a redirect that would send it again (a 307 or 308) rejects with a `TypeError`, as the fetch standard has it. Other bodies (a string, a buffer, a `Blob`, a `FormData`) can be sent again, so they go whole even there. Otherwise the request body is read whole first, and one larger than the transport's `maxRequestBody` is answered `413`, as through the proxy. A signal aborts the request at any stage. Redirects follow the fetch rules: at most 20, a 301 or 302 after a `POST` and a 303 turn into a `GET` without a body, `Authorization` is dropped across origins, and `redirect: 'manual'` returns the redirect itself. On a transport that follows redirects on its own (`fetchTransport()` in a browser) only `'follow'` works: `'manual'` and `'error'` reject with a `TypeError` whose `cause.code` is `ENOTSUP`, since such a transport never shows the program a redirect. A status the transport chose comes back as a response whose body is the reason. An unreachable host, or no transport at all, rejects with a `TypeError` whose `cause.code` is `ECONNREFUSED` or `ENETUNREACH`, as Node's `fetch` does. A `data:` URL is answered by `fetch` itself, as the fetch standard decodes it, without a transport: for any method, a `HEAD` without a body, and its type parsed and serialized as a MIME type (lowercase, no spaces, invalid parameters dropped), as Node's `fetch` answers; any other scheme than `http:` and `https:` rejects with a `TypeError` whose `cause.code` is `EPROTONOSUPPORT`, at the start as for a redirect target. A URL on one of the fetch standard's bad ports (25, 6000 and the rest) rejects too, as Node's `fetch` does: a `TypeError` `fetch failed` whose `cause` is an `Error` `bad port` (no `code`), at the start and as a redirect target, on the kernel's own loopback too ([#822](https://github.com/ai-ecoverse/slicc-kernel/issues/822)). `redirect: 'error'` rejects with a `TypeError` whose `cause` is an `Error` `unexpected redirect`, and a body whose connection ends early fails its read with `TypeError` `terminated` whose `cause` is undici's: `name` `SocketError`, `message` `other side closed`, `code` `UND_ERR_SOCKET`. The request goes out with its headers in the order the program gave them (an object's or an array's order; a `Headers` object's is sorted), lowercase as `Headers` keeps them. The global `fetch` stays withheld;
|
|
@@ -761,6 +762,7 @@ With `users: 'enforce'` the kernel checks every file call that reaches it agains
|
|
|
761
762
|
|
|
762
763
|
The kernel runs scheduled jobs itself, with no daemon process: a cron service starts with the kernel and stops with `terminate()`. It reads `/etc/crontab` and the per-user tables in `/var/spool/cron/crontabs/<user>`, and runs each due job as `$SHELL -c '<command>'` (`/bin/bash` unless the table sets `SHELL`) as the table's user, in that user's home (`/` if the home is missing), with no terminal and `/dev/null` as standard input.
|
|
763
764
|
|
|
765
|
+
- **`crontab`** is a command of the kernel itself, in `/usr/bin` (a package's `crontab` never wins over it), with Vixie's options: `crontab -l` prints the caller's table, `crontab -r` removes it, `crontab <file>` and `crontab -` (standard input) install one, and `crontab -e` copies the table to `/tmp`, runs `$VISUAL` (or `$EDITOR`) on the copy through `/bin/bash -c '<editor> "$1"'`, and installs it if it changed and the editor exited with 0. If the edit doesn't parse, the copy is kept and its path is printed. `-u <user>` works on another user's table and needs an effective uid of 0 (`sudo crontab -u cone -l`). An install is checked first: a table with a line that doesn't parse is refused with every error (`"-":<line>: <message>`), and the old one stays. The table becomes the user's `0600` file in the spool, written to a dotfile of a random name and renamed, so the service, which skips dotfiles, never reads half a table. The command only reads the file it is given and passes it on (`ctx.crontab`, see [JS programs](#js-programs)). The kernel decides whose table it is from the caller's real uid, so a user reaches only their own table, and `/etc/crontab` stays root's to edit.
|
|
764
766
|
- **Format:** Vixie's. Five fields (minute, hour, day of month, month, weekday) with lists, ranges, steps and the names `jan`–`dec` and `sun`–`sat` (7 is Sunday too), or one of `@yearly`/`@annually`, `@monthly`, `@weekly`, `@daily`/`@midnight`, `@hourly` and `@reboot`. As in cronie, when the day of month or the weekday starts with `*`, a day must match both fields; when both are restricted, it may match either. `/etc/crontab` has a user field after the schedule; a user's table doesn't. `NAME=value` lines set the environment for the jobs after them, `#` lines are comments, and `# name: <id>` on the line above a job gives it a stable id (letters, digits, `_`, `.`, `-`), which names its log. Without one, the id is a hash of the table, the user, the schedule and the command. `%` is not special. Times are the kernel's local time.
|
|
765
767
|
- **Environment:** only what a login of that user gets, and nothing from the page or the embedder's `env`. That is `HOME`, `USER`, `LOGNAME`, `PATH`, `PNPM_HOME`, `NPM_CONFIG_PREFIX`, the kernel's network settings (`http_proxy`, `SSL_CERT_FILE`, …), `SHELL`, `PWD`, the table's `NAME=value` lines, and `SLICC_CRON=1`, `SLICC_CRON_JOB=<id>`, `SLICC_CRON_DUE=<ISO time of the due run>` and `SLICC_CRON_MISSED=<runs folded into this one>`. An `agent send` from a job therefore knows it was scheduled.
|
|
766
768
|
- **Missed runs:** when the kernel wasn't running (a closed tab) or time jumped forward, every due run since the last one is folded into **one** run, with `SLICC_CRON_MISSED` saying how many more were due (counted up to 100 000). The next run is then computed from now. A clock set back more than two minutes runs nothing until the next due time after the new time.
|