@ai-ecoverse/slicc-kernel 1.125.2 → 1.125.4
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 +5 -1
- package/dist/kernel-worker.js +235 -110
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +167 -22
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +238 -113
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +167 -22
- package/dist/process-worker.js.map +4 -4
- package/dist/types/kernel/net/tls-chain.d.ts +7 -1
- package/dist/types/kernel/vfs-file.d.ts +5 -0
- package/dist/types/mount/mount-fs.d.ts +1 -0
- package/dist/types/process/kernel-streams.d.ts +3 -0
- package/dist/types/process/wasi/wasi-host.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -426,6 +426,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
426
426
|
- With `rejectUnauthorized: false`, `chainError` says what is wrong with the chain apart from the name, with Node's names, and is absent when the chain is fine:
|
|
427
427
|
- a self-signed leaf alone is `DEPTH_ZERO_SELF_SIGNED_CERT`;
|
|
428
428
|
- an untrusted chain ending in a self-signed root is `SELF_SIGNED_CERT_IN_CHAIN`;
|
|
429
|
+
- self-signed means the certificate's own key verifies its signature (RSA PKCS#1 v1.5, ECDSA P-256/384/521 or Ed25519), not just that its issuer and subject names match, so a self-issued certificate signed by another key, such as a CA rollover certificate, is not self-signed, as in Node ([#1059](https://github.com/ai-ecoverse/slicc-kernel/issues/1059));
|
|
429
430
|
- a lone leaf whose issuer is unknown is `UNABLE_TO_VERIFY_LEAF_SIGNATURE`;
|
|
430
431
|
- an untrusted longer chain is `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`;
|
|
431
432
|
- otherwise it's the reason's code, such as `CERT_HAS_EXPIRED`.
|
|
@@ -667,7 +668,7 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
667
668
|
- `@ai-ecoverse/slicc-kernel/driver` exports the types and `fsError`.
|
|
668
669
|
- **Capabilities:** `readonly`, `symlinks`, `links` (hard links: the driver answers `link(from, to)`, reports `nlink` in its attributes, and gives every attribute a stable `ino`, the same for all names of a file; a mount whose driver offers `links` but answers an attribute without `ino` loses them: `link` is `EPERM` there and `st_nlink` is 1), `chmod`, `owners`, `ranges`, `maxIo` (the largest read or write per message), `maxFile`, `listingStats`, `sessions`, `linkTimes`, and `attrTtl`/`entryTtl` (in ms, default 1000). `sessions` promises that a handle opened for writing keeps its writes to itself until it is released, so the kernel keeps one open per file being written instead of one per write (the `fsa` driver has it). `linkTimes` promises that `setattr` with `mtime` on a symlink sets the link's own time. Without it, setting a link's own times (`touch -h`, `utimensat` with `AT_SYMLINK_NOFOLLOW`) fails with `EOPNOTSUPP` and never reaches the target. tmpfs has it; hostfs does not. Setting times through a link follows it in the kernel, so `setattr` gets the target. `ranges` (driver protocol 1.1) promises that a write lands in the file at once, with no copy of the whole file on `open` or `release`, and that `setattr` takes `size`. `owners` (driver protocol 1.2) promises that the driver stores owners: its attributes carry `uid` and `gid`, `open` with `create`, `mkdir` and `symlink` may carry the new path's `uid`, `gid` and `mode` (the `owner` of the open flags, the second or third handler argument), a new path without them takes its directory's owner, and `setattr` takes `uid` and `gid`. tmpfs has it. `fifos` (driver protocol 1.3) promises that the driver answers `mkfifo(path, owner)` with a FIFO node: its attributes say `kind: 'fifo'`, `readdir` lists it so, and an `open` of it is `ENXIO`, since the kernel, not the driver, carries a FIFO's data ([#907](https://github.com/ai-ecoverse/slicc-kernel/issues/907)). tmpfs has it, so `mkfifo` works on `/dev/shm` and on a tmpfs `/tmp`; on hostfs, `fsa` and media mounts it's still `EPERM`.
|
|
669
670
|
- **Caching:** the kernel caches attributes and listings for those TTLs, and a driver can push `invalidate` for changes made outside.
|
|
670
|
-
- **File contents:** on a mount with `ranges`, as `tmpfs` is, and on the root file system when it is in OPFS (in a browser with sync access handles) or an in-memory root, a program reads and writes a file in 64 KiB pages: reads come from the driver 1 MiB at a time, and changed pages go back in 1 MiB runs once 8 MiB have changed, at `fsync`, and when the file is closed (from the kernel's own descriptors also 250 ms after a write). A file of any size the mount can hold works, with memory to spare for about one more copy of it. In OPFS a write opens a sync access handle for just that write. An `fsa` mount (a picked folder) has no sync access handles, and Chrome copies the whole file into a swap file for every writable stream, so it writes through one stream per open file: the first write back opens it, later runs go into it at their offsets, and it is committed, one copy of the file, when the last process writing the file closes it (or exits), before another process reads what was written, and before a rename, unlink or unmount. Until then other processes see the new size but read the old bytes outside what was written. Elsewhere (`hostfs` with an older proxy, and a root outside OPFS), file contents are read whole when a program opens a file and written back when it closes it. Before a program's next call into the kernel (any wait, read of a pipe or terminal, sleep or spawn), its changed pages also go back, so other processes and the embedder see a write as soon as the writer does anything but compute: a daemon's pidfile, a log another process follows. On a whole-file mount that holds only for files of up to 64 KiB; a larger one goes back at close, fsync and exit, as before. Beyond that, both give close-to-open consistency, as NFS has. A process that ends without closing its files keeps what it wrote: on exit, on a trap, and when a signal kills it. For a killed WASI program, the kernel lets its worker write its changed pages back before `waitpid` reports the death. The only exception is a worker that makes no call into the kernel for 2 seconds after the signal; it is stopped without that write-back.
|
|
671
|
+
- **File contents:** on a mount with `ranges`, as `tmpfs` is, and on the root file system when it is in OPFS (in a browser with sync access handles) or an in-memory root, a program reads and writes a file in 64 KiB pages: reads come from the driver 1 MiB at a time, and changed pages go back in 1 MiB runs once 8 MiB have changed, at `fsync`, and when the file is closed (from the kernel's own descriptors also 250 ms after a write). A file of any size the mount can hold works, with memory to spare for about one more copy of it. In OPFS a write opens a sync access handle for just that write. An `fsa` mount (a picked folder) has no sync access handles, and Chrome copies the whole file into a swap file for every writable stream, so it writes through one stream per open file: the first write back opens it, later runs go into it at their offsets, and it is committed, one copy of the file, when the last process writing the file closes it (or exits), before another process reads what was written, and before a rename, unlink or unmount. Until then other processes see the new size but read the old bytes outside what was written. Elsewhere (`hostfs` with an older proxy, and a root outside OPFS), file contents are read whole when a program opens a file and written back when it closes it. Before a program's next call into the kernel (any wait, read of a pipe or terminal, sleep or spawn), its changed pages also go back, so other processes and the embedder see a write as soon as the writer does anything but compute: a daemon's pidfile, a log another process follows. On a whole-file mount that holds only for files of up to 64 KiB; a larger one goes back at close, fsync and exit, as before. Beyond that, both give close-to-open consistency, as NFS has. A write-back that can't reach the folder (a `hostfs` port or proxy gone, a medium ejected) keeps the pages and isn't dropped quietly ([#1062](https://github.com/ai-ecoverse/slicc-kernel/issues/1062)): the next `fsync` or close of the file fails with the error (`ENOMEDIUM`, `ENODEV` for WASI), and so does every later write or truncate on it, each of which first retries the write-back, until one succeeds. When a mount's folder goes away, every file the kernel buffers for it is checked at its next write (one with unwritten pages retries them; one already written back asks the folder once, and fails while it's unreachable), so `for …; do echo …; done > /mnt/h/f` stops reporting success as soon as the folder is gone. A program's own whole-file buffer (`hostfs` without `ranges`, one process, no fork) still reports only at close or `fsync`, and a shell ignores a redirection's close error, as on Linux. A process that ends without closing its files keeps what it wrote: on exit, on a trap, and when a signal kills it. For a killed WASI program, the kernel lets its worker write its changed pages back before `waitpid` reports the death. The only exception is a worker that makes no call into the kernel for 2 seconds after the signal; it is stopped without that write-back.
|
|
671
672
|
- **Exclusive create across tabs** ([#912](https://github.com/ai-ecoverse/slicc-kernel/issues/912)): two kernels on one OPFS root (two tabs, or a tab and a worker page) agree on `O_CREAT|O_EXCL` and `set -C`: exactly one wins a name. While another kernel on the root is present, the create runs under a Web Lock for its path and checks the OPFS entry itself. A kernel alone on its root takes no lock. Each kernel holds an alive Web Lock, granted before it first looks for peers. A kernel that finds peers waits for their answer (at most 500 ms) before its first exclusive create, and a kernel answers a newcomer only once its unlocked creates are done. So of two kernels that boot at the same moment, the later one to look sees the other, and no unlocked create overlaps a locked one.
|
|
672
673
|
- **A name another process or tab just removed** ([#950](https://github.com/ai-ecoverse/slicc-kernel/issues/950)): sqlite3 deletes its `-journal` at every commit and the next writer creates it again, so two writers (two processes in one kernel, or two tabs on one OPFS root) keep removing and re-creating one name. Two things used to turn that into `unable to open database file`. The root removes a file's sidecar record a few milliseconds after the file itself (removals are batched), and an exclusive create read that pending record as the name being taken: a plain record whose file is gone now counts as gone, and the new file gets a record of its own. A symlink, which lives only in the sidecar, still takes its name. And an Emscripten program keeps the nodes it has looked up, so after another tab removed a name it still held the old node, and `open(O_CREAT)` opened the gone file instead of creating one. Now, when the kernel answers `ENOENT` for a cached node, for `open`, `stat`, `lstat`, `readlink`, `truncate`, `unlink` or a rename's source, or the program's own lookup answers `EEXIST` to `open(O_CREAT|O_EXCL)`, the node is dropped and the call runs once more, against what is there now. A node the program has open or has unwritten changes in is kept. A rename over a name another tab removed needs nothing: the kernel's rename replaces whatever is there.
|
|
673
674
|
- **One file, several processes** ([#665](https://github.com/ai-ecoverse/slicc-kernel/issues/665)): a process that keeps a file open sees what another process writes to it. Every change the kernel applies to a file (a write back, a truncate, an append, a rename or a removal) bumps a change ring the kernel shares with all its processes. Before each read, write or seek to the end, a process's open file checks the ring (one atomic load) and, if its file changed, drops its clean pages and reads the size again, keeping its own unwritten changes. A write by one process is visible to another one's next read once it has gone back to the kernel: at the latest at the writer's next `fsync`, close, exit or call into the kernel (see above), so sqlite, which `fsync`s every commit, sees another connection's commit at its next transaction. A write through a file the kernel holds (a JS program's `ctx.open`, a WASI program's file after a fork or spawn) is in the kernel already, so other processes see it at their next read, before it is written back. A file the kernel itself holds open (a JS program's file, a WASI program's file after a fork or spawn) is the one copy: reads, writes, truncates and appends by path from other processes go to it, and their writes go through to storage at once, so a commit is in storage (and in `kernel.readFile`) when the committing process returns from `fsync`. `O_APPEND` writes go to the kernel at once, and the kernel picks the offset, so appenders in several processes keep every line. A file system without ranged I/O (`hostfs` with an older proxy, a root outside OPFS) rereads a file another process changed, but writes it back whole, so two writers there still need locks. The same file under two names is one file too ([#669](https://github.com/ai-ecoverse/slicc-kernel/issues/669)): a write by path through a symlink reaches the file the kernel holds under its real path (resolved without following links in a `sudo` process's no-links view, so an untrusted link is still refused), a WASI program's open file also watches its real path (resolved once, the first time another file changes), and an open file with more than one hard link checks its size, version and mtime when anything changed, so a write through another name is seen. Limits for now: an alias on a mount that has no `realpath` is still only the name it was opened by; another tab's writes reach this tab's processes only as far as the root's change notifications (OPFS's `BroadcastChannel` watch) carry them. A by-path `stat` or `lstat` of a file the kernel holds open has its current size, writes not yet written back included ([#780](https://github.com/ai-ecoverse/slicc-kernel/issues/780)). Its mtime is the file system's until write-back, though, so a tool that compares times, like `make`, may see a just-written file as older than it is for up to the write-back delay.
|
|
@@ -744,6 +745,7 @@ A `hostfs` mount is a folder exported by the local proxy ([slicc-node](https://g
|
|
|
744
745
|
- **Grants:** `kernel.mount({ type: 'hostfs', source: 'project', target: '/mnt/project' })` asks the page's `hostfs(source, { readonly })` hook for a grant. The page holds the proxy key and asks the proxy (`POST /api/hostfs/grant`) for a token scoped to that one folder. The kernel sees only `{ url, token }`, never the proxy key, and the token never appears in mount options or `/proc/mounts`. When the proxy refuses a token, the kernel asks the hook once for a new one.
|
|
745
746
|
- **Options:** `ro` mounts read-only, and `maxfile` caps file size as on any mount. There is no other size limit: reads and writes go in `maxIo` pieces (16 MiB unless the proxy says otherwise), so a file of any size the host can hold works. A proxy that advertises `ranges` in its grant (slicc-node 2.4, slicc-swift 1.4) makes the mount ranged, so programs read and write its files in pages and truncate through `setattr` `size`. With an older proxy files go whole, and a truncate on a ranged mount whose proxy was replaced by an older one is done by rewriting the file.
|
|
746
747
|
- **Changes on the host** arrive as invalidations on a watch stream (`POST /api/hostfs/watch`), so an edit made outside shows up on the next access without a remount.
|
|
748
|
+
- **Writes when the link drops:** after a mount's link drops, a write that returned success may not be on the host unless the program fsyncs or checks close, as on Linux with NFS: the shell ignores a redirection's close error ([#1062](https://github.com/ai-ecoverse/slicc-kernel/issues/1062)).
|
|
747
749
|
- **Proxy loss:** when the watch stream ends, or is silent for 45 s, the kernel reconnects once at once. If that fails, the mount is `nomedium`, as with [removable media](#removable-media-fsa): operations fail with `ENOMEDIUM` (`ENODEV` for WASI), and the kernel keeps reconnecting with backoff (1 s to 30 s), so the folder comes back by itself when the proxy does. A mount made while the proxy is down starts as `nomedium`, and so does one whose grant or first watch takes more than 15 s; it comes up by itself when they arrive.
|
|
748
750
|
- **Consistency:** a file that changes on the host while a program reads it fails with `ESTALE`, and the kernel restarts the whole read (up to 3 times) instead of stitching two versions together. On a ranged mount an open file keeps the version it was opened at (its ETag) until it writes, also when it is handed to a child process: a page fetched after the host changed the file fails the program's read with `ESTALE` ("Stale file handle") instead of mixing versions. A file deleted on the host while a program holds it open fails the next read that goes to the host with `ENOENT`, not with NFS's `ESTALE`.
|
|
749
751
|
- **Over a port:** the hook may answer `{ port, token?, capabilities? }` instead, for a folder that doesn't come from slicc-node over HTTP: for example, a linked `slicc` CLI's export, which the page reaches over its own link. The page implements a `HostfsTransport` for the folder (`capabilities`, `token`, `maxIo`, `call(op, body)`, `read(fh, offset, size, ifMatch?)`, `write(fh, offset, bytes)`, `watch(signal, onEvent, onOpen)`, with slicc-node's ops and answers) and serves it with `serveHostfsPort(port, transport)`, exported from both entries, which returns a function that stops serving and closes the port.
|
|
@@ -802,7 +804,9 @@ WASI and WASIX have no call for file modes, so the kernel adds the import module
|
|
|
802
804
|
The kernel's device nodes answer the mode and owner an Emscripten program sees for them ([#1028](https://github.com/ai-ecoverse/slicc-kernel/issues/1028)):
|
|
803
805
|
- `/dev/null`, `/dev/zero`, `/dev/full`, `/dev/random`, `/dev/urandom` and `/dev/tty` are root's character devices with `0666`. `/dev/tty` also stats when the process has no controlling terminal; opening it then is `ENXIO`, as on Linux.
|
|
804
806
|
- A pty's `/dev/pts/N` is its owner's, with `0620`.
|
|
807
|
+
- `/dev/pts` is a directory listing the open ptys, for Emscripten and WASIX programs as for JS ones, and `/dev` lists it ([#1040](https://github.com/ai-ecoverse/slicc-kernel/issues/1040)); an Emscripten program also stats a pty through a symlink to it.
|
|
805
808
|
- `/dev/stdin`, `/dev/stdout`, `/dev/stderr` and `/dev/fd/N` answer for the descriptor they name.
|
|
809
|
+
- A symlink elsewhere that leads to one of them, such as `/tmp/console -> /dev/tty`, answers the same when followed ([#1042](https://github.com/ai-ecoverse/slicc-kernel/issues/1042)).
|
|
806
810
|
|
|
807
811
|
An Emscripten program sees those six character devices at `0666` with `users: 'off'` too.
|
|
808
812
|
|