qr_reader-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,1029 @@
1
+ /* sp_net.c -- POSIX TCP / poll / process / shell primitives.
2
+ * See sp_net.h. Pure C, no spinel-runtime dependency; no OpenSSL.
3
+ *
4
+ * Extracted from tep's lib/tep/sphttp.c (the POSIX-runtime core that
5
+ * is generic across HTTP-shaped Spinel programs), per matz/spinel#1054
6
+ * and OriPekelman/tep#12. HTTP framing + WebSocket accessors + TLS stay
7
+ * framework-side.
8
+ *
9
+ * sp_net is a POSIX prefork/poll runtime (sockets, poll(2), fork,
10
+ * sigaction). */
11
+ #include "sp_net.h"
12
+ #include "sp_alloc.h" /* sp_ffi_bin_len: the binary-safe return contract */
13
+
14
+
15
+ #include <stdio.h>
16
+ #include <stdlib.h>
17
+ #include <string.h>
18
+ #include <unistd.h>
19
+ #include <errno.h>
20
+ #include <fcntl.h>
21
+ #include <poll.h>
22
+ #include <netdb.h>
23
+ #include <sys/socket.h>
24
+ #include <sys/types.h>
25
+ #include <sys/wait.h>
26
+ #include <netinet/in.h>
27
+ #include <netinet/tcp.h>
28
+ #include <arpa/inet.h>
29
+ #include <signal.h>
30
+ #include <sys/un.h> /* UNIXSocket / UNIXServer */
31
+ #include <stddef.h> /* offsetof (sockaddr_un packed length) */
32
+ #include "sp_sched.h" /* sp_native_enter / sp_native_leave */
33
+
34
+ #define SP_NET_BUFSIZE 65536
35
+
36
+ /* ---------- graceful shutdown ---------- */
37
+
38
+ static volatile sig_atomic_t sp_net_term_flag = 0;
39
+
40
+ /* A DNS lookup can take seconds, so we leave the world while it runs and
41
+ the GC doesn't wait for us. The host is copied first, since a GC may
42
+ run meanwhile and nothing may be rooting it. */
43
+ static int sp_net_resolve(const char *host, const char *port,
44
+ const struct addrinfo *hints, struct addrinfo **res) {
45
+ char buf[NI_MAXHOST];
46
+ if (host) {
47
+ if (strlen(host) >= sizeof buf) return EAI_NONAME; /* longer than any DNS name */
48
+ strcpy(buf, host);
49
+ host = buf;
50
+ }
51
+ sp_native_enter();
52
+ int rc = getaddrinfo(host, port, hints, res);
53
+ sp_native_leave();
54
+ return rc;
55
+ }
56
+
57
+ /* Self-pipe: the term handler writes a byte here so a blocked accept (via
58
+ * poll) wakes even when the signal lands in the check-then-accept window. */
59
+ static int sp_net_sigpipe[2] = {-1, -1};
60
+
61
+ static void sp_net_term_signal(int sig) {
62
+ (void)sig;
63
+ sp_net_term_flag = 1;
64
+ if (sp_net_sigpipe[1] >= 0) {
65
+ ssize_t w = write(sp_net_sigpipe[1], "x", 1); /* write() is async-signal-safe */
66
+ (void)w;
67
+ }
68
+ }
69
+
70
+ int sp_net_install_term_handlers(void) {
71
+ if (sp_net_sigpipe[0] < 0 && pipe(sp_net_sigpipe) == 0) {
72
+ for (int i = 0; i < 2; i++) {
73
+ int fl = fcntl(sp_net_sigpipe[i], F_GETFL, 0);
74
+ if (fl >= 0) fcntl(sp_net_sigpipe[i], F_SETFL, fl | O_NONBLOCK);
75
+ int fd = fcntl(sp_net_sigpipe[i], F_GETFD, 0);
76
+ if (fd >= 0) fcntl(sp_net_sigpipe[i], F_SETFD, fd | FD_CLOEXEC);
77
+ }
78
+ }
79
+ struct sigaction sa;
80
+ memset(&sa, 0, sizeof(sa));
81
+ sa.sa_handler = sp_net_term_signal;
82
+ sigemptyset(&sa.sa_mask);
83
+ /* No SA_RESETHAND and no SA_RESTART: a repeated term signal must keep
84
+ * hitting this handler (idempotent flag set) rather than the default
85
+ * action -- the latter killed the process with 143 instead of letting
86
+ * the accept loop exit cleanly. Without SA_RESTART, a signal during a
87
+ * blocked accept/poll surfaces as EINTR. */
88
+ sa.sa_flags = 0;
89
+ sigaction(SIGTERM, &sa, NULL);
90
+ sigaction(SIGINT, &sa, NULL);
91
+ return 0;
92
+ }
93
+
94
+ int sp_net_shutdown_requested(void) {
95
+ return (int)sp_net_term_flag;
96
+ }
97
+
98
+ /* Byte count written into the recv buffer by the most recent recv. The FFI
99
+ * `:binstr` return mode reads this to build a binary-safe String of exactly
100
+ * this many bytes (rather than strlen-truncating at the first NUL), so the same
101
+ * recv functions serve both text (`:str`) and binary (`:binstr`) callers.
102
+ * Single-threaded (the Fiber model is cooperative), so no locking is needed. */
103
+
104
+ /* Disable Nagle on a connection fd. TCP_NODELAY is a per-connection option, so
105
+ * it must be set on each accepted fd, not just the listener -- otherwise small
106
+ * writes (response headers, WebSocket frames) stall against delayed-ACK. */
107
+ void sp_net_set_nodelay(int fd) {
108
+ int one = 1;
109
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
110
+ }
111
+
112
+ /* ---------- TCP socket lifecycle ---------- */
113
+
114
+ /* TCPServer.new(host, port): like sp_net_listen but bound to a specific
115
+ address, resolved through getaddrinfo (so "localhost" works). A NULL/empty
116
+ host binds INADDR_ANY. Returns the listening fd, or -1. (#2922) */
117
+ int sp_net_listen_host(const char *host, int port, int backlog) {
118
+ if (port < 0 || port > 65535) return -1;
119
+ signal(SIGPIPE, SIG_IGN);
120
+ struct addrinfo hints, *res = NULL;
121
+ memset(&hints, 0, sizeof(hints));
122
+ hints.ai_family = AF_UNSPEC;
123
+ hints.ai_socktype = SOCK_STREAM;
124
+ hints.ai_flags = AI_PASSIVE;
125
+ char portbuf[16];
126
+ snprintf(portbuf, sizeof(portbuf), "%d", port);
127
+ if (sp_net_resolve((host && *host) ? host : NULL, portbuf, &hints, &res) != 0) return -1;
128
+ int fd = -1;
129
+ for (struct addrinfo *ai = res; ai; ai = ai->ai_next) {
130
+ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
131
+ if (fd < 0) continue;
132
+ int one = 1;
133
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
134
+ if (bind(fd, ai->ai_addr, ai->ai_addrlen) == 0 &&
135
+ listen(fd, backlog > 0 ? backlog : 1024) == 0) break;
136
+ close(fd);
137
+ fd = -1;
138
+ }
139
+ freeaddrinfo(res);
140
+ return fd;
141
+ }
142
+
143
+ /* The local port a bound/listening fd ended up on (port 0 => ephemeral). */
144
+ int sp_net_local_port(int fd) {
145
+ struct sockaddr_storage ss;
146
+ socklen_t len = sizeof(ss);
147
+ if (getsockname(fd, (struct sockaddr *)&ss, &len) != 0) return -1;
148
+ if (ss.ss_family == AF_INET) return (int)ntohs(((struct sockaddr_in *)&ss)->sin_port);
149
+ if (ss.ss_family == AF_INET6) return (int)ntohs(((struct sockaddr_in6 *)&ss)->sin6_port);
150
+ return -1;
151
+ }
152
+
153
+ /* The local/peer IP of a socket fd as text into ipbuf; returns the port, or
154
+ -1. peer != 0 reads the remote end (getpeername). */
155
+ int sp_net_sock_ip(int fd, int peer, char *ipbuf, int cap) {
156
+ struct sockaddr_storage ss;
157
+ socklen_t len = sizeof(ss);
158
+ int r = peer ? getpeername(fd, (struct sockaddr *)&ss, &len)
159
+ : getsockname(fd, (struct sockaddr *)&ss, &len);
160
+ if (r != 0) return -1;
161
+ if (ss.ss_family == AF_INET) {
162
+ struct sockaddr_in *a = (struct sockaddr_in *)&ss;
163
+ if (!inet_ntop(AF_INET, &a->sin_addr, ipbuf, (socklen_t)cap)) return -1;
164
+ return (int)ntohs(a->sin_port);
165
+ }
166
+ if (ss.ss_family == AF_INET6) {
167
+ struct sockaddr_in6 *a = (struct sockaddr_in6 *)&ss;
168
+ if (!inet_ntop(AF_INET6, &a->sin6_addr, ipbuf, (socklen_t)cap)) return -1;
169
+ return (int)ntohs(a->sin6_port);
170
+ }
171
+ return -1;
172
+ }
173
+
174
+ /* The host name the local/peer address of a socket fd reverse-resolves to,
175
+ into hostbuf: 0, or -1 when it has none (the caller then reports the
176
+ numeric address, as CRuby does). A lookup may take seconds, so it runs
177
+ outside the world, as getaddrinfo does. */
178
+ int sp_net_sock_host(int fd, int peer, char *hostbuf, int cap) {
179
+ struct sockaddr_storage ss;
180
+ socklen_t len = sizeof(ss);
181
+ int r = peer ? getpeername(fd, (struct sockaddr *)&ss, &len)
182
+ : getsockname(fd, (struct sockaddr *)&ss, &len);
183
+ if (r != 0) return -1;
184
+ if (ss.ss_family != AF_INET && ss.ss_family != AF_INET6) return -1;
185
+ #ifdef NI_NAMEREQD
186
+ sp_native_enter();
187
+ int rc = getnameinfo((struct sockaddr *)&ss, len, hostbuf, (socklen_t)cap, NULL, 0, NI_NAMEREQD);
188
+ sp_native_leave();
189
+ return rc == 0 ? 0 : -1;
190
+ #else
191
+ /* no reverse lookup on this platform (wasm32-wasi): the numeric address */
192
+ (void)len; (void)hostbuf; (void)cap;
193
+ return -1;
194
+ #endif
195
+ }
196
+
197
+ int sp_net_listen(int port, int reuseport) {
198
+ if (port < 0 || port > 65535) return -1;
199
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
200
+ if (fd < 0) return -1;
201
+
202
+ int one = 1;
203
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
204
+ #ifdef SO_REUSEPORT
205
+ if (reuseport) {
206
+ setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
207
+ }
208
+ #endif
209
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
210
+ /* Don't die on a write to a peer that closed. */
211
+ signal(SIGPIPE, SIG_IGN);
212
+
213
+ struct sockaddr_in addr;
214
+ memset(&addr, 0, sizeof(addr));
215
+ addr.sin_family = AF_INET;
216
+ addr.sin_port = htons((unsigned short)port);
217
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
218
+
219
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(fd); return -1; }
220
+ if (listen(fd, 1024) < 0) { close(fd); return -1; }
221
+ return fd;
222
+ }
223
+
224
+ #ifdef SP_THREADS
225
+ int sp_sched_wait_io(int fd, short events); /* lib/sp_sched.c: scheduler-aware I/O wait */
226
+ #endif
227
+ int sp_net_accept(int sfd) {
228
+ struct sockaddr_in caddr;
229
+ socklen_t clen = sizeof(caddr);
230
+ int fd;
231
+ #ifdef SP_THREADS
232
+ /* Scheduler-aware accept: the listen fd is non-blocking, so accept returns
233
+ EAGAIN when no connection is pending and the green thread parks (freeing
234
+ its worker) until the monitor's poll reports the fd readable. Idempotent. */
235
+ sp_net_set_nonblock(sfd);
236
+ for (;;) {
237
+ if (sp_net_term_flag) return -1;
238
+ fd = accept(sfd, (struct sockaddr *)&caddr, &clen);
239
+ /* The connection fd is non-blocking too, so recv/send on it park the
240
+ green thread (scheduler-aware) instead of blocking the OS worker. */
241
+ if (fd >= 0) { sp_net_set_nodelay(fd); sp_net_set_nonblock(fd); return fd; }
242
+ if (errno == EINTR) continue;
243
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
244
+ if (!sp_sched_wait_io(sfd, POLLIN)) return -1;
245
+ continue;
246
+ }
247
+ return -1;
248
+ }
249
+ #else
250
+ for (;;) {
251
+ if (sp_net_term_flag) return -1;
252
+ /* Wait for an incoming connection OR a term signal (which wakes the
253
+ * self-pipe). Polling first closes the check-then-block race: a
254
+ * signal that set the flag/pipe before we block makes poll return
255
+ * immediately, so we never enter a blocking accept with the flag
256
+ * already set. */
257
+ struct pollfd pfds[2];
258
+ pfds[0].fd = sfd; pfds[0].events = POLLIN; pfds[0].revents = 0;
259
+ nfds_t npfd = 1;
260
+ if (sp_net_sigpipe[0] >= 0) {
261
+ pfds[1].fd = sp_net_sigpipe[0]; pfds[1].events = POLLIN; pfds[1].revents = 0;
262
+ npfd = 2;
263
+ }
264
+ int pr = poll(pfds, npfd, -1);
265
+ if (sp_net_term_flag) return -1;
266
+ if (pr < 0) {
267
+ if (errno == EINTR) continue;
268
+ return -1;
269
+ }
270
+ if (!(pfds[0].revents & POLLIN)) continue; /* woken by the self-pipe alone */
271
+ fd = accept(sfd, (struct sockaddr *)&caddr, &clen);
272
+ if (fd >= 0) { sp_net_set_nodelay(fd); return fd; }
273
+ if (errno == EINTR) {
274
+ if (sp_net_term_flag) return -1;
275
+ continue; /* unrelated signal (SIGCHLD, ...) -- retry */
276
+ }
277
+ return -1;
278
+ }
279
+ #endif
280
+ }
281
+
282
+ int sp_net_accept_nb(int sfd) {
283
+ struct sockaddr_in caddr;
284
+ socklen_t clen = sizeof(caddr);
285
+ int fd;
286
+ do {
287
+ fd = accept(sfd, (struct sockaddr *)&caddr, &clen);
288
+ } while (fd < 0 && errno == EINTR);
289
+ if (fd >= 0) sp_net_set_nodelay(fd);
290
+ return fd;
291
+ }
292
+
293
+ /* ---- UDP and UNIX-domain sockets ---- */
294
+
295
+ /* An unbound UDP socket; #bind attaches it to a local address later. */
296
+ int sp_net_udp_open(int family) {
297
+ signal(SIGPIPE, SIG_IGN);
298
+ return socket(family > 0 ? family : AF_INET, SOCK_DGRAM, 0);
299
+ }
300
+ /* The family the fd was opened with. An address resolved for the other family
301
+ cannot be bound or sent to, so every UDP call resolves within it. */
302
+ int sp_net_fd_family(int fd) {
303
+ struct sockaddr_storage ss;
304
+ socklen_t sl = sizeof ss;
305
+ if (fd < 0 || getsockname(fd, (struct sockaddr *)&ss, &sl) != 0) return AF_UNSPEC;
306
+ return (int)ss.ss_family;
307
+ }
308
+ /* First resolution matching `family` (any, when AF_UNSPEC). */
309
+ static struct addrinfo *sp_net_udp_resolve(int fd, const char *host, int port,
310
+ int passive, struct addrinfo **head) {
311
+ struct addrinfo hints;
312
+ memset(&hints, 0, sizeof(hints));
313
+ hints.ai_family = sp_net_fd_family(fd);
314
+ hints.ai_socktype = SOCK_DGRAM;
315
+ if (passive) hints.ai_flags = AI_PASSIVE;
316
+ char portbuf[16];
317
+ snprintf(portbuf, sizeof(portbuf), "%d", port);
318
+ *head = NULL;
319
+ if (sp_net_resolve((host && *host) ? host : NULL, portbuf, &hints, head) != 0) return NULL;
320
+ return *head;
321
+ }
322
+ int sp_net_udp_bind(int fd, const char *host, int port) {SP_GC_ROOT_STR(host);
323
+ if (fd < 0 || port < 0 || port > 65535) return -1;
324
+ struct addrinfo *head = NULL;
325
+ struct addrinfo *ai = sp_net_udp_resolve(fd, host, port, (host && *host) ? 0 : 1, &head);
326
+ if (!ai) return -1;
327
+ int rc = bind(fd, ai->ai_addr, ai->ai_addrlen);
328
+ freeaddrinfo(head);
329
+ return rc;
330
+ }
331
+ /* #connect fixes the peer so #send / #write need no address. */
332
+ int sp_net_udp_connect(int fd, const char *host, int port) {SP_GC_ROOT_STR(host);
333
+ if (fd < 0 || port < 0 || port > 65535) return -1;
334
+ struct addrinfo *head = NULL;
335
+ struct addrinfo *ai = sp_net_udp_resolve(fd, host, port, 0, &head);
336
+ if (!ai) return -1;
337
+ int rc = connect(fd, ai->ai_addr, ai->ai_addrlen);
338
+ freeaddrinfo(head);
339
+ return rc;
340
+ }
341
+ int sp_net_udp_send_to(int fd, const char *data, int len, const char *host, int port) {SP_GC_ROOT_STR(data);SP_GC_ROOT_STR(host);
342
+ if (fd < 0) return -1;
343
+ if (!host || !*host) return (int)send(fd, data, (size_t)len, 0);
344
+ struct addrinfo *head = NULL;
345
+ struct addrinfo *ai = sp_net_udp_resolve(fd, host, port, 0, &head);
346
+ if (!ai) return -1;
347
+ int rc = (int)sendto(fd, data, (size_t)len, 0, ai->ai_addr, ai->ai_addrlen);
348
+ freeaddrinfo(head);
349
+ return rc;
350
+ }
351
+ /* Reads one datagram into `buf`; fills the sender's IP/port when asked.
352
+ Returns the byte count, or -1. */
353
+ int sp_net_udp_recv_from(int fd, char *buf, int cap, char *ipbuf, int ipcap, int *port_out) {
354
+ if (fd < 0 || cap <= 0) return -1;
355
+ struct sockaddr_storage ss;
356
+ socklen_t sl = sizeof ss;
357
+ ssize_t n;
358
+ do { n = recvfrom(fd, buf, (size_t)cap, 0, (struct sockaddr *)&ss, &sl); }
359
+ while (n < 0 && errno == EINTR);
360
+ if (n < 0) return -1;
361
+ if (ipbuf && ipcap > 0) {
362
+ ipbuf[0] = '\0';
363
+ if (ss.ss_family == AF_INET) {
364
+ struct sockaddr_in *a = (struct sockaddr_in *)&ss;
365
+ inet_ntop(AF_INET, &a->sin_addr, ipbuf, (socklen_t)ipcap);
366
+ if (port_out) *port_out = ntohs(a->sin_port);
367
+ }
368
+ else if (ss.ss_family == AF_INET6) {
369
+ struct sockaddr_in6 *a = (struct sockaddr_in6 *)&ss;
370
+ inet_ntop(AF_INET6, &a->sin6_addr, ipbuf, (socklen_t)ipcap);
371
+ if (port_out) *port_out = ntohs(a->sin6_port);
372
+ }
373
+ else if (port_out) *port_out = 0;
374
+ }
375
+ return (int)n;
376
+ }
377
+
378
+ /* UNIX-domain stream sockets. `path` is a filesystem path; a server unlinks a
379
+ stale socket file first, as CRuby's UNIXServer does not (it raises) -- so we
380
+ do not either, and bind fails on a leftover file. */
381
+ static int sp_net_unix_addr(const char *path, struct sockaddr_un *sa) {
382
+ if (!path || !*path) return -1;
383
+ size_t n = strlen(path);
384
+ if (n >= sizeof sa->sun_path) return -1;
385
+ memset(sa, 0, sizeof *sa);
386
+ sa->sun_family = AF_UNIX;
387
+ memcpy(sa->sun_path, path, n + 1);
388
+ return 0;
389
+ }
390
+ int sp_net_unix_listen(const char *path, int backlog) {SP_GC_ROOT_STR(path);
391
+ struct sockaddr_un sa;
392
+ if (sp_net_unix_addr(path, &sa) != 0) return -1;
393
+ signal(SIGPIPE, SIG_IGN);
394
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
395
+ if (fd < 0) return -1;
396
+ if (bind(fd, (struct sockaddr *)&sa, sizeof sa) != 0) { close(fd); return -1; }
397
+ if (listen(fd, backlog > 0 ? backlog : 128) != 0) { close(fd); return -1; }
398
+ return fd;
399
+ }
400
+ int sp_net_unix_connect(const char *path) {SP_GC_ROOT_STR(path);
401
+ struct sockaddr_un sa;
402
+ if (sp_net_unix_addr(path, &sa) != 0) return -1;
403
+ signal(SIGPIPE, SIG_IGN);
404
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
405
+ if (fd < 0) return -1;
406
+ if (connect(fd, (struct sockaddr *)&sa, sizeof sa) != 0) { close(fd); return -1; }
407
+ return fd;
408
+ }
409
+ /* The bound path of a UNIX socket (empty for an unnamed peer). */
410
+ int sp_net_unix_path(int fd, int peer, char *buf, int cap) {
411
+ struct sockaddr_un sa;
412
+ socklen_t sl = sizeof sa;
413
+ memset(&sa, 0, sizeof sa);
414
+ int rc = peer ? getpeername(fd, (struct sockaddr *)&sa, &sl)
415
+ : getsockname(fd, (struct sockaddr *)&sa, &sl);
416
+ if (rc != 0 || cap <= 0) { if (cap > 0) buf[0] = '\0'; return -1; }
417
+ snprintf(buf, (size_t)cap, "%s", sa.sun_path);
418
+ return 0;
419
+ }
420
+
421
+ /* ---- Socket class methods ---- */
422
+ int sp_net_gethostname(char *buf, int cap) {
423
+ if (!buf || cap <= 0) return -1;
424
+ buf[0] = '\0';
425
+ return gethostname(buf, (size_t)cap);
426
+ }
427
+ /* A connected pair of the given domain/type; fds[0]/fds[1] on success. */
428
+ int sp_net_socketpair(int domain, int type, int protocol, int fds[2]) {
429
+ signal(SIGPIPE, SIG_IGN);
430
+ return socketpair(domain > 0 ? domain : AF_UNIX,
431
+ type > 0 ? type : SOCK_STREAM, protocol, fds);
432
+ }
433
+ int sp_net_socket(int domain, int type, int protocol) {
434
+ signal(SIGPIPE, SIG_IGN);
435
+ return socket(domain, type, protocol);
436
+ }
437
+ /* getaddrinfo, one resolution at a time: `idx` selects the entry so the caller
438
+ can walk them without owning the addrinfo list. Fills family/socktype/
439
+ protocol/ip/port; returns 0 on success, -1 past the end or on failure. */
440
+ int sp_net_getaddrinfo_at(const char *host, int port, int want_family, int socktype, int idx,
441
+ int *family, int *stype, int *proto,
442
+ char *ipbuf, int ipcap, int *port_out) {
443
+ struct addrinfo hints, *res = NULL, *ai;
444
+ memset(&hints, 0, sizeof(hints));
445
+ hints.ai_family = want_family > 0 ? want_family : AF_UNSPEC;
446
+ hints.ai_socktype = socktype > 0 ? socktype : 0;
447
+ char portbuf[16];
448
+ snprintf(portbuf, sizeof(portbuf), "%d", port);
449
+ if (sp_net_resolve((host && *host) ? host : NULL, portbuf, &hints, &res) != 0) return -1;
450
+ int i = 0, rc = -1;
451
+ for (ai = res; ai; ai = ai->ai_next, i++) {
452
+ if (i != idx) continue;
453
+ if (family) *family = ai->ai_family;
454
+ if (stype) *stype = ai->ai_socktype;
455
+ if (proto) *proto = ai->ai_protocol;
456
+ if (ipbuf && ipcap > 0) {
457
+ ipbuf[0] = '\0';
458
+ if (ai->ai_family == AF_INET) {
459
+ struct sockaddr_in *a = (struct sockaddr_in *)ai->ai_addr;
460
+ inet_ntop(AF_INET, &a->sin_addr, ipbuf, (socklen_t)ipcap);
461
+ if (port_out) *port_out = ntohs(a->sin_port);
462
+ }
463
+ else if (ai->ai_family == AF_INET6) {
464
+ struct sockaddr_in6 *a = (struct sockaddr_in6 *)ai->ai_addr;
465
+ inet_ntop(AF_INET6, &a->sin6_addr, ipbuf, (socklen_t)ipcap);
466
+ if (port_out) *port_out = ntohs(a->sin6_port);
467
+ }
468
+ else if (port_out) *port_out = 0;
469
+ }
470
+ rc = 0;
471
+ break;
472
+ }
473
+ freeaddrinfo(res);
474
+ return rc;
475
+ }
476
+
477
+ /* ---- socket options ---- */
478
+ /* getsockopt/setsockopt over the integer-valued options, which is every option
479
+ Ruby programs reach for in practice (SO_REUSEADDR, SO_KEEPALIVE,
480
+ TCP_NODELAY, SO_RCVBUF, ...). Returns -1 on failure. */
481
+ int sp_net_setsockopt_int(int fd, int level, int optname, int value) {
482
+ if (fd < 0) return -1;
483
+ return setsockopt(fd, level, optname, &value, (socklen_t)sizeof value);
484
+ }
485
+ int sp_net_getsockopt_int(int fd, int level, int optname) {
486
+ int value = 0;
487
+ socklen_t len = (socklen_t)sizeof value;
488
+ if (fd < 0 || getsockopt(fd, level, optname, &value, &len) != 0) return -1;
489
+ return value;
490
+ }
491
+ int sp_net_shutdown(int fd, int how) {
492
+ if (fd < 0) return -1;
493
+ return shutdown(fd, how);
494
+ }
495
+
496
+ /* A connect to a host that doesn't answer can take over a minute. With
497
+ threads we park until it finishes instead of holding the worker. A
498
+ signal doesn't cancel a connect, so on EINTR we wait for it too. */
499
+ static int sp_net_connect_wait(int fd, const struct sockaddr *sa, socklen_t len) {
500
+ #ifdef SP_THREADS
501
+ sp_net_set_nonblock(fd);
502
+ #endif
503
+ if (connect(fd, sa, len) == 0) return 0;
504
+ if (errno != EINPROGRESS && errno != EINTR) return -1;
505
+ #ifdef SP_THREADS
506
+ if (!sp_sched_wait_io(fd, POLLOUT)) return -1;
507
+ #else
508
+ struct pollfd pf = { fd, POLLOUT, 0 };
509
+ while (poll(&pf, 1, -1) < 0) if (errno != EINTR) return -1;
510
+ #endif
511
+ int err = 0;
512
+ socklen_t elen = sizeof err;
513
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen) != 0) return -1;
514
+ if (err) { errno = err; return -1; }
515
+ return 0;
516
+ }
517
+
518
+ int sp_net_connect(const char *host, int port) {
519
+ if (port < 0 || port > 65535) return -1;
520
+ /* A client that never listens still needs SIGPIPE ignored: a write
521
+ * to an upstream that closed would otherwise kill the process. */
522
+ signal(SIGPIPE, SIG_IGN);
523
+
524
+ struct addrinfo hints, *res = NULL;
525
+ memset(&hints, 0, sizeof(hints));
526
+ hints.ai_family = AF_UNSPEC;
527
+ hints.ai_socktype = SOCK_STREAM;
528
+
529
+ char portbuf[16];
530
+ snprintf(portbuf, sizeof(portbuf), "%d", port);
531
+ if (sp_net_resolve(host, portbuf, &hints, &res) != 0) return -1;
532
+
533
+ /* A Thread#raise or #kill wakes a parked connect, but is held back
534
+ until the socket and the address list are freed. */
535
+ sp_fiber_defer_inject();
536
+ int fd = -1;
537
+ for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) {
538
+ if (sp_fiber_current && sp_fiber_inject_pending(sp_fiber_current)) break;
539
+ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
540
+ if (fd < 0) continue;
541
+ if (sp_net_connect_wait(fd, ai->ai_addr, ai->ai_addrlen) == 0) break;
542
+ close(fd);
543
+ fd = -1;
544
+ }
545
+ freeaddrinfo(res);
546
+ sp_fiber_undefer_inject();
547
+ if (sp_fiber_current && sp_fiber_inject_pending(sp_fiber_current)) {
548
+ if (fd >= 0) { close(fd); fd = -1; } /* even if it connected */
549
+ sp_fiber_fire_inject_if_pending();
550
+ }
551
+ if (fd < 0) return -1;
552
+
553
+ int one = 1;
554
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
555
+ return fd;
556
+ }
557
+
558
+ int sp_net_close(int fd) {
559
+ return close(fd);
560
+ }
561
+
562
+ int sp_net_set_nonblock(int fd) {
563
+ int flags = fcntl(fd, F_GETFL, 0);
564
+ if (flags < 0) return -1;
565
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
566
+ }
567
+
568
+ /* ---------- TCP I/O ---------- */
569
+
570
+ /* Wait until `fd` is ready for `events` (POLLIN for a read, POLLOUT for a
571
+ write) before retrying a syscall that returned EAGAIN/EWOULDBLOCK. The
572
+ accepted client fds are non-blocking, so a recv/send can would-block even
573
+ after the scheduler's poll reported the fd ready (a transient kernel state,
574
+ a partial drain, etc.); treating that as EOF/error wrongly tears down a live
575
+ connection (#1500). Block on the single fd rather than busy-spinning, and
576
+ recheck the term flag on the 1s timeout so a shutdown still breaks the wait.
577
+ Returns 1 to retry the syscall, 0 to give up (term requested or poll error). */
578
+ static int sp_net_wait_io(int fd, short events) {
579
+ if (sp_net_term_flag) return 0;
580
+ #ifdef SP_THREADS
581
+ /* Scheduler-aware: park this green thread and hand its OS worker to another
582
+ thread until the fd is ready, rather than blocking the worker in poll. */
583
+ return sp_sched_wait_io(fd, events);
584
+ #else
585
+ for (;;) {
586
+ if (sp_net_term_flag) return 0;
587
+ struct pollfd pf;
588
+ pf.fd = fd; pf.events = events; pf.revents = 0;
589
+ int pr = poll(&pf, 1, 1000);
590
+ if (pr > 0) return 1;
591
+ if (pr == 0) continue; /* timeout: recheck term, keep waiting */
592
+ if (errno == EINTR) continue;
593
+ return 0;
594
+ }
595
+ #endif
596
+ }
597
+
598
+ static int sp_net_send_all(int fd, const char *data, size_t total) {
599
+ size_t off = 0;
600
+ while (off < total) {
601
+ ssize_t w = send(fd, data + off, total - off, 0);
602
+ if (w <= 0) {
603
+ if (w < 0 && errno == EINTR) continue;
604
+ if (w < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) && sp_net_wait_io(fd, POLLOUT)) continue;
605
+ return -1;
606
+ }
607
+ off += (size_t)w;
608
+ }
609
+ return 0;
610
+ }
611
+
612
+ int sp_net_write_str(int fd, const char *s) {
613
+ return sp_net_send_all(fd, s, strlen(s));
614
+ }
615
+
616
+ int sp_net_write_bytes(int fd, const char *data, int n) {
617
+ return sp_net_send_all(fd, data, (n < 0) ? 0 : (size_t)n);
618
+ }
619
+
620
+ /* Write what the socket will take right now and answer how much that was,
621
+ * leaving the rest to the caller. sp_net_write_str / _bytes above keep
622
+ * writing until everything is gone, parking the green thread whenever the
623
+ * peer's buffer is full -- correct for a request/response server, and the
624
+ * wrong shape for fan-out: one slow subscriber holds up delivery to all the
625
+ * others, and nothing at the Ruby level can see that it is happening.
626
+ *
627
+ * Only the caller knows what to do about a peer that cannot keep up -- buffer
628
+ * the frame, drop it, or disconnect -- so this hands the decision back rather
629
+ * than making it. Register the fd for WRITE readiness and call again with the
630
+ * remainder.
631
+ *
632
+ * >0 bytes accepted (may be fewer than n)
633
+ * 0 nothing could be written now; the peer is alive, its buffer is full
634
+ * -1 the connection is gone (or shutdown was requested)
635
+ */
636
+ int sp_net_write_partial(int fd, const char *data, int n) {
637
+ if (n <= 0) return 0;
638
+ for (;;) {
639
+ ssize_t w = send(fd, data, (size_t)n, 0);
640
+ if (w >= 0) return (int)w;
641
+ /* A signal landing mid-call is not a peer event, so retry -- except
642
+ when it was the shutdown signal, which the caller asked to see.
643
+ Checking the flag before the send instead would fail writes that
644
+ would have succeeded, and a draining server still wants those. */
645
+ if (errno == EINTR) {
646
+ if (sp_net_term_flag) return -1;
647
+ continue;
648
+ }
649
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return 0;
650
+ return -1;
651
+ }
652
+ }
653
+
654
+ /* Per-worker in the threaded build: Thread runs with real parallelism and no
655
+ * GVL, so two green threads on two OS workers calling this concurrently would
656
+ * otherwise hand back the same buffer and race on its contents. Allocated on
657
+ * first use rather than reserved in every thread's TLS block, so a worker that
658
+ * never touches a socket pays nothing (SP_MAX_WORKERS is 256, and three 64 KB
659
+ * buffers reserved unconditionally would be 48 MB of TLS). */
660
+ static SP_TLS char *sp_net_recv_buf;
661
+ const char *sp_net_recv_some(int fd, int maxlen) {
662
+ if (!sp_net_recv_buf && !(sp_net_recv_buf = (char *)malloc(SP_NET_BUFSIZE)))
663
+ return "";
664
+ if (maxlen <= 0 || maxlen >= SP_NET_BUFSIZE) maxlen = SP_NET_BUFSIZE - 1;
665
+ ssize_t n;
666
+ for (;;) {
667
+ /* Cooperative shutdown: bail rather than retry into the signal. */
668
+ if (sp_net_term_flag) {
669
+ sp_ffi_bin_len = 0;
670
+ sp_net_recv_buf[0] = '\0';
671
+ return sp_net_recv_buf;
672
+ }
673
+ n = recv(fd, sp_net_recv_buf, (size_t)maxlen, 0);
674
+ if (n >= 0) break;
675
+ if (errno == EINTR) continue; /* e.g. SIGCHLD in a prefork server */
676
+ /* Non-blocking fd would-block: wait for readability and retry rather
677
+ than reporting an empty result the caller can't tell from EOF. */
678
+ if ((errno == EAGAIN || errno == EWOULDBLOCK) && sp_net_wait_io(fd, POLLIN)) continue;
679
+ sp_ffi_bin_len = 0;
680
+ sp_net_recv_buf[0] = '\0';
681
+ return sp_net_recv_buf;
682
+ }
683
+ sp_ffi_bin_len = (int)n; /* exact byte count for the :binstr path */
684
+ sp_net_recv_buf[n] = '\0';
685
+ return sp_net_recv_buf;
686
+ }
687
+
688
+ static SP_TLS char *sp_net_recv_all_buf; /* per-worker, see sp_net_recv_buf */
689
+ const char *sp_net_recv_all(int fd, int max_bytes) {
690
+ if (!sp_net_recv_all_buf && !(sp_net_recv_all_buf = (char *)malloc(SP_NET_BUFSIZE)))
691
+ return "";
692
+ if (max_bytes <= 0 || max_bytes >= SP_NET_BUFSIZE) max_bytes = SP_NET_BUFSIZE - 1;
693
+ int total = 0;
694
+ while (total < max_bytes) {
695
+ if (sp_net_term_flag) break;
696
+ ssize_t n = recv(fd, sp_net_recv_all_buf + total, (size_t)(max_bytes - total), 0);
697
+ if (n < 0) {
698
+ if (errno == EINTR) continue; /* retry rather than truncate */
699
+ if ((errno == EAGAIN || errno == EWOULDBLOCK) && sp_net_wait_io(fd, POLLIN)) continue;
700
+ break;
701
+ }
702
+ if (n == 0) break; /* clean EOF */
703
+ total += (int)n;
704
+ }
705
+ sp_ffi_bin_len = total; /* exact byte count for the :binstr path */
706
+ sp_net_recv_all_buf[total] = '\0';
707
+ return sp_net_recv_all_buf;
708
+ }
709
+
710
+ /* ---------- poll(2) ---------- */
711
+
712
+ /* The set grows with the caller's connection count rather than sitting at a
713
+ * fixed size. It used to be a 256-entry array whose overflow answered -1, and
714
+ * -1 is also what a caller reads as "this fd is not in the set this round":
715
+ * from the 257th fd on, a connection went deaf with nothing said about it.
716
+ * A server holding one socket per user reaches 256 on its first busy minute.
717
+ *
718
+ * Growth doubles from 64. The only remaining failure is the allocator saying
719
+ * no, which is not something a caller can route around, so it is reported
720
+ * rather than returned quietly. */
721
+ #define SP_NET_POLL_INIT 64
722
+ /* Per-worker, like the recv buffers: a green thread is pinned to its worker
723
+ * (home_wid, see sp_alloc.h), so a thread that registers fds and then polls
724
+ * them always finds its own set, while two threads running their own accept
725
+ * loops on two workers no longer poll each other's fds. Only the pointers and
726
+ * counts live in TLS -- the sets themselves are malloc'd on first use, so a
727
+ * worker that never polls costs nothing. */
728
+ static SP_TLS struct pollfd *sp_net_poll_set;
729
+ static SP_TLS int sp_net_poll_n;
730
+ static SP_TLS int sp_net_poll_cap;
731
+
732
+ int sp_net_poll_reset(void) {
733
+ sp_net_poll_n = 0;
734
+ return 0;
735
+ }
736
+
737
+ int sp_net_poll_add(int fd, int mode_bits) {
738
+ if (sp_net_poll_n >= sp_net_poll_cap) {
739
+ int cap = sp_net_poll_cap ? sp_net_poll_cap * 2 : SP_NET_POLL_INIT;
740
+ struct pollfd *grown =
741
+ (struct pollfd *)realloc(sp_net_poll_set, (size_t)cap * sizeof *grown);
742
+ if (!grown) {
743
+ fprintf(stderr, "sp_net_poll_add: out of memory growing the poll set "
744
+ "to %d entries; fd %d is not being watched\n", cap, fd);
745
+ return -1;
746
+ }
747
+ sp_net_poll_set = grown;
748
+ sp_net_poll_cap = cap;
749
+ }
750
+ short ev = 0;
751
+ if (mode_bits & 1) ev |= POLLIN;
752
+ if (mode_bits & 2) ev |= POLLOUT;
753
+ sp_net_poll_set[sp_net_poll_n].fd = fd;
754
+ sp_net_poll_set[sp_net_poll_n].events = ev;
755
+ sp_net_poll_set[sp_net_poll_n].revents = 0;
756
+ return sp_net_poll_n++;
757
+ }
758
+
759
+ int sp_net_poll_run(int timeout_ms) {
760
+ int r;
761
+ for (;;) {
762
+ /* Don't retry into a pending shutdown -- surface it as -1 so a
763
+ * blocked scheduler tick can break and run shutdown hooks. */
764
+ if (sp_net_term_flag) return -1;
765
+ r = poll(sp_net_poll_set, sp_net_poll_n, timeout_ms);
766
+ if (r >= 0) return r;
767
+ if (errno == EINTR) {
768
+ if (sp_net_term_flag) return -1;
769
+ continue;
770
+ }
771
+ return -1;
772
+ }
773
+ }
774
+
775
+ int sp_net_poll_ready(int slot) {
776
+ if (slot < 0 || slot >= sp_net_poll_n) return 0;
777
+ short rev = sp_net_poll_set[slot].revents;
778
+ int out = 0;
779
+ if (rev & (POLLIN | POLLHUP | POLLERR)) out |= 1;
780
+ if (rev & POLLOUT) out |= 2;
781
+ return out;
782
+ }
783
+
784
+ /* ---------- poll(2), persistent registration ---------- */
785
+
786
+ /* The set above is rebuilt every tick: a caller with N parked fds walks all N,
787
+ * re-registers what has not changed, polls, then walks N again to read the
788
+ * results -- O(connections) per tick, whatever syscall is underneath. Hiding
789
+ * epoll behind that contract would keep the rebuild and only improve the
790
+ * syscall, so the shape is what changes here.
791
+ *
792
+ * Registration is paid once per connection. wait() answers how many fds are
793
+ * ready and event_fd/event_mode read only those, so the caller's work is
794
+ * proportional to EVENTS rather than to connections. poll(2) stays the
795
+ * backend; a caller written to this contract does not have to change again if
796
+ * epoll/kqueue is ever put underneath. (#4103)
797
+ *
798
+ * `slot_of` maps fd -> index into `reg`, direct-indexed because fds are small
799
+ * dense integers the kernel hands out from the bottom; -1 means unregistered.
800
+ * Removal swaps the last entry into the hole and repairs its map entry, so
801
+ * every operation is O(1). Because the ready set is a compacted copy rather
802
+ * than a view, that swap cannot disturb a walk in progress -- which is what
803
+ * a server does on every EOF: unregister the fd while iterating the events.
804
+ *
805
+ * Per-worker for the same reason as the set above. */
806
+ static SP_TLS struct pollfd *sp_net_reg; /* the registered set */
807
+ static SP_TLS int sp_net_reg_n;
808
+ static SP_TLS int sp_net_reg_cap;
809
+ static SP_TLS int *sp_net_slot_of; /* fd -> index into sp_net_reg */
810
+ static SP_TLS int sp_net_slot_cap;
811
+ static SP_TLS struct pollfd *sp_net_ev; /* the ready ones, compacted */
812
+ static SP_TLS int sp_net_ev_n;
813
+
814
+ static short sp_net_mode_to_events(int mode_bits) {
815
+ short ev = 0;
816
+ if (mode_bits & 1) ev |= POLLIN;
817
+ if (mode_bits & 2) ev |= POLLOUT;
818
+ return ev;
819
+ }
820
+
821
+ /* Grow the fd map so `fd` can be indexed. Unused entries read -1. */
822
+ static int sp_net_slot_reserve(int fd) {
823
+ if (fd < 0) return 0;
824
+ if (fd < sp_net_slot_cap) return 1;
825
+ int cap = sp_net_slot_cap ? sp_net_slot_cap : 64;
826
+ while (cap <= fd) cap *= 2;
827
+ int *grown = (int *)realloc(sp_net_slot_of, (size_t)cap * sizeof *grown);
828
+ if (!grown) return 0;
829
+ for (int i = sp_net_slot_cap; i < cap; i++) grown[i] = -1;
830
+ sp_net_slot_of = grown;
831
+ sp_net_slot_cap = cap;
832
+ return 1;
833
+ }
834
+
835
+ int sp_net_poll_register(int fd, int mode_bits) {
836
+ if (fd < 0) return -1;
837
+ if (!sp_net_slot_reserve(fd)) {
838
+ fprintf(stderr, "sp_net_poll_register: out of memory indexing fd %d\n", fd);
839
+ return -1;
840
+ }
841
+ if (sp_net_slot_of[fd] >= 0) return sp_net_poll_modify(fd, mode_bits);
842
+ if (sp_net_reg_n >= sp_net_reg_cap) {
843
+ int cap = sp_net_reg_cap ? sp_net_reg_cap * 2 : 64;
844
+ struct pollfd *grown =
845
+ (struct pollfd *)realloc(sp_net_reg, (size_t)cap * sizeof *grown);
846
+ struct pollfd *evg =
847
+ (struct pollfd *)realloc(sp_net_ev, (size_t)cap * sizeof *evg);
848
+ if (grown) sp_net_reg = grown;
849
+ if (evg) sp_net_ev = evg;
850
+ if (!grown || !evg) {
851
+ fprintf(stderr, "sp_net_poll_register: out of memory growing the set "
852
+ "to %d entries; fd %d is not being watched\n", cap, fd);
853
+ return -1;
854
+ }
855
+ sp_net_reg_cap = cap;
856
+ }
857
+ sp_net_reg[sp_net_reg_n].fd = fd;
858
+ sp_net_reg[sp_net_reg_n].events = sp_net_mode_to_events(mode_bits);
859
+ sp_net_reg[sp_net_reg_n].revents = 0;
860
+ sp_net_slot_of[fd] = sp_net_reg_n++;
861
+ return 0;
862
+ }
863
+
864
+ int sp_net_poll_modify(int fd, int mode_bits) {
865
+ if (fd < 0 || fd >= sp_net_slot_cap) return -1;
866
+ int i = sp_net_slot_of[fd];
867
+ if (i < 0) return -1;
868
+ sp_net_reg[i].events = sp_net_mode_to_events(mode_bits);
869
+ sp_net_reg[i].revents = 0;
870
+ return 0;
871
+ }
872
+
873
+ int sp_net_poll_unregister(int fd) {
874
+ if (fd < 0 || fd >= sp_net_slot_cap) return -1;
875
+ int i = sp_net_slot_of[fd];
876
+ if (i < 0) return -1;
877
+ int last = --sp_net_reg_n;
878
+ if (i != last) {
879
+ sp_net_reg[i] = sp_net_reg[last];
880
+ sp_net_slot_of[sp_net_reg[i].fd] = i;
881
+ }
882
+ sp_net_slot_of[fd] = -1;
883
+ return 0;
884
+ }
885
+
886
+ int sp_net_poll_registered(void) { return sp_net_reg_n; }
887
+
888
+ int sp_net_poll_wait(int timeout_ms) {
889
+ sp_net_ev_n = 0;
890
+ int r;
891
+ for (;;) {
892
+ /* Don't retry into a pending shutdown -- surface it as -1 so a blocked
893
+ * tick can break and run shutdown hooks, as sp_net_poll_run does. */
894
+ if (sp_net_term_flag) return -1;
895
+ r = poll(sp_net_reg, (nfds_t)sp_net_reg_n, timeout_ms);
896
+ if (r >= 0) break;
897
+ if (errno == EINTR) {
898
+ if (sp_net_term_flag) return -1;
899
+ continue;
900
+ }
901
+ return -1;
902
+ }
903
+ /* Compact the ready ones so event_fd/event_mode are O(1) and the caller
904
+ * walks events rather than registrations. */
905
+ for (int i = 0; i < sp_net_reg_n && sp_net_ev_n < r; i++) {
906
+ if (sp_net_reg[i].revents) sp_net_ev[sp_net_ev_n++] = sp_net_reg[i];
907
+ }
908
+ return sp_net_ev_n;
909
+ }
910
+
911
+ int sp_net_poll_event_fd(int i) {
912
+ if (i < 0 || i >= sp_net_ev_n) return -1;
913
+ return sp_net_ev[i].fd;
914
+ }
915
+
916
+ int sp_net_poll_event_mode(int i) {
917
+ if (i < 0 || i >= sp_net_ev_n) return 0;
918
+ short rev = sp_net_ev[i].revents;
919
+ int out = 0;
920
+ if (rev & (POLLIN | POLLHUP | POLLERR)) out |= 1;
921
+ if (rev & POLLOUT) out |= 2;
922
+ return out;
923
+ }
924
+
925
+ /* ---------- process (prefork) ---------- */
926
+
927
+ int sp_net_fork(void) {
928
+ return (int)fork();
929
+ }
930
+
931
+ int sp_net_exit(int status) {
932
+ _exit(status);
933
+ return 0; /* unreachable */
934
+ }
935
+
936
+ int sp_net_getpid(void) {
937
+ return (int)getpid();
938
+ }
939
+
940
+ int sp_net_wait_any(void) {
941
+ int status = 0;
942
+ pid_t p = wait(&status);
943
+ return (int)p;
944
+ }
945
+
946
+ /* ---------- shell ---------- */
947
+
948
+ static SP_TLS char *sp_net_shell_buf; /* per-worker, see sp_net_recv_buf */
949
+ const char *sp_net_shell_capture(const char *cmd, int max_bytes) {
950
+ if (!sp_net_shell_buf && !(sp_net_shell_buf = (char *)malloc(SP_NET_BUFSIZE)))
951
+ return "";
952
+ if (max_bytes <= 0 || max_bytes >= SP_NET_BUFSIZE) max_bytes = SP_NET_BUFSIZE - 1;
953
+ sp_net_shell_buf[0] = '\0';
954
+ FILE *fp = popen(cmd, "r");
955
+ if (!fp) return sp_net_shell_buf;
956
+ size_t total = 0;
957
+ while (total < (size_t)max_bytes) {
958
+ size_t n = fread(sp_net_shell_buf + total, 1, (size_t)max_bytes - total, fp);
959
+ if (n == 0) break;
960
+ total += n;
961
+ }
962
+ sp_ffi_bin_len = (int)total; /* exact byte count for the :binstr path */
963
+ sp_net_shell_buf[total] = '\0';
964
+ pclose(fp);
965
+ return sp_net_shell_buf;
966
+ }
967
+
968
+ /* ---- packed sockaddr ----
969
+ * The byte form `connect_nonblock(sockaddr)` and `bind`/`sendto` take: what
970
+ * CRuby's Socket.sockaddr_in and Addrinfo#to_sockaddr return. Built here
971
+ * rather than in emitted C because the layouts and the AF_* values are
972
+ * platform-dependent, and because the resolution is a getaddrinfo call.
973
+ *
974
+ * `out` receives the packed bytes and the return is their length, or -1 if the
975
+ * host does not resolve. The caller supplies the buffer; a sockaddr_un is the
976
+ * largest of the three and fits well inside sizeof(struct sockaddr_storage)
977
+ * plus the AF_UNIX path.
978
+ */
979
+ int sp_net_pack_sockaddr_in(const char *host, int port, void *out, int cap) {
980
+ struct addrinfo hints, *res = NULL;
981
+ char portbuf[16];
982
+ if (!out || cap <= 0) return -1;
983
+ snprintf(portbuf, sizeof portbuf, "%d", port < 0 ? 0 : port);
984
+ memset(&hints, 0, sizeof hints);
985
+ hints.ai_family = AF_UNSPEC;
986
+ hints.ai_socktype = SOCK_STREAM;
987
+ if (sp_net_resolve((host && *host) ? host : NULL, portbuf, &hints, &res) != 0 || !res)
988
+ return -1;
989
+ int len = (int)res->ai_addrlen;
990
+ if (len > cap) { freeaddrinfo(res); return -1; }
991
+ memcpy(out, res->ai_addr, (size_t)len);
992
+ freeaddrinfo(res);
993
+ return len;
994
+ }
995
+
996
+ /* The AF_UNIX form: a path rather than a host and port. Separate because
997
+ * getaddrinfo does not resolve one, and because the length CRuby packs is the
998
+ * offset of sun_path plus the path and its NUL, not the whole struct. */
999
+ int sp_net_pack_sockaddr_un(const char *path, void *out, int cap) {
1000
+ struct sockaddr_un su;
1001
+ size_t n = path ? strlen(path) : 0;
1002
+ if (!out || cap <= 0) return -1;
1003
+ if (n >= sizeof su.sun_path) return -1;
1004
+ memset(&su, 0, sizeof su);
1005
+ su.sun_family = AF_UNIX;
1006
+ memcpy(su.sun_path, path ? path : "", n);
1007
+ int len = (int)(offsetof(struct sockaddr_un, sun_path) + n + 1);
1008
+ if (len > cap) return -1;
1009
+ memcpy(out, &su, (size_t)len);
1010
+ return len;
1011
+ }
1012
+
1013
+ /* Read a packed sockaddr back: the port is the return, the numeric address
1014
+ * goes into `ipbuf`. -1 when the buffer is not a sockaddr this understands. */
1015
+ int sp_net_unpack_sockaddr_in(const void *sa, int salen, char *ipbuf, int cap) {
1016
+ if (!sa || salen < (int)sizeof(struct sockaddr) || !ipbuf || cap <= 0) return -1;
1017
+ const struct sockaddr *s = (const struct sockaddr *)sa;
1018
+ if (s->sa_family == AF_INET && salen >= (int)sizeof(struct sockaddr_in)) {
1019
+ const struct sockaddr_in *v4 = (const struct sockaddr_in *)sa;
1020
+ if (!inet_ntop(AF_INET, &v4->sin_addr, ipbuf, (socklen_t)cap)) return -1;
1021
+ return (int)ntohs(v4->sin_port);
1022
+ }
1023
+ if (s->sa_family == AF_INET6 && salen >= (int)sizeof(struct sockaddr_in6)) {
1024
+ const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *)sa;
1025
+ if (!inet_ntop(AF_INET6, &v6->sin6_addr, ipbuf, (socklen_t)cap)) return -1;
1026
+ return (int)ntohs(v6->sin6_port);
1027
+ }
1028
+ return -1;
1029
+ }