@ai-ecoverse/slicc-kernel 1.93.3 → 1.94.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 +4 -4
- package/dist/kernel-worker.js +86 -34
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +7 -4
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +105 -38
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +7 -4
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,7 +207,7 @@ By default `nodeTransport()` refuses destinations on the host itself, because No
|
|
|
207
207
|
- **How it checks:** it matches on the addresses the name resolves to, so a public name that resolves to `127.0.0.1` is refused too. It checks every request and WebSocket open, `host.slicc.internal` and each redirect hop included.
|
|
208
208
|
- **What a refusal looks like:** a `403` whose body names the option, and `EACCES` for a WebSocket, before any connection is made.
|
|
209
209
|
- **The opt-in:** `nodeTransport({ hostLoopback: true })` allows the host's loopback except ports 9222–9229. `nodeTransport({ hostLoopback: [8080, 9222] })` allows exactly the listed ports, so DevTools ports are reachable only when named. Link-local stays refused either way.
|
|
210
|
-
- **Connect-time enforcement:** the kernel also checks the address each connection actually reaches, before anything is sent, so a name whose DNS answer changes after the check is still refused. A keep-alive connection opened outside this kernel's policy (by the host application, or by another kernel in the same process whose `hostLoopback` allows it) to the same origin could be reused without a new check. When a proxy is in use (`HTTP_PROXY`/`HTTPS_PROXY` with `--use-env-proxy`), the
|
|
210
|
+
- **Connect-time enforcement:** the kernel also checks the address each connection actually reaches, before anything is sent, so a name whose DNS answer changes after the check is still refused. A keep-alive connection opened outside this kernel's policy (by the host application, or by another kernel in the same process whose `hostLoopback` allows it) to the same origin could be reused without a new check. When a proxy is in use (`HTTP_PROXY`/`HTTPS_PROXY` with `--use-env-proxy` or `NODE_USE_ENV_PROXY=1`), a connection opened to the proxy's own host and port is allowed even on the host's loopback, and so is any request later tunnelled over that same live TCP connection (matched by its local and remote address and port, and forgotten when it closes), so only the pre-check applies to the target; a direct connection, a `NO_PROXY` host's included, is checked as usual, and a proxy that resolves names itself closes the rebinding window on its side.
|
|
211
211
|
|
|
212
212
|
## Commands
|
|
213
213
|
|
|
@@ -223,7 +223,7 @@ A WASI preview1 or WASIX program (Zig, Rust, Go, wasi-libc or wasix-libc C) has
|
|
|
223
223
|
{ "slicc": { "abi": "wasi", "commands": { "rg": { "wasm": "bin/rg.wasm" }, "rustc": { "script": "bin/rustc" } } } }
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
A command's name is any single path component without whitespace or control characters, so coreutils' `[` is in `/usr/bin` too; a name that is empty, `.` or `..`, or has a `/`, a space, a tab, a newline, NUL or another control character is skipped. Entries may also set `argv0`, `args` and `env`, and a package can set `slicc.env` for all of its commands. A WASI entry with `"preopenRoot": true` also gets `/` as a preopen (see [WASI programs](#wasi-programs)). An entry with `"argv0Path": true` gets the absolute path it was run by as `argv[0]` when that path is a link outside `/bin` and `/usr/bin` to the command, as a venv's `bin/python` is (the `#!` line of a script naming such a link included), so it can find what sits beside that link; other entries keep their `argv0`. In `env`, `${package}` is the package directory, `${cwd}` the process's working directory, and `${NAME}` the caller's `NAME` when the program starts (a default naming an unset variable is left out); a relative path that exists in the package (`lib/python3.14`, `./`) becomes that absolute path. The caller's environment wins over these defaults; a `null` value removes the variable for the command, after the caller's environment is applied (a host-networked program can drop the kernel's proxy settings that way), and a command entry can set a variable its package's `slicc.env` removes.
|
|
226
|
+
A command's name is any single path component without whitespace or control characters, so coreutils' `[` is in `/usr/bin` too; a name that is empty, `.` or `..`, or has a `/`, a space, a tab, a newline, NUL or another control character is skipped. Entries may also set `argv0`, `args` and `env`, and a package can set `slicc.env` for all of its commands. A WASI entry with `"preopenRoot": true` also gets `/` as a preopen (see [WASI programs](#wasi-programs)). An entry with `"argv0Path": true` gets the absolute path it was run by as `argv[0]` when that path is a link outside `/bin` and `/usr/bin` to the command, as a venv's `bin/python` is (the `#!` line of a script naming such a link included), so it can find what sits beside that link; other entries keep their `argv0`. In `env`, `${package}` is the package directory, `${cwd}` the process's working directory, and `${NAME}` the caller's `NAME` when the program starts (a default naming an unset variable is left out); a relative path that exists in the package (`lib/python3.14`, `./`) becomes that absolute path. The caller's environment wins over these defaults; a `null` value removes the variable for the command, after the caller's environment is applied (a host-networked program can drop the kernel's proxy settings that way), and a command entry can set a variable its package's `slicc.env` removes. The catalog reads its roots in order, and the first root that provides a name wins it: a user's own global packages (for that user's processes), `/node_modules`, root's global packages, then each appended root. Within a root, packages are read by name and the first wins (compared as strings by UTF-16 code unit, so a scoped `@scope/name` comes before names that start with a letter, but after those that start with a digit), whatever order the file system lists them in; a `PNPM_HOME`'s install groups count as one root, read group by group (by the group directory's name, effectively arbitrary for pnpm's hashed names), each by package name. A variant can take over from the package it extends with `"slicc": { "replaces": ["@ai-ecoverse/wasm-imagemagick"] }`: when it and a package it names are in the same root, it wins every command name they share, whatever the order; names only the other provides stay the other's, a third package that provides the name and sorts first still wins it, and packages that replace each other fall back to the name order. A replace never reaches into an earlier root, so root's global packages can't take a name from `/node_modules` or a user's own packages. The field must be an array of package names; anything else is ignored. `sh` runs bash as `sh` unless a package provides its own. The installed commands appear as executables in a virtual `/usr/bin` and `/bin`, so `PATH` lookups, `command -v` and `ls /usr/bin` work without writing anything to OPFS. A process can also run a program by the path of its glue when the `.wasm` sits next to it, a wasm module by its own path (as a WASI program), and a script through its `#!` line (including `#!/usr/bin/env name`). Executing any other file fails with `ENOEXEC`, so bash runs it as a shell script, as POSIX shells do. Commands also come from pnpm's global directory. Every process gets a `PNPM_HOME` and has `$PNPM_HOME/bin` on `PATH`: root's is `/usr/local/share/pnpm` (unless the embedder sets its own), a user's `~/.local/share/pnpm` (see [Users](#users)). The kernel scans each of pnpm's global install groups (`$PNPM_HOME/global/*/<group>/node_modules`) after `/node_modules`, which wins when both provide a command. The catalog is re-read whenever anything under either changes, so `pnpm add -g @ai-ecoverse/wasi-ripgrep` makes `rg` work in the same shell, and `pnpm remove -g` removes it again.
|
|
227
227
|
|
|
228
228
|
**More roots:** `createKernel({ commands: { append: ['/opt/user/node_modules'] } })` (and `createNodeKernel`) scans more directories, each absolute and normalised (anything else throws a `TypeError`). A name is looked up in a user's own global packages first, then in `modules`, then in root's global packages, then in the appended roots in the order given, and the first match wins, so an appended root never shadows another command (or `sudo`). Their commands are in `/usr/bin` for every user, like root's global packages, and their `binfmt` and file system entries count too. A package in an appended root also gets a command for each `bin` entry of its `package.json` (a string `bin` is named after the package, without its scope), which runs as a `#!` script; a name its `slicc.commands` gives wins. A `bin` entry is skipped when its name isn't a command name (see above), or its file, with symlinks resolved, isn't a regular file inside the package. `/node_modules` and the global packages keep reading only `slicc.commands`. A missing root counts as empty until it appears, and an install or removal in a root is picked up as it happens. With `users: 'enforce'` a root counts only when it and every directory above it are root's and not writable by group or others; another root is skipped without a word. Under `'report'` such a root is still scanned, and reported once to `onDenial` and the report log as `{ pid: 0, uid: 0, op: 'commands', path, errno: 'EACCES' }`. Running a `bin` command needs `r` and `x` on its file under `users` (see [Permissions](#permissions)), checked when it runs, so a `0644` file is `EACCES` under `'enforce'`.
|
|
229
229
|
|
|
@@ -673,7 +673,7 @@ With `users: 'enforce'` the kernel checks every file call that reaches it agains
|
|
|
673
673
|
- **Approval:** `approve(req, { signal })` gets `{ id, kind: 'sudo', user, uid, target, argv, path, cwd, pid, tag?, tty?, createdAt, expiresAt, always: true }`: the caller's name and real uid, the target user, the argv as typed, the resolved `path`, the caller's directory, pid, tag and terminal, and the times in ms. It answers `'once'` (run it), `'always'` (run it, and append `NOPASSWD <user> (<target>) Cwd <dir> Cmnd <path> <args…>` to `/etc/sudoers.d/<user>`, root's `0440`, so it's never asked again for this command, these arguments, in this directory; if that can't be written, nothing runs) or `'deny'`; a throw, any other answer or no hook at all is `sudo: approval denied`. After 5 minutes the request expires (`signal` aborts with `'expired'`, and `sudo` fails with `sudo: the request expired`); when the requester ends or gets a signal while it waits, `^C` included, `signal` aborts with `'withdrawn'` and `sudo` ends with that signal's status. A user has at most one request waiting; another fails with `sudo: another request is pending`. On the page, `createKernel` relays the request from the kernel worker and back, and the abort too; `terminate()` or a failed worker aborts every request still waiting with `'withdrawn'`.
|
|
674
674
|
- **What `'always'` covers:** the same command with the same arguments in the same directory, so a relative argument (`tee config.toml`) can't be pointed elsewhere by running it from another directory. It doesn't make a command that writes, or an interpreter (`bash -c`, `tee`, `cp`), safe to allow: what such a command touches depends on the files it is given, which the caller may control (a symlink, a file it owns). Keep `'always'` to commands that are safe in any state of the files they're given. Lines that kernels before `Cwd` recorded have no `Cwd`, so they match in any directory; to drop them, rewrite `/etc/sudoers.d/<user>` as root.
|
|
675
675
|
- **Running:** the command runs as the caller's child, with all six ids and the groups of the target user, in the caller's directory, on its terminal, standard input, output and error, in a **process group of its own** in the caller's session, as sudo(8) runs it without a pty. When `sudo` has the terminal's foreground, it hands the terminal to the command's group, so `^C`, `^\` and `^Z` reach the command, and takes it back when the command stops or ends. `sudo` exits with the command's status, and passes `SIGTERM`, `SIGHUP`, `SIGINT`, `SIGQUIT`, `SIGUSR1`, `SIGUSR2` and `SIGALRM` on to the command's whole group, so its children get them too; it installs these relays before it asks, so one that comes while the command starts is passed on once it runs (the kernel lets `sudo` signal the group of the command it started, which its own user may not). When the command stops (`^Z`), `sudo` stops by the same signal, so the shell's `jobs`, `fg` and `bg` work on it; when it is continued, it hands the terminal back to the command if it is in the foreground (`fg`) and continues the command's group. A `sudo` killed with `-9` leaves the command running in its group, as sudo(8) does, and the shell takes the terminal back as for any job;
|
|
676
|
-
- **Symlinks:** a command `sudo` started, and every process it starts in turn, follows a symlink only when root or the target user owns it: Linux's `protected_symlinks`, applied in every directory rather than only in sticky world-writable ones. Any other link is `EACCES`, and the denial names the link. That holds in every runtime: in the kernel's own path walk (JS programs' `ctx.fs` and `ctx.open`, WASI paths, `exec` and `cd` by path), in an Emscripten program's lookups (where the denial's op is `follow`), and where a WASI program resolves `..` itself. So a link the caller put where a rule's file or directory is expected (`/tmp/k4l` → `/etc/fnox`, or `config.toml` → `/etc/fnox/config.toml` in the caller's own directory) leads nowhere. `readlink`, `ls -l`, `lstat`, and removing or renaming a link still work, since they don't follow it.
|
|
676
|
+
- **Symlinks:** a command `sudo` started, and every process it starts in turn, follows a symlink only when root or the target user owns it: Linux's `protected_symlinks`, applied in every directory rather than only in sticky world-writable ones. Any other link is `EACCES`, and the denial names the link. That holds in every runtime: in the kernel's own path walk (JS programs' `ctx.fs` and `ctx.open`, WASI paths, `exec` and `cd` by path), in an Emscripten program's lookups (where the denial's op is `follow`), and where a WASI program resolves `..` itself. So a link the caller put where a rule's file or directory is expected (`/tmp/k4l` → `/etc/fnox`, or `config.toml` → `/etc/fnox/config.toml` in the caller's own directory) leads nowhere. `readlink`, `ls -l`, `lstat`, and removing or renaming a link still work, since they don't follow it.
|
|
677
677
|
- **Cost:** with `users: 'off'`, and with owners on for programs that don't call `sudo`, nothing is added to any call. The symlink check costs those processes one owner compare per link in the kernel's walk (the owner is already loaded) and one kernel call per link an Emscripten program follows; other processes pay nothing for it.
|
|
678
678
|
|
|
679
679
|
### `/dev/sealed`
|
|
@@ -748,7 +748,7 @@ A character device (`0666`), there in every `users` mode, through which programs
|
|
|
748
748
|
|
|
749
749
|
### `/proc`
|
|
750
750
|
|
|
751
|
-
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, 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 (a
|
|
751
|
+
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, 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). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
|
|
752
752
|
|
|
753
753
|
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.
|
|
754
754
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -1800,6 +1800,7 @@ var OpfsFs = class {
|
|
|
1800
1800
|
this.touch(now, parent.path, made2);
|
|
1801
1801
|
}
|
|
1802
1802
|
async mkdirExcl(path, owner) {
|
|
1803
|
+
if (normalizePath(path) === "/") throw fsError("EEXIST", path);
|
|
1803
1804
|
const parent = await this.locate(parentOf3(path), true);
|
|
1804
1805
|
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
1805
1806
|
const dir = parent.handle;
|
|
@@ -7787,10 +7788,15 @@ async function binCommandsOf(physical, pkg, manifest, taken) {
|
|
|
7787
7788
|
}
|
|
7788
7789
|
return out;
|
|
7789
7790
|
}
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
const
|
|
7793
|
-
|
|
7791
|
+
var PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
7792
|
+
function replacesOf(manifest) {
|
|
7793
|
+
const replaces = manifest.slicc?.replaces;
|
|
7794
|
+
if (!Array.isArray(replaces)) return [];
|
|
7795
|
+
return replaces.every((name2) => typeof name2 === "string" && PACKAGE_NAME.test(name2)) ? replaces : [];
|
|
7796
|
+
}
|
|
7797
|
+
async function tierPackages(fs, physical, roots, bins) {
|
|
7798
|
+
const out = [];
|
|
7799
|
+
for (const root of roots) {
|
|
7794
7800
|
for (const pkg of await packages(fs, root)) {
|
|
7795
7801
|
let manifest;
|
|
7796
7802
|
try {
|
|
@@ -7800,11 +7806,47 @@ async function scanCommands(fs, modules, binsFrom = Number.POSITIVE_INFINITY) {
|
|
|
7800
7806
|
}
|
|
7801
7807
|
const own = commandsOf(pkg, manifest);
|
|
7802
7808
|
const named = new Set(own.map((command) => command.name));
|
|
7803
|
-
const
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7809
|
+
const extra = bins ? await binCommandsOf(physical, pkg, manifest, named) : [];
|
|
7810
|
+
out.push({
|
|
7811
|
+
name: pkg.slice(root.length + 1),
|
|
7812
|
+
pkg,
|
|
7813
|
+
replaces: replacesOf(manifest),
|
|
7814
|
+
commands: [...own, ...extra]
|
|
7815
|
+
});
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
return out;
|
|
7819
|
+
}
|
|
7820
|
+
function winners(tier) {
|
|
7821
|
+
const providers = /* @__PURE__ */ new Map();
|
|
7822
|
+
for (const entry of tier) {
|
|
7823
|
+
for (const command of entry.commands) {
|
|
7824
|
+
const list = providers.get(command.name) ?? [];
|
|
7825
|
+
if (!list.includes(entry)) list.push(entry);
|
|
7826
|
+
providers.set(command.name, list);
|
|
7827
|
+
}
|
|
7828
|
+
}
|
|
7829
|
+
const out = /* @__PURE__ */ new Map();
|
|
7830
|
+
for (const [name2, list] of providers) {
|
|
7831
|
+
const kept = list.filter(
|
|
7832
|
+
(entry2) => !list.some((other) => other !== entry2 && other.replaces.includes(entry2.name))
|
|
7833
|
+
);
|
|
7834
|
+
const entry = kept[0] ?? list[0];
|
|
7835
|
+
const command = entry.commands.find((c) => c.name === name2);
|
|
7836
|
+
out.set(name2, { pkg: entry.pkg, command });
|
|
7837
|
+
}
|
|
7838
|
+
return out;
|
|
7839
|
+
}
|
|
7840
|
+
async function scanCommands(fs, modules, binsFrom = Number.POSITIVE_INFINITY) {
|
|
7841
|
+
const found = /* @__PURE__ */ new Map();
|
|
7842
|
+
const physical = physicalResolver(fs);
|
|
7843
|
+
const tiers = (typeof modules === "string" ? [modules] : modules).map(
|
|
7844
|
+
(tier) => typeof tier === "string" ? [tier] : tier
|
|
7845
|
+
);
|
|
7846
|
+
for (const [index, roots] of tiers.entries()) {
|
|
7847
|
+
const tier = await tierPackages(fs, physical, roots, index >= binsFrom);
|
|
7848
|
+
for (const [name2, { pkg, command }] of winners(tier)) {
|
|
7849
|
+
if (!found.has(name2)) found.set(name2, await withPackagePaths(fs, pkg, command));
|
|
7808
7850
|
}
|
|
7809
7851
|
}
|
|
7810
7852
|
if (!found.has("sh") && found.has("bash")) {
|
|
@@ -11178,14 +11220,6 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
11178
11220
|
}
|
|
11179
11221
|
|
|
11180
11222
|
// src/kernel/cpu-clock.ts
|
|
11181
|
-
var IDLE_OPS = /* @__PURE__ */ new Set([
|
|
11182
|
-
"fd-select",
|
|
11183
|
-
"proc-wait",
|
|
11184
|
-
"proc-exec",
|
|
11185
|
-
"sig-pause",
|
|
11186
|
-
"sock-accept",
|
|
11187
|
-
"sock-connect"
|
|
11188
|
-
]);
|
|
11189
11223
|
var SYS_CAP_US = 2e4;
|
|
11190
11224
|
var LOCAL = SAB_CLOCK_OFFSET;
|
|
11191
11225
|
var BLOCKED = 0;
|
|
@@ -11217,9 +11251,16 @@ var CpuClock = class {
|
|
|
11217
11251
|
this.now = now;
|
|
11218
11252
|
this.start = now();
|
|
11219
11253
|
}
|
|
11220
|
-
enter(
|
|
11254
|
+
enter() {
|
|
11221
11255
|
this.since = this.now();
|
|
11222
|
-
this.kind = this.stopped
|
|
11256
|
+
this.kind = this.stopped ? "idle" : "sys";
|
|
11257
|
+
}
|
|
11258
|
+
block() {
|
|
11259
|
+
if (this.since === 0 || this.kind === "idle" || this.frozen !== void 0) return;
|
|
11260
|
+
const now = this.now();
|
|
11261
|
+
this.add(now - this.since);
|
|
11262
|
+
this.since = now;
|
|
11263
|
+
this.kind = "idle";
|
|
11223
11264
|
}
|
|
11224
11265
|
leave() {
|
|
11225
11266
|
if (this.since === 0) return;
|
|
@@ -11309,9 +11350,10 @@ var ProcessClocks = class {
|
|
|
11309
11350
|
}
|
|
11310
11351
|
timed(clock, dispatch2) {
|
|
11311
11352
|
const c = clock ?? this.main;
|
|
11353
|
+
const block = () => c.block();
|
|
11312
11354
|
return (req) => {
|
|
11313
|
-
c.enter(
|
|
11314
|
-
return dispatch2(req).finally(() => c.leave());
|
|
11355
|
+
c.enter();
|
|
11356
|
+
return dispatch2(req, block).finally(() => c.leave());
|
|
11315
11357
|
};
|
|
11316
11358
|
}
|
|
11317
11359
|
all() {
|
|
@@ -11570,7 +11612,7 @@ function mountTable(mounts = []) {
|
|
|
11570
11612
|
}
|
|
11571
11613
|
|
|
11572
11614
|
// src/version.ts
|
|
11573
|
-
var KERNEL_VERSION = true ? "1.
|
|
11615
|
+
var KERNEL_VERSION = true ? "1.94.1" : "0.0.0-dev";
|
|
11574
11616
|
var OS_RELEASE = "6.0.0-slicc";
|
|
11575
11617
|
function procVersion() {
|
|
11576
11618
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -14498,7 +14540,8 @@ var WasmProcess = class {
|
|
|
14498
14540
|
this.wake?.();
|
|
14499
14541
|
for (const listener of this.stateListeners) listener("continued", SIG.CONT);
|
|
14500
14542
|
}
|
|
14501
|
-
|
|
14543
|
+
blocks = /* @__PURE__ */ new WeakMap();
|
|
14544
|
+
async syscall(req, cancelled, block) {
|
|
14502
14545
|
this.options.onSyscall?.(req);
|
|
14503
14546
|
for (; ; ) {
|
|
14504
14547
|
await this.resumed;
|
|
@@ -14508,6 +14551,7 @@ var WasmProcess = class {
|
|
|
14508
14551
|
const forward = () => scope.abort();
|
|
14509
14552
|
if (cancelled?.aborted) scope.abort();
|
|
14510
14553
|
cancelled?.addEventListener("abort", forward, { once: true });
|
|
14554
|
+
if (block) this.blocks.set(scope.signal, block);
|
|
14511
14555
|
const result = await this.dispatch(req, scope.signal).finally(() => {
|
|
14512
14556
|
cancelled?.removeEventListener("abort", forward);
|
|
14513
14557
|
this.waiting.delete(scope.signal);
|
|
@@ -14590,6 +14634,7 @@ var WasmProcess = class {
|
|
|
14590
14634
|
}
|
|
14591
14635
|
if (this.options.hasPending?.() || cancelled.aborted) throw new KernelError("EINTR");
|
|
14592
14636
|
if (owing) this.waiting.add(cancelled);
|
|
14637
|
+
if (owing) this.blocks.get(cancelled)?.();
|
|
14593
14638
|
return this.cancelSignal(cancelled);
|
|
14594
14639
|
}
|
|
14595
14640
|
cancelSignal(cancelled) {
|
|
@@ -15231,6 +15276,7 @@ var WasmProcess = class {
|
|
|
15231
15276
|
case "proc-identity":
|
|
15232
15277
|
return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
|
|
15233
15278
|
case "proc-exec": {
|
|
15279
|
+
this.blocks.get(cancelled)?.();
|
|
15234
15280
|
this.adoptExec(req.pid);
|
|
15235
15281
|
this.options.jobs?.exec(this.pid, req.pid);
|
|
15236
15282
|
await this.fds.closeAll();
|
|
@@ -15561,6 +15607,9 @@ function killThread(thread, caught, sig) {
|
|
|
15561
15607
|
function unblocked(header3) {
|
|
15562
15608
|
return Atomics.load(header3, SAB_I_SIGNALS) & ~Atomics.load(header3, SAB_I_BLOCKED);
|
|
15563
15609
|
}
|
|
15610
|
+
function threadCall(process, req, cancelled, block) {
|
|
15611
|
+
return isWasmSyscall(req) ? process.syscall(req, cancelled, block) : dispatchSyncFs(req);
|
|
15612
|
+
}
|
|
15564
15613
|
function threadSignalsOf(sab) {
|
|
15565
15614
|
return { header: new Int32Array(sab, 0, SAB_HEADER_I32), interrupt: new AbortController() };
|
|
15566
15615
|
}
|
|
@@ -15764,7 +15813,9 @@ function spawnWasmProcess(opts) {
|
|
|
15764
15813
|
process.keptHandle = (handle2) => holds.keptFor(handle2)?.hidden;
|
|
15765
15814
|
const worker = opts.createWorker();
|
|
15766
15815
|
const dying = new Dying();
|
|
15767
|
-
const dispatch2 = (req) => dying.serving(
|
|
15816
|
+
const dispatch2 = (req, block) => dying.serving(
|
|
15817
|
+
() => isWasmSyscall(req) ? process.syscall(req, void 0, block) : dispatchSyncFs(req)
|
|
15818
|
+
);
|
|
15768
15819
|
const clocks = new ProcessClocks(sab, opts.program.abi !== "js", process);
|
|
15769
15820
|
const responder = attachSyncSabResponder(worker, sab, token, {
|
|
15770
15821
|
dispatch: clocks.timed(void 0, dispatch2)
|
|
@@ -15834,9 +15885,7 @@ function spawnWasmProcess(opts) {
|
|
|
15834
15885
|
const tsignals = threadSignalsOf(tsab);
|
|
15835
15886
|
if (thread.blocked) storeBlocked(tsignals.header, thread.blocked);
|
|
15836
15887
|
threadSignals.set(thread.tid, tsignals);
|
|
15837
|
-
const tdispatch = (req) => dying.serving(
|
|
15838
|
-
() => isWasmSyscall(req) ? process.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
|
|
15839
|
-
);
|
|
15888
|
+
const tdispatch = (req, block) => dying.serving(() => threadCall(process, req, threadCancel(tsignals), block));
|
|
15840
15889
|
const tresponder = attachSyncSabResponder(tw, tsab, token, {
|
|
15841
15890
|
dispatch: clocks.timed(clocks.thread(thread.tid, tsab), tdispatch)
|
|
15842
15891
|
});
|
|
@@ -19458,8 +19507,8 @@ var Launcher = class {
|
|
|
19458
19507
|
let found = this.catalogs.get(scope);
|
|
19459
19508
|
if (!found) {
|
|
19460
19509
|
found = this.trustedAppended().then(async (appended) => {
|
|
19461
|
-
const
|
|
19462
|
-
return scanCommands(this.scanned,
|
|
19510
|
+
const tiers = await this.tiers(scope, appended);
|
|
19511
|
+
return scanCommands(this.scanned, tiers, tiers.length - appended.length);
|
|
19463
19512
|
}).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
|
|
19464
19513
|
this.catalogs.set(scope, found);
|
|
19465
19514
|
}
|
|
@@ -19487,13 +19536,16 @@ var Launcher = class {
|
|
|
19487
19536
|
}
|
|
19488
19537
|
return found;
|
|
19489
19538
|
}
|
|
19490
|
-
async
|
|
19539
|
+
async tiers(scope = "", appended) {
|
|
19491
19540
|
const system = [
|
|
19492
|
-
this.modulesDir,
|
|
19493
|
-
|
|
19494
|
-
...appended ?? await this.trustedAppended()
|
|
19541
|
+
[this.modulesDir],
|
|
19542
|
+
await pnpmGlobalRoots(this.base, this.pnpmHome),
|
|
19543
|
+
...(appended ?? await this.trustedAppended()).map((root) => [root])
|
|
19495
19544
|
];
|
|
19496
|
-
return scope ? [
|
|
19545
|
+
return scope ? [await pnpmGlobalRoots(this.base, scope), ...system] : system;
|
|
19546
|
+
}
|
|
19547
|
+
async roots(scope = "") {
|
|
19548
|
+
return (await this.tiers(scope)).flat();
|
|
19497
19549
|
}
|
|
19498
19550
|
async rootOnlyPath(root) {
|
|
19499
19551
|
for (const at of chain(root)) {
|