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.
- checksums.yaml +7 -0
- data/LICENSE-mruby.txt +22 -0
- data/LICENSE-spinel.txt +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/ext/qr_reader_native/SPINEL_VERSION +1 -0
- data/ext/qr_reader_native/extconf.rb +8 -0
- data/ext/qr_reader_native/mruby/error.h +13 -0
- data/ext/qr_reader_native/mruby/internal.h +145 -0
- data/ext/qr_reader_native/mruby/string.h +22 -0
- data/ext/qr_reader_native/mruby.h +93 -0
- data/ext/qr_reader_native/mruby_shim.h +118 -0
- data/ext/qr_reader_native/qr_reader_native.c +13513 -0
- data/ext/qr_reader_native/qr_reader_native.h +29 -0
- data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
- data/ext/qr_reader_native/re_cased.h +71 -0
- data/ext/qr_reader_native/re_compile.c +5784 -0
- data/ext/qr_reader_native/re_ctype.h +3540 -0
- data/ext/qr_reader_native/re_exec.c +2190 -0
- data/ext/qr_reader_native/re_internal.h +671 -0
- data/ext/qr_reader_native/re_prop.h +4629 -0
- data/ext/qr_reader_native/re_spinel.c +363 -0
- data/ext/qr_reader_native/re_spinel.h +22 -0
- data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
- data/ext/qr_reader_native/re_utf8.c +156 -0
- data/ext/qr_reader_native/sp_alloc.c +1447 -0
- data/ext/qr_reader_native/sp_alloc.h +1196 -0
- data/ext/qr_reader_native/sp_argf.h +33 -0
- data/ext/qr_reader_native/sp_array.c +1052 -0
- data/ext/qr_reader_native/sp_array.h +456 -0
- data/ext/qr_reader_native/sp_bigint.c +5826 -0
- data/ext/qr_reader_native/sp_bigint.h +117 -0
- data/ext/qr_reader_native/sp_cold.c +5259 -0
- data/ext/qr_reader_native/sp_compat.h +402 -0
- data/ext/qr_reader_native/sp_core.c +818 -0
- data/ext/qr_reader_native/sp_core.h +56 -0
- data/ext/qr_reader_native/sp_crypto.c +877 -0
- data/ext/qr_reader_native/sp_crypto.h +169 -0
- data/ext/qr_reader_native/sp_dtoa.c +1533 -0
- data/ext/qr_reader_native/sp_dtoa.h +29 -0
- data/ext/qr_reader_native/sp_encoding_names.h +172 -0
- data/ext/qr_reader_native/sp_enum.h +86 -0
- data/ext/qr_reader_native/sp_exc.c +1185 -0
- data/ext/qr_reader_native/sp_exc.h +186 -0
- data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
- data/ext/qr_reader_native/sp_fiber.c +889 -0
- data/ext/qr_reader_native/sp_fiber.h +162 -0
- data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
- data/ext/qr_reader_native/sp_format.c +695 -0
- data/ext/qr_reader_native/sp_format.h +83 -0
- data/ext/qr_reader_native/sp_gc.c +1793 -0
- data/ext/qr_reader_native/sp_gc.h +762 -0
- data/ext/qr_reader_native/sp_hash.c +131 -0
- data/ext/qr_reader_native/sp_hash.h +131 -0
- data/ext/qr_reader_native/sp_inspect.c +215 -0
- data/ext/qr_reader_native/sp_inspect.h +128 -0
- data/ext/qr_reader_native/sp_io.c +1676 -0
- data/ext/qr_reader_native/sp_io.h +272 -0
- data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
- data/ext/qr_reader_native/sp_iobuffer.h +167 -0
- data/ext/qr_reader_native/sp_marshal.c +487 -0
- data/ext/qr_reader_native/sp_marshal.h +60 -0
- data/ext/qr_reader_native/sp_net.c +1029 -0
- data/ext/qr_reader_native/sp_net.h +205 -0
- data/ext/qr_reader_native/sp_pack.c +1254 -0
- data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
- data/ext/qr_reader_native/sp_proc.c +410 -0
- data/ext/qr_reader_native/sp_proc.h +327 -0
- data/ext/qr_reader_native/sp_process.c +547 -0
- data/ext/qr_reader_native/sp_process_status.c +211 -0
- data/ext/qr_reader_native/sp_process_status.h +66 -0
- data/ext/qr_reader_native/sp_random.c +287 -0
- data/ext/qr_reader_native/sp_random.h +52 -0
- data/ext/qr_reader_native/sp_range.h +144 -0
- data/ext/qr_reader_native/sp_re.c +1379 -0
- data/ext/qr_reader_native/sp_re.h +186 -0
- data/ext/qr_reader_native/sp_re_names.h +59 -0
- data/ext/qr_reader_native/sp_sched.c +4044 -0
- data/ext/qr_reader_native/sp_sched.h +295 -0
- data/ext/qr_reader_native/sp_slab.c +1507 -0
- data/ext/qr_reader_native/sp_str.c +1462 -0
- data/ext/qr_reader_native/sp_str.h +306 -0
- data/ext/qr_reader_native/sp_str_crypt.c +23 -0
- data/ext/qr_reader_native/sp_string.c +92 -0
- data/ext/qr_reader_native/sp_string.h +253 -0
- data/ext/qr_reader_native/sp_system.c +78 -0
- data/ext/qr_reader_native/sp_system.h +18 -0
- data/ext/qr_reader_native/sp_time.c +860 -0
- data/ext/qr_reader_native/sp_time.h +100 -0
- data/ext/qr_reader_native/sp_types.h +292 -0
- data/ext/qr_reader_native/spinel_rt.h +19017 -0
- data/ext/qr_reader_native/unicase.c +399 -0
- data/ext/qr_reader_native/unicase.h +901 -0
- data/lib/qr_reader/native.rb +6 -0
- 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
|
+
}
|