@ai-ecoverse/slicc-kernel 1.112.0 → 1.113.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 +3 -2
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +24 -3
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-process-worker.js +17 -1
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +24 -3
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +17 -1
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/opfs.d.ts +3 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/process/js/js-net.d.ts +5 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -370,6 +370,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
370
370
|
- `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;
|
|
371
371
|
- `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`;
|
|
372
372
|
- `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`;
|
|
373
|
+
- `net.resolve(name, { family, signal })` asks the kernel's resolver, which WASIX `resolve` and Emscripten `getaddrinfo` use too: entries in a real `/etc/hosts`, the kernel's own names (`localhost`, the node name, `host.slicc.internal`) and then the uplink's DNS ([#913](https://github.com/ai-ecoverse/slicc-kernel/issues/913)). It answers the addresses, in the resolver's order, for `family` `4`, `6` or `0` (both, IPv4 first; the default). A name with no answer rejects with a `JsCallError` of code `ENOTFOUND`, as Node's `dns.lookup` does, another family with `EAI_FAMILY`, and an aborted `signal` with its reason.
|
|
373
374
|
- `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;
|
|
374
375
|
- `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;
|
|
375
376
|
- **`fetch` and `websocket` to the kernel's own loopback:** a URL whose host is `127.0.0.0/8`, `0.0.0.0`, `[::1]`, `localhost` or `*.localhost` goes to the kernel's own loopback, as a guest's sockets do (`<port>.kernel.localhost` takes its port from the name). So it reaches whatever listens on that port in the kernel, the CDP facade at `SLICC_CDP_URL` and its `/json` included. It never goes to the transport: with nothing listening, it rejects with a `TypeError` whose `cause.code` is `ECONNREFUSED`, with or without a transport. The kernel speaks HTTP/1.1 and WebSocket (RFC 6455) to the listener itself, and `fetch` handles the redirects and compressed bodies it answers, as for any server. Its request head starts as undici's does, `host` then `connection`, then the program's headers in their order, then `content-length`. It sends `connection: close` where undici sends `keep-alive`, because each request is a connection of its own; a server sees no other difference. A WebSocket's `closed` settles after the closing handshake once the server has closed the connection (RFC 6455 §7.1.4), as undici's `close` event does, or at the latest 5 s after the Close frames. A refused upgrade rejects with a `TypeError` whose `cause` is an `Error` naming the status, `the server answered the upgrade with 503 Service Unavailable`, with `code` `EPROTO`. `https:` and `wss:` there reject with `ENOTSUP`. The listener sees a connection from `127.0.0.1` whose `SO_PEERCRED` is the program's pid, uid and gid, as if the program had connected itself. `host.slicc.internal` still goes to the transport as the real machine's loopback, for `fetch` too, under the transport's policy (`nodeTransport({ hostLoopback })`). A redirect from any other URL into the kernel's loopback rejects with `cause.code` `EACCES`, so a server elsewhere can't steer a program's request to the kernel's own servers; `redirect: 'manual'` still returns it;
|
|
@@ -432,7 +433,7 @@ The kernel has one node name, `slicc` unless `createKernel({ hostname })` (or `c
|
|
|
432
433
|
- **What `uname` says:** for an Emscripten program built with homescoop's `__syscall_uname` import (wasm-coreutils 9.12.0-7 on), the kernel answers `uname(2)`: sysname `Linux`, nodename the node name, release `6.0.0-slicc` (as `/proc/sys/kernel/osrelease`), version `#1 SMP PREEMPT_DYNAMIC` (as `/proc/version`), machine `wasm32` and domainname `(none)`, so `uname -a` and musl's `gethostname()` see them. `uname -o` is coreutils' compiled-in `Emscripten`. Older Emscripten builds still report their C library's `Emscripten 4.0.23` and `emscripten`, and WASIX programs `wasmer.sh`; the kernel has nothing to answer there.
|
|
433
434
|
- **Resolving it:** `/etc/hosts` maps the node name and both of those names to `127.0.1.1` (Debian's convention), and name resolution answers them, and `localhost.localdomain`, itself, as `127.0.1.1` and `::1`. So a program that looks up its own host name, as git does for every commit, never asks the [uplink](#uplink), and `127.0.1.1` is the kernel's loopback.
|
|
434
435
|
- **The trade-off:** a raw connection to the real `wasmer.sh` resolves to loopback too. HTTP(S) to it still goes through the realm proxy and the transport, which resolve names themselves; `wasmer.sh` is deliberately not in `no_proxy`.
|
|
435
|
-
- **A real file wins:** a root that has its own `/etc/hostname` (or `/etc/hosts`, `/etc/passwd`) keeps it, and programs read that file rather than the synthetic one. The kernel's name still drives `HOSTNAME` and name resolution. A program that opens one of these synthetic files for writing (as root, since `/etc` is root's) gets a real one first: a root-owned `0644` copy of the defaults. So `echo '10.0.0.9 db' >> /etc/hosts` keeps `localhost` and the other default entries, `>` replaces them as on Linux, and `rm` returns to the synthetic file. The copy is `0644` whatever the umask. Programs that read `/etc/hosts` themselves (WASIX/musl ones, such as python's `socket.gethostbyname`) resolve an added name. So does the kernel's own resolver, which Emscripten programs' `getaddrinfo`, WASIX `resolve` and `ctx.net` use ([#898](https://github.com/ai-ecoverse/slicc-kernel/issues/898)). It reads a real `/etc/hosts` as glibc does (`#` comments, aliases, IPv4 and IPv6, first entries first), re-reads it when its inode, size or mtime change, and answers a name the file lists before its built-in names. HTTP that goes through the transport by URL is resolved there, so `curl http://db/` doesn't use the file.
|
|
436
|
+
- **A real file wins:** a root that has its own `/etc/hostname` (or `/etc/hosts`, `/etc/passwd`) keeps it, and programs read that file rather than the synthetic one. The kernel's name still drives `HOSTNAME` and name resolution. A program that opens one of these synthetic files for writing (as root, since `/etc` is root's) gets a real one first: a root-owned `0644` copy of the defaults. So `echo '10.0.0.9 db' >> /etc/hosts` keeps `localhost` and the other default entries, `>` replaces them as on Linux, and `rm` returns to the synthetic file. The copy is `0644` whatever the umask. Programs that read `/etc/hosts` themselves (WASIX/musl ones, such as python's `socket.gethostbyname`) resolve an added name. So does the kernel's own resolver, which Emscripten programs' `getaddrinfo`, WASIX `resolve` and `ctx.net` use ([#898](https://github.com/ai-ecoverse/slicc-kernel/issues/898)). It reads a real `/etc/hosts` as glibc does (`#` comments, aliases, IPv4 and IPv6, first entries first), re-reads it when its inode, size or mtime change, and answers a name the file lists before its built-in names. HTTP that goes through the transport by URL is resolved there, so `curl http://db/` doesn't use the file. A root-written `/etc/hosts` entry for `localhost` applies to `ctx.net` and `getaddrinfo`, as with glibc. The WASI imports' `net.connect` maps the literal name `localhost` to `127.0.0.1`.
|
|
436
437
|
|
|
437
438
|
### Uplink
|
|
438
439
|
|
|
@@ -890,7 +891,7 @@ Not covered: hard links (OPFS cannot share contents between names; they work onl
|
|
|
890
891
|
|
|
891
892
|
Reads retry briefly when a concurrent write has invalidated the OPFS file snapshot (`NotReadableError`).
|
|
892
893
|
|
|
893
|
-
A kernel caches the directory handles it has walked and the sidecar entries it has read. Its own changes keep both caches current, and the other kernels on the origin hear of them through a `BroadcastChannel` named after the database (one for the sidecar, one for directories). A cached directory is checked against its path (`resolve()`) at most once a second before a lookup goes through it, so a directory moved, removed or replaced by another writer, such as a page using the OPFS API, is found at its new place or reported missing within a second. It also keeps the file handles it has looked up (up to 4,096, the least recently used dropped first) for a second after each lookup. Before it uses one, it reads the file through it, which is the read a `stat` needs anyway, so a file another writer removed is missing at once and a rewritten one shows its new contents. One replaced by a directory is seen as a directory within a second. Its own changes, and the other kernels' notices, drop the handles they touch at once.
|
|
894
|
+
A kernel caches the directory handles it has walked and the sidecar entries it has read. Its own changes keep both caches current, and the other kernels on the origin hear of them through a `BroadcastChannel` named after the database (one for the sidecar, one for directories). A cached directory is checked against its path (`resolve()`) at most once a second before a lookup goes through it, so a directory moved, removed or replaced by another writer, such as a page using the OPFS API, is found at its new place or reported missing within a second. It also keeps the file handles it has looked up (up to 4,096, the least recently used dropped first) for a second after each lookup. Before it uses one, it reads the file through it, which is the read a `stat` needs anyway, so a file another writer removed is missing at once and a rewritten one shows its new contents. One replaced by a directory is seen as a directory within a second. Its own changes, and the other kernels' notices, drop the handles they touch at once. A lookup of a directory it has cached and checked within the second is answered from the cache, so a directory replaced by another writer is seen as replaced within a second, as above. A name it has just found missing stays missing for 2 ms, which serves the several checks a single create makes of a name that isn't there yet (about ten per file in a pnpm install); its own creates, renames and links in that directory end that at once, so only a file another kernel or a page using the OPFS API makes in those 2 ms is seen up to 2 ms late.
|
|
894
895
|
|
|
895
896
|
The filesystem is our own rather than [ZenFS](https://github.com/zen-fs/core) (which SLICC uses), because OPFS stays the single source of truth: other writers, such as the BIOS installing packages or a page writing files, need no index to stay consistent with, and nothing is preloaded into memory at mount. POSIX metadata lives in the IndexedDB sidecar described above, which has per-entry transactions instead of one JSON file rewritten on every change.
|
|
896
897
|
|