@ai-ecoverse/slicc-kernel 1.98.4 → 1.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/kernel-worker.js +240 -15
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +63 -10
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +240 -15
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +63 -10
- 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.
|
|
@@ -933,6 +963,17 @@ Each side prints `pattern` (a static array's damage), `low` (bytes of low memory
|
|
|
933
963
|
|
|
934
964
|
**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
965
|
|
|
966
|
+
### `semtest`
|
|
967
|
+
|
|
968
|
+
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:
|
|
969
|
+
- `handoff`, `inherit`, `spawn` (a spawned and an exec'd image open the semaphore by name; exec drops the handles);
|
|
970
|
+
- `try`, `timed`, `eintr`;
|
|
971
|
+
- `threads` (FIFO, one thread blocked at a time);
|
|
972
|
+
- `errors`, `unlink`, `owners setup|user`;
|
|
973
|
+
- `killed` (a waiter killed before posts loses no unit), `exited`, `cap`, `handles`.
|
|
974
|
+
|
|
975
|
+
**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`.
|
|
976
|
+
|
|
936
977
|
## License
|
|
937
978
|
|
|
938
979
|
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
|
};
|
|
@@ -12082,7 +12085,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
|
|
|
12082
12085
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
12083
12086
|
|
|
12084
12087
|
// src/version.ts
|
|
12085
|
-
var KERNEL_VERSION = true ? "1.
|
|
12088
|
+
var KERNEL_VERSION = true ? "1.99.0" : "0.0.0-dev";
|
|
12086
12089
|
var OS_RELEASE = "6.0.0-slicc";
|
|
12087
12090
|
function procVersion() {
|
|
12088
12091
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -14516,6 +14519,206 @@ async function waitReady(fds, read, write, deadline, interrupted3) {
|
|
|
14516
14519
|
}
|
|
14517
14520
|
}
|
|
14518
14521
|
|
|
14522
|
+
// src/kernel/sem.ts
|
|
14523
|
+
var SEM_VALUE_MAX = 2147483647;
|
|
14524
|
+
var SEM_NAMES_MAX = 4096;
|
|
14525
|
+
var SEM_HANDLES_MAX = 256;
|
|
14526
|
+
var NAME_MAX = 251;
|
|
14527
|
+
var TIMER_MAX_MS = 2147483647;
|
|
14528
|
+
var SEM_OPS = [
|
|
14529
|
+
"sem-open",
|
|
14530
|
+
"sem-close",
|
|
14531
|
+
"sem-unlink",
|
|
14532
|
+
"sem-post",
|
|
14533
|
+
"sem-wait",
|
|
14534
|
+
"sem-getvalue"
|
|
14535
|
+
];
|
|
14536
|
+
var Semaphore = class {
|
|
14537
|
+
value;
|
|
14538
|
+
uid;
|
|
14539
|
+
gid;
|
|
14540
|
+
mode;
|
|
14541
|
+
waiters = [];
|
|
14542
|
+
constructor(value, uid, gid, mode) {
|
|
14543
|
+
this.value = value;
|
|
14544
|
+
this.uid = uid;
|
|
14545
|
+
this.gid = gid;
|
|
14546
|
+
this.mode = mode;
|
|
14547
|
+
}
|
|
14548
|
+
post() {
|
|
14549
|
+
const next2 = this.waiters.shift();
|
|
14550
|
+
if (next2) next2.grant();
|
|
14551
|
+
else if (this.value >= SEM_VALUE_MAX) throw new KernelError("EOVERFLOW");
|
|
14552
|
+
else this.value++;
|
|
14553
|
+
}
|
|
14554
|
+
take() {
|
|
14555
|
+
if (this.value === 0) return false;
|
|
14556
|
+
this.value--;
|
|
14557
|
+
return true;
|
|
14558
|
+
}
|
|
14559
|
+
wait(signal, timeoutMs) {
|
|
14560
|
+
if (this.take()) return Promise.resolve(true);
|
|
14561
|
+
if (signal.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
14562
|
+
return new Promise((resolve2, reject) => {
|
|
14563
|
+
const deadline = timeoutMs < 0 ? Number.POSITIVE_INFINITY : performance.now() + timeoutMs;
|
|
14564
|
+
let timer;
|
|
14565
|
+
const leave = () => {
|
|
14566
|
+
const at = this.waiters.indexOf(waiter);
|
|
14567
|
+
if (at >= 0) this.waiters.splice(at, 1);
|
|
14568
|
+
clearTimeout(timer);
|
|
14569
|
+
signal.removeEventListener("abort", abort);
|
|
14570
|
+
};
|
|
14571
|
+
const waiter = {
|
|
14572
|
+
grant: () => {
|
|
14573
|
+
leave();
|
|
14574
|
+
resolve2(true);
|
|
14575
|
+
}
|
|
14576
|
+
};
|
|
14577
|
+
const abort = () => {
|
|
14578
|
+
leave();
|
|
14579
|
+
reject(new KernelError("EINTR"));
|
|
14580
|
+
};
|
|
14581
|
+
const expire = () => {
|
|
14582
|
+
const left = deadline - performance.now();
|
|
14583
|
+
if (left > 0) {
|
|
14584
|
+
timer = setTimeout(expire, Math.min(left, TIMER_MAX_MS));
|
|
14585
|
+
return;
|
|
14586
|
+
}
|
|
14587
|
+
leave();
|
|
14588
|
+
resolve2(false);
|
|
14589
|
+
};
|
|
14590
|
+
this.waiters.push(waiter);
|
|
14591
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
14592
|
+
if (deadline !== Number.POSITIVE_INFINITY) {
|
|
14593
|
+
timer = setTimeout(expire, Math.min(timeoutMs, TIMER_MAX_MS));
|
|
14594
|
+
}
|
|
14595
|
+
});
|
|
14596
|
+
}
|
|
14597
|
+
};
|
|
14598
|
+
var SemTable = class {
|
|
14599
|
+
names = /* @__PURE__ */ new Map();
|
|
14600
|
+
limit;
|
|
14601
|
+
constructor(limit = SEM_NAMES_MAX) {
|
|
14602
|
+
this.limit = limit;
|
|
14603
|
+
}
|
|
14604
|
+
open(name2, create, exclusive, make, check, adopt) {
|
|
14605
|
+
const found = this.names.get(name2);
|
|
14606
|
+
if (found) {
|
|
14607
|
+
if (create && exclusive) throw new KernelError("EEXIST");
|
|
14608
|
+
check(found);
|
|
14609
|
+
return adopt(found);
|
|
14610
|
+
}
|
|
14611
|
+
if (!create) throw new KernelError("ENOENT");
|
|
14612
|
+
if (this.names.size >= this.limit) throw new KernelError("ENOSPC");
|
|
14613
|
+
const sem = make();
|
|
14614
|
+
const handle = adopt(sem);
|
|
14615
|
+
this.names.set(name2, sem);
|
|
14616
|
+
return handle;
|
|
14617
|
+
}
|
|
14618
|
+
unlink(name2, check) {
|
|
14619
|
+
const found = this.names.get(name2);
|
|
14620
|
+
if (!found) throw new KernelError("ENOENT");
|
|
14621
|
+
check(found);
|
|
14622
|
+
this.names.delete(name2);
|
|
14623
|
+
}
|
|
14624
|
+
};
|
|
14625
|
+
var SemHandles = class _SemHandles {
|
|
14626
|
+
byHandle = /* @__PURE__ */ new Map();
|
|
14627
|
+
next = 1;
|
|
14628
|
+
add(sem) {
|
|
14629
|
+
for (const [handle2, entry] of this.byHandle) {
|
|
14630
|
+
if (entry.sem === sem) {
|
|
14631
|
+
entry.opens++;
|
|
14632
|
+
return handle2;
|
|
14633
|
+
}
|
|
14634
|
+
}
|
|
14635
|
+
if (this.byHandle.size >= SEM_HANDLES_MAX) throw new KernelError("EMFILE");
|
|
14636
|
+
const handle = this.next++;
|
|
14637
|
+
this.byHandle.set(handle, { sem, opens: 1 });
|
|
14638
|
+
return handle;
|
|
14639
|
+
}
|
|
14640
|
+
get(handle) {
|
|
14641
|
+
const entry = this.byHandle.get(handle);
|
|
14642
|
+
if (!entry) throw new KernelError("EINVAL");
|
|
14643
|
+
return entry.sem;
|
|
14644
|
+
}
|
|
14645
|
+
close(handle) {
|
|
14646
|
+
const entry = this.byHandle.get(handle);
|
|
14647
|
+
if (!entry) throw new KernelError("EINVAL");
|
|
14648
|
+
if (--entry.opens === 0) this.byHandle.delete(handle);
|
|
14649
|
+
}
|
|
14650
|
+
fork() {
|
|
14651
|
+
const child = new _SemHandles();
|
|
14652
|
+
child.next = this.next;
|
|
14653
|
+
for (const [handle, entry] of this.byHandle) child.byHandle.set(handle, { ...entry });
|
|
14654
|
+
return child;
|
|
14655
|
+
}
|
|
14656
|
+
clear() {
|
|
14657
|
+
this.byHandle.clear();
|
|
14658
|
+
}
|
|
14659
|
+
};
|
|
14660
|
+
function semName(raw) {
|
|
14661
|
+
const name2 = raw.replace(/^\/+/, "");
|
|
14662
|
+
if (name2 === "" || name2.includes("/") || name2 === "." || name2 === "..") {
|
|
14663
|
+
throw new KernelError("EINVAL");
|
|
14664
|
+
}
|
|
14665
|
+
if (new TextEncoder().encode(name2).length > NAME_MAX) throw new KernelError("ENAMETOOLONG");
|
|
14666
|
+
return name2;
|
|
14667
|
+
}
|
|
14668
|
+
var ok2 = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
14669
|
+
async function semSyscall(req, proc) {
|
|
14670
|
+
const { table, handles, cred } = proc;
|
|
14671
|
+
if (!table) throw new KernelError("ENOSYS");
|
|
14672
|
+
const { denied: denied2 } = proc;
|
|
14673
|
+
const shown = (name2) => `/dev/shm/sem.${name2}`;
|
|
14674
|
+
const opens = (name2) => (sem) => {
|
|
14675
|
+
const perm = { uid: sem.uid, gid: sem.gid, mode: sem.mode };
|
|
14676
|
+
if (denied2 && !permits(cred, perm, R_OK | W_OK, false)) denied2("sem_open", shown(name2));
|
|
14677
|
+
};
|
|
14678
|
+
const unlinks = (name2) => (sem) => {
|
|
14679
|
+
if (denied2 && cred.euid !== 0 && cred.euid !== sem.uid) denied2("sem_unlink", shown(name2));
|
|
14680
|
+
};
|
|
14681
|
+
switch (req.op) {
|
|
14682
|
+
case "sem-open": {
|
|
14683
|
+
if (req.value < 0 || req.value > SEM_VALUE_MAX) throw new KernelError("EINVAL");
|
|
14684
|
+
const name2 = semName(req.name);
|
|
14685
|
+
const mode = req.mode & 511 & ~proc.umask;
|
|
14686
|
+
return ok2(
|
|
14687
|
+
table.open(
|
|
14688
|
+
name2,
|
|
14689
|
+
req.create,
|
|
14690
|
+
req.exclusive,
|
|
14691
|
+
() => new Semaphore(req.value, cred.euid, cred.egid, mode),
|
|
14692
|
+
opens(name2),
|
|
14693
|
+
(sem) => handles.add(sem)
|
|
14694
|
+
)
|
|
14695
|
+
);
|
|
14696
|
+
}
|
|
14697
|
+
case "sem-close":
|
|
14698
|
+
handles.close(req.handle);
|
|
14699
|
+
return ok2();
|
|
14700
|
+
case "sem-unlink": {
|
|
14701
|
+
const name2 = semName(req.name);
|
|
14702
|
+
table.unlink(name2, unlinks(name2));
|
|
14703
|
+
return ok2();
|
|
14704
|
+
}
|
|
14705
|
+
case "sem-post":
|
|
14706
|
+
handles.get(req.handle).post();
|
|
14707
|
+
return ok2();
|
|
14708
|
+
case "sem-getvalue":
|
|
14709
|
+
return ok2(handles.get(req.handle).value);
|
|
14710
|
+
case "sem-wait": {
|
|
14711
|
+
const sem = handles.get(req.handle);
|
|
14712
|
+
if (sem.take()) return ok2();
|
|
14713
|
+
if (req.how === "try") throw new KernelError("EAGAIN");
|
|
14714
|
+
if (req.how === "timed" && req.timeoutMs === 0) throw new KernelError("ETIMEDOUT");
|
|
14715
|
+
const got = await sem.wait(proc.blocking(), req.how === "block" ? -1 : req.timeoutMs);
|
|
14716
|
+
if (!got) throw new KernelError("ETIMEDOUT");
|
|
14717
|
+
return ok2();
|
|
14718
|
+
}
|
|
14719
|
+
}
|
|
14720
|
+
}
|
|
14721
|
+
|
|
14519
14722
|
// src/kernel/net/datagram-wire.ts
|
|
14520
14723
|
function encodeReceived(got) {
|
|
14521
14724
|
const host = new TextEncoder().encode(got.host);
|
|
@@ -14562,20 +14765,20 @@ function dgramAt(fds, fd) {
|
|
|
14562
14765
|
if (!(file instanceof KernelDgram)) throw new KernelError("EOPNOTSUPP");
|
|
14563
14766
|
return file;
|
|
14564
14767
|
}
|
|
14565
|
-
var
|
|
14768
|
+
var ok3 = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
14566
14769
|
async function socketSyscall(req, proc) {
|
|
14567
14770
|
const { fds, net } = proc;
|
|
14568
14771
|
switch (req.op) {
|
|
14569
14772
|
case "sock-open": {
|
|
14570
14773
|
if (req.type === "dgram" && req.domain !== "inet") throw new KernelError("EPROTONOSUPPORT");
|
|
14571
14774
|
const file = req.type === "dgram" ? new KernelDgram(net) : net.socket(req.domain);
|
|
14572
|
-
return
|
|
14775
|
+
return ok3(fds.install(new OpenFile(file), 3));
|
|
14573
14776
|
}
|
|
14574
14777
|
case "sock-pair": {
|
|
14575
14778
|
const [a, b] = KernelSocket.pair(net, req.domain, proc.cred?.());
|
|
14576
14779
|
const first = fds.install(new OpenFile(a), 3);
|
|
14577
14780
|
try {
|
|
14578
|
-
return
|
|
14781
|
+
return ok3([first, fds.install(new OpenFile(b), 3)]);
|
|
14579
14782
|
} catch (e) {
|
|
14580
14783
|
await Promise.resolve(fds.close(first));
|
|
14581
14784
|
throw e;
|
|
@@ -14583,32 +14786,32 @@ async function socketSyscall(req, proc) {
|
|
|
14583
14786
|
}
|
|
14584
14787
|
case "sock-bind":
|
|
14585
14788
|
anySocketAt(fds, req.fd).bind(req.addr);
|
|
14586
|
-
return
|
|
14789
|
+
return ok3();
|
|
14587
14790
|
case "sock-listen":
|
|
14588
14791
|
socketAt(fds, req.fd).listen(req.backlog, proc.cred?.());
|
|
14589
|
-
return
|
|
14792
|
+
return ok3();
|
|
14590
14793
|
case "sock-accept":
|
|
14591
|
-
return
|
|
14794
|
+
return ok3(await accept(proc, req.fd, req.nonblock));
|
|
14592
14795
|
case "sock-connect":
|
|
14593
14796
|
return connect(req, proc);
|
|
14594
14797
|
case "sock-shutdown":
|
|
14595
14798
|
anySocketAt(fds, req.fd).shutdown(req.how);
|
|
14596
|
-
return
|
|
14799
|
+
return ok3();
|
|
14597
14800
|
case "sock-name": {
|
|
14598
14801
|
const socket = anySocketAt(fds, req.fd);
|
|
14599
14802
|
const addr = req.peer ? socket.peer : socket.local;
|
|
14600
14803
|
if (req.peer && !addr) throw new KernelError("ENOTCONN");
|
|
14601
|
-
return
|
|
14804
|
+
return ok3(addr ?? unnamed(socket instanceof KernelDgram ? "inet" : socket.domain));
|
|
14602
14805
|
}
|
|
14603
14806
|
case "sock-getopt":
|
|
14604
|
-
return
|
|
14807
|
+
return ok3(anySocketAt(fds, req.fd).getOption(req.level, req.name));
|
|
14605
14808
|
case "sock-setopt":
|
|
14606
14809
|
anySocketAt(fds, req.fd).setOption(req.level, req.name, req.value);
|
|
14607
|
-
return
|
|
14810
|
+
return ok3();
|
|
14608
14811
|
case "sock-resolve": {
|
|
14609
14812
|
const addresses = await proc.resolve(req.name, req.family, () => proc.blocking());
|
|
14610
14813
|
if (addresses.length === 0) throw new KernelError("ENOENT");
|
|
14611
|
-
return
|
|
14814
|
+
return ok3(addresses);
|
|
14612
14815
|
}
|
|
14613
14816
|
default:
|
|
14614
14817
|
return datagram(req, proc);
|
|
@@ -14618,7 +14821,7 @@ async function connect(req, proc) {
|
|
|
14618
14821
|
const target = anySocketAt(proc.fds, req.fd);
|
|
14619
14822
|
if (target instanceof KernelDgram) {
|
|
14620
14823
|
target.connectTo(req.addr);
|
|
14621
|
-
return
|
|
14824
|
+
return ok3();
|
|
14622
14825
|
}
|
|
14623
14826
|
const stamp = proc.net.facadeAt(req.addr) && await proc.stamp?.();
|
|
14624
14827
|
const dialing = target.connect(req.addr, true, proc.cred?.(), stamp || void 0);
|
|
@@ -14627,13 +14830,13 @@ async function connect(req, proc) {
|
|
|
14627
14830
|
throw new KernelError("EINPROGRESS");
|
|
14628
14831
|
}
|
|
14629
14832
|
if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
|
|
14630
|
-
return
|
|
14833
|
+
return ok3();
|
|
14631
14834
|
}
|
|
14632
14835
|
async function datagram(req, proc) {
|
|
14633
14836
|
const dgram = dgramAt(proc.fds, req.fd);
|
|
14634
14837
|
if (req.op === "sock-sendto") {
|
|
14635
14838
|
if (req.nonblock && !pollFile(dgram).writable) throw new KernelError("EAGAIN");
|
|
14636
|
-
return
|
|
14839
|
+
return ok3(await dgram.send(req.body, req.addr));
|
|
14637
14840
|
}
|
|
14638
14841
|
const ready2 = pollFile(dgram).readable;
|
|
14639
14842
|
if (req.nonblock && !ready2) throw new KernelError("EAGAIN");
|
|
@@ -15399,6 +15602,9 @@ function isHttpSyscall(req) {
|
|
|
15399
15602
|
function isSocketSyscall(req) {
|
|
15400
15603
|
return req.op.startsWith("sock-");
|
|
15401
15604
|
}
|
|
15605
|
+
function isSemSyscall(req) {
|
|
15606
|
+
return req.op.startsWith("sem-");
|
|
15607
|
+
}
|
|
15402
15608
|
function isEpollSyscall(req) {
|
|
15403
15609
|
return EPOLL_OPS.includes(req.op);
|
|
15404
15610
|
}
|
|
@@ -15493,6 +15699,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
15493
15699
|
"sig-pause",
|
|
15494
15700
|
...SOCKET_OPS,
|
|
15495
15701
|
...EPOLL_OPS,
|
|
15702
|
+
...SEM_OPS,
|
|
15496
15703
|
...HTTP_OPS,
|
|
15497
15704
|
...PTY_OPS,
|
|
15498
15705
|
...HOST_OPS
|
|
@@ -15725,6 +15932,7 @@ var WasmProcess = class {
|
|
|
15725
15932
|
if (isTtySyscall(req)) return this.ttySyscall(req);
|
|
15726
15933
|
if (isJobSyscall(req)) return this.jobSyscall(req);
|
|
15727
15934
|
if (isSocketSyscall(req)) return await this.socketSyscall(req, cancelled);
|
|
15935
|
+
if (isSemSyscall(req)) return await this.semSyscall(req, cancelled);
|
|
15728
15936
|
if (isEpollSyscall(req)) {
|
|
15729
15937
|
return await epollSyscall(req, {
|
|
15730
15938
|
fds: this.fds,
|
|
@@ -16444,6 +16652,20 @@ var WasmProcess = class {
|
|
|
16444
16652
|
if (!http) return Promise.resolve({ ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT });
|
|
16445
16653
|
return http.syscall(req);
|
|
16446
16654
|
}
|
|
16655
|
+
semSyscall(req, cancelled) {
|
|
16656
|
+
const g = this.guarded();
|
|
16657
|
+
const known = this.fds.handles;
|
|
16658
|
+
const handles = known instanceof SemHandles ? known : new SemHandles();
|
|
16659
|
+
this.fds.handles = handles;
|
|
16660
|
+
return semSyscall(req, {
|
|
16661
|
+
table: this.options.sems,
|
|
16662
|
+
handles,
|
|
16663
|
+
cred: this.cred,
|
|
16664
|
+
denied: g ? (op, path) => deny(g, op, path) : void 0,
|
|
16665
|
+
umask: this.umask,
|
|
16666
|
+
blocking: () => this.blockingSignal(cancelled)
|
|
16667
|
+
});
|
|
16668
|
+
}
|
|
16447
16669
|
socketSyscall(req, cancelled) {
|
|
16448
16670
|
this.net ??= this.options.net ?? new LoopbackNet();
|
|
16449
16671
|
const resolver = this.options.resolver ?? HOSTS_ONLY;
|
|
@@ -16954,6 +17176,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
16954
17176
|
net: opts.net,
|
|
16955
17177
|
http: opts.http,
|
|
16956
17178
|
locks: opts.locks,
|
|
17179
|
+
...opts.sems ? { sems: opts.sems } : {},
|
|
16957
17180
|
onReap: opts.onReap,
|
|
16958
17181
|
resolver: opts.resolver,
|
|
16959
17182
|
...notifiers(header3),
|
|
@@ -20512,6 +20735,7 @@ var Launcher = class {
|
|
|
20512
20735
|
sudoer;
|
|
20513
20736
|
jobs = new JobTable();
|
|
20514
20737
|
locks = new LockTable();
|
|
20738
|
+
sems = new SemTable();
|
|
20515
20739
|
settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
|
|
20516
20740
|
ptys = new PtyTable(
|
|
20517
20741
|
(tty, sig) => this.keySignal(tty, 0, sig, () => this.jobs.signalOwnedForeground(tty, sig)),
|
|
@@ -21370,6 +21594,7 @@ var Launcher = class {
|
|
|
21370
21594
|
net: this.net,
|
|
21371
21595
|
http: new HttpHandles(this.transport, pid, this.loopbackFor(pid)),
|
|
21372
21596
|
locks: this.locks,
|
|
21597
|
+
sems: this.sems,
|
|
21373
21598
|
resolver: this.resolver,
|
|
21374
21599
|
onReap: (child) => this.reaped(child, pid),
|
|
21375
21600
|
...req.fork ? { fork: req.fork } : {},
|