@ai-ecoverse/slicc-kernel 1.98.4 → 1.99.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 +44 -1
- package/dist/kernel-worker.js +275 -27
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +89 -14
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +275 -27
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +89 -14
- package/dist/process-worker.js.map +4 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -278,6 +278,36 @@ On top of preview1 a WASI program gets:
|
|
|
278
278
|
- **Toolchain problems:**
|
|
279
279
|
- **wasix-sysroot 2025.9.30-28's `epoll_ctl`:** it passes Linux's operation numbers (ADD 1, DEL 2, MOD 3) to WASIX untranslated, where WASIX numbers them ADD 0, MOD 1, DEL 2. The kernel takes both: 0 adds, 2 deletes, 3 modifies, and 1 adds a descriptor that isn't registered and modifies one that is. So a C program's second ADD of a descriptor modifies it rather than failing with `EEXIST`, and a WASIX MOD of an unregistered one adds it. That libc also returns the error number instead of -1, so its callers can't see a failure. Fixing the sysroot is [homescoop#443](https://github.com/ai-ecoverse/homescoop/issues/443); dropping the dual numbering once the fixed sysroot ships is [#684](https://github.com/ai-ecoverse/slicc-kernel/issues/684).
|
|
280
280
|
- **The WASIX mio fork (1.0.3+wasix.1):** it opens its sockets blocking, unlike upstream mio's `SOCK_NONBLOCK`. On a socket whose send buffer is full, a tokio program blocks its runtime's thread, as it would on Wasmer.
|
|
281
|
+
- **Named semaphores** (#717): `sem_open` and its family are kernel objects, so processes share them by name. Python's `multiprocessing` `Lock`, `Queue` and `Pool` use them; wasix-libc's `sem_*` calls these imports. Every import returns a WASI errno, and handles are u32 ≥ 1:
|
|
282
|
+
- **The imports:**
|
|
283
|
+
- `slicc.sem_open(name_ptr, name_len, oflag, mode, value, handle_out)`;
|
|
284
|
+
- `slicc.sem_close(handle)`;
|
|
285
|
+
- `slicc.sem_unlink(name_ptr, name_len)`;
|
|
286
|
+
- `slicc.sem_post(handle)`;
|
|
287
|
+
- `slicc.sem_wait(handle, how, clock, deadline_ns: i64)`;
|
|
288
|
+
- `slicc.sem_getvalue(handle, value_out)`.
|
|
289
|
+
- **Names:** checked as musl's are. Leading `/`s are skipped, so `/a` and `a` are one semaphore. Another `/`, an empty name, `.` or `..` is `EINVAL`, and more than 251 bytes is `ENAMETOOLONG`.
|
|
290
|
+
- **`sem_open` arguments:**
|
|
291
|
+
- `oflag` is the kernel's own bits, `1` for `O_CREAT` and `2` for `O_EXCL`, not libc's `O_*` values;
|
|
292
|
+
- `mode` has the process's umask applied;
|
|
293
|
+
- `value` must be 0 to `SEM_VALUE_MAX` (2^31-1), else `EINVAL`.
|
|
294
|
+
- **`sem_open` results:**
|
|
295
|
+
- an existing name with `O_CREAT|O_EXCL` is `EEXIST`, and a missing one without `O_CREAT` is `ENOENT`;
|
|
296
|
+
- with users on, opening needs read and write permission by the mode and owner, and only the owner or root may `sem_unlink`. A denial goes to `onDenial` as `sem_open` or `sem_unlink` of `/dev/shm/sem.<name>`: `enforce` refuses it with `EACCES`, `report` logs it and goes ahead;
|
|
297
|
+
- opening a semaphore the process already has open returns the same handle (one `sem_close` per open).
|
|
298
|
+
- **Waiting:**
|
|
299
|
+
- `how` 0 blocks; 1 tries (`EAGAIN`); 2 waits until `deadline_ns` on `clock` (0 `CLOCK_REALTIME`, as `sem_timedwait`; 1 `CLOCK_MONOTONIC`, as `sem_clockwait`).
|
|
300
|
+
- A timed wait gives `ETIMEDOUT`, never early, also when the deadline has already passed. A negative deadline is `EINVAL`.
|
|
301
|
+
- A wait blocks only its own thread, and a signal handler ends it with `EINTR`.
|
|
302
|
+
- **Posting:** `sem_post` gives the unit to the longest-waiting thread, or adds it to the value; at the maximum it's `EOVERFLOW`.
|
|
303
|
+
- **Lifetime:**
|
|
304
|
+
- A forked child inherits the open handles; spawn and exec start with none, and exit closes them.
|
|
305
|
+
- `sem_unlink` removes the name, but the semaphore lives on while handles to it remain.
|
|
306
|
+
- Nothing is undone when a process exits.
|
|
307
|
+
- **Limits:**
|
|
308
|
+
- Semaphores exist only within one kernel (not across tabs), and they don't appear in `/dev/shm`.
|
|
309
|
+
- A kernel holds at most 4,096 named ones (`ENOSPC`), and a process at most 256 open handles (`EMFILE`; a refused `O_CREAT` leaves no name behind).
|
|
310
|
+
- `MAP_SHARED` mappings aren't shared between processes, so unnamed semaphores (`sem_init` with `pshared`) work only between a process's threads, and `multiprocessing.shared_memory` doesn't work.
|
|
281
311
|
- **Foreign calls** (libffi's WASIX backend, so Python's `ctypes`): `call_dynamic` calls a function through the program's indirect function table (a static module's exported `__indirect_function_table`, or the dynamic linker's), its arguments and results packed in memory one after another (`i32`/`f32` 4 bytes, `i64`/`f64` 8). With `strict` set, the buffers must fit the function exactly (else `EINVAL`, before the function is called); without it, missing arguments are zero (one cut short keeps the bytes given, with zeros above them) and results that do not fit are left out. A trap or exception of the callee goes through to the caller. JS cannot ask a function for its type, so the kernel reads the types from the module files (the program's and each side module's, once, when a program first calls through `call_dynamic`): a table slot that is empty, past the end, holds a function of no module the kernel knows of, or takes or returns a `v128`, gives `EINVAL`. For closures (callbacks such as `CFUNCTYPE`), a dynamically linked program gets table slots from `closure_allocate`, and `closure_free` gives them back for reuse and empties the slot in every thread; a static program gets `ENOTSUP`. `closure_prepare` puts a trampoline with the requested signature (any of `i32`, `i64`, `f32`, `f64`, `v128`) into such a slot, in every thread: each call stores its arguments below the stack pointer and calls the backing function as `backing(values, results, user_data)`, then returns what it wrote to `results`. The trampolines are small wasm modules made and compiled at run time, once per signature in each thread (an instance per closure), so a page whose Content Security Policy forbids compiling WebAssembly (no `'wasm-unsafe-eval'`) cannot run them; it cannot run the program either. A `fork` or a `longjmp` (both through Asyncify) inside a function reached through `call_dynamic` cannot unwind through it.
|
|
282
312
|
- **Job control:** `^Z`, `SIGTSTP`, `SIGTTIN` and `SIGTTOU` stop a WASIX program at once, unless it has a handler for them. A stopped program that is busy computing is stopped for job control (`jobs` shows it, `fg`, `bg` and `SIGCONT` work), but its worker keeps a core busy until its next system call, since a worker's running code cannot be frozen from outside. Agents should `kill` such a job rather than leave it stopped. An Emscripten program stops at its next kernel call as well, and at its next write, read or sleep that its glue answers without the kernel (a write to its own `/dev/null`, as in `yes > /dev/null`), so a stopped one keeps no core busy unless it computes without any of these calls. A stopped program doesn't move on while stopped: a `sleep` (or a blocked read) that would end meanwhile ends after `SIGCONT`. A signal for such a program is also delivered at those glue-answered calls, so a handler runs in a loop that only writes to `/dev/null`. For the same reason, a signal with a handler or a terminating default (`^C`, `SIGTERM`) reaches a program that is busy computing only at its next system call, so a CPU-bound loop may not end on `^C`; `kill -9` always ends it. A pending signal is delivered at the program's next WASI or WASIX call, including the calls its worker answers without the kernel (`random_get`, `args_get`, …), except the exempt ones: `clock_time_get` (a tight loop of it measured up to 30% slower), `proc_exit`, `proc_exit2`, `thread_exit`, `thread_spawn_v2` and `wasi.thread-spawn` (wasi-threads), the calls that install handlers (`callback_signal`, `slicc.sigaction_set`), and calls made while Asyncify unwinds or rewinds the stack for `fork` or `longjmp`. A signal that arrives while a thread computes is thus delivered at its next call; when that call is a wait (`poll_oneoff` with a timeout or a descriptor, so `nanosleep` and `poll`/`select` too, `sigmask_poll`, or a futex wait), the handler has run during the call, so the wait ends at once: `EINTR` for a libc of -22 or later (an older one sees its clocks fire, as for any interruption), and a futex wait returns woken. A thread blocked in the kernel or waiting on a lock (`pthread_cond_wait`, a futex) is woken by a signal for it (a futex wait checks for signals every 50 ms, so each waiting thread wakes briefly that often), and `pthread_kill` runs the handler on the thread it names. A sleep, or a clock in `poll_oneoff`, never ends before the time asked (it can end later, never earlier). A signal that ends a sleep or a `poll`/`select` with a timeout makes the call fail with `EINTR` in a program whose libc says it is wasix-sysroot 2025.9.30-22 or later (its `slicc.libc` custom section). Older libcs turn that answer into `ENOTSUP` from `nanosleep` (up to -18), so for a program without the section the wait ends as if it had timed out, unless it imports `fd_fdflags_set` (then `EINTR`, as before).
|
|
283
313
|
- **Blocked signals** (`sigprocmask`, `pthread_sigmask`, `sigpending`): a program whose libc imports `slicc.sigmask_set(how, set_lo, set_hi, old_ptr)` and `slicc.sigpending_get(ptr)` (wasix-sysroot 2025.9.30-28 and later) has a blocked mask per thread. `how` is musl's `SIG_BLOCK` 0, `SIG_UNBLOCK` 1 or `SIG_SETMASK` 2, the set is the 64-bit `sigset_t` (bit `sig - 1`), and the old mask is written as a u64 at `old_ptr`; any other `how` is `EINVAL`. `SIGKILL` and `SIGSTOP` cannot be blocked. A blocked signal is not delivered, does not interrupt a sleep or a blocked call, and its default action (terminate or stop) waits: it stays pending, `sigpending` shows it (the thread's pending signals and the process's), and it is delivered when it is unblocked, before `sigmask_set` returns. A signal ignored with `SIG_IGN` is discarded even while blocked. A forked child (with the mask of the thread that forked), the program an `exec` starts and a spawned child keep the mask, all 64 bits of it, and so does a new thread; `/proc/<pid>/status` shows the main thread's as `SigBlk`. The mask is kept in a word of the worker's shared header, so changing it costs no kernel call. A program without the import (an older libc) has no mask and gets every signal at once, as before, and one it starts by `exec` with a mask set starts with none. A signal for the whole process goes to the main thread, even if the main thread blocks it and another thread does not. A signal sent to one thread (`pthread_kill`) that it blocks waits for that thread, and if it has no handler, the thread's unblock ends the whole process by the signal, whatever the other threads block. `slicc.sigmask_poll(set_lo, set_hi, in_ptr, out_ptr, nsubscriptions, nevents_ptr)` is `pselect`, `ppoll` and `sigsuspend`: it swaps the thread's mask to the given set for the call and puts the old one back before it returns. It delivers a pending signal the new mask unblocks, without polling, or polls as `poll_oneoff` does, under the new mask, so a signal it unblocks interrupts the wait with no window between the swap and the wait; with no subscriptions it waits until a signal is delivered. An interrupted call returns `EINTR` with no events, after the handler has run.
|
|
@@ -316,7 +346,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
316
346
|
- `fdStatus(fd)` (`{ type, tty, seekable }`, `type` being `file`, `pipe`, `tty`, `socket`, `device` or `directory`) and `isatty(fd)`, to tell `< file` from a pipe;
|
|
317
347
|
- `open(path, { read, write, append, create, exclusive, truncate })`, which returns a file with `read(position, length)`, `write(position, bytes)`, `size()`, `truncate(size)`, `sync()` and `close()`. Reads and writes go through the kernel's paged files at those positions, so a large file is never read whole;
|
|
318
348
|
- `fs`: `stat`, `lstat`, `exists`, `readdir`, `mkdir` (with parents, or, with `{ recursive: false }`, as Node's `fs.mkdir` without `recursive`: `EEXIST` for an existing entry and `ENOENT` for a missing parent, atomically among the processes of one kernel, so it can serve as a lock there, but not between two kernels on one origin, such as two tabs ([#519](https://github.com/ai-ecoverse/slicc-kernel/issues/519)); `sync.fs.mkdir` takes the same option), `rm` (recursive), `unlink`, `rename`, `symlink`, `readlink`, `statfs`, `chmod(path, mode)`, and `readFile`/`writeFile` for small files. `chmod` takes the permission bits (`0` to `0o7777`, `EINVAL` otherwise) and follows a symlink; as `chmod(2)`, only the file's owner or root may (`EPERM`), so a program can make a script it wrote runnable by path. `sync.fs.chmod` is the same, synchronously. `statfs(path)` answers `{ type, bsize, blocks, bfree, bavail, files, ffree, quota, usage }`: the fields of Node's `StatFs`, plus `quota` and `usage` in bytes (what jsh 0.17 reads). The values come from the same source as an Emscripten program's `statfs`/`df`: the driver of the mount the path is on, or, on the root file system, the origin's storage estimate (`navigator.storage.estimate()`). `bsize` is 4096, `bfree` and `bavail` are what's left of the quota, and `type`, `files` and `ffree` are 0, since the kernel doesn't know them. Where there's no estimate (the Node entry's root), every count is 0. A missing path is `ENOENT`. `sync.fs` has it too. `rm` removes a whole tree in one kernel call (on the OPFS root, one recursive removal and one metadata delete: 50,000 files take about 3 s in Chrome, where `rm -r` takes about 25 s) with the result of a file-by-file removal: symlinks inside the tree are removed, never followed; a tree holding a protected path is refused with `EROFS` and nothing is removed; a mount inside the tree is emptied and its mount point answers `EBUSY`, while the rest is removed; under `users`, what the user may remove is removed and the call then fails with the first `EACCES`/`EPERM`; files a process holds open stay readable to it;
|
|
319
|
-
- `/dev`, in `open`, `fs` and their `sync` forms, as a WASI or Emscripten program sees it, with the same modes and owners as under `users`: `null`, `zero`, `full`, `random`, `urandom` and `tty` (character devices, `0666`, root's), `stdin`, `stdout` and `stderr` (links to `/proc/self/fd/0`, `1`, `2`), `fd` (a link to `/proc/self/fd`, so `/dev/fd/N` is descriptor N) and the kernel's terminals `/dev/ttyN` and `/dev/pts/N` (with users on, owned by the session's user or the master's opener, `0620`). `/dev/shm` and `/dev/ptmx`
|
|
349
|
+
- `/dev`, in `open`, `fs` and their `sync` forms, as a WASI or Emscripten program sees it, with the same modes and owners as under `users`: `null`, `zero`, `full`, `random`, `urandom` and `tty` (character devices, `0666`, root's), `stdin`, `stdout` and `stderr` (links to `/proc/self/fd/0`, `1`, `2`), `fd` (a link to `/proc/self/fd`, so `/dev/fd/N` is descriptor N) and the kernel's terminals `/dev/ttyN` and `/dev/pts/N` (with users on, owned by the session's user or the master's opener, `0620`). `/dev/shm` is the kernel's shared tmpfs (see [Mounts](#mounts)), read and written like any other mount; `/dev/ptmx` isn't there. `stat` through `/dev/stdin`, `/dev/stdout` or `/dev/fd/N` describes the program's own descriptor (the file it is redirected to, a pipe, a terminal), `readlink` of `/dev/fd/N` names it (`pipe:[N]` and `socket:[N]` for a pipe or a socket), and a closed descriptor is `ENOENT`. Opening a device gives a kernel descriptor on it; opening `/dev/std*` or `/dev/fd/N` opens the file again with the flags asked for when the descriptor is on a file (as Linux reopens `/proc/self/fd/N`, so `append` appends and `truncate` truncates), and otherwise duplicates the descriptor; `/dev/tty` is the program's controlling terminal (`ENXIO` without one); and `/dev/ttyN` and `/dev/pts/N` are opened by name through the kernel, so a terminal that isn't the user's is `EACCES` under `enforce`. A file on anything but a regular file reads and writes as a stream (the position is ignored) and only in the directions it was opened for (`EBADF` otherwise), its `size()` is 0 and `truncate` is `EINVAL`. `readFile` reads a device or a descriptor to its end, up to 64 MiB, and fails with `EFBIG` past that (where Node's `readFile` throws `ERR_FS_FILE_TOO_LARGE`), so `readFile('/dev/zero')` fails instead of hanging, while a bounded `read` of it works. Other paths pay only a prefix check: they make the same kernel calls as before. Creating, removing or renaming names in `/dev` goes to the root file system as before;
|
|
320
350
|
- `/proc`, in `fs`, `open` and their `sync` forms, the same as a WASI program's (see [`/proc`](#proc)): `stat`, `lstat`, `exists`, `readdir`, `readlink` and `readFile` answer it, and `/proc/self` is the program's own pid. It's read-only, so every change in it is `EROFS`: `writeFile`, `mkdir`, `rm`, `unlink`, `chmod`, a `rename`, `link` or `symlink` into or out of it, and an `open` for writing, creating or truncating. A file opened in it is a snapshot read at the open, with no descriptor (`fd` is `-1`): its `read` and `size` answer from the snapshot until it's closed, and `write` and `truncate` are `EBADF`. Other paths pay only a prefix check;
|
|
321
351
|
- `signals`: `on(signal, handler)` catches a signal (by number or as `'SIGINT'`), `ignore(signal)` ignores it and `reset(signal)` restores its default action. A call blocked in a read or write is restarted after a handler runs; signals without a handler keep their default actions, so an uncaught `SIGINT` ends the program with 130 and `SIGKILL` always does;
|
|
322
352
|
- `sync`: `read`, `write`, `close`, `open` (whose files' methods are synchronous too) and `fs`, made synchronously, for code that cannot `await`, such as a compiled program's file system or a `readFileSync`. A synchronous call holds the worker until the kernel answers, so nothing else of the program runs meanwhile, timers and callbacks included; a signal that arrives during a blocked read or write runs its handler, and the call is then restarted. Descriptors are shared with the rest of the context, so a file opened with `sync.open` can be closed with `close`, and the other way around; `sync.run` and `sync.exit` are described next;
|
|
@@ -524,6 +554,8 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
524
554
|
|
|
525
555
|
`kernel.mount({ type, source, target, options })` mounts a file system on an existing directory. Every process sees it, Emscripten and WASI alike, and so does every attached client. `kernel.umount(target)` unmounts it, and fails with `EBUSY` while a process has a file open under it. `kernel.mounts()` lists the table, as does `/proc/mounts`. The Node entry and attached clients have the same three calls (client protocol 1.2).
|
|
526
556
|
|
|
557
|
+
`/dev/shm` is a tmpfs the kernel mounts at boot (source `shm`), mode `1777` and owned by root, so with `users` the sticky bit keeps one user from removing or renaming another's file there (`EPERM`). It's in `/proc/mounts`, but not in `kernel.mounts()`, which lists the mounts a caller made. Every process sees the same files, Emscripten (whose glue otherwise keeps a private `/dev/shm` in memory), WASI and JS programs alike, and they last until the kernel terminates, which is what musl's `shm_open` and `sem_open` need.
|
|
558
|
+
|
|
527
559
|
- **Built in:** `tmpfs`, which lives in memory until unmounted, in 1 MiB chunks; `fsa`, a folder the user picks (see [Removable media](#removable-media-fsa)); and `hostfs`, a folder the local proxy exports (see [Host folders](#host-folders-hostfs)).
|
|
528
560
|
- **Package drivers:** a package declares a type in `"slicc": { "filesystems": { "<type>": { "module": "<file>" } } }`, and the kernel starts that module in a worker of its own for each mount. The module is a single self-contained file: it can't import bare specifiers.
|
|
529
561
|
- Its default export receives `{ fetch }`, the kernel's network transport. It returns `{ handlers, capabilities }`.
|
|
@@ -933,6 +965,17 @@ Each side prints `pattern` (a static array's damage), `low` (bytes of low memory
|
|
|
933
965
|
|
|
934
966
|
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 and binaryen's `wasm-opt` (version 132) on `PATH`. `bin/piefork.wasm` is 823,983 bytes, sha256 `0477598c71b02356145d1725548ee0f2b9e53ca0d43c7349f17acc2ff12b59f8`.
|
|
935
967
|
|
|
968
|
+
### `semtest`
|
|
969
|
+
|
|
970
|
+
A WASIX C program for `test/integration/semaphore.test.mjs` (#717). It calls the `slicc.sem_*` imports directly (`import_module("slicc")`), so the rows don't wait for the libc side. `semtest MODE` runs one of:
|
|
971
|
+
- `handoff`, `inherit`, `spawn` (a spawned and an exec'd image open the semaphore by name; exec drops the handles);
|
|
972
|
+
- `try`, `timed`, `eintr`;
|
|
973
|
+
- `threads` (FIFO, one thread blocked at a time);
|
|
974
|
+
- `errors`, `unlink`, `owners setup|user`;
|
|
975
|
+
- `killed` (a waiter killed before posts loses no unit), `exited`, `cap`, `handles`.
|
|
976
|
+
|
|
977
|
+
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 (legacy exceptions, `-pthread`) and binaryen's `wasm-opt` (version 132, `--asyncify` for `proc_fork`) on `PATH`. `bin/semtest.wasm` is 59,187 bytes, sha256 `c2091c18d799881721bb8e0e2fc7269732d52d6cd545f4f57266e8e8864c9ee6`.
|
|
978
|
+
|
|
936
979
|
## License
|
|
937
980
|
|
|
938
981
|
Apache-2.0
|
package/dist/kernel-worker.js
CHANGED
|
@@ -3052,6 +3052,7 @@ function kernelFdKind(file) {
|
|
|
3052
3052
|
}
|
|
3053
3053
|
var FdTable = class _FdTable {
|
|
3054
3054
|
static MAX_FDS = 1024;
|
|
3055
|
+
handles;
|
|
3055
3056
|
fds = /* @__PURE__ */ new Map();
|
|
3056
3057
|
cloexec = /* @__PURE__ */ new Set();
|
|
3057
3058
|
status = /* @__PURE__ */ new Map();
|
|
@@ -3148,6 +3149,7 @@ var FdTable = class _FdTable {
|
|
|
3148
3149
|
for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
|
|
3149
3150
|
for (const fd of this.cloexec) child.cloexec.add(fd);
|
|
3150
3151
|
for (const [fd, flags2] of this.status) child.status.set(fd, flags2);
|
|
3152
|
+
child.handles = this.handles?.fork();
|
|
3151
3153
|
return child;
|
|
3152
3154
|
}
|
|
3153
3155
|
async closeAll() {
|
|
@@ -3155,6 +3157,7 @@ var FdTable = class _FdTable {
|
|
|
3155
3157
|
this.fds.clear();
|
|
3156
3158
|
this.cloexec.clear();
|
|
3157
3159
|
this.status.clear();
|
|
3160
|
+
this.handles?.clear();
|
|
3158
3161
|
await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
|
|
3159
3162
|
}
|
|
3160
3163
|
};
|
|
@@ -5060,7 +5063,7 @@ function serveClient(port, host) {
|
|
|
5060
5063
|
case "umount":
|
|
5061
5064
|
return { result: await l.umountAs(clientCred(self), req.target).then(() => true) };
|
|
5062
5065
|
case "mounts":
|
|
5063
|
-
return { result: l.mounts.
|
|
5066
|
+
return { result: l.mounts.listed() };
|
|
5064
5067
|
case "dial":
|
|
5065
5068
|
return dialFor(l, req, watches);
|
|
5066
5069
|
case "serve-cdp":
|
|
@@ -8446,6 +8449,18 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
8446
8449
|
};
|
|
8447
8450
|
}
|
|
8448
8451
|
|
|
8452
|
+
// src/fs/dev-removal.ts
|
|
8453
|
+
var SHM = "/dev/shm";
|
|
8454
|
+
function inDevTree(path) {
|
|
8455
|
+
if (path.startsWith(`${SHM}/`)) return false;
|
|
8456
|
+
return path === "/dev" || path.startsWith("/dev/");
|
|
8457
|
+
}
|
|
8458
|
+
function devRemoval(path, cred) {
|
|
8459
|
+
if (!inDevTree(path)) return void 0;
|
|
8460
|
+
if (cred && cred.euid !== 0) return "EACCES";
|
|
8461
|
+
return path === "/dev" ? "EBUSY" : "EPERM";
|
|
8462
|
+
}
|
|
8463
|
+
|
|
8449
8464
|
// src/fs/fresh-modules.ts
|
|
8450
8465
|
var MAGIC = [0, 97, 115, 109];
|
|
8451
8466
|
var MAX_FRESH = 65536;
|
|
@@ -9623,6 +9638,7 @@ var MountTable = class {
|
|
|
9623
9638
|
devices = 0;
|
|
9624
9639
|
pending = /* @__PURE__ */ new Set();
|
|
9625
9640
|
notes = /* @__PURE__ */ new Map();
|
|
9641
|
+
system = /* @__PURE__ */ new Set();
|
|
9626
9642
|
deps;
|
|
9627
9643
|
constructor(deps) {
|
|
9628
9644
|
this.deps = deps;
|
|
@@ -9644,6 +9660,13 @@ var MountTable = class {
|
|
|
9644
9660
|
const noted = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
|
|
9645
9661
|
return [...mounted, ...noted.map((note) => ({ ...note, options: { ...note.options } }))];
|
|
9646
9662
|
}
|
|
9663
|
+
listed() {
|
|
9664
|
+
return this.list().filter((entry) => !this.system.has(entry.target));
|
|
9665
|
+
}
|
|
9666
|
+
async mountSystem(spec, fs) {
|
|
9667
|
+
await this.mount(spec, fs);
|
|
9668
|
+
this.system.add(normalizePath(spec.target));
|
|
9669
|
+
}
|
|
9647
9670
|
note(spec, state, error) {
|
|
9648
9671
|
const target = normalizePath(spec.target);
|
|
9649
9672
|
this.notes.set(target, {
|
|
@@ -9750,6 +9773,7 @@ var MountTable = class {
|
|
|
9750
9773
|
if (!mount && this.notes.delete(at)) return;
|
|
9751
9774
|
if (!mount) throw errnoError("EINVAL", `${at} is not mounted`);
|
|
9752
9775
|
if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
|
|
9776
|
+
this.system.delete(at);
|
|
9753
9777
|
this.notes.delete(at);
|
|
9754
9778
|
this.mounts.delete(at);
|
|
9755
9779
|
this.deps.reshaped?.();
|
|
@@ -10658,16 +10682,6 @@ var MountFs = class _MountFs {
|
|
|
10658
10682
|
}
|
|
10659
10683
|
};
|
|
10660
10684
|
|
|
10661
|
-
// src/fs/dev-removal.ts
|
|
10662
|
-
function inDevTree(path) {
|
|
10663
|
-
return path === "/dev" || path.startsWith("/dev/");
|
|
10664
|
-
}
|
|
10665
|
-
function devRemoval(path, cred) {
|
|
10666
|
-
if (!inDevTree(path)) return void 0;
|
|
10667
|
-
if (cred && cred.euid !== 0) return "EACCES";
|
|
10668
|
-
return path === "/dev" ? "EBUSY" : "EPERM";
|
|
10669
|
-
}
|
|
10670
|
-
|
|
10671
10685
|
// src/realm/sync-fs-token-registry.ts
|
|
10672
10686
|
var registry = /* @__PURE__ */ new Map();
|
|
10673
10687
|
function mintSyncFsToken(entry) {
|
|
@@ -12082,7 +12096,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
|
|
|
12082
12096
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
12083
12097
|
|
|
12084
12098
|
// src/version.ts
|
|
12085
|
-
var KERNEL_VERSION = true ? "1.
|
|
12099
|
+
var KERNEL_VERSION = true ? "1.99.1" : "0.0.0-dev";
|
|
12086
12100
|
var OS_RELEASE = "6.0.0-slicc";
|
|
12087
12101
|
function procVersion() {
|
|
12088
12102
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -14516,6 +14530,206 @@ async function waitReady(fds, read, write, deadline, interrupted3) {
|
|
|
14516
14530
|
}
|
|
14517
14531
|
}
|
|
14518
14532
|
|
|
14533
|
+
// src/kernel/sem.ts
|
|
14534
|
+
var SEM_VALUE_MAX = 2147483647;
|
|
14535
|
+
var SEM_NAMES_MAX = 4096;
|
|
14536
|
+
var SEM_HANDLES_MAX = 256;
|
|
14537
|
+
var NAME_MAX = 251;
|
|
14538
|
+
var TIMER_MAX_MS = 2147483647;
|
|
14539
|
+
var SEM_OPS = [
|
|
14540
|
+
"sem-open",
|
|
14541
|
+
"sem-close",
|
|
14542
|
+
"sem-unlink",
|
|
14543
|
+
"sem-post",
|
|
14544
|
+
"sem-wait",
|
|
14545
|
+
"sem-getvalue"
|
|
14546
|
+
];
|
|
14547
|
+
var Semaphore = class {
|
|
14548
|
+
value;
|
|
14549
|
+
uid;
|
|
14550
|
+
gid;
|
|
14551
|
+
mode;
|
|
14552
|
+
waiters = [];
|
|
14553
|
+
constructor(value, uid, gid, mode) {
|
|
14554
|
+
this.value = value;
|
|
14555
|
+
this.uid = uid;
|
|
14556
|
+
this.gid = gid;
|
|
14557
|
+
this.mode = mode;
|
|
14558
|
+
}
|
|
14559
|
+
post() {
|
|
14560
|
+
const next2 = this.waiters.shift();
|
|
14561
|
+
if (next2) next2.grant();
|
|
14562
|
+
else if (this.value >= SEM_VALUE_MAX) throw new KernelError("EOVERFLOW");
|
|
14563
|
+
else this.value++;
|
|
14564
|
+
}
|
|
14565
|
+
take() {
|
|
14566
|
+
if (this.value === 0) return false;
|
|
14567
|
+
this.value--;
|
|
14568
|
+
return true;
|
|
14569
|
+
}
|
|
14570
|
+
wait(signal, timeoutMs) {
|
|
14571
|
+
if (this.take()) return Promise.resolve(true);
|
|
14572
|
+
if (signal.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
14573
|
+
return new Promise((resolve2, reject) => {
|
|
14574
|
+
const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : performance.now() + timeoutMs;
|
|
14575
|
+
let timer;
|
|
14576
|
+
const leave = () => {
|
|
14577
|
+
const at = this.waiters.indexOf(waiter);
|
|
14578
|
+
if (at >= 0) this.waiters.splice(at, 1);
|
|
14579
|
+
clearTimeout(timer);
|
|
14580
|
+
signal.removeEventListener("abort", abort);
|
|
14581
|
+
};
|
|
14582
|
+
const waiter = {
|
|
14583
|
+
grant: () => {
|
|
14584
|
+
leave();
|
|
14585
|
+
resolve2(true);
|
|
14586
|
+
}
|
|
14587
|
+
};
|
|
14588
|
+
const abort = () => {
|
|
14589
|
+
leave();
|
|
14590
|
+
reject(new KernelError("EINTR"));
|
|
14591
|
+
};
|
|
14592
|
+
const expire = () => {
|
|
14593
|
+
const left = deadline - performance.now();
|
|
14594
|
+
if (left > 0) {
|
|
14595
|
+
timer = setTimeout(expire, Math.min(left, TIMER_MAX_MS));
|
|
14596
|
+
return;
|
|
14597
|
+
}
|
|
14598
|
+
leave();
|
|
14599
|
+
resolve2(false);
|
|
14600
|
+
};
|
|
14601
|
+
this.waiters.push(waiter);
|
|
14602
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
14603
|
+
if (deadline !== Number.POSITIVE_INFINITY) {
|
|
14604
|
+
timer = setTimeout(expire, Math.min(timeoutMs, TIMER_MAX_MS));
|
|
14605
|
+
}
|
|
14606
|
+
});
|
|
14607
|
+
}
|
|
14608
|
+
};
|
|
14609
|
+
var SemTable = class {
|
|
14610
|
+
names = /* @__PURE__ */ new Map();
|
|
14611
|
+
limit;
|
|
14612
|
+
constructor(limit = SEM_NAMES_MAX) {
|
|
14613
|
+
this.limit = limit;
|
|
14614
|
+
}
|
|
14615
|
+
open(name2, create, exclusive, make, check, adopt) {
|
|
14616
|
+
const found = this.names.get(name2);
|
|
14617
|
+
if (found) {
|
|
14618
|
+
if (create && exclusive) throw new KernelError("EEXIST");
|
|
14619
|
+
check(found);
|
|
14620
|
+
return adopt(found);
|
|
14621
|
+
}
|
|
14622
|
+
if (!create) throw new KernelError("ENOENT");
|
|
14623
|
+
if (this.names.size >= this.limit) throw new KernelError("ENOSPC");
|
|
14624
|
+
const sem = make();
|
|
14625
|
+
const handle = adopt(sem);
|
|
14626
|
+
this.names.set(name2, sem);
|
|
14627
|
+
return handle;
|
|
14628
|
+
}
|
|
14629
|
+
unlink(name2, check) {
|
|
14630
|
+
const found = this.names.get(name2);
|
|
14631
|
+
if (!found) throw new KernelError("ENOENT");
|
|
14632
|
+
check(found);
|
|
14633
|
+
this.names.delete(name2);
|
|
14634
|
+
}
|
|
14635
|
+
};
|
|
14636
|
+
var SemHandles = class _SemHandles {
|
|
14637
|
+
byHandle = /* @__PURE__ */ new Map();
|
|
14638
|
+
next = 1;
|
|
14639
|
+
add(sem) {
|
|
14640
|
+
for (const [handle2, entry] of this.byHandle) {
|
|
14641
|
+
if (entry.sem === sem) {
|
|
14642
|
+
entry.opens++;
|
|
14643
|
+
return handle2;
|
|
14644
|
+
}
|
|
14645
|
+
}
|
|
14646
|
+
if (this.byHandle.size >= SEM_HANDLES_MAX) throw new KernelError("EMFILE");
|
|
14647
|
+
const handle = this.next++;
|
|
14648
|
+
this.byHandle.set(handle, { sem, opens: 1 });
|
|
14649
|
+
return handle;
|
|
14650
|
+
}
|
|
14651
|
+
get(handle) {
|
|
14652
|
+
const entry = this.byHandle.get(handle);
|
|
14653
|
+
if (!entry) throw new KernelError("EINVAL");
|
|
14654
|
+
return entry.sem;
|
|
14655
|
+
}
|
|
14656
|
+
close(handle) {
|
|
14657
|
+
const entry = this.byHandle.get(handle);
|
|
14658
|
+
if (!entry) throw new KernelError("EINVAL");
|
|
14659
|
+
if (--entry.opens === 0) this.byHandle.delete(handle);
|
|
14660
|
+
}
|
|
14661
|
+
fork() {
|
|
14662
|
+
const child = new _SemHandles();
|
|
14663
|
+
child.next = this.next;
|
|
14664
|
+
for (const [handle, entry] of this.byHandle) child.byHandle.set(handle, { ...entry });
|
|
14665
|
+
return child;
|
|
14666
|
+
}
|
|
14667
|
+
clear() {
|
|
14668
|
+
this.byHandle.clear();
|
|
14669
|
+
}
|
|
14670
|
+
};
|
|
14671
|
+
function semName(raw) {
|
|
14672
|
+
const name2 = raw.replace(/^\/+/, "");
|
|
14673
|
+
if (name2 === "" || name2.includes("/") || name2 === "." || name2 === "..") {
|
|
14674
|
+
throw new KernelError("EINVAL");
|
|
14675
|
+
}
|
|
14676
|
+
if (new TextEncoder().encode(name2).length > NAME_MAX) throw new KernelError("ENAMETOOLONG");
|
|
14677
|
+
return name2;
|
|
14678
|
+
}
|
|
14679
|
+
var ok2 = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
14680
|
+
async function semSyscall(req, proc) {
|
|
14681
|
+
const { table, handles, cred } = proc;
|
|
14682
|
+
if (!table) throw new KernelError("ENOSYS");
|
|
14683
|
+
const { denied: denied2 } = proc;
|
|
14684
|
+
const shown = (name2) => `/dev/shm/sem.${name2}`;
|
|
14685
|
+
const opens = (name2) => (sem) => {
|
|
14686
|
+
const perm = { uid: sem.uid, gid: sem.gid, mode: sem.mode };
|
|
14687
|
+
if (denied2 && !permits(cred, perm, R_OK | W_OK, false)) denied2("sem_open", shown(name2));
|
|
14688
|
+
};
|
|
14689
|
+
const unlinks = (name2) => (sem) => {
|
|
14690
|
+
if (denied2 && cred.euid !== 0 && cred.euid !== sem.uid) denied2("sem_unlink", shown(name2));
|
|
14691
|
+
};
|
|
14692
|
+
switch (req.op) {
|
|
14693
|
+
case "sem-open": {
|
|
14694
|
+
if (req.value < 0 || req.value > SEM_VALUE_MAX) throw new KernelError("EINVAL");
|
|
14695
|
+
const name2 = semName(req.name);
|
|
14696
|
+
const mode = req.mode & 511 & ~proc.umask;
|
|
14697
|
+
return ok2(
|
|
14698
|
+
table.open(
|
|
14699
|
+
name2,
|
|
14700
|
+
req.create,
|
|
14701
|
+
req.exclusive,
|
|
14702
|
+
() => new Semaphore(req.value, cred.euid, cred.egid, mode),
|
|
14703
|
+
opens(name2),
|
|
14704
|
+
(sem) => handles.add(sem)
|
|
14705
|
+
)
|
|
14706
|
+
);
|
|
14707
|
+
}
|
|
14708
|
+
case "sem-close":
|
|
14709
|
+
handles.close(req.handle);
|
|
14710
|
+
return ok2();
|
|
14711
|
+
case "sem-unlink": {
|
|
14712
|
+
const name2 = semName(req.name);
|
|
14713
|
+
table.unlink(name2, unlinks(name2));
|
|
14714
|
+
return ok2();
|
|
14715
|
+
}
|
|
14716
|
+
case "sem-post":
|
|
14717
|
+
handles.get(req.handle).post();
|
|
14718
|
+
return ok2();
|
|
14719
|
+
case "sem-getvalue":
|
|
14720
|
+
return ok2(handles.get(req.handle).value);
|
|
14721
|
+
case "sem-wait": {
|
|
14722
|
+
const sem = handles.get(req.handle);
|
|
14723
|
+
if (sem.take()) return ok2();
|
|
14724
|
+
if (req.how === "try") throw new KernelError("EAGAIN");
|
|
14725
|
+
if (req.how === "timed" && req.timeoutMs === 0) throw new KernelError("ETIMEDOUT");
|
|
14726
|
+
const got = await sem.wait(proc.blocking(), req.how === "block" ? -1 : req.timeoutMs);
|
|
14727
|
+
if (!got) throw new KernelError("ETIMEDOUT");
|
|
14728
|
+
return ok2();
|
|
14729
|
+
}
|
|
14730
|
+
}
|
|
14731
|
+
}
|
|
14732
|
+
|
|
14519
14733
|
// src/kernel/net/datagram-wire.ts
|
|
14520
14734
|
function encodeReceived(got) {
|
|
14521
14735
|
const host = new TextEncoder().encode(got.host);
|
|
@@ -14562,20 +14776,20 @@ function dgramAt(fds, fd) {
|
|
|
14562
14776
|
if (!(file instanceof KernelDgram)) throw new KernelError("EOPNOTSUPP");
|
|
14563
14777
|
return file;
|
|
14564
14778
|
}
|
|
14565
|
-
var
|
|
14779
|
+
var ok3 = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
14566
14780
|
async function socketSyscall(req, proc) {
|
|
14567
14781
|
const { fds, net } = proc;
|
|
14568
14782
|
switch (req.op) {
|
|
14569
14783
|
case "sock-open": {
|
|
14570
14784
|
if (req.type === "dgram" && req.domain !== "inet") throw new KernelError("EPROTONOSUPPORT");
|
|
14571
14785
|
const file = req.type === "dgram" ? new KernelDgram(net) : net.socket(req.domain);
|
|
14572
|
-
return
|
|
14786
|
+
return ok3(fds.install(new OpenFile(file), 3));
|
|
14573
14787
|
}
|
|
14574
14788
|
case "sock-pair": {
|
|
14575
14789
|
const [a, b] = KernelSocket.pair(net, req.domain, proc.cred?.());
|
|
14576
14790
|
const first = fds.install(new OpenFile(a), 3);
|
|
14577
14791
|
try {
|
|
14578
|
-
return
|
|
14792
|
+
return ok3([first, fds.install(new OpenFile(b), 3)]);
|
|
14579
14793
|
} catch (e) {
|
|
14580
14794
|
await Promise.resolve(fds.close(first));
|
|
14581
14795
|
throw e;
|
|
@@ -14583,32 +14797,32 @@ async function socketSyscall(req, proc) {
|
|
|
14583
14797
|
}
|
|
14584
14798
|
case "sock-bind":
|
|
14585
14799
|
anySocketAt(fds, req.fd).bind(req.addr);
|
|
14586
|
-
return
|
|
14800
|
+
return ok3();
|
|
14587
14801
|
case "sock-listen":
|
|
14588
14802
|
socketAt(fds, req.fd).listen(req.backlog, proc.cred?.());
|
|
14589
|
-
return
|
|
14803
|
+
return ok3();
|
|
14590
14804
|
case "sock-accept":
|
|
14591
|
-
return
|
|
14805
|
+
return ok3(await accept(proc, req.fd, req.nonblock));
|
|
14592
14806
|
case "sock-connect":
|
|
14593
14807
|
return connect(req, proc);
|
|
14594
14808
|
case "sock-shutdown":
|
|
14595
14809
|
anySocketAt(fds, req.fd).shutdown(req.how);
|
|
14596
|
-
return
|
|
14810
|
+
return ok3();
|
|
14597
14811
|
case "sock-name": {
|
|
14598
14812
|
const socket = anySocketAt(fds, req.fd);
|
|
14599
14813
|
const addr = req.peer ? socket.peer : socket.local;
|
|
14600
14814
|
if (req.peer && !addr) throw new KernelError("ENOTCONN");
|
|
14601
|
-
return
|
|
14815
|
+
return ok3(addr ?? unnamed(socket instanceof KernelDgram ? "inet" : socket.domain));
|
|
14602
14816
|
}
|
|
14603
14817
|
case "sock-getopt":
|
|
14604
|
-
return
|
|
14818
|
+
return ok3(anySocketAt(fds, req.fd).getOption(req.level, req.name));
|
|
14605
14819
|
case "sock-setopt":
|
|
14606
14820
|
anySocketAt(fds, req.fd).setOption(req.level, req.name, req.value);
|
|
14607
|
-
return
|
|
14821
|
+
return ok3();
|
|
14608
14822
|
case "sock-resolve": {
|
|
14609
14823
|
const addresses = await proc.resolve(req.name, req.family, () => proc.blocking());
|
|
14610
14824
|
if (addresses.length === 0) throw new KernelError("ENOENT");
|
|
14611
|
-
return
|
|
14825
|
+
return ok3(addresses);
|
|
14612
14826
|
}
|
|
14613
14827
|
default:
|
|
14614
14828
|
return datagram(req, proc);
|
|
@@ -14618,7 +14832,7 @@ async function connect(req, proc) {
|
|
|
14618
14832
|
const target = anySocketAt(proc.fds, req.fd);
|
|
14619
14833
|
if (target instanceof KernelDgram) {
|
|
14620
14834
|
target.connectTo(req.addr);
|
|
14621
|
-
return
|
|
14835
|
+
return ok3();
|
|
14622
14836
|
}
|
|
14623
14837
|
const stamp = proc.net.facadeAt(req.addr) && await proc.stamp?.();
|
|
14624
14838
|
const dialing = target.connect(req.addr, true, proc.cred?.(), stamp || void 0);
|
|
@@ -14627,13 +14841,13 @@ async function connect(req, proc) {
|
|
|
14627
14841
|
throw new KernelError("EINPROGRESS");
|
|
14628
14842
|
}
|
|
14629
14843
|
if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
|
|
14630
|
-
return
|
|
14844
|
+
return ok3();
|
|
14631
14845
|
}
|
|
14632
14846
|
async function datagram(req, proc) {
|
|
14633
14847
|
const dgram = dgramAt(proc.fds, req.fd);
|
|
14634
14848
|
if (req.op === "sock-sendto") {
|
|
14635
14849
|
if (req.nonblock && !pollFile(dgram).writable) throw new KernelError("EAGAIN");
|
|
14636
|
-
return
|
|
14850
|
+
return ok3(await dgram.send(req.body, req.addr));
|
|
14637
14851
|
}
|
|
14638
14852
|
const ready2 = pollFile(dgram).readable;
|
|
14639
14853
|
if (req.nonblock && !ready2) throw new KernelError("EAGAIN");
|
|
@@ -15399,6 +15613,9 @@ function isHttpSyscall(req) {
|
|
|
15399
15613
|
function isSocketSyscall(req) {
|
|
15400
15614
|
return req.op.startsWith("sock-");
|
|
15401
15615
|
}
|
|
15616
|
+
function isSemSyscall(req) {
|
|
15617
|
+
return req.op.startsWith("sem-");
|
|
15618
|
+
}
|
|
15402
15619
|
function isEpollSyscall(req) {
|
|
15403
15620
|
return EPOLL_OPS.includes(req.op);
|
|
15404
15621
|
}
|
|
@@ -15493,6 +15710,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
15493
15710
|
"sig-pause",
|
|
15494
15711
|
...SOCKET_OPS,
|
|
15495
15712
|
...EPOLL_OPS,
|
|
15713
|
+
...SEM_OPS,
|
|
15496
15714
|
...HTTP_OPS,
|
|
15497
15715
|
...PTY_OPS,
|
|
15498
15716
|
...HOST_OPS
|
|
@@ -15725,6 +15943,7 @@ var WasmProcess = class {
|
|
|
15725
15943
|
if (isTtySyscall(req)) return this.ttySyscall(req);
|
|
15726
15944
|
if (isJobSyscall(req)) return this.jobSyscall(req);
|
|
15727
15945
|
if (isSocketSyscall(req)) return await this.socketSyscall(req, cancelled);
|
|
15946
|
+
if (isSemSyscall(req)) return await this.semSyscall(req, cancelled);
|
|
15728
15947
|
if (isEpollSyscall(req)) {
|
|
15729
15948
|
return await epollSyscall(req, {
|
|
15730
15949
|
fds: this.fds,
|
|
@@ -16444,6 +16663,20 @@ var WasmProcess = class {
|
|
|
16444
16663
|
if (!http) return Promise.resolve({ ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT });
|
|
16445
16664
|
return http.syscall(req);
|
|
16446
16665
|
}
|
|
16666
|
+
semSyscall(req, cancelled) {
|
|
16667
|
+
const g = this.guarded();
|
|
16668
|
+
const known = this.fds.handles;
|
|
16669
|
+
const handles = known instanceof SemHandles ? known : new SemHandles();
|
|
16670
|
+
this.fds.handles = handles;
|
|
16671
|
+
return semSyscall(req, {
|
|
16672
|
+
table: this.options.sems,
|
|
16673
|
+
handles,
|
|
16674
|
+
cred: this.cred,
|
|
16675
|
+
denied: g ? (op, path) => deny(g, op, path) : void 0,
|
|
16676
|
+
umask: this.umask,
|
|
16677
|
+
blocking: () => this.blockingSignal(cancelled)
|
|
16678
|
+
});
|
|
16679
|
+
}
|
|
16447
16680
|
socketSyscall(req, cancelled) {
|
|
16448
16681
|
this.net ??= this.options.net ?? new LoopbackNet();
|
|
16449
16682
|
const resolver = this.options.resolver ?? HOSTS_ONLY;
|
|
@@ -16954,6 +17187,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
16954
17187
|
net: opts.net,
|
|
16955
17188
|
http: opts.http,
|
|
16956
17189
|
locks: opts.locks,
|
|
17190
|
+
...opts.sems ? { sems: opts.sems } : {},
|
|
16957
17191
|
onReap: opts.onReap,
|
|
16958
17192
|
resolver: opts.resolver,
|
|
16959
17193
|
...notifiers(header3),
|
|
@@ -20324,6 +20558,9 @@ var NOT_EXECUTABLE = 126;
|
|
|
20324
20558
|
var DECIDE_MS = 100;
|
|
20325
20559
|
var INIT_PID = 1;
|
|
20326
20560
|
var SHARED_DIRS = ["/tmp", "/home"];
|
|
20561
|
+
function unref2(port) {
|
|
20562
|
+
port.unref?.();
|
|
20563
|
+
}
|
|
20327
20564
|
var LAYOUT = [
|
|
20328
20565
|
["/", 493],
|
|
20329
20566
|
["/root", 448],
|
|
@@ -20512,6 +20749,7 @@ var Launcher = class {
|
|
|
20512
20749
|
sudoer;
|
|
20513
20750
|
jobs = new JobTable();
|
|
20514
20751
|
locks = new LockTable();
|
|
20752
|
+
sems = new SemTable();
|
|
20515
20753
|
settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
|
|
20516
20754
|
ptys = new PtyTable(
|
|
20517
20755
|
(tty, sig) => this.keySignal(tty, 0, sig, () => this.jobs.signalOwnedForeground(tty, sig)),
|
|
@@ -20533,6 +20771,7 @@ var Launcher = class {
|
|
|
20533
20771
|
pnpmMade;
|
|
20534
20772
|
pnpmEpoch = 0;
|
|
20535
20773
|
mounts;
|
|
20774
|
+
shmPorts = [];
|
|
20536
20775
|
createDriverWorker;
|
|
20537
20776
|
media;
|
|
20538
20777
|
onMountPending;
|
|
@@ -21370,6 +21609,7 @@ var Launcher = class {
|
|
|
21370
21609
|
net: this.net,
|
|
21371
21610
|
http: new HttpHandles(this.transport, pid, this.loopbackFor(pid)),
|
|
21372
21611
|
locks: this.locks,
|
|
21612
|
+
sems: this.sems,
|
|
21373
21613
|
resolver: this.resolver,
|
|
21374
21614
|
onReap: (child) => this.reaped(child, pid),
|
|
21375
21615
|
...req.fork ? { fork: req.fork } : {},
|
|
@@ -21796,6 +22036,7 @@ var Launcher = class {
|
|
|
21796
22036
|
},
|
|
21797
22037
|
{ owned: true }
|
|
21798
22038
|
);
|
|
22039
|
+
if (spec.target === SHM) this.shmPorts = [port1, port2];
|
|
21799
22040
|
return {
|
|
21800
22041
|
port: port1,
|
|
21801
22042
|
dispose: () => {
|
|
@@ -21898,6 +22139,12 @@ var Launcher = class {
|
|
|
21898
22139
|
this.pnpmStale();
|
|
21899
22140
|
return entry;
|
|
21900
22141
|
}
|
|
22142
|
+
async mountShm() {
|
|
22143
|
+
await this.base.mkdir(SHM, { recursive: true });
|
|
22144
|
+
await this.mounts.mountSystem({ type: "tmpfs", source: "shm", target: SHM }, this.fs);
|
|
22145
|
+
for (const port of this.shmPorts) unref2(port);
|
|
22146
|
+
await this.fs.chmod(SHM, 1023);
|
|
22147
|
+
}
|
|
21901
22148
|
async ownTmp() {
|
|
21902
22149
|
const real = await this.realLayoutPath("/tmp");
|
|
21903
22150
|
const st = real === void 0 ? void 0 : await this.base.lstat(real).catch(() => void 0);
|
|
@@ -22054,6 +22301,7 @@ var Launcher = class {
|
|
|
22054
22301
|
await this.resolvePrefixes();
|
|
22055
22302
|
for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
|
|
22056
22303
|
if (this.owners) await this.ownTmp();
|
|
22304
|
+
await this.mountShm();
|
|
22057
22305
|
await this.pnpmReady();
|
|
22058
22306
|
await this.users.ensure();
|
|
22059
22307
|
if (this.owners) await this.stampLayout();
|
|
@@ -22602,7 +22850,7 @@ function serveKernel(port, deps) {
|
|
|
22602
22850
|
if (req.op === "mount") return l.mount(req.spec);
|
|
22603
22851
|
if (req.op === "umount") return l.umount(req.target);
|
|
22604
22852
|
if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
|
|
22605
|
-
return l.mounts.
|
|
22853
|
+
return l.mounts.listed();
|
|
22606
22854
|
}
|
|
22607
22855
|
const attestOp = attestOps(port);
|
|
22608
22856
|
async function handle(req) {
|