@ai-ecoverse/slicc-kernel 1.175.1 → 1.176.1
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 +6 -5
- package/dist/driver-worker.js +2 -1
- package/dist/driver-worker.js.map +2 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +53 -8
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-driver-worker.js +2 -1
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.cjs +84 -19
- package/dist/node-process-worker.cjs.map +3 -3
- package/dist/node-process-worker.js +84 -19
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +54 -9
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +84 -19
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/dac.d.ts +1 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/kernel/embedder.d.ts +3 -0
- package/dist/types/realm/live-vfs-fs.d.ts +5 -0
- package/dist/types/serve.d.ts +5 -0
- package/dist/types/users.d.ts +5 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -114,10 +114,11 @@ The groups of `/etc/group`, on the page and the Node entry (not on attached clie
|
|
|
114
114
|
| | |
|
|
115
115
|
| --- | --- |
|
|
116
116
|
| `list()` | every group as `{ name, gid, members }`, `members` being the users listed in it (a user's own group lists nobody) |
|
|
117
|
+
| `add(name, { gid })` | adds a system group with no members to `/etc/group` and resolves with it. A name that exists resolves with that group unchanged, whatever its gid, so an embedder calls it at every boot to bring an older `/etc/group` up to date: `kernel.groups.add('dialout', { gid: 20 })`. A gid another group has rejects with `EEXIST`; a name that doesn't match `[a-z_][a-z0-9_.-]*` or a gid that isn't an integer from 0 to 4294967294 with `EINVAL` |
|
|
117
118
|
| `addMember(group, user)` | adds `user` to `group`'s members and resolves with the group; a member already in it changes nothing |
|
|
118
119
|
| `removeMember(group, user)` | removes `user` from `group`'s members, the same way |
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
For `addMember` and `removeMember`, an unknown group or user rejects with `ENOENT`, and a user's own (primary) group with `EINVAL`. Every user has an own group with its name and its uid as gid, which is how a cone shares with its scoops: the scoops join the cone's group. As on Linux, a change counts from the user's next process (and the next client connected for it): processes already running keep the groups they started with, and so does a client already connected. The embedder's recipe, which bios and slicc-agent share, run at every boot:
|
|
121
122
|
|
|
122
123
|
```js
|
|
123
124
|
await kernel.users.add({ name: 'cone', groups: ['sudo'], homeMode: 0o710, umask: 0o077 });
|
|
@@ -1043,7 +1044,7 @@ Lowering a soft resource limit (`ulimit -S -n 64`) still fails with `EPERM` in E
|
|
|
1043
1044
|
|
|
1044
1045
|
Every process has credentials in the kernel, as on Linux: real, effective and saved user and group ids, and supplementary groups. A process gets them from the user it was started as (`run`, `openTerminal`, a client's `spawn`, or the client's own user; root by default) and keeps its parent's across fork, spawn and exec. Setuid and setgid bits are never honoured on exec. Files may have owners (see [File owners](#file-owners)), checked against the credentials with `users: 'enforce'` (see [Permissions](#permissions)); the credentials also decide who may signal whom, and what `/proc` and `ps` show.
|
|
1045
1046
|
|
|
1046
|
-
- **The database:** `/etc/passwd` and `/etc/group` are real files. On first start the kernel writes them when they're missing, with `root` (uid 0, home `/root`, created `0700`) and `nobody` (65534), and the groups `root`, `sudo` (27), `users` (100) and `nogroup` (65534); files already there are kept as they are. The kernel reads them for every lookup, so an edit counts at once. [`kernel.users`](#kernelusers) adds and removes users.
|
|
1047
|
+
- **The database:** `/etc/passwd` and `/etc/group` are real files. On first start the kernel writes them when they're missing, with `root` (uid 0, home `/root`, created `0700`) and `nobody` (65534), and the groups `root`, `dialout` (20, for serial ports), `sudo` (27), `users` (100) and `nogroup` (65534); files already there are kept as they are, and [`kernel.groups.add`](#kernelgroups) adds a group one lacks. The kernel reads them for every lookup, so an edit counts at once. [`kernel.users`](#kernelusers) adds and removes users.
|
|
1047
1048
|
- **Environment:** a process started as a user gets `HOME`, `USER`, `LOGNAME`, `PNPM_HOME`, `NPM_CONFIG_PREFIX` and `PATH`, and `TZ` (the kernel's [time zone](#time-zone)) with the rest of the base or login environment. A user's `PNPM_HOME` is `~/.local/share/pnpm` and its npm prefix `~/.local`; root's are `/usr/local/share/pnpm` (or the embedder's `env.PNPM_HOME`) and `/usr/local`. `PATH` is `$PNPM_HOME/bin:/usr/local/bin:/usr/bin:/bin`. pnpm makes only `$PNPM_HOME/bin`, so the kernel makes root's `PNPM_HOME` when it is missing, just before a process starts: once at boot, and again after any mount or unmount, a removable medium inserted, a `hostfs` folder coming back, or a change at that directory or above it (removing it, say), and after a failed try (no medium yet). With owners on, the directories it makes there get root's owner and `0755`, as the boot layout does. A process already running when a medium arrives gets it at its next child launch. Writes inside it cost nothing extra.
|
|
1048
1049
|
- **Commands per user:** root's global packages (`pnpm add -g` as root) are commands every user finds in `/usr/bin`. A user's own global packages are commands for that user only: they show up in the user's `~/.local/share/pnpm/bin`, first on its `PATH`, and a program the user starts finds them by name before the system's; another user, root included, doesn't. A command that only a dependency of those packages provides shows up there only when no other root has the name (see [Commands](#commands)). A user's packages can map file suffixes (`binfmt`) for that user too. The kernel picks up an install, a removal and a new user as they happen.
|
|
1049
1050
|
- **Changing credentials:** POSIX `setresuid`/`setresgid` rules. Root (effective uid 0) may set any id and the group list; anyone else may only swap among their own real, effective and saved ids, and gets `EPERM` otherwise.
|
|
@@ -1210,12 +1211,12 @@ printf '{"kind":"open.url","url":"%s"}\n' "$url" | cat > /dev/embedder
|
|
|
1210
1211
|
```
|
|
1211
1212
|
|
|
1212
1213
|
- **The request:** the first line is a JSON object with a `kind` (`a.b-c`: lower-case words with dots, at most 64 bytes) and options (every other key), at most 64 KiB. Everything written after it is the body, streamed to the page as it is written: a write waits while 1 MiB of it is unread by the page. A bad header fails that `write()` with `EINVAL`, and the description stays failed. A page that answers with bytes, a string or nothing, or cancels the body, before it has read all of it gets no more: the rest the program writes is dropped ([#1536](https://github.com/ai-ecoverse/slicc-kernel/issues/1536)), so a writer never waits on a body nobody reads. A page that answers with a stream keeps the body until that stream ends, so it must read or cancel the body: until it does, a writer past the 1 MiB window waits. The request goes to the page when its header line ends.
|
|
1213
|
-
- **The reply:** reads return the page's reply in order, pulled as the program reads, and its end is end-of-file. Opened `O_RDWR`, the first `read()` ends the body. Opened `O_WRONLY` (nobody reads), the last close ends the body and waits for the page to finish, so `close()` returns its error (an Emscripten program's standard output follows `/dev/clipboard`'s exit rule, so `printf … | cat > /dev/embedder` fails with `cat: write error: …`). There is no timeout: a reply may wait for a prompt or a model download; run `timeout` for one.
|
|
1214
|
+
- **The reply:** reads return the page's reply in order, pulled as the program reads, and its end is end-of-file. The kernel, and the relay to the page, take a chunk from the page's stream only when the program reads past what they hold, so a program that stops reading holds the page back: the page is ahead of it by at most the rest of the chunk it is reading plus what its own stream queues (its `highWaterMark`, one chunk by default): 64 KiB in the tests, which read 64 KiB chunks whole. Opened `O_RDWR`, the first `read()` ends the body. Opened `O_WRONLY` (nobody reads), the last close ends the body and waits for the page to finish, so `close()` returns its error (an Emscripten program's standard output follows `/dev/clipboard`'s exit rule, so `printf … | cat > /dev/embedder` fails with `cat: write error: …`). There is no timeout: a reply may wait for a prompt or a model download; run `timeout` for one.
|
|
1214
1215
|
- **Who waits:** the process that does the last close waits for the reply, and only a signal to that process interrupts it. So put the open inside the command that `timeout` bounds: `timeout 12 sh -c 'cat request > /dev/embedder'`. In `timeout 12 cat request > /dev/embedder` the redirection is `timeout`'s own. `cat` writes the header and exits at once, so the timer never fires, and then `timeout` waits in its own last close, on its way out (coreutils closes its standard output), until the page answers.
|
|
1215
1216
|
- **Cancel:** a signal during a blocked read, write or `O_WRONLY` close fails it with `EINTR`, and the page's `signal` aborts with reason `interrupted`. Closing the description before the end of the reply aborts it with reason `closed`, and that is no error: it is how `hear` stops recording. A process that ends during a request aborts it too.
|
|
1216
1217
|
- **Errors:** the hook's error gives the errno: `NotAllowedError` or `AbortError` (a dismissed prompt, a refused permission) `EACCES`; `NotSupportedError` `ENOTSUP`; `NotFoundError` `ENOENT`; anything else `EIO`. An Emscripten or WASI program first gets `<argv0>: /dev/embedder: <first line of the message>` on standard error, as for `/dev/clipboard`; a JS program has it in the error. `request.progress(line)` from the page writes `<argv0>: <line>` to standard error the same way, at most 4 lines a second.
|
|
1217
1218
|
- **Who answers:** the hook of the client the process's session was started through, inherited by its children, forks and `exec`s: `createKernel({ requests })` (and `createNodeKernel`) for the page's own `run` and `openTerminal`, the hook a port was made with (`connect({ requests })`), or the hook its attached client brought (`connect({ requests: 'attach' })`, then `attachKernel(port, { requests })`). Nothing falls back to another client's hook, so a request from another tab's terminal never opens in this one. With no hook, or a kind the hook doesn't list, the request is `ENOTSUP` before the page sees it. A program has no way to register, replace or see a hook.
|
|
1218
|
-
- **The allow-list and policy:** `kinds` maps each kind the hook answers to its policy: `{}`, `{ group: 'video' }`, `{ users: ['alice', 1001] }`. With `users: 'report'` or `'enforce'` the kernel lets a request through only from root, the session's user (the user its port was connected as, or the user `run` or `openTerminal` ran as), a member of the kind's `group
|
|
1219
|
+
- **The allow-list and policy:** `kinds` maps each kind the hook answers to its policy: `{}`, `{ group: 'video' }`, `{ users: ['alice', 1001] }`, `{ group: 'dialout', session: false }`. With `users: 'report'` or `'enforce'` the kernel lets a request through only from root, the session's user (the user its port was connected as, or the user `run` or `openTerminal` ran as), a member of the kind's `group` (by effective or supplementary group), or a listed user: a process that `sudo -u`'d to someone else gets `EACCES` (`<kind> is not allowed for this user`), and the page never sees it. `session: false` drops the session's user from that list, so only root, the group's members and the listed users pass: `{ session: false }` alone is root only (for an agent, `sudo`), and `{ group: 'dialout', session: false }` lets the `dialout` members read a serial port while a write kind stays root's. `session` missing or `true` is the rule above. Each refusal also goes to `onDenial` as `{ pid, uid, op: 'embedder', path: '/dev/embedder', errno: 'EACCES', kind, tag? }`. With `users: 'off'` nothing is checked. The handler can still prompt or refuse by the stamp.
|
|
1219
1220
|
- **The hook:** `handle({ kind, options, body, stamp, signal, progress })` gets the body as a `ReadableStream<Uint8Array>` and resolves with the reply: a `ReadableStream<Uint8Array>`, bytes, a string, or nothing (an empty reply). `stamp` is `{ pid, uid, gid, groups, users, kind, tag?, tags? }`: the process that sent the header line, with its effective ids and groups.
|
|
1220
1221
|
- **Limits:** at most 16 open requests per process (`EMFILE`) and 256 MiB of request windows per kernel, 128 requests at 2 MiB each (`EAGAIN` at `open()` beyond); a closed request frees its share.
|
|
1221
1222
|
- `/dev/clipboard` stays as it is, text only. Image clipboards are kinds on this channel (`clipboard.read`, `clipboard.write`), answered by the embedder.
|
|
@@ -1278,7 +1279,7 @@ In a browser every process worker of the page shares one renderer. V8 reserves a
|
|
|
1278
1279
|
- **Below a learned ceiling,** a process that still runs out of memory is started again once another process exits, and the spawn fails with `EAGAIN` after 10 s with none.
|
|
1279
1280
|
- **`/proc/sys/kernel/slicc_wasm_memories`** reads `<live> <ceiling> <source>`. The source is `default`, `learned`, or `none` (with the ceiling 0).
|
|
1280
1281
|
|
|
1281
|
-
`/proc/sys/kernel/slicc_shared_root` reads `1` while another kernel shares the root (a tab, or a second kernel in the page) and `0` otherwise. A peer whose kernel ends (its tab closes or reloads) stops counting at once: each kernel queues a request for every peer's alive Web Lock, and the grant tells it the peer has gone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)). `/proc/sys/kernel/slicc_fs_calls` reads the number of path calls (lookups, stats, opens, creates, removals and the like) the kernel has served its programs since it started. `/proc/sys/kernel/slicc_opfs_calls` counts the OPFS calls the kernel has made to look names up and read stats (`getFile`, `getFileHandle`, `getDirectoryHandle`, `resolve`). `/proc/sys/kernel/slicc_opfs_ops` counts every request the root file system makes of OPFS: those lookups, plus creates, writes (one per write, truncate or write session), reads, listings, renames and removals ([#1017](https://github.com/ai-ecoverse/slicc-kernel/issues/1017)). `/proc/sys/kernel/slicc_meta_transactions` reads two numbers: the read and the write transactions the kernel has opened on its metadata database (IndexedDB) since it started; it reads `0 0` with `metadata: false`. With an idle peer, a tar unpack makes at most 1.5 times the calls it makes alone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)). `/proc/sys/kernel/slicc_cwd_checks` counts the times a process asked the kernel whether its [working directory](#the-working-directory) is still the one it holds.
|
|
1282
|
+
`/proc/sys/kernel/slicc_shared_root` reads `1` while another kernel shares the root (a tab, or a second kernel in the page) and `0` otherwise. A peer whose kernel ends (its tab closes or reloads) stops counting at once: each kernel queues a request for every peer's alive Web Lock, and the grant tells it the peer has gone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)). `/proc/sys/kernel/slicc_fs_calls` reads the number of path calls (lookups, stats, opens, creates, removals and the like) the kernel has served its programs since it started. `/proc/sys/kernel/slicc_opfs_calls` counts the OPFS calls the kernel has made to look names up and read stats (`getFile`, `getFileHandle`, `getDirectoryHandle`, `resolve`). `/proc/sys/kernel/slicc_opfs_ops` counts every request the root file system makes of OPFS: those lookups, plus creates, writes (one per write, truncate or write session), reads, listings, renames and removals ([#1017](https://github.com/ai-ecoverse/slicc-kernel/issues/1017)). `/proc/sys/kernel/slicc_create_parent_refresh` counts the times a create (mkdir, `O_CREAT` or symlink) got `ENOENT` for a parent that was only a cached miss, listed the directory above that parent once and retried. `/proc/sys/kernel/slicc_meta_transactions` reads two numbers: the read and the write transactions the kernel has opened on its metadata database (IndexedDB) since it started; it reads `0 0` with `metadata: false`. With an idle peer, a tar unpack makes at most 1.5 times the calls it makes alone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)). `/proc/sys/kernel/slicc_cwd_checks` counts the times a process asked the kernel whether its [working directory](#the-working-directory) is still the one it holds.
|
|
1282
1283
|
|
|
1283
1284
|
### Metadata
|
|
1284
1285
|
|
package/dist/driver-worker.js
CHANGED
|
@@ -216,7 +216,8 @@ function kindsOf(hook) {
|
|
|
216
216
|
kind,
|
|
217
217
|
{
|
|
218
218
|
...policy?.group !== void 0 ? { group: String(policy.group) } : {},
|
|
219
|
-
...policy?.users ? { users: [...policy.users] } : {}
|
|
219
|
+
...policy?.users ? { users: [...policy.users] } : {},
|
|
220
|
+
...policy?.session === false ? { session: false } : {}
|
|
220
221
|
}
|
|
221
222
|
])
|
|
222
223
|
);
|