@gjsify/sab-native 0.4.12
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/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/types/index.d.ts +186 -0
- package/lib/types/shared-buffer.gjs.spec.d.ts +2 -0
- package/meson.build +57 -0
- package/package.json +65 -0
- package/prebuilds/linux-aarch64/GjsifySabNative-1.0.gir +430 -0
- package/prebuilds/linux-aarch64/GjsifySabNative-1.0.typelib +0 -0
- package/prebuilds/linux-aarch64/libgjsifysabnative.so +0 -0
- package/prebuilds/linux-ppc64/GjsifySabNative-1.0.gir +430 -0
- package/prebuilds/linux-ppc64/GjsifySabNative-1.0.typelib +0 -0
- package/prebuilds/linux-ppc64/libgjsifysabnative.so +0 -0
- package/prebuilds/linux-riscv64/GjsifySabNative-1.0.gir +429 -0
- package/prebuilds/linux-riscv64/GjsifySabNative-1.0.typelib +0 -0
- package/prebuilds/linux-riscv64/libgjsifysabnative.so +0 -0
- package/prebuilds/linux-s390x/GjsifySabNative-1.0.gir +430 -0
- package/prebuilds/linux-s390x/GjsifySabNative-1.0.typelib +0 -0
- package/prebuilds/linux-s390x/libgjsifysabnative.so +0 -0
- package/prebuilds/linux-x86_64/GjsifySabNative-1.0.gir +430 -0
- package/prebuilds/linux-x86_64/GjsifySabNative-1.0.typelib +0 -0
- package/prebuilds/linux-x86_64/libgjsifysabnative.so +0 -0
- package/src/vala/fd-passing.vala +101 -0
- package/src/vala/sab-helpers.c +461 -0
- package/src/vala/sab-helpers.h +150 -0
- package/src/vala/shared-buffer.vala +201 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* sab-helpers.c — see sab-helpers.h for the contract.
|
|
3
|
+
*
|
|
4
|
+
* Linux-only. Compile with -D_GNU_SOURCE so memfd_create, MFD_CLOEXEC,
|
|
5
|
+
* and the FUTEX_*_PRIVATE constants are visible.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "sab-helpers.h"
|
|
9
|
+
|
|
10
|
+
#include <errno.h>
|
|
11
|
+
#include <fcntl.h>
|
|
12
|
+
#include <linux/futex.h>
|
|
13
|
+
#include <stdatomic.h>
|
|
14
|
+
#include <stdint.h>
|
|
15
|
+
#include <string.h>
|
|
16
|
+
#include <sys/mman.h>
|
|
17
|
+
#include <sys/socket.h>
|
|
18
|
+
#include <sys/syscall.h>
|
|
19
|
+
#include <sys/time.h>
|
|
20
|
+
#include <sys/types.h>
|
|
21
|
+
#include <sys/un.h>
|
|
22
|
+
#include <time.h>
|
|
23
|
+
#include <unistd.h>
|
|
24
|
+
|
|
25
|
+
/* glibc < 2.27 doesn't expose memfd_create() as a libc wrapper. We use the
|
|
26
|
+
* raw syscall to stay portable across the prebuild distro matrix. */
|
|
27
|
+
#ifndef MFD_CLOEXEC
|
|
28
|
+
# define MFD_CLOEXEC 0x0001U
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
static int
|
|
32
|
+
gjsify_memfd_create (const char *name, unsigned int flags)
|
|
33
|
+
{
|
|
34
|
+
return (int) syscall (SYS_memfd_create, name, flags);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
38
|
+
* GjsifySabRegion: opaque shared-memory region
|
|
39
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
40
|
+
|
|
41
|
+
struct _GjsifySabRegion {
|
|
42
|
+
void *ptr;
|
|
43
|
+
gsize size;
|
|
44
|
+
gint fd;
|
|
45
|
+
/* Refcount for GBytes-borrow lifetimes (read_bytes returns a GBytes that
|
|
46
|
+
* pins the region until the bytes object is released). Strong count is
|
|
47
|
+
* always ≥ 1 (the JS-side SharedBuffer owns one); each outstanding GBytes
|
|
48
|
+
* adds one more. */
|
|
49
|
+
gint refcount;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
static GjsifySabRegion *
|
|
53
|
+
region_ref (GjsifySabRegion *region)
|
|
54
|
+
{
|
|
55
|
+
g_atomic_int_inc (®ion->refcount);
|
|
56
|
+
return region;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static void
|
|
60
|
+
region_unref (gpointer data)
|
|
61
|
+
{
|
|
62
|
+
GjsifySabRegion *region = (GjsifySabRegion *) data;
|
|
63
|
+
if (!g_atomic_int_dec_and_test (®ion->refcount)) return;
|
|
64
|
+
|
|
65
|
+
if (region->ptr != NULL && region->ptr != MAP_FAILED) {
|
|
66
|
+
munmap (region->ptr, region->size);
|
|
67
|
+
}
|
|
68
|
+
if (region->fd >= 0) {
|
|
69
|
+
close (region->fd);
|
|
70
|
+
}
|
|
71
|
+
g_slice_free (GjsifySabRegion, region);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
GjsifySabRegion *
|
|
75
|
+
gjsify_sab_region_new_anonymous (gsize size)
|
|
76
|
+
{
|
|
77
|
+
if (size == 0) { errno = EINVAL; return NULL; }
|
|
78
|
+
|
|
79
|
+
int fd = gjsify_memfd_create ("gjsify-sab", MFD_CLOEXEC);
|
|
80
|
+
if (fd < 0) return NULL;
|
|
81
|
+
|
|
82
|
+
if (ftruncate (fd, (off_t) size) < 0) {
|
|
83
|
+
int e = errno;
|
|
84
|
+
close (fd);
|
|
85
|
+
errno = e;
|
|
86
|
+
return NULL;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
void *ptr = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
90
|
+
if (ptr == MAP_FAILED) {
|
|
91
|
+
int e = errno;
|
|
92
|
+
close (fd);
|
|
93
|
+
errno = e;
|
|
94
|
+
return NULL;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
GjsifySabRegion *region = g_slice_new0 (GjsifySabRegion);
|
|
98
|
+
region->ptr = ptr;
|
|
99
|
+
region->size = size;
|
|
100
|
+
region->fd = fd;
|
|
101
|
+
region->refcount = 1;
|
|
102
|
+
return region;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
GjsifySabRegion *
|
|
106
|
+
gjsify_sab_region_new_from_fd (gint fd, gsize size)
|
|
107
|
+
{
|
|
108
|
+
if (fd < 0 || size == 0) { errno = EINVAL; return NULL; }
|
|
109
|
+
|
|
110
|
+
/* dup so the caller can close their copy without unmapping ours. */
|
|
111
|
+
int dup_fd = fcntl (fd, F_DUPFD_CLOEXEC, 0);
|
|
112
|
+
if (dup_fd < 0) return NULL;
|
|
113
|
+
|
|
114
|
+
void *ptr = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, dup_fd, 0);
|
|
115
|
+
if (ptr == MAP_FAILED) {
|
|
116
|
+
int e = errno;
|
|
117
|
+
close (dup_fd);
|
|
118
|
+
errno = e;
|
|
119
|
+
return NULL;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
GjsifySabRegion *region = g_slice_new0 (GjsifySabRegion);
|
|
123
|
+
region->ptr = ptr;
|
|
124
|
+
region->size = size;
|
|
125
|
+
region->fd = dup_fd;
|
|
126
|
+
region->refcount = 1;
|
|
127
|
+
return region;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
void
|
|
131
|
+
gjsify_sab_region_free (GjsifySabRegion *region)
|
|
132
|
+
{
|
|
133
|
+
if (region == NULL) return;
|
|
134
|
+
region_unref (region);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
gint gjsify_sab_region_get_fd (const GjsifySabRegion *region) { return region->fd; }
|
|
138
|
+
gsize gjsify_sab_region_get_size (const GjsifySabRegion *region) { return region->size; }
|
|
139
|
+
|
|
140
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
141
|
+
* Plain read / write
|
|
142
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
143
|
+
|
|
144
|
+
#define BOUNDS_CHECK(reg, off, sz) do { \
|
|
145
|
+
if (G_UNLIKELY ((off) + (sz) > (reg)->size)) { \
|
|
146
|
+
g_error ("gjsify-sab: out-of-bounds access (offset=%" G_GSIZE_FORMAT \
|
|
147
|
+
", size=%zu, region=%" G_GSIZE_FORMAT ")", (gsize)(off), (size_t)(sz), \
|
|
148
|
+
(reg)->size); \
|
|
149
|
+
} \
|
|
150
|
+
} while (0)
|
|
151
|
+
|
|
152
|
+
guint8
|
|
153
|
+
gjsify_sab_region_read_u8 (const GjsifySabRegion *region, gsize offset)
|
|
154
|
+
{
|
|
155
|
+
BOUNDS_CHECK (region, offset, 1);
|
|
156
|
+
return ((const guint8 *) region->ptr)[offset];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
void
|
|
160
|
+
gjsify_sab_region_write_u8 (GjsifySabRegion *region, gsize offset, guint8 v)
|
|
161
|
+
{
|
|
162
|
+
BOUNDS_CHECK (region, offset, 1);
|
|
163
|
+
((guint8 *) region->ptr)[offset] = v;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
guint32
|
|
167
|
+
gjsify_sab_region_read_u32_le (const GjsifySabRegion *region, gsize offset)
|
|
168
|
+
{
|
|
169
|
+
BOUNDS_CHECK (region, offset, 4);
|
|
170
|
+
guint32 v;
|
|
171
|
+
memcpy (&v, (const guint8 *) region->ptr + offset, 4);
|
|
172
|
+
return GUINT32_FROM_LE (v);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void
|
|
176
|
+
gjsify_sab_region_write_u32_le (GjsifySabRegion *region, gsize offset, guint32 v)
|
|
177
|
+
{
|
|
178
|
+
BOUNDS_CHECK (region, offset, 4);
|
|
179
|
+
guint32 le = GUINT32_TO_LE (v);
|
|
180
|
+
memcpy ((guint8 *) region->ptr + offset, &le, 4);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
gint32
|
|
184
|
+
gjsify_sab_region_read_i32_le (const GjsifySabRegion *region, gsize offset)
|
|
185
|
+
{
|
|
186
|
+
BOUNDS_CHECK (region, offset, 4);
|
|
187
|
+
guint32 u;
|
|
188
|
+
memcpy (&u, (const guint8 *) region->ptr + offset, 4);
|
|
189
|
+
u = GUINT32_FROM_LE (u);
|
|
190
|
+
gint32 i;
|
|
191
|
+
memcpy (&i, &u, 4);
|
|
192
|
+
return i;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
void
|
|
196
|
+
gjsify_sab_region_write_i32_le (GjsifySabRegion *region, gsize offset, gint32 v)
|
|
197
|
+
{
|
|
198
|
+
BOUNDS_CHECK (region, offset, 4);
|
|
199
|
+
guint32 u;
|
|
200
|
+
memcpy (&u, &v, 4);
|
|
201
|
+
u = GUINT32_TO_LE (u);
|
|
202
|
+
memcpy ((guint8 *) region->ptr + offset, &u, 4);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
guint64
|
|
206
|
+
gjsify_sab_region_read_u64_le (const GjsifySabRegion *region, gsize offset)
|
|
207
|
+
{
|
|
208
|
+
BOUNDS_CHECK (region, offset, 8);
|
|
209
|
+
guint64 v;
|
|
210
|
+
memcpy (&v, (const guint8 *) region->ptr + offset, 8);
|
|
211
|
+
return GUINT64_FROM_LE (v);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
void
|
|
215
|
+
gjsify_sab_region_write_u64_le (GjsifySabRegion *region, gsize offset, guint64 v)
|
|
216
|
+
{
|
|
217
|
+
BOUNDS_CHECK (region, offset, 8);
|
|
218
|
+
guint64 le = GUINT64_TO_LE (v);
|
|
219
|
+
memcpy ((guint8 *) region->ptr + offset, &le, 8);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
223
|
+
* Bulk read / write (zero-copy for read, memcpy for write)
|
|
224
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
225
|
+
|
|
226
|
+
GBytes *
|
|
227
|
+
gjsify_sab_region_read_bytes (GjsifySabRegion *region, gsize offset, gsize length)
|
|
228
|
+
{
|
|
229
|
+
BOUNDS_CHECK (region, offset, length);
|
|
230
|
+
/* GBytes wraps the mmap'd memory without copying. We hand the GBytes a
|
|
231
|
+
* region_ref so the mmap survives until the bytes object is released. */
|
|
232
|
+
region_ref (region);
|
|
233
|
+
return g_bytes_new_with_free_func ((const guint8 *) region->ptr + offset,
|
|
234
|
+
length,
|
|
235
|
+
region_unref,
|
|
236
|
+
region);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
void
|
|
240
|
+
gjsify_sab_region_write_bytes (GjsifySabRegion *region, gsize offset, GBytes *data)
|
|
241
|
+
{
|
|
242
|
+
gsize len = 0;
|
|
243
|
+
gconstpointer src = g_bytes_get_data (data, &len);
|
|
244
|
+
BOUNDS_CHECK (region, offset, len);
|
|
245
|
+
if (len > 0) memcpy ((guint8 *) region->ptr + offset, src, len);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
249
|
+
* Atomics: __atomic_* builtins with SEQ_CST memory order
|
|
250
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
251
|
+
|
|
252
|
+
static inline int32_t *
|
|
253
|
+
i32_ptr (const GjsifySabRegion *region, gsize offset)
|
|
254
|
+
{
|
|
255
|
+
BOUNDS_CHECK (region, offset, 4);
|
|
256
|
+
return (int32_t *) ((guint8 *) region->ptr + offset);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
gint32
|
|
260
|
+
gjsify_sab_region_atomic_add_i32 (GjsifySabRegion *region, gsize offset, gint32 v)
|
|
261
|
+
{
|
|
262
|
+
/* fetch_add: returns the value BEFORE the addition. */
|
|
263
|
+
return __atomic_fetch_add (i32_ptr (region, offset), v, __ATOMIC_SEQ_CST);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
gint32
|
|
267
|
+
gjsify_sab_region_atomic_sub_i32 (GjsifySabRegion *region, gsize offset, gint32 v)
|
|
268
|
+
{
|
|
269
|
+
return __atomic_fetch_sub (i32_ptr (region, offset), v, __ATOMIC_SEQ_CST);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
gint32
|
|
273
|
+
gjsify_sab_region_atomic_load_i32 (const GjsifySabRegion *region, gsize offset)
|
|
274
|
+
{
|
|
275
|
+
return __atomic_load_n (i32_ptr (region, offset), __ATOMIC_SEQ_CST);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
void
|
|
279
|
+
gjsify_sab_region_atomic_store_i32 (GjsifySabRegion *region, gsize offset, gint32 v)
|
|
280
|
+
{
|
|
281
|
+
__atomic_store_n (i32_ptr (region, offset), v, __ATOMIC_SEQ_CST);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
gint32
|
|
285
|
+
gjsify_sab_region_atomic_xchg_i32 (GjsifySabRegion *region, gsize offset, gint32 v)
|
|
286
|
+
{
|
|
287
|
+
return __atomic_exchange_n (i32_ptr (region, offset), v, __ATOMIC_SEQ_CST);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
gboolean
|
|
291
|
+
gjsify_sab_region_atomic_cmpxchg_i32 (GjsifySabRegion *region,
|
|
292
|
+
gsize offset,
|
|
293
|
+
gint32 *expected,
|
|
294
|
+
gint32 desired)
|
|
295
|
+
{
|
|
296
|
+
/* weak=FALSE so spurious failures don't happen — JS caller expects a
|
|
297
|
+
* strong CAS. */
|
|
298
|
+
return __atomic_compare_exchange_n (i32_ptr (region, offset),
|
|
299
|
+
expected, desired,
|
|
300
|
+
FALSE,
|
|
301
|
+
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
|
|
302
|
+
? TRUE : FALSE;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
306
|
+
* Futex wait / wake
|
|
307
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
308
|
+
|
|
309
|
+
gint
|
|
310
|
+
gjsify_sab_region_futex_wait (GjsifySabRegion *region,
|
|
311
|
+
gsize offset,
|
|
312
|
+
gint32 expected,
|
|
313
|
+
gint64 timeout_ms)
|
|
314
|
+
{
|
|
315
|
+
BOUNDS_CHECK (region, offset, 4);
|
|
316
|
+
int32_t *addr = (int32_t *) ((guint8 *) region->ptr + offset);
|
|
317
|
+
|
|
318
|
+
struct timespec ts;
|
|
319
|
+
struct timespec *ts_ptr = NULL;
|
|
320
|
+
if (timeout_ms >= 0) {
|
|
321
|
+
ts.tv_sec = timeout_ms / 1000;
|
|
322
|
+
ts.tv_nsec = (timeout_ms % 1000) * 1000000L;
|
|
323
|
+
ts_ptr = &ts;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* FUTEX_WAIT (no _PRIVATE flag) so the kernel keys by underlying
|
|
327
|
+
* physical page rather than the calling process's virtual address.
|
|
328
|
+
* SharedBuffer regions are mmap'd at different addresses in each
|
|
329
|
+
* Worker, but back the same physical pages via memfd_create —
|
|
330
|
+
* FUTEX_WAIT_PRIVATE would fail to match across processes. */
|
|
331
|
+
long ret = syscall (SYS_futex, addr, FUTEX_WAIT,
|
|
332
|
+
(int) expected, ts_ptr, NULL, 0);
|
|
333
|
+
if (ret == 0) return 0; /* woken via FUTEX_WAKE */
|
|
334
|
+
int e = errno;
|
|
335
|
+
if (e == EAGAIN) return -1; /* value didn't match expected */
|
|
336
|
+
if (e == ETIMEDOUT) return -2;
|
|
337
|
+
if (e == EINTR) return -3;
|
|
338
|
+
return -e;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
gint
|
|
342
|
+
gjsify_sab_region_futex_wake (GjsifySabRegion *region,
|
|
343
|
+
gsize offset,
|
|
344
|
+
gint count)
|
|
345
|
+
{
|
|
346
|
+
BOUNDS_CHECK (region, offset, 4);
|
|
347
|
+
int32_t *addr = (int32_t *) ((guint8 *) region->ptr + offset);
|
|
348
|
+
long ret = syscall (SYS_futex, addr, FUTEX_WAKE, count,
|
|
349
|
+
NULL, NULL, 0);
|
|
350
|
+
return (gint) ret;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
354
|
+
* IPC side-channel: socketpair + SCM_RIGHTS
|
|
355
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
356
|
+
|
|
357
|
+
gboolean
|
|
358
|
+
gjsify_sab_socketpair (gint *parent_fd, gint *child_fd)
|
|
359
|
+
{
|
|
360
|
+
int sv[2];
|
|
361
|
+
if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sv) < 0) {
|
|
362
|
+
return FALSE;
|
|
363
|
+
}
|
|
364
|
+
*parent_fd = sv[0];
|
|
365
|
+
*child_fd = sv[1];
|
|
366
|
+
return TRUE;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
gboolean
|
|
370
|
+
gjsify_sab_send_fd (gint socket_fd, gint fd_to_send, guint32 tag)
|
|
371
|
+
{
|
|
372
|
+
struct msghdr msg;
|
|
373
|
+
struct iovec iov;
|
|
374
|
+
union {
|
|
375
|
+
char buf[CMSG_SPACE (sizeof (int))];
|
|
376
|
+
struct cmsghdr align;
|
|
377
|
+
} cmsg_buf;
|
|
378
|
+
|
|
379
|
+
/* Payload: 4-byte big-endian tag. */
|
|
380
|
+
guint32 tag_be = GUINT32_TO_BE (tag);
|
|
381
|
+
|
|
382
|
+
memset (&msg, 0, sizeof msg);
|
|
383
|
+
memset (&cmsg_buf, 0, sizeof cmsg_buf);
|
|
384
|
+
|
|
385
|
+
iov.iov_base = &tag_be;
|
|
386
|
+
iov.iov_len = sizeof tag_be;
|
|
387
|
+
msg.msg_iov = &iov;
|
|
388
|
+
msg.msg_iovlen = 1;
|
|
389
|
+
msg.msg_control = cmsg_buf.buf;
|
|
390
|
+
msg.msg_controllen = sizeof cmsg_buf.buf;
|
|
391
|
+
|
|
392
|
+
struct cmsghdr *cmsg = CMSG_FIRSTHDR (&msg);
|
|
393
|
+
cmsg->cmsg_level = SOL_SOCKET;
|
|
394
|
+
cmsg->cmsg_type = SCM_RIGHTS;
|
|
395
|
+
cmsg->cmsg_len = CMSG_LEN (sizeof (int));
|
|
396
|
+
memcpy (CMSG_DATA (cmsg), &fd_to_send, sizeof (int));
|
|
397
|
+
|
|
398
|
+
ssize_t n;
|
|
399
|
+
do {
|
|
400
|
+
n = sendmsg (socket_fd, &msg, MSG_NOSIGNAL);
|
|
401
|
+
} while (n < 0 && errno == EINTR);
|
|
402
|
+
return n >= 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
gint
|
|
406
|
+
gjsify_sab_recv_fd (gint socket_fd, guint32 *tag)
|
|
407
|
+
{
|
|
408
|
+
struct msghdr msg;
|
|
409
|
+
struct iovec iov;
|
|
410
|
+
union {
|
|
411
|
+
char buf[CMSG_SPACE (sizeof (int))];
|
|
412
|
+
struct cmsghdr align;
|
|
413
|
+
} cmsg_buf;
|
|
414
|
+
|
|
415
|
+
guint32 tag_be = 0;
|
|
416
|
+
|
|
417
|
+
memset (&msg, 0, sizeof msg);
|
|
418
|
+
memset (&cmsg_buf, 0, sizeof cmsg_buf);
|
|
419
|
+
|
|
420
|
+
iov.iov_base = &tag_be;
|
|
421
|
+
iov.iov_len = sizeof tag_be;
|
|
422
|
+
msg.msg_iov = &iov;
|
|
423
|
+
msg.msg_iovlen = 1;
|
|
424
|
+
msg.msg_control = cmsg_buf.buf;
|
|
425
|
+
msg.msg_controllen = sizeof cmsg_buf.buf;
|
|
426
|
+
|
|
427
|
+
ssize_t n;
|
|
428
|
+
do {
|
|
429
|
+
n = recvmsg (socket_fd, &msg, MSG_CMSG_CLOEXEC);
|
|
430
|
+
} while (n < 0 && errno == EINTR);
|
|
431
|
+
|
|
432
|
+
if (n == 0) return 0; /* orderly EOF */
|
|
433
|
+
if (n < 0) return -1; /* error — errno preserved */
|
|
434
|
+
|
|
435
|
+
*tag = GUINT32_FROM_BE (tag_be);
|
|
436
|
+
|
|
437
|
+
/* Extract the fd from the control message. */
|
|
438
|
+
for (struct cmsghdr *cm = CMSG_FIRSTHDR (&msg); cm != NULL; cm = CMSG_NXTHDR (&msg, cm)) {
|
|
439
|
+
if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) {
|
|
440
|
+
int fd;
|
|
441
|
+
memcpy (&fd, CMSG_DATA (cm), sizeof (int));
|
|
442
|
+
return fd;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* No fd in the message — protocol violation. */
|
|
447
|
+
errno = EBADMSG;
|
|
448
|
+
return -1;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
gboolean
|
|
452
|
+
gjsify_sab_close_fd (gint fd)
|
|
453
|
+
{
|
|
454
|
+
if (fd < 0) return FALSE;
|
|
455
|
+
int r;
|
|
456
|
+
do {
|
|
457
|
+
r = close (fd);
|
|
458
|
+
} while (r < 0 && errno == EINTR);
|
|
459
|
+
/* EBADF: already closed (or never opened) — caller's intent satisfied. */
|
|
460
|
+
return r == 0 || errno == EBADF;
|
|
461
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tiny C shim around Linux shared-memory + atomics primitives that Vala
|
|
3
|
+
* can't express cleanly:
|
|
4
|
+
*
|
|
5
|
+
* - memfd_create(2) — anonymous shared-memory fd (no path needed)
|
|
6
|
+
* - mmap(2) / munmap(2) — MAP_SHARED region creation
|
|
7
|
+
* - SYS_futex (FUTEX_WAIT_PRIVATE / FUTEX_WAKE_PRIVATE) — wait/notify
|
|
8
|
+
* - __atomic_* GCC builtins — load/store/add/cmpxchg with SEQ_CST
|
|
9
|
+
* - socketpair(AF_UNIX, SOCK_SEQPACKET) — IPC side-channel for fd-passing
|
|
10
|
+
* - sendmsg/recvmsg + SCM_RIGHTS — cross-process fd transfer
|
|
11
|
+
*
|
|
12
|
+
* Same design as @gjsify/http2-native: opaque struct (full definition in .c),
|
|
13
|
+
* `gjsify_sab_<verb>` naming, `_free` paired with `_new`. Buffer ownership
|
|
14
|
+
* stays C-side; JS only sees the GObject wrapper from shared-buffer.vala
|
|
15
|
+
* and the GBytes objects returned by read_bytes (which borrow the mmap'd
|
|
16
|
+
* memory, so callers can do zero-copy reads via imports.byteArray.toArray).
|
|
17
|
+
*
|
|
18
|
+
* Reference: man 2 memfd_create, man 2 mmap, man 2 futex, man 3 cmsg.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
#ifndef GJSIFY_SAB_HELPERS_H
|
|
22
|
+
#define GJSIFY_SAB_HELPERS_H
|
|
23
|
+
|
|
24
|
+
#include <glib.h>
|
|
25
|
+
#include <glib-object.h>
|
|
26
|
+
|
|
27
|
+
G_BEGIN_DECLS
|
|
28
|
+
|
|
29
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
30
|
+
* Shared-memory region (opaque)
|
|
31
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
32
|
+
|
|
33
|
+
typedef struct _GjsifySabRegion GjsifySabRegion;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* gjsify_sab_region_new_anonymous:
|
|
37
|
+
* @size: byte length of the region (multiple-of-page strongly preferred)
|
|
38
|
+
*
|
|
39
|
+
* Allocate a fresh memfd, ftruncate it to @size, and mmap it MAP_SHARED.
|
|
40
|
+
* Region owns the fd and will close+munmap it on free. Returns %NULL on
|
|
41
|
+
* any syscall failure (errno preserved).
|
|
42
|
+
*/
|
|
43
|
+
GjsifySabRegion *gjsify_sab_region_new_anonymous (gsize size);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* gjsify_sab_region_new_from_fd:
|
|
47
|
+
* @fd: caller-owned fd referring to a shared-memory object (typically
|
|
48
|
+
* received via SCM_RIGHTS from a parent process). Will be dup'd —
|
|
49
|
+
* caller retains ownership of the original fd.
|
|
50
|
+
* @size: byte length of the region (must match the sender's view)
|
|
51
|
+
*
|
|
52
|
+
* mmap the fd MAP_SHARED into the calling process's address space.
|
|
53
|
+
* Returns %NULL on dup() or mmap() failure (errno preserved).
|
|
54
|
+
*/
|
|
55
|
+
GjsifySabRegion *gjsify_sab_region_new_from_fd (gint fd, gsize size);
|
|
56
|
+
|
|
57
|
+
void gjsify_sab_region_free (GjsifySabRegion *region);
|
|
58
|
+
|
|
59
|
+
gint gjsify_sab_region_get_fd (const GjsifySabRegion *region);
|
|
60
|
+
gsize gjsify_sab_region_get_size (const GjsifySabRegion *region);
|
|
61
|
+
|
|
62
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
63
|
+
* Plain read / write (no memory barriers — see _atomic_* below for SEQ_CST)
|
|
64
|
+
* ────────────────────────────────────────────────────────────────────── *
|
|
65
|
+
*
|
|
66
|
+
* All multi-byte accessors are little-endian on the wire. On x86_64 /
|
|
67
|
+
* aarch64 (the native arches) this is a no-op; on s390x / ppc64 the C
|
|
68
|
+
* shim swaps via __builtin_bswap*. Bounds-check `offset + sizeof(T) <=
|
|
69
|
+
* size`; on overflow we g_error() (programmer error, fail-fast).
|
|
70
|
+
*/
|
|
71
|
+
guint8 gjsify_sab_region_read_u8 (const GjsifySabRegion *region, gsize offset);
|
|
72
|
+
void gjsify_sab_region_write_u8 (GjsifySabRegion *region, gsize offset, guint8 v);
|
|
73
|
+
guint32 gjsify_sab_region_read_u32_le (const GjsifySabRegion *region, gsize offset);
|
|
74
|
+
void gjsify_sab_region_write_u32_le(GjsifySabRegion *region, gsize offset, guint32 v);
|
|
75
|
+
gint32 gjsify_sab_region_read_i32_le (const GjsifySabRegion *region, gsize offset);
|
|
76
|
+
void gjsify_sab_region_write_i32_le(GjsifySabRegion *region, gsize offset, gint32 v);
|
|
77
|
+
guint64 gjsify_sab_region_read_u64_le (const GjsifySabRegion *region, gsize offset);
|
|
78
|
+
void gjsify_sab_region_write_u64_le(GjsifySabRegion *region, gsize offset, guint64 v);
|
|
79
|
+
|
|
80
|
+
/* Bulk: return a fresh GBytes that borrows the mmap'd memory (no copy).
|
|
81
|
+
* Caller must release the GBytes BEFORE freeing the region — we wrap with
|
|
82
|
+
* g_bytes_new_with_free_func(ptr, length, region_ref, region_unref) so the
|
|
83
|
+
* region is reference-counted via a tiny refcount in the shim. */
|
|
84
|
+
GBytes *gjsify_sab_region_read_bytes (GjsifySabRegion *region, gsize offset, gsize length);
|
|
85
|
+
void gjsify_sab_region_write_bytes(GjsifySabRegion *region, gsize offset, GBytes *data);
|
|
86
|
+
|
|
87
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
88
|
+
* Atomics — __atomic_* builtins, memory_order = SEQ_CST
|
|
89
|
+
* ────────────────────────────────────────────────────────────────────── *
|
|
90
|
+
*
|
|
91
|
+
* `cmpxchg32`: weak compare-and-swap. *expected is updated to the actual
|
|
92
|
+
* value on failure (standard C11 pattern). Returns TRUE on success.
|
|
93
|
+
*/
|
|
94
|
+
gint32 gjsify_sab_region_atomic_add_i32 (GjsifySabRegion *region, gsize offset, gint32 v);
|
|
95
|
+
gint32 gjsify_sab_region_atomic_sub_i32 (GjsifySabRegion *region, gsize offset, gint32 v);
|
|
96
|
+
gint32 gjsify_sab_region_atomic_load_i32 (const GjsifySabRegion *region, gsize offset);
|
|
97
|
+
void gjsify_sab_region_atomic_store_i32 (GjsifySabRegion *region, gsize offset, gint32 v);
|
|
98
|
+
gint32 gjsify_sab_region_atomic_xchg_i32 (GjsifySabRegion *region, gsize offset, gint32 v);
|
|
99
|
+
gboolean gjsify_sab_region_atomic_cmpxchg_i32 (GjsifySabRegion *region, gsize offset, gint32 *expected, gint32 desired);
|
|
100
|
+
|
|
101
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
102
|
+
* Futex wait / wake (Linux SYS_futex, FUTEX_*_PRIVATE flavour)
|
|
103
|
+
* ────────────────────────────────────────────────────────────────────── *
|
|
104
|
+
*
|
|
105
|
+
* `futex_wait`:
|
|
106
|
+
* - Compares *(int32_t*)(ptr+offset) against `expected`.
|
|
107
|
+
* - If equal: blocks until woken or timeout (`timeout_ms` < 0 = infinite).
|
|
108
|
+
* - If not equal: returns immediately (no block).
|
|
109
|
+
* Returns:
|
|
110
|
+
* 0 = woken via FUTEX_WAKE
|
|
111
|
+
* -1 = value did not match expected (EAGAIN) — no wait happened
|
|
112
|
+
* -2 = timed out (ETIMEDOUT)
|
|
113
|
+
* -3 = interrupted by signal (EINTR) — caller may retry
|
|
114
|
+
* other negative = generic errno, sign-flipped
|
|
115
|
+
*
|
|
116
|
+
* `futex_wake`:
|
|
117
|
+
* Wakes up to @count waiters on the address. Returns number actually woken.
|
|
118
|
+
*/
|
|
119
|
+
gint gjsify_sab_region_futex_wait (GjsifySabRegion *region, gsize offset, gint32 expected, gint64 timeout_ms);
|
|
120
|
+
gint gjsify_sab_region_futex_wake (GjsifySabRegion *region, gsize offset, gint count);
|
|
121
|
+
|
|
122
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
123
|
+
* IPC side-channel: AF_UNIX/SOCK_SEQPACKET pair + SCM_RIGHTS fd transfer
|
|
124
|
+
* ────────────────────────────────────────────────────────────────────── *
|
|
125
|
+
*
|
|
126
|
+
* Used by @gjsify/worker_threads to attach the fd of a SharedBuffer to a
|
|
127
|
+
* postMessage() call. The parent calls _socketpair() at worker spawn,
|
|
128
|
+
* inherits the child end via Gio.SubprocessLauncher.take_fd(child_fd, 3),
|
|
129
|
+
* then send_fd() each SharedBuffer's fd over the parent end. Child runs a
|
|
130
|
+
* recv_fd() loop on its end to pick them up in lockstep with the JSON IPC.
|
|
131
|
+
*
|
|
132
|
+
* Tag is a 4-byte sequence number the sender encodes so the receiver can
|
|
133
|
+
* map the received fd back to the JSON placeholder {__sab: <tag>, size: N}.
|
|
134
|
+
*/
|
|
135
|
+
gboolean gjsify_sab_socketpair (gint *parent_fd, gint *child_fd);
|
|
136
|
+
gboolean gjsify_sab_send_fd (gint socket_fd, gint fd_to_send, guint32 tag);
|
|
137
|
+
|
|
138
|
+
/* recv_fd: returns received fd, or -1 on error (errno preserved).
|
|
139
|
+
* Returns 0 on orderly EOF (peer closed socket). *tag receives the
|
|
140
|
+
* sender-encoded tag value. */
|
|
141
|
+
gint gjsify_sab_recv_fd (gint socket_fd, guint32 *tag);
|
|
142
|
+
|
|
143
|
+
/* close_fd: just `close(2)` wrapped so JS callers don't have to drag in
|
|
144
|
+
* GioUnix's typelib for a single syscall. Returns TRUE on success
|
|
145
|
+
* (or EBADF — already closed is harmless), FALSE on other errno. */
|
|
146
|
+
gboolean gjsify_sab_close_fd (gint fd);
|
|
147
|
+
|
|
148
|
+
G_END_DECLS
|
|
149
|
+
|
|
150
|
+
#endif /* GJSIFY_SAB_HELPERS_H */
|