@ai-ecoverse/slicc-kernel 1.77.2 → 1.78.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 +12 -10
- package/dist/driver-worker.js +73 -4
- package/dist/driver-worker.js.map +3 -3
- package/dist/index.js +104 -14
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +30 -17
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-driver-worker.js +73 -4
- package/dist/node-driver-worker.js.map +3 -3
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +67 -26
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ const { status, stdout, stderr } = await kernel.run(['bash', '-c', 'echo hi > he
|
|
|
38
38
|
| `onMountPending` | none | `({ target, source, insert }) => void`: an `fsa` mount needs a folder; call `insert()` from a user gesture |
|
|
39
39
|
| `processMounts` | `true` | whether programs may call `mount(2)` and `umount2(2)`: `false`, or `(req) => boolean \| Promise<boolean>` to decide each call (see [Mounting from a program](#mounting-from-a-program)) |
|
|
40
40
|
| `hostname` | `'slicc'` | the kernel's node name (see [Node name](#node-name)) |
|
|
41
|
-
| `users` | `'
|
|
41
|
+
| `users` | `'enforce'` | `'off'`, `'report'` or `'enforce'`: whether files have owners and their modes are checked (see [File owners](#file-owners) and [Permissions](#permissions)), and whether `sudo` elevates (see [Sudo](#sudo)). `'off'` behaves exactly as kernels before owners did; any other value rejects. `createNodeKernel` takes it too, with the same default. Kernels up to 1.73 defaulted to `'off'` |
|
|
42
42
|
| `onDenial` | none | `({ pid, uid, op, path, errno, tag? }) => void`: called for every permission check that fails, under `'report'` and `'enforce'` (see [Permissions](#permissions)). `createNodeKernel` takes it too |
|
|
43
43
|
| `sealed` | none | `(req, { signal }) => Promise<object>`: answers requests written to [`/dev/sealed`](#devsealed). `createNodeKernel` takes it too |
|
|
44
44
|
| `clipboard` | none | `{ read(stamp) => Promise<string>, write(text, stamp) => Promise<void> }`: the clipboard behind [`/dev/clipboard`](#devclipboard). `createNodeKernel` takes it too |
|
|
@@ -46,9 +46,9 @@ const { status, stdout, stderr } = await kernel.run(['bash', '-c', 'echo hi > he
|
|
|
46
46
|
| `cdp` | none | `({ runtime }) => Promise<CdpConnection>`: a browser-level DevTools connection for programs that drive a browser (see [Browser automation](#browser-automation-cdp)) |
|
|
47
47
|
| `protect` | none | `{ paths, writers? }`: directories no process may change, root included, in every `users` mode, except processes with one of the `writers` tags (see [Protected paths](#protected-paths)). `createNodeKernel` takes it too |
|
|
48
48
|
|
|
49
|
-
### `kernel.run(argv, options?) → Promise<{ status, stdout, stderr }>`
|
|
49
|
+
### `kernel.run(argv, options?) → Promise<{ status, signal, stdout, stderr }>`
|
|
50
50
|
|
|
51
|
-
Runs `argv[0]` with `argv` as its arguments and resolves when it exits. `stdout` and `stderr` are decoded as UTF-8; `status` is the exit status, `128 + n` for a process killed by signal `n`, and `127` when `argv[0]` cannot be found.
|
|
51
|
+
Runs `argv[0]` with `argv` as its arguments and resolves when it exits. `stdout` and `stderr` are decoded as UTF-8; `status` is the exit status, `128 + n` for a process killed by signal `n`, and `127` when `argv[0]` cannot be found. `signal` is there only when a signal ended the process, named as `'SIGKILL'`, so a `kill -9` (`status: 137, signal: 'SIGKILL'`) can be told from an `exit 137` (`status: 137`, no `signal`).
|
|
52
52
|
|
|
53
53
|
| option | |
|
|
54
54
|
| --- | --- |
|
|
@@ -77,6 +77,7 @@ Starts `argv` as the session leader on a new terminal and resolves once it is ru
|
|
|
77
77
|
| `resize(cols, rows)` | sets the window size (`TIOCGWINSZ`) and sends `SIGWINCH` to the foreground process group |
|
|
78
78
|
| `signal(name)` | sends a signal (`'SIGINT'`, `'SIGTSTP'`, `'SIGQUIT'`, `'SIGHUP'`, …) to the foreground process group, regardless of termios |
|
|
79
79
|
| `exited` | resolves with the session leader's exit status (`128 + n` if killed by signal `n`) |
|
|
80
|
+
| `ended` | resolves with `{ status, signal }` at the same time: `status` as `exited`, and `signal` named (`'SIGKILL'`) only when a signal ended it |
|
|
80
81
|
| `close()` | hangs up: `SIGHUP` to the foreground process group, and reads from the terminal return end-of-file |
|
|
81
82
|
|
|
82
83
|
### `kernel.terminate()`
|
|
@@ -131,6 +132,7 @@ Reach a server a program runs on the kernel's loopback (a dev server, `python -m
|
|
|
131
132
|
- **`loopbackFetch`** sends an HTTP/1.1 request over `dial` and resolves with a `Response` as soon as the headers arrive. The body streams (chunked, counted, or until the connection closes), so server-sent events and long polls work; cancelling the body or aborting `signal` closes the connection. The request is routed by `port`, not by the URL, whose host is sent as `Host`; a request body is sent with `Content-Length`, and every request uses a connection of its own (`Connection: close`).
|
|
132
133
|
- **Errors** reject with an `Error` whose `code` is the errno: `ECONNREFUSED` when nothing listens on the port or the port is kernel-only (`9222`, the [CDP facade](#browser-automation-cdp)), `ENETUNREACH` for a host that is not the loopback, `EINVAL` for a port outside 1–65535.
|
|
133
134
|
- **Names**: a page reaches the kernel's loopback as `<port>.kernel.localhost`, which is the loopback and a secure context, so a request no service worker routes lands on port 80 of the real machine instead of a real server on that port; the service worker routes the name to `loopbackFetch` with that port. Programs find the name in `SLICC_PAGE_LOOPBACK` (`kernel.localhost`) and `SLICC_PAGE_LOOPBACK_URL` (`http://{port}.kernel.localhost`), to print or inject the right URL.
|
|
135
|
+
- **No per-user isolation**: as on Linux, any user's process can connect to any listener on `127.0.0.1`, and the page reaches every one as `<port>.kernel.localhost`, so the loopback is no privilege boundary; a service that needs one must authenticate its clients.
|
|
134
136
|
|
|
135
137
|
### `kernel.attest(path)` and `kernel.watch(paths, onChange)`
|
|
136
138
|
|
|
@@ -152,9 +154,9 @@ Attaches to a kernel over a port from `kernel.connect()`, in any realm: a worker
|
|
|
152
154
|
|
|
153
155
|
| Client | |
|
|
154
156
|
|---|---|
|
|
155
|
-
| `spawn(argv, { cwd, user, env, stdin, pgid, onStdout, onStderr })` | runs over pipes and resolves with `{ pid, pgid, exited, signal(name) }` once the process has started; 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'` |
|
|
156
|
-
| `run(argv, options?)` | `spawn` and wait: `{ pid, status, stdout, stderr }` as text, with `onStdout`/`onStderr` streaming text |
|
|
157
|
-
| `openTerminal(argv, options?)` | a pty session, as `kernel.openTerminal` |
|
|
157
|
+
| `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'` |
|
|
158
|
+
| `run(argv, options?)` | `spawn` and wait: `{ pid, status, signal, stdout, stderr }` as text, with `onStdout`/`onStderr` streaming text; `signal` as for `kernel.run` |
|
|
159
|
+
| `openTerminal(argv, options?)` | a pty session, as `kernel.openTerminal`, `ended` included |
|
|
158
160
|
| `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). A client bound to a user sees only that user's processes |
|
|
159
161
|
| `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'` |
|
|
160
162
|
| `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` |
|
|
@@ -276,7 +278,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
276
278
|
- `status` is the exit status, or `null` with `signal` set when a signal ended the child. A non-zero status is not an error. A command that cannot start (it does not exist, or is not executable) answers `error: { code: 'ENOENT' }` (or the errno), `status: null` and `pid: 0` instead of throwing.
|
|
277
279
|
- More than `maxBuffer` bytes on `stdout` or `stderr` (1 MiB by default) sends the child `killSignal` (`SIGTERM` by default), keeps the first `maxBuffer` bytes, and answers `error: { code: 'ENOBUFS' }`. The kernel keeps at most 64 MiB of each stream whatever `maxBuffer` says, `Infinity` included, since the buffers live in the kernel worker that all processes share. `timeout` (ms) sends `killSignal` when it runs out and answers `error: { code: 'ETIMEDOUT' }`. A child that ignores `killSignal` is still waited for, as in Node.
|
|
278
280
|
- The child is in the program's process group, so `^C` reaches it. The kernel reaps it, so it leaves no zombie, and its CPU time counts as the program's children's. A signal the program catches does not cut the run short; its handler runs when the call returns. A signal that kills the program kills it as usual;
|
|
279
|
-
- `sync.kill(pid, signal)`, a synchronous `kill`. When the signal reaches the program itself (its own pid, `0` for its process group, or `-pgid` of its own group) and its action is to end the program (no handler, not ignored), the call never returns: the program's writes reach their descriptors and its terminal modes are put back, as for `sync.exit`, then the rest of the group is signalled and the program ends by the signal, so a parent's `wait` and `sync.run` report `signal`, as Node does for `process.kill(process.pid)`. A signal with a handler runs it when the call returns, an ignored one does nothing, and a stop signal stops the program. A signal whose default action dumps core (`SIGQUIT`, `SIGABRT`, `SIGSEGV`, …) ends it the same way; the kernel writes no core and sets no core flag in the wait status. `-1` signals every process the program may signal except itself and pid 1, as on Linux, and returns (`ESRCH` when there was none). Without users, every process runs as root, so `kill -9 -1` from any program ends every other process; with users it reaches only what the sender may signal (its own uid's processes, or all of them as root);
|
|
281
|
+
- `sync.kill(pid, signal)`, a synchronous `kill`. When the signal reaches the program itself (its own pid, `0` for its process group, or `-pgid` of its own group) and its action is to end the program (no handler, not ignored), the call never returns: the program's writes reach their descriptors and its terminal modes are put back, as for `sync.exit`, then the rest of the group is signalled and the program ends by the signal, so a parent's `wait` and `sync.run` report `signal`, as Node does for `process.kill(process.pid)`. A signal with a handler runs it when the call returns, an ignored one does nothing, and a stop signal stops the program. A signal whose default action dumps core (`SIGQUIT`, `SIGABRT`, `SIGSEGV`, …) ends it the same way; the kernel writes no core and sets no core flag in the wait status. `-1` signals every process the program may signal except itself and pid 1, as on Linux, and returns (`ESRCH` when there was none, `EPERM` when there were some but it may signal none of them); a process that exec'd gets the signal once. Without users, every process runs as root, so `kill -9 -1` from any program ends every other process; with users it reaches only what the sender may signal (its own uid's processes, or all of them as root);
|
|
280
282
|
- `sync.exit(status)`, which ends the process at once, as Node's `process.exit()` does: no `catch`, `finally`, microtask, timer or signal handler runs after it, because the call never returns. First the writes the program has made reach their descriptors, in order (a broken pipe drops the rest for that descriptor), and terminal modes the program set with `tty` are put back. Then the process ends the way any exit does: its descriptors are closed and written back, its parent gets `SIGCHLD`, and `wait` reports `status`. Children keep running. A JS program has no threads of its own (`Worker` is withheld), so nothing of it outlives the call. `exit(status)` (below) unwinds instead, so the program's `catch` and `finally` blocks run;
|
|
281
283
|
- `spawn({ argv, argv0, env, cwd, pgid, session, stdin, stdout, stderr })` starts a command (found as `kernel.run` finds it) as a child process and resolves with `{ pid, stdin, stdout, stderr, wait(), kill(signal) }`. Each stdio is `'inherit'` (the default: the program's own), `'pipe'` (a web stream on a kernel pipe), `'null'`, or one of the program's descriptors. `env` is the child's whole environment (the program's by default), and `cwd` defaults to the program's. The command run is `argv[0]`; `argv0`, when given, is the `argv[0]` the child sees instead, as given (no basename; an empty string too), as with Node's `options.argv0`: `spawn({ argv: ['/bin/sh', '-c', 'echo $0'], argv0: '/bin/sh' })` prints `/bin/sh`, where without it the child sees the command's own name (`sh`). A command whose package manifest sets its `argv0` (a multi-call program's applets, such as coreutils') keeps that one. An `argv0` that isn't a string is `EINVAL`. The child is in the program's process group and session, as after `fork` (Node's `detached: false`), so the terminal's `^C` and `^Z` reach it along with the program. `pgid: 0` makes it lead a new group in the program's session, set before it runs, so a `kill(-pid)` right after the call reaches it and everything it starts (`npx`, then `node`); on the program's terminal that group is in the background, so a read from the terminal stops it with `SIGTTIN` (`EIO` when it ignores `SIGTTIN`), as on Linux. `pgid: N` joins group N, which must be in the program's session: `EPERM` for another session's group, `ESRCH` when there is none. `session: true` makes it lead a new session and a new group (`setsid`, what Node's `detached: true` does on POSIX): its pid is its `pgid` and its `sid`, it has no controlling terminal (`/dev/tty` is `ENXIO`) and the terminal's keys don't signal it. `session` together with `pgid`, or a `pgid` that isn't a non-negative integer, is `EINVAL`. A command that does not exist rejects with `ENOENT`;
|
|
282
284
|
- `wait(pid)` resolves with `{ status }` once a child ends, or `{ status: 128 + signal, signal }` when a signal ended it, without blocking the program; `kill(pid, signal)` signals any process, by number or as `'SIGTERM'` (the default);
|
|
@@ -570,7 +572,7 @@ Programs ask the kernel for their ids:
|
|
|
570
572
|
|
|
571
573
|
### File owners
|
|
572
574
|
|
|
573
|
-
With `
|
|
575
|
+
With `users: 'enforce'`, the default, or `'report'`, files have owners, and [Permissions](#permissions) checks them against a process's credentials. With `'off'` nothing below happens: the kernel stores no owner, and every file looks owned by the caller as described under [Metadata](#metadata).
|
|
574
576
|
|
|
575
577
|
- **Where owners live:** on the root file system, in the sidecar record's `uid` and `gid` (see [Metadata](#metadata)). A path with no owner of its own has the owner of its nearest ancestor that has one, and `/` belongs to root, so a root written before owners existed, or by the BIOS through the OPFS API, is root's throughout. A directory whose owner changes keeps the owner its unowned contents had, in `heir`, so `chown cone /old` gives `cone` the directory and leaves the files below it to their old owner. A rename keeps the owner of what it moves.
|
|
576
578
|
- **New files:** a file, directory or symlink a process creates belongs to its effective uid, and to its effective gid, or to the directory's group when the directory has the setgid bit; a new directory in a setgid directory gets the setgid bit too. Every file and directory a process creates gets the mode it asked for (`0666` and `0777` when it can't ask, as a JS program's `ctx.fs` can't) less the process's umask, whichever runtime made it; an Emscripten program's file no longer gets `0644` whatever it asked for. A file written through a client's `fs` belongs to the client's user (root for a page) and gets `0666`, a directory `0777`, less the umask `kernel.users.add` gave that user (`022` for root and by default); the kernel's own files, such as `/etc/passwd`, have no owner of their own.
|
|
@@ -596,7 +598,7 @@ With `users: 'enforce'` the kernel checks every file call that reaches it agains
|
|
|
596
598
|
- **A client's `fs`:** checked as the client's user; a page's client (root) passes.
|
|
597
599
|
- **Removed files still open:** a process that holds a removed file may look it up (`stat`, `lstat`) under the hidden name the kernel gave it in `/.slicc-unlinked`, which is root's `0700`; every other process is refused there, so the kernel's own bookkeeping of held files raises no denials for their holders.
|
|
598
600
|
|
|
599
|
-
`users: 'enforce'` is
|
|
601
|
+
`users: 'enforce'` is the default of `createKernel` and `createNodeKernel`; pass `'report'` to find what a program would be refused without refusing it, or `'off'` for kernels without owners. A `Launcher` made directly (see [What is in here](#what-is-in-here)) still defaults to `'off'`.
|
|
600
602
|
|
|
601
603
|
### Sudo
|
|
602
604
|
|
|
@@ -608,7 +610,7 @@ With `users: 'enforce'` the kernel checks every file call that reaches it agains
|
|
|
608
610
|
- **The command** is found once, on `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin` for a bare name or against the caller's directory for a path, and its real path (symlinks resolved) is what runs and what the policy sees. That file, and every file that will really run for it (a catalog command's glue, module and imports module, and the `package.json` that defines it, a script and the interpreter its `#!` line or `binfmt` suffix names), must be root's and writable by root only, and so must every directory above each, or `sudo` refuses (`EPERM`), whatever the policy says. A command that wouldn't start as the target user (a `#!` line naming an interpreter that is only the caller's, say) fails with `command not found` before anything is asked. Other files a program loads at run time (a package's libraries) are the admin's to keep safe, as on Linux. The caller's directory, which the program passes on, must be a directory the caller may enter, or `sudo` refuses (`EACCES`). The kernel resolves it to its real path once, shows that in the request, and runs the command there; if it no longer resolves the same, or the caller can no longer enter it, when the answer comes or when the command starts, nothing runs.
|
|
609
611
|
- **The decision:** a caller whose effective uid is 0 runs anything (the check on the command's path still applies). Otherwise a matching `NOPASSWD` line runs the command at once; any other matching line asks `approve`; nothing matching is refused (`sudo: cone is not allowed to run /usr/bin/ls as root`).
|
|
610
612
|
- **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>) Cmnd <path> <args…>` to `/etc/sudoers.d/<user>`, root's `0440`, so it's never asked again; 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'`.
|
|
611
|
-
- **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, and in its process group; `sudo` exits with its status, and passes `SIGTERM` and `SIGHUP` on to it, also one that comes while it starts (the kernel lets it signal the command it started, which its own user may not); `SIGINT` and `SIGQUIT` it leaves to the command, which gets them from the terminal itself. It's found among the system's commands only, never a user's own. Its environment is the kernel's (the embedder's `env` and the proxy settings), the target's `HOME`, `USER`, `LOGNAME`, `SHELL`, `PNPM_HOME` and `NPM_CONFIG_PREFIX`, `PATH` set to the path above, `PWD`, the caller's `TERM` when it is a plain name (letters, digits, `.`, `_`, `+`, `-`), and `SUDO_USER`, `SUDO_UID`, `SUDO_GID` and `SUDO_COMMAND`; nothing else of the caller's comes through.
|
|
613
|
+
- **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, and in its process group; `sudo` exits with its status, and passes `SIGTERM` and `SIGHUP` on to it, also one that comes while it starts (the kernel lets it signal the command it started, which its own user may not); `SIGINT` and `SIGQUIT` it leaves to the command, which gets them from the terminal itself. Two narrow gaps remain ([#424](https://github.com/ai-ecoverse/slicc-kernel/issues/424)): a relayed signal reaches the command but not the processes it started itself, which keep running as the target user if it ends; and right after the command starts, a `SIGINT` or `SIGQUIT` sent to `sudo`'s own pid (not through the terminal) ends `sudo` and leaves the command running. Closing both needs the command in a process group of its own, which changes job control. It's found among the system's commands only, never a user's own. Its environment is the kernel's (the embedder's `env` and the proxy settings), the target's `HOME`, `USER`, `LOGNAME`, `SHELL`, `PNPM_HOME` and `NPM_CONFIG_PREFIX`, `PATH` set to the path above, `PWD`, the caller's `TERM` when it is a plain name (letters, digits, `.`, `_`, `+`, `-`), and `SUDO_USER`, `SUDO_UID`, `SUDO_GID` and `SUDO_COMMAND`; nothing else of the caller's comes through.
|
|
612
614
|
- **Cost:** with `users: 'off'`, and with owners on for programs that don't call `sudo`, nothing is added to any call.
|
|
613
615
|
|
|
614
616
|
### `/dev/sealed`
|
package/dist/driver-worker.js
CHANGED
|
@@ -280,6 +280,54 @@ var RemoteTransport = class {
|
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
282
|
|
|
283
|
+
// src/kernel/signals.ts
|
|
284
|
+
var SIG = {
|
|
285
|
+
HUP: 1,
|
|
286
|
+
INT: 2,
|
|
287
|
+
QUIT: 3,
|
|
288
|
+
ILL: 4,
|
|
289
|
+
TRAP: 5,
|
|
290
|
+
ABRT: 6,
|
|
291
|
+
BUS: 7,
|
|
292
|
+
FPE: 8,
|
|
293
|
+
KILL: 9,
|
|
294
|
+
USR1: 10,
|
|
295
|
+
SEGV: 11,
|
|
296
|
+
USR2: 12,
|
|
297
|
+
PIPE: 13,
|
|
298
|
+
ALRM: 14,
|
|
299
|
+
TERM: 15,
|
|
300
|
+
STKFLT: 16,
|
|
301
|
+
CHLD: 17,
|
|
302
|
+
CONT: 18,
|
|
303
|
+
STOP: 19,
|
|
304
|
+
TSTP: 20,
|
|
305
|
+
TTIN: 21,
|
|
306
|
+
TTOU: 22,
|
|
307
|
+
URG: 23,
|
|
308
|
+
XCPU: 24,
|
|
309
|
+
XFSZ: 25,
|
|
310
|
+
VTALRM: 26,
|
|
311
|
+
PROF: 27,
|
|
312
|
+
WINCH: 28,
|
|
313
|
+
POLL: 29,
|
|
314
|
+
PWR: 30,
|
|
315
|
+
SYS: 31
|
|
316
|
+
};
|
|
317
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
318
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
319
|
+
function quiet(p) {
|
|
320
|
+
p.catch(() => void 0);
|
|
321
|
+
return p;
|
|
322
|
+
}
|
|
323
|
+
var SIGNAL_BY_NAME = {
|
|
324
|
+
SIGINT: SIG.INT,
|
|
325
|
+
SIGTERM: SIG.TERM,
|
|
326
|
+
SIGKILL: SIG.KILL,
|
|
327
|
+
SIGSTOP: SIG.STOP,
|
|
328
|
+
SIGCONT: SIG.CONT
|
|
329
|
+
};
|
|
330
|
+
|
|
283
331
|
// src/bytes.ts
|
|
284
332
|
function fresh(bytes, start = 0, end = bytes.length) {
|
|
285
333
|
return new Uint8Array(bytes.subarray(start, end));
|
|
@@ -665,6 +713,7 @@ var Channel = class {
|
|
|
665
713
|
return;
|
|
666
714
|
}
|
|
667
715
|
this.pending.delete(reply.id);
|
|
716
|
+
call.ended?.(reply.signal);
|
|
668
717
|
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
669
718
|
else call.resolve(reply.result);
|
|
670
719
|
}
|
|
@@ -713,12 +762,14 @@ async function spawnOn(channel, argv, opts) {
|
|
|
713
762
|
if (opts.user !== void 0) await since(channel, 8, "spawn as a user", async () => {
|
|
714
763
|
});
|
|
715
764
|
const started = Promise.withResolvers();
|
|
765
|
+
let endedBy;
|
|
716
766
|
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf(stdin) } : {} };
|
|
717
767
|
const { done } = channel.request(
|
|
718
768
|
{ op: "spawn", argv, options },
|
|
719
769
|
{
|
|
720
770
|
output: (fd, bytes) => (fd === 1 ? onStdout : onStderr)?.(bytes),
|
|
721
|
-
started: started.resolve
|
|
771
|
+
started: started.resolve,
|
|
772
|
+
ended: (signal) => endedBy = signal
|
|
722
773
|
}
|
|
723
774
|
);
|
|
724
775
|
done.catch(started.reject);
|
|
@@ -728,9 +779,18 @@ async function spawnOn(channel, argv, opts) {
|
|
|
728
779
|
pid,
|
|
729
780
|
pgid,
|
|
730
781
|
exited: done,
|
|
782
|
+
ended: endedOf(done, () => endedBy),
|
|
731
783
|
signal: async (name = "SIGTERM") => void await channel.call({ op: "kill", pid: -pgid, signal: name })
|
|
732
784
|
};
|
|
733
785
|
}
|
|
786
|
+
function endedOf(done, signal) {
|
|
787
|
+
return quiet(
|
|
788
|
+
done.then((status) => {
|
|
789
|
+
const by = signal();
|
|
790
|
+
return { status, ...by ? { signal: by } : {} };
|
|
791
|
+
})
|
|
792
|
+
);
|
|
793
|
+
}
|
|
734
794
|
async function runOn(channel, argv, opts) {
|
|
735
795
|
const { onStdout, onStderr, ...rest } = opts;
|
|
736
796
|
const chunks = { 1: [], 2: [] };
|
|
@@ -744,9 +804,15 @@ async function runOn(channel, argv, opts) {
|
|
|
744
804
|
onStdout: tee(1, onStdout),
|
|
745
805
|
onStderr: tee(2, onStderr)
|
|
746
806
|
});
|
|
747
|
-
const status = await child.
|
|
807
|
+
const { status, signal } = await child.ended;
|
|
748
808
|
const text = (fd) => new TextDecoder().decode(concat(chunks[fd]));
|
|
749
|
-
return {
|
|
809
|
+
return {
|
|
810
|
+
pid: child.pid,
|
|
811
|
+
status,
|
|
812
|
+
...signal ? { signal } : {},
|
|
813
|
+
stdout: text(1),
|
|
814
|
+
stderr: text(2)
|
|
815
|
+
};
|
|
750
816
|
}
|
|
751
817
|
async function terminalOn(channel, argv, opts) {
|
|
752
818
|
const { onData, ...options } = opts;
|
|
@@ -755,11 +821,13 @@ async function terminalOn(channel, argv, opts) {
|
|
|
755
821
|
let listener = onData ?? null;
|
|
756
822
|
const backlog = [];
|
|
757
823
|
const started = Promise.withResolvers();
|
|
824
|
+
let endedBy;
|
|
758
825
|
const { id, done } = channel.request(
|
|
759
826
|
{ op: "open-terminal", argv, options },
|
|
760
827
|
{
|
|
761
828
|
output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
|
|
762
|
-
started: started.resolve
|
|
829
|
+
started: started.resolve,
|
|
830
|
+
ended: (signal) => endedBy = signal
|
|
763
831
|
}
|
|
764
832
|
);
|
|
765
833
|
done.catch(started.reject);
|
|
@@ -768,6 +836,7 @@ async function terminalOn(channel, argv, opts) {
|
|
|
768
836
|
return {
|
|
769
837
|
pid,
|
|
770
838
|
exited: done,
|
|
839
|
+
ended: endedOf(done, () => endedBy),
|
|
771
840
|
get onData() {
|
|
772
841
|
return listener;
|
|
773
842
|
},
|