@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.
Files changed (54) hide show
  1. package/INSTALL.md +16 -8
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +118 -26
  4. package/package.json +1 -1
  5. package/runtime/core/host.d.ts +2 -1
  6. package/runtime/core/host.mjs +4 -0
  7. package/runtime/core.d.ts +47 -0
  8. package/runtime/core.mjs +67 -0
  9. package/runtime/nish.d.ts +176 -0
  10. package/runtime/nish.h +120 -6
  11. package/runtime/nish.mjs +24 -4
  12. package/runtime/runtime-checksum.c +270 -0
  13. package/runtime/runtime-net.c +386 -5
  14. package/runtime/runtime-os.c +59 -8
  15. package/runtime/runtime-parallel.c +81 -5
  16. package/runtime/runtime-sandbox.c +479 -0
  17. package/runtime/runtime-sha256.c +239 -0
  18. package/runtime/runtime-shm.c +541 -0
  19. package/runtime/runtime.c +827 -791
  20. package/scripts/build.sh +79 -28
  21. package/std/README.md +25 -13
  22. package/std/collections.ts +5 -5
  23. package/std/compress/checksum.ts +88 -75
  24. package/std/compress/deflate.ts +224 -0
  25. package/std/compress/gzip.ts +52 -0
  26. package/std/crypto/bcrypt.ts +816 -280
  27. package/std/crypto/hmac.ts +305 -2
  28. package/std/crypto/jwt.ts +1661 -0
  29. package/std/json-equal.ts +335 -0
  30. package/std/json-format.ts +1070 -0
  31. package/std/json-number.ts +428 -0
  32. package/std/json-pointer.ts +245 -0
  33. package/std/json-typed.ts +34 -0
  34. package/std/json-value.ts +510 -17
  35. package/std/json.ts +35 -6
  36. package/std/net/handler.ts +1348 -0
  37. package/std/net/http-client.ts +97 -16
  38. package/std/net/http-compress.ts +301 -16
  39. package/std/net/http1-client.ts +104 -19
  40. package/std/net/http1-server.ts +127 -24
  41. package/std/net/http1.ts +24 -4
  42. package/std/net/outbound.ts +409 -0
  43. package/std/net/postgres.ts +405 -0
  44. package/std/net/prefork.ts +344 -20
  45. package/std/net/websocket.ts +295 -14
  46. package/std/regex.ts +613 -52
  47. package/std/secret.ts +1 -1
  48. package/std/sync/ring.ts +587 -0
  49. package/std/sync/shm.ts +284 -0
  50. package/std/term-io.ts +35 -8
  51. package/std/term-rate.ts +13 -1
  52. package/std/threads.ts +35 -3
  53. package/std/yaml-json.ts +841 -0
  54. /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
+ }