@amritk/nish-x86_64-linux 0.27.2 → 0.29.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/INSTALL.md +16 -8
- package/bin/nish +0 -0
- package/docs/AI.md +118 -26
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/core.d.ts +47 -0
- package/runtime/core.mjs +67 -0
- package/runtime/nish.d.ts +176 -0
- package/runtime/nish.h +120 -6
- package/runtime/nish.mjs +24 -4
- package/runtime/runtime-checksum.c +270 -0
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +81 -5
- package/runtime/runtime-sandbox.c +479 -0
- package/runtime/runtime-sha256.c +239 -0
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +827 -791
- package/scripts/build.sh +79 -28
- package/std/README.md +25 -13
- package/std/collections.ts +5 -5
- package/std/compress/checksum.ts +88 -75
- package/std/compress/deflate.ts +224 -0
- package/std/compress/gzip.ts +52 -0
- package/std/crypto/bcrypt.ts +816 -280
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-equal.ts +335 -0
- package/std/json-format.ts +1070 -0
- package/std/json-number.ts +428 -0
- package/std/json-pointer.ts +245 -0
- package/std/json-typed.ts +34 -0
- package/std/json-value.ts +510 -17
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +301 -16
- package/std/net/http1-client.ts +104 -19
- package/std/net/http1-server.ts +127 -24
- package/std/net/http1.ts +24 -4
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres.ts +405 -0
- package/std/net/prefork.ts +344 -20
- package/std/net/websocket.ts +295 -14
- package/std/regex.ts +613 -52
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-io.ts +35 -8
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- package/std/yaml-json.ts +841 -0
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/* The C half of `nish/sync/shm` and `nish/sync/ring` (std/sync/): memory that
|
|
2
|
+
* several processes map at once, the atomics a program runs on it, and the
|
|
3
|
+
* futex that lets one of them sleep until another rings.
|
|
4
|
+
*
|
|
5
|
+
* A region is a named POSIX shared-memory object, `/dev/shm/<name>` on Linux
|
|
6
|
+
* and `shm_open("/<name>")` on Darwin, mapped MAP_SHARED. A program cannot hold
|
|
7
|
+
* the mapping's address, so this file keeps every region in a table and hands
|
|
8
|
+
* the program an `int32_t` handle into it, as runtime/sqlite/nish-sqlite.c
|
|
9
|
+
* does: the slot's index in the low 16 bits and its generation in the 15 above
|
|
10
|
+
* them, so a handle is never 0 or negative. `nishshm_close` unmaps the region
|
|
11
|
+
* and moves the slot's generation on, so a handle used after it names nothing
|
|
12
|
+
* and panics rather than reaching an unmapped page. A slot whose generation
|
|
13
|
+
* reaches 32,767 is retired, so no handle ever names a slot twice.
|
|
14
|
+
*
|
|
15
|
+
* The table is thread-local under --threads (`NISH_TLS`, nish.h): a region
|
|
16
|
+
* belongs to the thread that mapped it, and a handle passed to another thread
|
|
17
|
+
* names nothing there and panics. That is what lets a `close` on one thread
|
|
18
|
+
* never unmap a page another thread is reading, and why the table needs no
|
|
19
|
+
* lock. A thread that wants the region opens it again by name.
|
|
20
|
+
*
|
|
21
|
+
* Only a pointer to the table is thread-local. The table itself is one
|
|
22
|
+
* anonymous page, mapped by the thread's first create or open. Under
|
|
23
|
+
* --threads the runtime's TLS is initial-exec, and a Node addon is loaded
|
|
24
|
+
* with dlopen, where glibc has only a small surplus of static TLS to give it
|
|
25
|
+
* (about 1.6 KiB). A 1.5 KiB table there made `--emit-napi-async` addons fail
|
|
26
|
+
* to load with "cannot allocate memory in static TLS block". A thread that
|
|
27
|
+
* exits keeps its page mapped; one page per thread that ever mapped a region
|
|
28
|
+
* is the whole cost.
|
|
29
|
+
*
|
|
30
|
+
* **Every access is checked.** An offset is an `int32_t` byte offset into the
|
|
31
|
+
* region (a region is at most 1 GiB), widened before any sum; a 32-bit
|
|
32
|
+
* slot must sit at a multiple of 4 and a 64-bit one at a multiple of 8, wholly
|
|
33
|
+
* inside the region, and a byte copy must fit both the region and the
|
|
34
|
+
* program's buffer. A failed check panics with the offset and
|
|
35
|
+
* the region's size (`shm_fail`), so an out-of-range slot or a misaligned
|
|
36
|
+
* atomic is a checked error, never a store through a bad address. So is an
|
|
37
|
+
* ordering an operation cannot take, such as a load with `release`.
|
|
38
|
+
*
|
|
39
|
+
* **Orderings are C11's numbers** (`__ATOMIC_RELAXED` 0 to `__ATOMIC_SEQ_CST`
|
|
40
|
+
* 5, `consume` refused), and each access is one `__atomic_*` builtin on the
|
|
41
|
+
* slot's address. The runtime is LTO bitcode on the speed and size profiles
|
|
42
|
+
* (scripts/build.sh), so an access inlines into the program and a constant
|
|
43
|
+
* ordering folds to one LLVM `load atomic`, `store atomic`, `atomicrmw` or
|
|
44
|
+
* `cmpxchg`. A compare-exchange takes one ordering and derives its failure
|
|
45
|
+
* ordering from it as C++ does: `acq_rel` fails `acquire`, `release` fails
|
|
46
|
+
* `relaxed`.
|
|
47
|
+
*
|
|
48
|
+
* **Waiting** is Linux's futex on a 32-bit slot, the shared (not
|
|
49
|
+
* FUTEX_PRIVATE) form, so a wake in one process reaches a waiter in another.
|
|
50
|
+
* Darwin has no public cross-process equivalent before macOS 14.4, and wasi
|
|
51
|
+
* has no shared memory at all: there `nishshm_wait_i32` and `nishshm_wake`
|
|
52
|
+
* answer -95, as `setIdlePriority` does on Darwin, and on wasi creating or
|
|
53
|
+
* opening a region answers -95 too.
|
|
54
|
+
*
|
|
55
|
+
* Results are 0 or a count on success and a negated errno on failure. Nothing
|
|
56
|
+
* here allocates: the table is a fixed array, and a mapping is the kernel's. */
|
|
57
|
+
|
|
58
|
+
#define _GNU_SOURCE /* `syscall`, for the futex */
|
|
59
|
+
#include <errno.h>
|
|
60
|
+
#include <stdint.h>
|
|
61
|
+
#include <string.h>
|
|
62
|
+
|
|
63
|
+
#include "nish.h"
|
|
64
|
+
|
|
65
|
+
#ifndef __wasi__
|
|
66
|
+
#include <fcntl.h>
|
|
67
|
+
#include <sys/mman.h>
|
|
68
|
+
#include <sys/stat.h>
|
|
69
|
+
#include <unistd.h>
|
|
70
|
+
#endif
|
|
71
|
+
#ifdef __linux__
|
|
72
|
+
#include <linux/futex.h>
|
|
73
|
+
#include <sys/syscall.h>
|
|
74
|
+
#include <time.h>
|
|
75
|
+
#endif
|
|
76
|
+
#if defined(__APPLE__) && !defined(O_NOFOLLOW)
|
|
77
|
+
#define O_NOFOLLOW 0x0100
|
|
78
|
+
#endif
|
|
79
|
+
#if defined(__APPLE__) && !defined(O_CLOEXEC)
|
|
80
|
+
#define O_CLOEXEC 0x1000000
|
|
81
|
+
#endif
|
|
82
|
+
|
|
83
|
+
#define NISHSHM_SLOTS 64
|
|
84
|
+
#define NISHSHM_INDEX 0xffff
|
|
85
|
+
#define NISHSHM_GEN_MAX 0x7fff
|
|
86
|
+
/* The largest region: 1 GiB, so every offset is a Nish `i32` (a method's
|
|
87
|
+
argument takes no width from context, and `region.loadI32(8, ...)` should
|
|
88
|
+
not need a `toI64`), and a page-rounded size on Darwin still fits one. */
|
|
89
|
+
#define NISHSHM_MAX_BYTES ((uint64_t)1 << 30)
|
|
90
|
+
/* The longest name. Darwin's shm_open takes 31 bytes with its leading '/'. */
|
|
91
|
+
#define NISHSHM_NAME_MAX 30
|
|
92
|
+
|
|
93
|
+
typedef struct nishshm_region {
|
|
94
|
+
uint8_t *base; /* the mapping, NULL while the slot is free */
|
|
95
|
+
uint64_t size; /* its length in bytes */
|
|
96
|
+
int32_t gen; /* the high bits of the slot's handle: 0 never used, 32767 retired */
|
|
97
|
+
} nishshm_region;
|
|
98
|
+
|
|
99
|
+
/* NULL until this thread first maps a region. Slot 0 is never used, so no
|
|
100
|
+
handle is 0. */
|
|
101
|
+
static NISH_TLS nishshm_region *nishshm_table;
|
|
102
|
+
|
|
103
|
+
/* -- panics ---------------------------------------------------------------- */
|
|
104
|
+
|
|
105
|
+
/* The text of a panic, built in place on the failing call's stack:
|
|
106
|
+
`nish_panic` takes a `nish_str` and does not return. */
|
|
107
|
+
typedef struct shm_msg {
|
|
108
|
+
uint64_t len;
|
|
109
|
+
char data[192];
|
|
110
|
+
} shm_msg;
|
|
111
|
+
|
|
112
|
+
static void shm_put(shm_msg *msg, const char *s) {
|
|
113
|
+
while (*s && msg->len < sizeof msg->data - 1) msg->data[msg->len++] = *s++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static void shm_put_i64(shm_msg *msg, int64_t v) {
|
|
117
|
+
char tmp[21], *p = tmp + sizeof tmp;
|
|
118
|
+
uint64_t u = v < 0 ? (uint64_t)0 - (uint64_t)v : (uint64_t)v;
|
|
119
|
+
*--p = 0;
|
|
120
|
+
do {
|
|
121
|
+
*--p = (char)('0' + u % 10);
|
|
122
|
+
u /= 10;
|
|
123
|
+
} while (u);
|
|
124
|
+
if (v < 0) *--p = '-';
|
|
125
|
+
shm_put(msg, p);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* "nish/sync/shm: <what> <n><rest>", and exit 1. */
|
|
129
|
+
__attribute__((noreturn, cold)) static void shm_fail(const char *what, int64_t n, const char *rest,
|
|
130
|
+
int64_t m, const char *tail) {
|
|
131
|
+
shm_msg msg;
|
|
132
|
+
msg.len = 0;
|
|
133
|
+
shm_put(&msg, "nish/sync/shm: ");
|
|
134
|
+
shm_put(&msg, what);
|
|
135
|
+
shm_put_i64(&msg, n);
|
|
136
|
+
shm_put(&msg, rest);
|
|
137
|
+
if (tail) {
|
|
138
|
+
shm_put_i64(&msg, m);
|
|
139
|
+
shm_put(&msg, tail);
|
|
140
|
+
}
|
|
141
|
+
msg.data[msg.len] = 0;
|
|
142
|
+
nish_panic((const nish_str *)&msg);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* -- the table ------------------------------------------------------------- */
|
|
146
|
+
|
|
147
|
+
static inline nishshm_region *region_of(int32_t h) {
|
|
148
|
+
int32_t i = h & NISHSHM_INDEX;
|
|
149
|
+
if (h <= 0 || i == 0 || i >= NISHSHM_SLOTS || nishshm_table == NULL) return NULL;
|
|
150
|
+
nishshm_region *r = &nishshm_table[i];
|
|
151
|
+
if (r->base == NULL || r->gen != (h >> 16)) return NULL;
|
|
152
|
+
return r;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static inline nishshm_region *region_checked(int32_t h) {
|
|
156
|
+
nishshm_region *r = region_of(h);
|
|
157
|
+
if (__builtin_expect(r == NULL, 0))
|
|
158
|
+
shm_fail("handle ", h, " names no open region: it was closed, or mapped on another thread", 0,
|
|
159
|
+
NULL);
|
|
160
|
+
return r;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* The address of the `width`-byte slot at `at`, checked for alignment and
|
|
164
|
+
bounds. `at + width` cannot overflow: `at` is below the size, which is at
|
|
165
|
+
most 2^34. */
|
|
166
|
+
static inline uint8_t *slot_at(int32_t h, int64_t at, int64_t width) {
|
|
167
|
+
nishshm_region *r = region_checked(h);
|
|
168
|
+
if (__builtin_expect(at < 0 || (uint64_t)at >= r->size || (uint64_t)(at + width) > r->size, 0))
|
|
169
|
+
shm_fail("slot offset ", at, " is outside the region of ", (int64_t)r->size, " bytes");
|
|
170
|
+
if (__builtin_expect((at & (width - 1)) != 0, 0))
|
|
171
|
+
shm_fail("slot offset ", at, " is not a multiple of ", width, ", the slot's width");
|
|
172
|
+
return r->base + at;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* -- orderings ------------------------------------------------------------- */
|
|
176
|
+
|
|
177
|
+
enum { SHM_LOAD, SHM_STORE, SHM_RMW };
|
|
178
|
+
|
|
179
|
+
/* Panics unless this kind of access takes `order`. */
|
|
180
|
+
static inline void order_check(int32_t order, int kind) {
|
|
181
|
+
int ok;
|
|
182
|
+
switch (order) {
|
|
183
|
+
case __ATOMIC_RELAXED:
|
|
184
|
+
case __ATOMIC_SEQ_CST: ok = 1; break;
|
|
185
|
+
case __ATOMIC_ACQUIRE: ok = kind != SHM_STORE; break;
|
|
186
|
+
case __ATOMIC_RELEASE: ok = kind != SHM_LOAD; break;
|
|
187
|
+
case __ATOMIC_ACQ_REL: ok = kind == SHM_RMW; break;
|
|
188
|
+
default: ok = 0; break;
|
|
189
|
+
}
|
|
190
|
+
if (__builtin_expect(!ok, 0))
|
|
191
|
+
shm_fail("ordering ", order,
|
|
192
|
+
kind == SHM_LOAD ? " cannot order a load: use RELAXED, ACQUIRE or SEQ_CST"
|
|
193
|
+
: kind == SHM_STORE ? " cannot order a store: use RELAXED, RELEASE or SEQ_CST"
|
|
194
|
+
: " is not an ordering: use RELAXED, ACQUIRE, RELEASE, ACQ_REL "
|
|
195
|
+
"or SEQ_CST",
|
|
196
|
+
0, NULL);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* -- 32- and 64-bit slots ---------------------------------------------------- */
|
|
200
|
+
|
|
201
|
+
/* The builtins want a constant ordering, so each access switches on it; the
|
|
202
|
+
switch folds to one access when the program's ordering is a constant and
|
|
203
|
+
the call inlines. A compare-exchange that fails orders as C++ derives it. */
|
|
204
|
+
#define SHM_SLOT_OPS(BITS, T, U) \
|
|
205
|
+
T nishshm_load_i##BITS(int32_t h, int32_t at, int32_t order) { \
|
|
206
|
+
T *p = (T *)slot_at(h, at, BITS / 8); \
|
|
207
|
+
order_check(order, SHM_LOAD); \
|
|
208
|
+
switch (order) { \
|
|
209
|
+
case __ATOMIC_RELAXED: return __atomic_load_n(p, __ATOMIC_RELAXED); \
|
|
210
|
+
case __ATOMIC_ACQUIRE: return __atomic_load_n(p, __ATOMIC_ACQUIRE); \
|
|
211
|
+
default: return __atomic_load_n(p, __ATOMIC_SEQ_CST); \
|
|
212
|
+
} \
|
|
213
|
+
} \
|
|
214
|
+
void nishshm_store_i##BITS(int32_t h, int32_t at, T v, int32_t order) { \
|
|
215
|
+
T *p = (T *)slot_at(h, at, BITS / 8); \
|
|
216
|
+
order_check(order, SHM_STORE); \
|
|
217
|
+
switch (order) { \
|
|
218
|
+
case __ATOMIC_RELAXED: __atomic_store_n(p, v, __ATOMIC_RELAXED); break; \
|
|
219
|
+
case __ATOMIC_RELEASE: __atomic_store_n(p, v, __ATOMIC_RELEASE); break; \
|
|
220
|
+
default: __atomic_store_n(p, v, __ATOMIC_SEQ_CST); break; \
|
|
221
|
+
} \
|
|
222
|
+
} \
|
|
223
|
+
/* The value before the add, which wraps: it is done unsigned. */ \
|
|
224
|
+
T nishshm_add_i##BITS(int32_t h, int32_t at, T delta, int32_t order) { \
|
|
225
|
+
U *p = (U *)slot_at(h, at, BITS / 8); \
|
|
226
|
+
order_check(order, SHM_RMW); \
|
|
227
|
+
switch (order) { \
|
|
228
|
+
case __ATOMIC_RELAXED: return (T)__atomic_fetch_add(p, (U)delta, __ATOMIC_RELAXED); \
|
|
229
|
+
case __ATOMIC_ACQUIRE: return (T)__atomic_fetch_add(p, (U)delta, __ATOMIC_ACQUIRE); \
|
|
230
|
+
case __ATOMIC_RELEASE: return (T)__atomic_fetch_add(p, (U)delta, __ATOMIC_RELEASE); \
|
|
231
|
+
case __ATOMIC_ACQ_REL: return (T)__atomic_fetch_add(p, (U)delta, __ATOMIC_ACQ_REL); \
|
|
232
|
+
default: return (T)__atomic_fetch_add(p, (U)delta, __ATOMIC_SEQ_CST); \
|
|
233
|
+
} \
|
|
234
|
+
} \
|
|
235
|
+
/* The value before the exchange. */ \
|
|
236
|
+
T nishshm_exchange_i##BITS(int32_t h, int32_t at, T v, int32_t order) { \
|
|
237
|
+
T *p = (T *)slot_at(h, at, BITS / 8); \
|
|
238
|
+
order_check(order, SHM_RMW); \
|
|
239
|
+
switch (order) { \
|
|
240
|
+
case __ATOMIC_RELAXED: return __atomic_exchange_n(p, v, __ATOMIC_RELAXED); \
|
|
241
|
+
case __ATOMIC_ACQUIRE: return __atomic_exchange_n(p, v, __ATOMIC_ACQUIRE); \
|
|
242
|
+
case __ATOMIC_RELEASE: return __atomic_exchange_n(p, v, __ATOMIC_RELEASE); \
|
|
243
|
+
case __ATOMIC_ACQ_REL: return __atomic_exchange_n(p, v, __ATOMIC_ACQ_REL); \
|
|
244
|
+
default: return __atomic_exchange_n(p, v, __ATOMIC_SEQ_CST); \
|
|
245
|
+
} \
|
|
246
|
+
} \
|
|
247
|
+
/* The value the slot held: the exchange happened when it equals `expected`. */ \
|
|
248
|
+
T nishshm_cas_i##BITS(int32_t h, int32_t at, T expected, T desired, int32_t order) { \
|
|
249
|
+
T *p = (T *)slot_at(h, at, BITS / 8); \
|
|
250
|
+
T seen = expected; \
|
|
251
|
+
order_check(order, SHM_RMW); \
|
|
252
|
+
switch (order) { \
|
|
253
|
+
case __ATOMIC_RELAXED: \
|
|
254
|
+
__atomic_compare_exchange_n(p, &seen, desired, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED); \
|
|
255
|
+
break; \
|
|
256
|
+
case __ATOMIC_ACQUIRE: \
|
|
257
|
+
__atomic_compare_exchange_n(p, &seen, desired, 0, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE); \
|
|
258
|
+
break; \
|
|
259
|
+
case __ATOMIC_RELEASE: \
|
|
260
|
+
__atomic_compare_exchange_n(p, &seen, desired, 0, __ATOMIC_RELEASE, __ATOMIC_RELAXED); \
|
|
261
|
+
break; \
|
|
262
|
+
case __ATOMIC_ACQ_REL: \
|
|
263
|
+
__atomic_compare_exchange_n(p, &seen, desired, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE); \
|
|
264
|
+
break; \
|
|
265
|
+
default: \
|
|
266
|
+
__atomic_compare_exchange_n(p, &seen, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); \
|
|
267
|
+
break; \
|
|
268
|
+
} \
|
|
269
|
+
return seen; \
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
SHM_SLOT_OPS(32, int32_t, uint32_t)
|
|
273
|
+
SHM_SLOT_OPS(64, int64_t, uint64_t)
|
|
274
|
+
|
|
275
|
+
/* A fence of `order`, which a read-modify-write would take. */
|
|
276
|
+
void nishshm_fence(int32_t order) {
|
|
277
|
+
order_check(order, SHM_RMW);
|
|
278
|
+
switch (order) {
|
|
279
|
+
case __ATOMIC_RELAXED: break;
|
|
280
|
+
case __ATOMIC_ACQUIRE: __atomic_thread_fence(__ATOMIC_ACQUIRE); break;
|
|
281
|
+
case __ATOMIC_RELEASE: __atomic_thread_fence(__ATOMIC_RELEASE); break;
|
|
282
|
+
case __ATOMIC_ACQ_REL: __atomic_thread_fence(__ATOMIC_ACQ_REL); break;
|
|
283
|
+
default: __atomic_thread_fence(__ATOMIC_SEQ_CST); break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* -- bytes ----------------------------------------------------------------- */
|
|
288
|
+
|
|
289
|
+
/* The window `[at, at + len)` of the region, checked against it, and
|
|
290
|
+
`[off, off + len)` of a program buffer of `n` bytes, checked against that. */
|
|
291
|
+
static inline uint8_t *window(int32_t h, int64_t at, uint64_t n, uint64_t off, uint64_t len) {
|
|
292
|
+
nishshm_region *r = region_checked(h);
|
|
293
|
+
if (__builtin_expect(at < 0 || (uint64_t)at > r->size || len > r->size - (uint64_t)at, 0))
|
|
294
|
+
shm_fail("byte window at ", at, " is outside the region of ", (int64_t)r->size, " bytes");
|
|
295
|
+
if (__builtin_expect(off > n || len > n - off, 0))
|
|
296
|
+
shm_fail("buffer window at ", (int64_t)off, " is outside the buffer of ", (int64_t)n, " bytes");
|
|
297
|
+
return r->base + at;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Copies `len` bytes of the region at `at` into `dst[off..]`. The bytes are
|
|
301
|
+
copied once, so a writer in another process can tear them but never make
|
|
302
|
+
the copy read past what was checked. */
|
|
303
|
+
void nishshm_read(int32_t h, int32_t at, uint8_t *dst, uint64_t n, uint64_t off, uint64_t len) {
|
|
304
|
+
uint8_t *src = window(h, at, n, off, len);
|
|
305
|
+
memcpy(dst + off, src, len);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Copies `src[off, off + len)` into the region at `at`. */
|
|
309
|
+
void nishshm_write(int32_t h, int32_t at, const uint8_t *src, uint64_t n, uint64_t off,
|
|
310
|
+
uint64_t len) {
|
|
311
|
+
uint8_t *dst = window(h, at, n, off, len);
|
|
312
|
+
memcpy(dst, src + off, len);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* -- regions ----------------------------------------------------------------- */
|
|
316
|
+
|
|
317
|
+
int32_t nishshm_size(int32_t h) { return (int32_t)region_checked(h)->size; }
|
|
318
|
+
|
|
319
|
+
#ifdef __wasi__
|
|
320
|
+
|
|
321
|
+
int32_t nishshm_create(const char *name, int32_t bytes) {
|
|
322
|
+
(void)name;
|
|
323
|
+
(void)bytes;
|
|
324
|
+
return -95;
|
|
325
|
+
}
|
|
326
|
+
int32_t nishshm_open(const char *name) {
|
|
327
|
+
(void)name;
|
|
328
|
+
return -95;
|
|
329
|
+
}
|
|
330
|
+
int32_t nishshm_unlink(const char *name) {
|
|
331
|
+
(void)name;
|
|
332
|
+
return -95;
|
|
333
|
+
}
|
|
334
|
+
int32_t nishshm_close(int32_t h) {
|
|
335
|
+
(void)h;
|
|
336
|
+
return -9;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#else
|
|
340
|
+
|
|
341
|
+
/* 1 if `name` is 1..30 bytes of [A-Za-z0-9._-] not starting with '.'. */
|
|
342
|
+
static int name_ok(const char *name) {
|
|
343
|
+
size_t i = 0;
|
|
344
|
+
if (name[0] == '.') return 0;
|
|
345
|
+
for (; name[i]; i++) {
|
|
346
|
+
char c = name[i];
|
|
347
|
+
int ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
|
|
348
|
+
c == '.' || c == '_' || c == '-';
|
|
349
|
+
if (!ok || i >= NISHSHM_NAME_MAX) return 0;
|
|
350
|
+
}
|
|
351
|
+
return i > 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* The path the object is opened by: `/dev/shm/<name>` on Linux, which is
|
|
355
|
+
what glibc's shm_open opens and needs no librt, and `/<name>` on Darwin. */
|
|
356
|
+
static void path_of(char *out, const char *name) {
|
|
357
|
+
#ifdef __linux__
|
|
358
|
+
memcpy(out, "/dev/shm/", 9);
|
|
359
|
+
strcpy(out + 9, name);
|
|
360
|
+
#else
|
|
361
|
+
out[0] = '/';
|
|
362
|
+
strcpy(out + 1, name);
|
|
363
|
+
#endif
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
static int shm_open_path(const char *path, int flags, mode_t mode) {
|
|
367
|
+
#ifdef __linux__
|
|
368
|
+
return open(path, flags, mode);
|
|
369
|
+
#else
|
|
370
|
+
return shm_open(path, flags, mode);
|
|
371
|
+
#endif
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* A free slot of the table, or 0 when all are in use or retired, or when
|
|
375
|
+
this thread's table could not be mapped. */
|
|
376
|
+
static int32_t slot_take(void) {
|
|
377
|
+
if (nishshm_table == NULL) {
|
|
378
|
+
void *t = mmap(NULL, sizeof(nishshm_region) * NISHSHM_SLOTS, PROT_READ | PROT_WRITE,
|
|
379
|
+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
380
|
+
if (t == MAP_FAILED) return 0;
|
|
381
|
+
nishshm_table = (nishshm_region *)t; /* the kernel gives it zeroed */
|
|
382
|
+
}
|
|
383
|
+
for (int32_t i = 1; i < NISHSHM_SLOTS; i++) {
|
|
384
|
+
nishshm_region *r = &nishshm_table[i];
|
|
385
|
+
if (r->base != NULL) continue;
|
|
386
|
+
if (r->gen == 0) {
|
|
387
|
+
r->gen = 1; /* generations start at 1, so no handle is a bare index */
|
|
388
|
+
return i;
|
|
389
|
+
}
|
|
390
|
+
if (r->gen < NISHSHM_GEN_MAX) return i;
|
|
391
|
+
}
|
|
392
|
+
return 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Maps `fd` (`size` bytes) into a slot and answers its handle. Closes `fd`
|
|
396
|
+
either way: the mapping keeps the object alive. */
|
|
397
|
+
static int32_t adopt(int fd, uint64_t size) {
|
|
398
|
+
int32_t i = slot_take();
|
|
399
|
+
if (i == 0) {
|
|
400
|
+
close(fd);
|
|
401
|
+
return -24;
|
|
402
|
+
}
|
|
403
|
+
void *p = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
404
|
+
int e = errno;
|
|
405
|
+
close(fd);
|
|
406
|
+
if (p == MAP_FAILED) return -e;
|
|
407
|
+
nishshm_table[i].base = (uint8_t *)p;
|
|
408
|
+
nishshm_table[i].size = size;
|
|
409
|
+
return (nishshm_table[i].gen << 16) | i;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* Creates the region `name` of `bytes` zero bytes, mode 0600, and maps it.
|
|
413
|
+
-17 when it exists already, -22 for a bad name or size. O_EXCL and
|
|
414
|
+
O_NOFOLLOW mean a name another user planted in the world-writable
|
|
415
|
+
directory is refused, never adopted. */
|
|
416
|
+
int32_t nishshm_create(const char *name, int32_t bytes) {
|
|
417
|
+
char path[64];
|
|
418
|
+
if (!name_ok(name) || bytes <= 0 || (uint64_t)bytes > NISHSHM_MAX_BYTES) return -22;
|
|
419
|
+
path_of(path, name);
|
|
420
|
+
int fd = shm_open_path(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
|
|
421
|
+
if (fd < 0) return -errno;
|
|
422
|
+
if (ftruncate(fd, (off_t)bytes) != 0) {
|
|
423
|
+
int e = errno;
|
|
424
|
+
close(fd);
|
|
425
|
+
#ifdef __linux__
|
|
426
|
+
unlink(path);
|
|
427
|
+
#else
|
|
428
|
+
shm_unlink(path);
|
|
429
|
+
#endif
|
|
430
|
+
return -e;
|
|
431
|
+
}
|
|
432
|
+
return adopt(fd, (uint64_t)bytes);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* Opens and maps the region `name` some process created. Its size is the
|
|
436
|
+
object's (on Darwin, rounded up to a page). -13 unless this user owns it and
|
|
437
|
+
no one else may read or write it, so a region another user made is never
|
|
438
|
+
trusted; -2 when there is none. */
|
|
439
|
+
int32_t nishshm_open(const char *name) {
|
|
440
|
+
char path[64];
|
|
441
|
+
struct stat st;
|
|
442
|
+
if (!name_ok(name)) return -22;
|
|
443
|
+
path_of(path, name);
|
|
444
|
+
int fd = shm_open_path(path, O_RDWR | O_NOFOLLOW | O_CLOEXEC, 0);
|
|
445
|
+
if (fd < 0) return -errno;
|
|
446
|
+
if (fstat(fd, &st) != 0) {
|
|
447
|
+
int e = errno;
|
|
448
|
+
close(fd);
|
|
449
|
+
return -e;
|
|
450
|
+
}
|
|
451
|
+
if (st.st_uid != geteuid() || (st.st_mode & 077) != 0) {
|
|
452
|
+
close(fd);
|
|
453
|
+
return -13;
|
|
454
|
+
}
|
|
455
|
+
#ifdef __linux__
|
|
456
|
+
/* A second link is a name someone else made for one of our regions: in a
|
|
457
|
+
world-writable /dev/shm without fs.protected_hardlinks, another user can
|
|
458
|
+
link ours under the name we are about to open. */
|
|
459
|
+
if (!S_ISREG(st.st_mode) || st.st_nlink != 1) {
|
|
460
|
+
close(fd);
|
|
461
|
+
return -13;
|
|
462
|
+
}
|
|
463
|
+
#endif
|
|
464
|
+
if (st.st_size <= 0 || (uint64_t)st.st_size > NISHSHM_MAX_BYTES) {
|
|
465
|
+
close(fd);
|
|
466
|
+
return -22;
|
|
467
|
+
}
|
|
468
|
+
return adopt(fd, (uint64_t)st.st_size);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/* Removes the name; every mapping of it stays valid until it is closed. */
|
|
472
|
+
int32_t nishshm_unlink(const char *name) {
|
|
473
|
+
char path[64];
|
|
474
|
+
if (!name_ok(name)) return -22;
|
|
475
|
+
path_of(path, name);
|
|
476
|
+
#ifdef __linux__
|
|
477
|
+
if (unlink(path) != 0) return -errno;
|
|
478
|
+
#else
|
|
479
|
+
if (shm_unlink(path) != 0) return -errno;
|
|
480
|
+
#endif
|
|
481
|
+
return 0;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/* Unmaps the region. -9 for a handle that names nothing, so a second close
|
|
485
|
+
is an error rather than a panic. */
|
|
486
|
+
int32_t nishshm_close(int32_t h) {
|
|
487
|
+
nishshm_region *r = region_of(h);
|
|
488
|
+
if (r == NULL) return -9;
|
|
489
|
+
munmap(r->base, (size_t)r->size);
|
|
490
|
+
r->base = NULL;
|
|
491
|
+
r->size = 0;
|
|
492
|
+
r->gen = r->gen + 1; /* NISHSHM_GEN_MAX retires the slot (`slot_take`) */
|
|
493
|
+
return 0;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
#endif
|
|
497
|
+
|
|
498
|
+
/* -- waiting ----------------------------------------------------------------- */
|
|
499
|
+
|
|
500
|
+
/* Sleeps while the 32-bit slot at `at` holds `expected`, at most `timeout_ms`
|
|
501
|
+
(negative: no limit). 0 when woken, or for no reason the caller can see
|
|
502
|
+
(a signal, a spurious wake): the caller loads the slot again. -11 when the
|
|
503
|
+
slot did not hold `expected`, -110 when the time ran out, -95 where there is
|
|
504
|
+
no cross-process futex. The slot is checked as a load's would be. */
|
|
505
|
+
int32_t nishshm_wait_i32(int32_t h, int32_t at, int32_t expected, int32_t timeout_ms) {
|
|
506
|
+
int32_t *p = (int32_t *)slot_at(h, at, 4);
|
|
507
|
+
#ifdef __linux__
|
|
508
|
+
struct timespec ts, *tp = NULL;
|
|
509
|
+
if (timeout_ms >= 0) {
|
|
510
|
+
ts.tv_sec = (time_t)(timeout_ms / 1000);
|
|
511
|
+
ts.tv_nsec = (long)(timeout_ms % 1000) * 1000000L;
|
|
512
|
+
tp = &ts;
|
|
513
|
+
}
|
|
514
|
+
if (syscall(SYS_futex, p, FUTEX_WAIT, expected, tp, NULL, 0) == 0) return 0;
|
|
515
|
+
switch (errno) {
|
|
516
|
+
case EAGAIN: return -11;
|
|
517
|
+
case ETIMEDOUT: return -110;
|
|
518
|
+
case EINTR: return 0;
|
|
519
|
+
default: return -errno;
|
|
520
|
+
}
|
|
521
|
+
#else
|
|
522
|
+
(void)p;
|
|
523
|
+
(void)expected;
|
|
524
|
+
(void)timeout_ms;
|
|
525
|
+
return -95;
|
|
526
|
+
#endif
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* Wakes at most `count` waiters on the 32-bit slot at `at`, in any process
|
|
530
|
+
that maps the region: the number woken, or -95 as `nishshm_wait_i32`. */
|
|
531
|
+
int32_t nishshm_wake(int32_t h, int32_t at, int32_t count) {
|
|
532
|
+
int32_t *p = (int32_t *)slot_at(h, at, 4);
|
|
533
|
+
#ifdef __linux__
|
|
534
|
+
long n = syscall(SYS_futex, p, FUTEX_WAKE, count < 0 ? 0 : count, NULL, NULL, 0);
|
|
535
|
+
return n < 0 ? -errno : (int32_t)n;
|
|
536
|
+
#else
|
|
537
|
+
(void)p;
|
|
538
|
+
(void)count;
|
|
539
|
+
return -95;
|
|
540
|
+
#endif
|
|
541
|
+
}
|