@ai-ecoverse/slicc-kernel 1.143.5 → 1.144.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 +15 -7
- package/dist/driver-worker.js +5 -1
- package/dist/driver-worker.js.map +2 -2
- package/dist/index.js +5 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +7830 -7524
- package/dist/kernel-worker.js.map +4 -4
- package/dist/meta.js +1 -1
- package/dist/meta.js.map +2 -2
- package/dist/node-driver-worker.js +5 -1
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.js +212 -35
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +24174 -23864
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +212 -35
- package/dist/process-worker.js.map +3 -3
- package/dist/types/client/attach.d.ts +3 -0
- package/dist/types/client/protocol.d.ts +3 -0
- package/dist/types/client/serve-client.d.ts +1 -1
- package/dist/types/cron/crontab.d.ts +13 -1
- package/dist/types/cron/service.d.ts +2 -0
- package/dist/types/fs/changes.d.ts +4 -0
- package/dist/types/fs/dac.d.ts +2 -0
- package/dist/types/fs/meta.d.ts +4 -0
- package/dist/types/fs/opfs.d.ts +10 -0
- package/dist/types/fs/remote.d.ts +4 -0
- package/dist/types/fs/types.d.ts +4 -0
- package/dist/types/kernel/clipboard.d.ts +3 -2
- package/dist/types/kernel/host.d.ts +5 -0
- package/dist/types/kernel/proc-info.d.ts +1 -0
- package/dist/types/kernel/process.d.ts +1 -1
- package/dist/types/kernel/rename-log.d.ts +14 -0
- package/dist/types/kernel/sealed.d.ts +8 -3
- package/dist/types/kernel/vfs-file.d.ts +1 -0
- package/dist/types/launcher.d.ts +12 -1
- package/dist/types/process/wasi/wasi-fds.d.ts +4 -1
- package/dist/types/process/wasi/wasi-files.d.ts +2 -0
- package/dist/types/realm/live-vfs-fs.d.ts +8 -0
- package/dist/types/realm/sync-fs-token-registry.d.ts +5 -0
- package/dist/types/realm/sync-fs-wire.d.ts +7 -2
- package/dist/types/sudo.d.ts +2 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ Stops the kernel worker and every process. Pending and later calls reject.
|
|
|
90
90
|
|
|
91
91
|
Makes a port for another client of the same kernel. Hand it to a dedicated worker, or through a SharedWorker to another tab: it can be transferred any number of times. All clients share one process table, so a terminal on the page can `ps` and `kill` what a worker started.
|
|
92
92
|
|
|
93
|
-
**`user`** (a name or uid) binds the client to that user; without it the client is root. The binding is a capability: the client's `spawn`, `run` and `openTerminal` run as that user, its `ps` lists only that user's processes, and its `kill` follows POSIX permission. A bound client can't run anything as another user (`code: 'EPERM'`). A root client may pass `user` to `spawn`, `run` or `openTerminal` to run one command as any user. An unknown user rejects with `code: 'ENOENT'`. **`tag`** is the tag (see `kernel.run`) of every process the client starts; its own `spawn`, `run` and `openTerminal` cannot name another.
|
|
93
|
+
**`user`** (a name or uid) binds the client to that user; without it the client is root. The binding is a capability: the client's `spawn`, `run` and `openTerminal` run as that user, its `ps` lists only that user's processes, and its `kill` follows POSIX permission. A bound client can't run anything as another user (`code: 'EPERM'`). A root client may pass `user` to `spawn`, `run` or `openTerminal` to run one command as any user. An unknown user rejects with `code: 'ENOENT'`. **`tag`** is the tag (see `kernel.run`) of every process the client starts; its own `spawn`, `run` and `openTerminal` cannot name another. They may add one with their own `tag` option (protocol 1.12; an older kernel answers `ENOSYS`): the process then has both tags, the connection's first, and so does everything it starts. A per-spawn tag only adds; no client, program or child can remove or replace a tag ([#1380](https://github.com/ai-ecoverse/slicc-kernel/issues/1380)).
|
|
94
94
|
|
|
95
95
|
### `kernel.users`
|
|
96
96
|
|
|
@@ -173,7 +173,7 @@ Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker
|
|
|
173
173
|
| `spawn(argv, { cwd, user, env, stdin, pgid, onStdout, onStderr })` | runs over pipes and resolves with `{ pid, pgid, exited, ended, signal(name) }` once the process has started; `ended` resolves with `{ status, signal }`, `signal` named only when a signal ended the process, as for `kernel.run` (an older kernel never names one); output arrives in chunks, as bytes, and the kernel keeps no copy; every spawn leads its own process group, which `signal` (default `SIGTERM`) signals, unless `pgid` names an existing group to join (protocol 1.3): the process then joins that group and that group's session, as `setpgid` would (a client has no session of its own: each of its spawns and terminals leads one, and `setpgid` cannot cross sessions), and `signal` signals the whole group. The group must have been started by this client (a spawn or a terminal it opened), even if its leader has exited; an unknown group rejects with `code: 'ESRCH'`, another session's with `code: 'EPERM'`, and against a 1.2 kernel the client rejects with `code: 'ENOSYS'` before sending anything, as an older kernel would ignore the option; a program that cannot start rejects with `code: 'ENOENT'`. `user` runs it as another user, which only a root client may (see `kernel.connect`); against a 1.7 kernel the client rejects with `code: 'ENOSYS'` |
|
|
174
174
|
| `run(argv, options?)` | `spawn` and wait: `{ pid, status, signal, stdout, stderr }` as text, with `onStdout`/`onStderr` streaming text; `signal` as for `kernel.run` |
|
|
175
175
|
| `openTerminal(argv, options?)` | a pty session, as `kernel.openTerminal`, `ended` included |
|
|
176
|
-
| `ps()` | the kernel's processes: `{ pid, ppid, pgid, sid, argv, tty, started, state, rss, utime, stime, uid, gid }`, `state` being `'S'`, `'T'` (stopped) or `'Z'`, `rss` the process's resident memory in bytes (the size of its wasm memory, what `/proc/<pid>/status` shows as `VmRSS`; absent from kernels before 1.61), `utime`/`stime` the CPU time it has used, in milliseconds, in its program and in the kernel's calls (see [`/proc`](#proc); absent for a JS program and from kernels before 1.63) and `uid`/`gid` the effective ids (absent from a 1.7 kernel), and `tag` the process's tag (see `kernel.run`), when it has one, on rows the client's user may see as root or as the same uid. A client bound to a user sees only that user's processes |
|
|
176
|
+
| `ps()` | the kernel's processes: `{ pid, ppid, pgid, sid, argv, tty, started, state, rss, utime, stime, uid, gid }`, `state` being `'S'`, `'T'` (stopped) or `'Z'`, `rss` the process's resident memory in bytes (the size of its wasm memory, what `/proc/<pid>/status` shows as `VmRSS`; absent from kernels before 1.61), `utime`/`stime` the CPU time it has used, in milliseconds, in its program and in the kernel's calls (see [`/proc`](#proc); absent for a JS program and from kernels before 1.63) and `uid`/`gid` the effective ids (absent from a 1.7 kernel), and `tag` the process's tag (see `kernel.run`), when it has one, with `tags`, all of them in order, when a spawn added one (see `kernel.connect`), on rows the client's user may see as root or as the same uid. A client bound to a user sees only that user's processes |
|
|
177
177
|
| `kill(pid, signal?)` | a process, or a process group with a negative pid; no such process rejects with `code: 'ESRCH'`, and one the client's user may not signal with `code: 'EPERM'` |
|
|
178
178
|
| `fs` | `readFile`, `readText`, `writeFile(path, string \| bytes)`, `stat`, `lstat`, `readdir`, `mkdir` (with parents), `rm(path, { force })` (recursive), `rename`, `realpath`, `symlink(target, path)`, `readlink`, `exists`, on the processes' file system, mounts included (a path under a `tmpfs`, `fsa` or `hostfs` mount reads and writes the mounted file system, as `cat` in a process would); failures reject with `KernelCallError` and a POSIX `code` |
|
|
179
179
|
| `fetch({ url, method, headers, body, signal })` | through the kernel's network transport (the one the page passed to `createKernel`), with a streaming body; `transport` is the same as a `NetworkTransport` |
|
|
@@ -185,7 +185,7 @@ Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker
|
|
|
185
185
|
| `closed` | resolves with the error that ended the client |
|
|
186
186
|
| `users` | the kernel's `users` mode, `'off'`, `'report'` or `'enforce'`; `'off'` from kernels that don't say |
|
|
187
187
|
|
|
188
|
-
When the kernel goes away (the page closed or reloaded, `terminate()`), pending calls and `exited` reject with `KernelGoneError`, and so do later calls. Each side holds a Web Lock and waits on the other's, since a `MessagePort` reports no close in browsers; in Node, the port's `close` event does the same. The first message is a handshake on the protocol version, `1.
|
|
188
|
+
When the kernel goes away (the page closed or reloaded, `terminate()`), pending calls and `exited` reject with `KernelGoneError`, and so do later calls. Each side holds a Web Lock and waits on the other's, since a `MessagePort` reports no close in browsers; in Node, the port's `close` event does the same. The first message is a handshake on the protocol version, `1.12` (`1.0` had no `watch`, `1.1` no mounts, `1.2` no `spawn` into a group, `1.3` no `dial` and `1.4` no `serveCdp`, which reject with `code: 'ENOSYS'` on such a kernel; a `1.5` kernel has no [uplink](#uplink), a `1.6` kernel resolves names through one but connects nothing to it, a `1.7` kernel has no users, a `1.8` kernel has no `sudo` and no tags, a `1.9` kernel never announces new transport traits, and a `1.11` kernel has no per-spawn `tag`): a client or kernel of another major version is refused with an error naming both.
|
|
189
189
|
|
|
190
190
|
### Headless in Node, for tests
|
|
191
191
|
|
|
@@ -754,6 +754,13 @@ The proxy answers `200` with `Content-Type: application/vnd.slicc.raw-fetch`: a
|
|
|
754
754
|
|
|
755
755
|
**Renaming a directory on the OPFS root** ([#1122](https://github.com/ai-ecoverse/slicc-kernel/issues/1122)): Chrome's OPFS can move a file but not a directory. So the kernel renames a directory by creating the new tree and moving each file into it (copying a file only if it can't move, e.g. while it's open elsewhere), then removing the old tree. Within the kernel the rename is atomic to readers: a lookup or listing under the old or the new name, or of either parent, waits until the whole tree has moved. So a stage-then-rename updater (pnpm's, an atomic config write) is never seen half there. Another tab sharing the root sees it all at once too (see below). A root whose directories can move (the Node memory root) renames them in one step. **If the page reloads or crashes mid-rename**, some files are already in the new tree and the rest are still in the old one. So before the first move, the kernel writes the rename's intent (`{ from, to }`) to its metadata store, and clears it after the last. At the next boot, when no other tab shares the root (the same rule as for the boot's other repair of stale metadata), the kernel finishes every recorded rename: it moves what's left into the new tree, removes the old name, and clears the record. The moved directory and its files keep their owner, mode and inode, as after a rename that wasn't stopped. While another tab shares the root, a kernel finishes only renames whose kernel is gone ([#1141](https://github.com/ai-ecoverse/slicc-kernel/issues/1141)): the renaming kernel holds a Web Lock (`slicc-rename:<id>`, named in the record) for the whole rename, and the browser releases it when that tab closes or its worker ends. A kernel that boots beside a peer, and every kernel that sees a peer go, finishes each recorded rename whose lock is free, taking that lock while it does, so two survivors never replay the same rename and a live kernel's rename is never touched. A record without a lock (written by an older kernel, or where Web Locks are missing) waits for a boot that's alone, as before. While another tab finishes a rename, this kernel's readers wait as for its own, and so do the other tabs'. A kernel whose wait for another tab's rename ends with the lock free and the record still there finishes the rename itself, under that lock, and a boot that's alone takes a record's lock too, so a third tab that boots meanwhile waits for it.
|
|
756
756
|
|
|
757
|
+
**A rename by another process** ([#1257](https://github.com/ai-ecoverse/slicc-kernel/issues/1257)): as on Linux, where a descriptor names the file and not its path, a program's open file follows a rename of that file, or of a directory above it, by another process.
|
|
758
|
+
- **The journal:** the kernel keeps the last 4096 renames it applied, each from and to a path, and counts them in the change buffer it shares with its processes. A rename in another tab on the same root reaches it through the sidecar's move notice.
|
|
759
|
+
- **Catching up:** before a WASI or Emscripten program uses an open file's path (a read, a write, a seek, an `fsync`, a close, an open), it reads that count once. If another process renamed something since, and the program holds files open, it asks the kernel once for the renames of the paths it holds and moves its open files to their new names. The kernel answers only about the paths the program sent, and, under `users: 'enforce'`, drops every rename whose old or new name the program couldn't look up (search permission on each directory above it), so no process learns names in a directory it can't search. A program's own rename moves the count without asking, when nothing else came in between.
|
|
760
|
+
- **A late rename:** a write that finds its file's name gone asks for renames once more and tries again at the new name before it fails.
|
|
761
|
+
- **Watches:** a typed watch (`fs.watch`) follows a rename in another tab of its root or a directory above it, as it does for a rename in this kernel ([#1356](https://github.com/ai-ecoverse/slicc-kernel/issues/1356)), so this tab's later changes there arrive.
|
|
762
|
+
- **Limits:** a program that falls more than 4096 renames behind, or holds a file by a name that goes through a symlink to the renamed path, keeps the old name, and its next write fails with `ENOENT` as before. A file removed while open isn't moved by a later rename of its old name.
|
|
763
|
+
|
|
757
764
|
**Directory renames across tabs** ([#1140](https://github.com/ai-ecoverse/slicc-kernel/issues/1140)): a tab sharing the root sees a directory rename in another tab all at once, as the renaming kernel's own readers do.
|
|
758
765
|
- **The notice:** before the first file moves, the renaming kernel tells the other kernels on the root, and waits until each has answered. A kernel answers once the calls it has in flight under either name, or on either parent, are done (a file rename into or out of the tree included, a symlink by the link's own path, calls that began before the other kernel joined too, and calls through a symlink into the tree, and an open write session there counts until it's closed). From then on, its calls under either name, and its listings of either parent, wait until the rename's Web Lock is released. So a listing in the other tab shows the old tree or the new one, never a mix, and every write that returned success there is in the new tree.
|
|
759
766
|
- **A missed notice:** a kernel also checks the held Web Locks while it shares the root, and reads the recorded renames when it sees a rename lock it doesn't know. Only a call starts a check: it runs in the background once the last answer is 50 ms old, and a call waits for it only when that answer is 100 ms old. An idle tab makes no checks. A rename lock whose record isn't written yet holds the call until the record appears or the lock is released. A call through a symlink whose target is in the renaming tree waits as one under the tree does. So a kernel that missed the notice, or joined the root in the middle of a rename, trusts "no rename in flight" for at most 100 ms.
|
|
@@ -764,7 +771,7 @@ The proxy answers `200` with `Content-Type: application/vnd.slicc.raw-fetch`: a
|
|
|
764
771
|
- **Alone on the root,** nothing else runs: no notice, no ack, and a call costs one counter.
|
|
765
772
|
- **Limits:**
|
|
766
773
|
- A kernel without `/proc/sys/kernel/slicc_rename_acks` (older than #1140) doesn't answer and isn't waited for ([#1309](https://github.com/ai-ecoverse/slicc-kernel/issues/1309)). It can still see a tree mid-move, and a write it has in flight when the rename starts can land in the old copy and be lost.
|
|
767
|
-
- An open descriptor
|
|
774
|
+
- An open descriptor follows a rename by another process, of its file or of a directory above it ([#1257](https://github.com/ai-ecoverse/slicc-kernel/issues/1257), see "A rename by another process" above), also when the rename was in another tab.
|
|
768
775
|
- A peer's call that is already in flight and then reaches into the renaming tree through a call of its own (a rename removing the name it replaces) waits for the rename, while the rename waits for that peer's answer. After the 10 s cap the rename fails with `EBUSY`, nothing has moved, and the peer's call goes on.
|
|
769
776
|
|
|
770
777
|
A lookup follows at most 40 symlinks, as Linux does: the 41st is `ELOOP`, for WASI programs, JS programs and the kernel API alike, with or without `..` in a link's target. Emscripten programs resolve paths in their glue, which stops one link earlier, at 40.
|
|
@@ -1048,6 +1055,7 @@ The kernel runs scheduled jobs itself, with no daemon process: a cron service st
|
|
|
1048
1055
|
- **`@reboot`** runs once per kernel boot, when that kernel first fires jobs.
|
|
1049
1056
|
- **Overlap:** a job's runs don't wait for each other, so a slow job can still be running when it is due again. At most 16 jobs of one user run at once; a run past that is skipped, with a line in the job's log and in `/var/log/cron/cron.err`.
|
|
1050
1057
|
- **Logs:** each run appends `--- <due time> exit=<status> missed=<n> ---` (or `signal=<name>`), then the job's standard output and error, to `/var/log/cron/<user>/<id>.log`. The directory is root's `0755`, so a user can't put a symlink or another file in place of a log; the file is the user's `0600` and keeps its last 256 KiB. The service writes a log only when its directory is a real directory of root and the log is a regular file of that user or root. Otherwise it leaves the path alone and says why in `cron.err`. There is no mail. Lines that don't parse and tables the service won't run go to `/var/log/cron/cron.err` (root's `0600`), once per version of the table, and so does each skipped run.
|
|
1058
|
+
- **Tags:** a job runs with the tags of the process that installed its line through `crontab` (see `kernel.connect`), kept in the root-only `/var/spool/cron/tags` and never in the table's text. A line reinstalled unchanged keeps its tags, with the new installer's added; a new or edited line gets the installer's. With `users` on, a process with any tag can't change `/etc/crontab` or anything under `/var/spool/cron` itself, also through a symlink or a hard link (`EACCES`; renaming or removing a symlink that points there is refused too), so it schedules only through `crontab`; untagged processes keep direct access. With `users: 'off'` this isn't enforced, as every process is root there ([#1380](https://github.com/ai-ecoverse/slicc-kernel/issues/1380)).
|
|
1051
1059
|
- **Trust:** with owners on (`users` not `'off'`), a user's table runs only if it is a regular file (not a symlink) owned by that user or root and writable by its owner only, and `/etc/crontab` only if it is root's and writable by root only. A table named for no user is skipped. The kernel creates `/var/spool/cron` (`0755`), `/var/spool/cron/crontabs` (`0700`) and `/var/log/cron` (`0755`), all root's.
|
|
1052
1060
|
- **Changes** to a table (here or in another tab) take effect without a restart. The service also re-reads the tables at least once a minute.
|
|
1053
1061
|
- **Several tabs:** only one kernel per root fires jobs. It holds the Web Lock `slicc-kernel-cron:<metadata database>` (the database that holds the root's owners and modes, so tabs on one root share it), and when its tab closes, another kernel takes over and catches up. In a background tab the browser throttles timers to once a minute, so a job may run up to a minute late there. A tab that was closed catches up on its next open, by the missed-run rule above.
|
|
@@ -1114,7 +1122,7 @@ A character device (`0666`), there in every `users` mode, through which programs
|
|
|
1114
1122
|
- **Mounts:** nothing can be mounted at, under or above a path, not even by the embedder (`kernel.mount`, `/etc/fstab`), and not by a writer. Above includes `/`: with any path protected, nothing can be mounted on `/`. So a path always lies on the root file system, where that check holds.
|
|
1115
1123
|
- **Clients:** a client's (`kernel.connect`) file calls, mounts and unmounts see the paths read-only, as its processes do.
|
|
1116
1124
|
- **Not affected:** the page's own writes to OPFS.
|
|
1117
|
-
- **Writers:** `protect: { paths, writers: ['bios-installer'] }` names process tags whose processes may change the paths, such as the embedder's own installs (`kernel.run(['pnpm', 'install'], { tag: 'bios-installer' })`). A tag matches exactly, with no prefix or case folding. A process gets its tag only from the embedder, through `kernel.run`, `kernel.openTerminal` or `kernel.connect({ tag })`, and passes it to its children, forks and `exec`s; a program or a client can't choose one. Writers work on their own view of the root file system, so a file a writer changes is seen by other processes as between two tabs: close-to-open, at their next open after the writer closes it.
|
|
1125
|
+
- **Writers:** `protect: { paths, writers: ['bios-installer'] }` names process tags whose processes may change the paths, such as the embedder's own installs (`kernel.run(['pnpm', 'install'], { tag: 'bios-installer' })`). A tag matches exactly, with no prefix or case folding. A process with several tags (see `kernel.connect`) is a writer only when every one of them is a writer tag, so a tag added to a writer's spawn takes away its write access, and one added to a connection without a writer tag never makes a writer. A process gets its tag only from the embedder, through `kernel.run`, `kernel.openTerminal` or `kernel.connect({ tag })`, and passes it to its children, forks and `exec`s; a program or a client can't choose one. Writers work on their own view of the root file system, so a file a writer changes is seen by other processes as between two tabs: close-to-open, at their next open after the writer closes it.
|
|
1118
1126
|
- **Integrity:** `protect: { paths, integrity: { lookup, unknown, sources } }` also checks what a program is made of before it runs. That covers a wasm module, an Emscripten program's glue, a WASI program's imports module, a JS program's module, the `package.json` of each package under a path (the kernel builds its commands, `binfmt` suffixes and drivers only from manifests that pass, so a package whose manifest changed has no commands, 127, and no drivers, `ENODEV`), a script started through its `#!` line or a `binfmt` suffix, a package driver's module and the TLS engine, when the file lies under a path.
|
|
1119
1127
|
- **The check:** the kernel hashes the exact bytes it is about to compile or hand to a worker (SHA-256, hex), and asks `lookup(path)` (the real path; on the page, it may be async) for the hash it expects. On a mismatch the program doesn't start: `kernel.run` rejects and a spawn fails with `EACCES` ("integrity check failed"; 126 in a shell, not `ENOEXEC`, which would make a shell run the file as a script), a driver fails to mount, TLS fails, and `onDenial` gets `{ op: 'exec-integrity', path, errno: 'EACCES', pid, uid, tag?, expected, actual }`. A `lookup` that throws counts as a mismatch.
|
|
1120
1128
|
- **No hash known:** when `lookup` has none (`undefined`), `unknown: 'allow'` (the default) runs the file and reports `{ op: 'exec-unverified', errno: 'ENODATA', … }` once per path, while `unknown: 'deny'` refuses it as a mismatch.
|
|
@@ -1140,7 +1148,7 @@ A character device (`0666`), there in every `users` mode, through which programs
|
|
|
1140
1148
|
|
|
1141
1149
|
### `/proc`
|
|
1142
1150
|
|
|
1143
|
-
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `exe`, a link to the file the process was started from: the glue or `.wasm` that ran, as resolved (a copy run by its own path names the copy, a symlink its target) (#1264). For WASI and WASIX programs, as for Emscripten ones, `stat`, `open` and reading `/proc/self/exe` act on the program file, while `lstat` and `readlink` show the link (#1273). A symlink to it is followed the same way, and once the program's name is moved away or holds another file, `exe` no longer opens it: `readlink` ends in ` (deleted)` and `stat` and `open` are `ENOENT` (#1317). As on Linux without ptrace access, reading another process's `exe` link is `EACCES` unless the reader is root or its uid and gid match all of the target's real, effective and saved ids; `cmdline` and `status` stay readable, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). WASI and WASIX programs also have `/proc/<pid>/task/<tid>/stat` for each of a process's threads (the main thread is tid 1), whose state is `S` while the thread is blocked in the kernel (a `sem_wait`, a blocking read, a sleep, a futex) and `R` otherwise (`T` and `Z` as for the process). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
|
|
1151
|
+
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `exe`, a link to the file the process was started from: the glue or `.wasm` that ran, as resolved (a copy run by its own path names the copy, a symlink its target) (#1264). For WASI and WASIX programs, as for Emscripten ones, `stat`, `open` and reading `/proc/self/exe` act on the program file, while `lstat` and `readlink` show the link (#1273). A symlink to it is followed the same way, and once the program's name is moved away or holds another file, `exe` no longer opens it: `readlink` ends in ` (deleted)` and `stat` and `open` are `ENOENT` (#1317). As on Linux without ptrace access, reading another process's `exe` link is `EACCES` unless the reader is root or its uid and gid match all of the target's real, effective and saved ids; `cmdline` and `status` stay readable, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, one line per tag when it has several, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). WASI and WASIX programs also have `/proc/<pid>/task/<tid>/stat` for each of a process's threads (the main thread is tid 1), whose state is `S` while the thread is blocked in the kernel (a `sem_wait`, a blocking read, a sleep, a futex) and `R` otherwise (`T` and `Z` as for the process). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
|
|
1144
1152
|
|
|
1145
1153
|
The kernel writes `/etc/passwd` and `/etc/group` when it starts (see [Users](#users)). While the root has none of them, or no `/etc/mtab`, reading them gets per-process ones: root is uid 0 and the realm user, uid 1000, is named after `USER`, or `web_user` (Emscripten's default) without one, so it matches `$USER`, with `HOME` as its home, so `id`, `whoami`, `ls -l` and `ps` show names. Nothing is written to OPFS, and a real file there wins.
|
|
1146
1154
|
|
|
@@ -1293,7 +1301,7 @@ The tools in `tools/cert-review/`:
|
|
|
1293
1301
|
- and record, which re-hashes the candidate's artifact against the pack job's sha256.
|
|
1294
1302
|
- **The verdict** goes into the commit status on the pull request's head and a comment on the pull request. Certified is green. A FAIL is red with the line, and the pull request needs a fix. **INCOMPLETE** (a job that didn't succeed, no verdict, a suite without results) is red with `re-run the failed jobs`, never a silent pass. Main moving on after a review doesn't trigger another one; a push does.
|
|
1295
1303
|
- **Merge groups only look up.** On `merge_group`, the job lists the group's pull requests (its merge commits) and requires, for each one, a green status on the head being merged, or no behaviour change. Otherwise it's red, naming the pull request. Then it adds the group's downstream verdict. The group's `ci / test` covers the combined tree.
|
|
1296
|
-
- **Downstream, once per merge group:** the `merge group pack` job packs the group's tree with `main`'s tools (`x.y.z-group.<run>`), and `downstream bios canary` calls slicc-bios's `canary.yml` at a pinned full sha (`
|
|
1304
|
+
- **Downstream, once per merge group:** the `merge group pack` job packs the group's tree with `main`'s tools (`x.y.z-group.<run>`), and `downstream bios canary` calls slicc-bios's `canary.yml` at a pinned full sha (`b59afdf6…`, never a tag) with that tarball, bound by artifact id, digest and sha256, on the `sweep`, `guard-prompt`, `guard-lagging`, `k4-upgrade`, `k4-agent-modes` and `link-serve` shards, each run once, on the runner the `canary runner` job picks: `.github/runner.mjs`'s `decide()` from `main`, as `ci.yml` uses it, gives `vars.CERT_CANARY_RUNNER` only to a merge group whose entries are all same-repo PRs, and `ubuntu-latest` otherwise, without the variable, or if the decider fails (bios honours a runner only for merge groups and pushes to main in ai-ecoverse, so rolling back is unsetting the variable). The verdict line names the shards, or their count past 32 characters (`6 shards`), so a status description (140 characters) keeps the reason. `downstream.mjs` judges its result (`downstreamOf` in `lib.mjs`), and the lookup requires it. The `merge group` job reads that result as `result.json` from the canary's own artifact, downloaded by the call's `artifact-id` with `digest-mismatch: error`, not from the `result` output: that output can be up to 512 KB, and one environment variable holds 128 KB, so passing it that way stopped the step from starting at all (`Argument list too long`, runs 39166647175 and 39168823799). A missing or unreadable artifact leaves no result, so the group is INCOMPLETE:
|
|
1297
1305
|
- **PASS:** the candidate passes, `downstream: PASS bios … N pass`;
|
|
1298
1306
|
- **FAIL:** a regression, or a failing test (by the counts, even without its rows) is **not certified**, and the group is red;
|
|
1299
1307
|
- **INCOMPLETE** blocks too: no result, a result from another bios than the pinned one, a missing shard, a shard that exited without a failing test, a refused candidate, 0 tests run, or no test passed;
|
package/dist/driver-worker.js
CHANGED
|
@@ -665,7 +665,7 @@ async function loopbackFetch(dial, input, options) {
|
|
|
665
665
|
}
|
|
666
666
|
|
|
667
667
|
// src/client/protocol.ts
|
|
668
|
-
var PROTOCOL = [1,
|
|
668
|
+
var PROTOCOL = [1, 12];
|
|
669
669
|
function locksOf(scope = globalThis) {
|
|
670
670
|
const locks = scope.navigator?.locks;
|
|
671
671
|
return typeof locks?.request === "function" ? locks : void 0;
|
|
@@ -896,6 +896,8 @@ async function spawnOn(channel, argv, opts) {
|
|
|
896
896
|
});
|
|
897
897
|
if (opts.user !== void 0) await since(channel, 8, "spawn as a user", async () => {
|
|
898
898
|
});
|
|
899
|
+
if (opts.tag !== void 0) await since(channel, 12, "a tag per spawn", async () => {
|
|
900
|
+
});
|
|
899
901
|
const started = Promise.withResolvers();
|
|
900
902
|
let endedBy;
|
|
901
903
|
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf(stdin) } : {} };
|
|
@@ -953,6 +955,8 @@ async function terminalOn(channel, argv, opts) {
|
|
|
953
955
|
const { onData, ...options } = opts;
|
|
954
956
|
if (opts.user !== void 0) await since(channel, 8, "a terminal as a user", async () => {
|
|
955
957
|
});
|
|
958
|
+
if (opts.tag !== void 0) await since(channel, 12, "a tag per terminal", async () => {
|
|
959
|
+
});
|
|
956
960
|
let listener = onData ?? null;
|
|
957
961
|
const backlog = [];
|
|
958
962
|
const started = Promise.withResolvers();
|