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,1676 @@
1
+ /* sp_io.c -- File / IO handle ops in libspinel_rt.a.
2
+ *
3
+ * The allocation-free handle ops (open / pipe / fdopen / write / close /
4
+ * closed? / puts / print / flush / eof?); the string-returning readers
5
+ * (gets / read / read_n / path) stay inline in spinel_rt.h.
6
+ *
7
+ * Self-contained: includes sp_io.h (the sp_File layout) + sp_gc.h
8
+ * (sp_mark_string), not spinel_rt.h. */
9
+ #include "sp_io.h"
10
+ #include "sp_gc.h" /* sp_mark_string */
11
+ #include <stdlib.h>
12
+ #include <stddef.h> /* offsetof, for the static-stream layout assertion */
13
+ #include <string.h>
14
+ #include <unistd.h> /* pipe, isatty */
15
+ #include <sys/stat.h> /* stat() for the File predicates */
16
+ #include <sys/ioctl.h> /* TIOCGWINSZ for #winsize */
17
+ #include <sys/socket.h> /* the Socket:: constants */
18
+ #include <netinet/in.h>
19
+ #include <netinet/tcp.h>
20
+ #include <sys/un.h>
21
+ #include <sys/time.h> /* utimes() for File.utime */
22
+ #include <errno.h>
23
+ #include <fcntl.h> /* fcntl flags for #close_on_exec?, #fcntl */
24
+ #include <poll.h> /* POLLIN for the socket read park */
25
+
26
+ /* Per-call "do not block" for send(2). Linux honours it; Darwin and the BSDs
27
+ accept the flag and sleep anyway once the send buffer is full, which is why
28
+ sp_sock_room below exists. Where the flag is missing entirely it is 0 and
29
+ the room cap carries the socket on its own. */
30
+ #ifdef MSG_DONTWAIT
31
+ #define SP_MSG_DONTWAIT MSG_DONTWAIT
32
+ #else
33
+ #define SP_MSG_DONTWAIT 0
34
+ #endif
35
+
36
+ /* Bytes the send buffer can take right now, or -1 when the kernel will not
37
+ say. Capping a send at this is what keeps a blocking socket's write out of
38
+ the kernel's sleep: poll(POLLOUT) answers "there is room", not "there is
39
+ room for all of n", so a 64 KB write into a nearly full buffer blocks even
40
+ though the descriptor was reported writable. */
41
+ static long sp_sock_room(int fd) {
42
+ int capacity = 0, queued = 0;
43
+ socklen_t len = sizeof capacity;
44
+ if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &capacity, &len) != 0 || capacity <= 0) return -1;
45
+ #if defined(SO_NWRITE) /* Darwin: bytes not yet handed to the peer */
46
+ len = sizeof queued;
47
+ if (getsockopt(fd, SOL_SOCKET, SO_NWRITE, &queued, &len) != 0) return -1;
48
+ #elif defined(TIOCOUTQ) /* Linux (SIOCOUTQ): same question, via ioctl */
49
+ if (ioctl(fd, TIOCOUTQ, &queued) != 0) return -1;
50
+ #else
51
+ return -1;
52
+ #endif
53
+ long room = (long)capacity - (long)queued;
54
+ return room > 0 ? room : 0;
55
+ }
56
+ #if !defined(__APPLE__) && !defined(__GLIBC__)
57
+ #include <stdio_ext.h> /* musl __freadahead: pending stdio read-buffer bytes */
58
+ #endif
59
+
60
+ /* Provided by the generated TU / libspinel_rt.a. */
61
+ extern void *sp_gc_alloc(size_t sz, void (*fin)(void *), void (*scn)(void *));
62
+ extern SP_NORETURN void sp_raise_cls(const char *cls, const char *msg);
63
+ extern const char *sp_errno_class_name(int e); /* lib/sp_exc.c: the Errno:: class for a C errno */
64
+ extern const char *sp_sprintf(const char *fmt, ...);
65
+
66
+ /* The finalizer honors autoclose: an IO whose fd the program disowned
67
+ (io.autoclose = false after wrapping the fd itself) flushes and abandons
68
+ the FILE rather than closing the caller's descriptor -- the FILE structure
69
+ leaks, which is the price of libc having no fdclose(3); the for_fd
70
+ autoclose:false path wraps a dup(2) instead and never pays it (#4208). */
71
+ static void sp_File_fin(void *p) {
72
+ sp_File *f = (sp_File *)p;
73
+ if (!f->fp || f->closed) return; /* closed: the FILE is the shared sentinel */
74
+ if (f->no_autoclose && !f->fno_plus1) { fflush(f->fp); f->fp = NULL; return; }
75
+ fclose(f->fp); f->fp = NULL;
76
+ }
77
+ static void sp_File_scan(void *p) { sp_File *f = (sp_File *)p; if (f->path) sp_mark_string(f->path); if (f->mode) sp_mark_string(f->mode); }
78
+
79
+ /* The two-argument form is the permission form with CRuby's default bits:
80
+ the same mode scan (which refuses "rx"), O_CLOEXEC, and the errno-named
81
+ error, rather than fopen's looser mode and a flat ENOENT. */
82
+ sp_File *sp_File_open(const char *path, const char *mode) {
83
+ return sp_File_open_perm(path, mode ? mode : "r", SP_INT_NIL);
84
+ }
85
+
86
+ /* Returns 0 on success, -1 on error. */
87
+ int sp_io_make_pipe(int fds[2]) {
88
+ return pipe(fds);
89
+ }
90
+
91
+ /* IO.pipe end: wrap a raw pipe fd in a GC-managed sp_File so the
92
+ sp_File_* I/O ops work on it. Same finalizer/scan as sp_File_open. */
93
+ /* owns_fd: the descriptor is this handle's to release when fdopen fails --
94
+ true for one open(2) or pipe(2) just made, false for IO.for_fd, whose fd
95
+ belongs to the program (closing its stdout on a bad mode lost the output). */
96
+ sp_File *sp_io_fdopen_ex(int fd, const char *mode, int owns_fd) {SP_GC_ROOT_STR(mode);
97
+ sp_File *f = (sp_File *)sp_gc_alloc(sizeof(sp_File), sp_File_fin, sp_File_scan);
98
+ f->fp = fdopen(fd, mode ? mode : "r");
99
+ if (!f->fp) {
100
+ if (owns_fd) close(fd);
101
+ sp_raise_cls("IOError", "fdopen failed");
102
+ return NULL;
103
+ }
104
+ f->path = NULL;
105
+ f->mode = mode;
106
+ f->lineno = 0;
107
+ return f;
108
+ }
109
+ sp_File *sp_io_fdopen(int fd, const char *mode) { return sp_io_fdopen_ex(fd, mode, 1); }
110
+
111
+ /* BasicSocket.do_not_reverse_lookup: true unless the program changes it; a
112
+ socket takes it when it is made, and keeps its own copy after that */
113
+ sp_bool sp_sock_dnrl_default = 1;
114
+
115
+ /* Wrap a socket fd (#2922). The FILE* serves the buffered READ side (gets and
116
+ friends need lookahead); every write bypasses stdio straight to write(2) --
117
+ see sp_sock_write below -- so a response is on the wire immediately, like
118
+ CRuby sockets' sync = true default. `kind` lands in ->mode for #class. */
119
+ sp_File *sp_io_fdopen_sock(int fd, const char *kind) {SP_GC_ROOT_STR(kind);
120
+ if (fd < 0) sp_raise_cls("Errno::ECONNREFUSED", "Connection refused");
121
+ /* The scheduler-aware accept/connect helpers may hand us a non-blocking
122
+ fd; the stdio read side must BLOCK (fgets treats EAGAIN as EOF), so
123
+ clear O_NONBLOCK. Green threads still yield: every read entry point
124
+ parks on sp_io_wait_readable below before touching the stream. */
125
+ { int fl = fcntl(fd, F_GETFL); if (fl >= 0) fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); }
126
+ sp_File *f = (sp_File *)sp_gc_alloc(sizeof(sp_File), sp_File_fin, sp_File_scan);
127
+ f->fp = fdopen(fd, "r+");
128
+ if (!f->fp) { sp_raise_cls("IOError", "fdopen failed"); return NULL; }
129
+ f->path = NULL;
130
+ f->mode = kind;
131
+ f->lineno = 0;
132
+ f->is_sock = 1; f->rev_lookup = !sp_sock_dnrl_default;
133
+ return f;
134
+ }
135
+
136
+ /* Park the calling green thread until the handle has readable data, so a
137
+ blocking fgets/fread does not pin its worker while the peer is idle. A
138
+ no-op when stdio already buffered data (waiting then would stall on the
139
+ WIRE while the answer sits in the buffer), and in the single-threaded
140
+ build.
141
+
142
+ WHICH handles: the ones whose read can block indefinitely. This was the
143
+ socket path and asked `is_sock`, so a PIPE went straight into fread and
144
+ blocked its OS worker in the kernel -- and the thread that would write to
145
+ that pipe is often a green thread on the very same worker, so it had
146
+ nowhere to run: two threads passing a byte back and forth never completed
147
+ on SPINEL_WORKERS=1 (#4307). A character device (a tty, /dev/urandom) is
148
+ the same shape. A regular file is always ready and pays only the one
149
+ fstat, once per handle. */
150
+ /* Bytes stdio has already buffered for this stream. They are readable without
151
+ a read(2), so a drain of exactly this many cannot block -- which is what
152
+ lets a slurp fill from a pipe without ever sitting in the kernel. */
153
+ size_t sp_io_stdio_buffered(FILE *fp) {
154
+ if (!fp) return 0;
155
+ #if defined(__GLIBC__)
156
+ return fp->_IO_read_end > fp->_IO_read_ptr ? (size_t)(fp->_IO_read_end - fp->_IO_read_ptr) : 0;
157
+ #elif defined(__APPLE__)
158
+ return fp->_r > 0 ? (size_t)fp->_r : 0;
159
+ #else
160
+ return __freadahead(fp); /* musl */
161
+ #endif
162
+ }
163
+
164
+ /* Can this handle's I/O block indefinitely? Asked once per handle, from an
165
+ fstat on first use: a FIFO, a socket and a character device can; a regular
166
+ file is always ready. */
167
+ static int sp_io_parkable(sp_File *f) {
168
+ if (!f->park) {
169
+ struct stat st;
170
+ int pfd = fileno(f->fp);
171
+ f->park = (f->is_sock ||
172
+ (pfd >= 0 && fstat(pfd, &st) == 0 &&
173
+ (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode) || S_ISCHR(st.st_mode)))) ? 2 : 1;
174
+ }
175
+ return f->park == 2;
176
+ }
177
+
178
+ /* Park until the descriptor can take bytes. A write into a full pipe or socket
179
+ buffer sits in the kernel holding the OS worker, and the READER that would
180
+ drain it is often a green thread on that same worker -- so it never runs and
181
+ the two deadlock, the write-side twin of the read park. Only for the handles
182
+ whose peer is that reader: a socket, and the write end of an IO.pipe (which
183
+ spinel marks sync and leaves unbuffered). A file and the standard streams
184
+ keep their plain path, and their write cost with it. */
185
+ void sp_io_wait_writable(sp_File *f) {SP_GC_ROOT(f);
186
+ if (!f || !f->fp || !sp_io_parkable(f)) return;
187
+ int wfd = fileno(f->fp);
188
+ if (wfd < 0) return;
189
+ { struct pollfd wp; wp.fd = wfd; wp.events = POLLOUT; wp.revents = 0;
190
+ if (poll(&wp, 1, 0) > 0) return; } /* room already: no park */
191
+ #ifdef SP_THREADS
192
+ {
193
+ extern int sp_sched_wait_io_unless(int fd, short events, const unsigned char *cancel);
194
+ sp_sched_wait_io_unless(wfd, POLLOUT, &f->closed);
195
+ if (f->closed) sp_raise_cls("IOError", "stream closed in another thread"); /* see sp_io_wait_readable */
196
+ }
197
+ #else
198
+ {
199
+ extern void sp_Thread_pass(void);
200
+ struct pollfd p;
201
+ p.fd = wfd; p.events = POLLOUT;
202
+ for (;;) {
203
+ p.revents = 0;
204
+ int r = poll(&p, 1, 1);
205
+ if (r > 0 || (r < 0 && errno != EINTR && errno != EAGAIN)) return;
206
+ sp_Thread_pass();
207
+ }
208
+ }
209
+ #endif
210
+ }
211
+
212
+ /* The park on a bare descriptor. `closed` is the owning handle's closed flag
213
+ when there is one: a close from another thread between the poll below and
214
+ the park's registration is then seen under the scheduler lock, instead of
215
+ the registration going in after the close dropped it (a read with no
216
+ deadline waited forever, one macOS run in three of the #4546 test). */
217
+ static void sp_io_park_fd_readable(int fd, const unsigned char *closed) {
218
+ if (fd < 0) return;
219
+ /* Already readable: answer from one poll rather than a park. The park is a
220
+ monitor round trip -- register the waiter, write the self-pipe to break
221
+ the monitor out of ITS poll, wait to be requeued and rescheduled -- and
222
+ paying that when the bytes are already there is most of the cost in the
223
+ shape that reads right after an IO.select says the fd is ready. */
224
+ { struct pollfd rp; rp.fd = fd; rp.events = POLLIN; rp.revents = 0;
225
+ if (poll(&rp, 1, 0) > 0) return; }
226
+ #ifdef SP_THREADS
227
+ {
228
+ extern int sp_sched_wait_io_unless(int fd, short events, const unsigned char *cancel);
229
+ sp_sched_wait_io_unless(fd, POLLIN, closed);
230
+ }
231
+ #else
232
+ (void)closed;
233
+ /* Cooperative build: a blocking read here would stall EVERY green thread
234
+ (the peer that will produce our data included). Poll with a short
235
+ timeout and hand the scheduler to the other threads until readable. */
236
+ {
237
+ extern void sp_Thread_pass(void);
238
+ struct pollfd p;
239
+ p.fd = fd;
240
+ p.events = POLLIN;
241
+ for (;;) {
242
+ p.revents = 0;
243
+ int r = poll(&p, 1, 1);
244
+ if (r > 0 || (r < 0 && errno != EINTR && errno != EAGAIN)) return;
245
+ sp_Thread_pass();
246
+ }
247
+ }
248
+ #endif
249
+ }
250
+ /* The descriptor form, for a pipe the runtime holds without a handle (a
251
+ backtick's child): the same park, on the bare fd. */
252
+ void sp_io_wait_fd_readable(int fd) { sp_io_park_fd_readable(fd, NULL); }
253
+ void sp_io_wait_readable(sp_File *f) {SP_GC_ROOT(f);
254
+ if (!f || !f->fp) return;
255
+ if (!sp_io_parkable(f)) return;
256
+ if (sp_io_stdio_buffered(f->fp) > 0) return;
257
+ sp_io_park_fd_readable(fileno(f->fp), &f->closed);
258
+ /* woken by a close from another thread: the handle is gone, and a read
259
+ would touch a descriptor that is closed or already someone else's */
260
+ if (f->closed) sp_raise_cls("IOError", "stream closed in another thread");
261
+ }
262
+
263
+ /* The socket write path: straight to the descriptor, looping over short
264
+ writes. The stdio stream is never written through, so there is nothing to
265
+ flush and no read/write switching hazard on the shared FILE*. */
266
+ static sp_int sp_sock_write(sp_File *f, const char *s, size_t n) {
267
+ int fd = fileno(f->fp);
268
+ size_t off = 0;
269
+ /* A pipe's WRITE end is write-only -- nothing reads through its stream -- so
270
+ it can be O_NONBLOCK, and then a write that would block says so instead of
271
+ blocking. That is the cheap shape: no probe on the way in, and the park
272
+ only on the attempt that actually could not proceed. A socket shares its
273
+ descriptor with the stdio READ side, where fgets treats EAGAIN as EOF
274
+ (see sp_io_fdopen_sock), so it cannot carry O_NONBLOCK -- it takes
275
+ MSG_DONTWAIT per send(2) instead, which says the same thing for this one
276
+ call and leaves the descriptor's flags, and the read side, untouched.
277
+ The probe alone was not enough: it answers "there is room", not "there is
278
+ room for all of n", so a 64 KB write into a nearly full buffer still slept
279
+ in the kernel with the collector waiting on this worker (the #4528 shape,
280
+ on the write side). */
281
+ int nb = 0;
282
+ if (!f->is_sock) {
283
+ if (!f->wnonblock) {
284
+ int fl = fcntl(fd, F_GETFL);
285
+ f->wnonblock = (fl >= 0 && (fl & O_NONBLOCK ? 1 : fcntl(fd, F_SETFL, fl | O_NONBLOCK) == 0)) ? 1 : 2;
286
+ }
287
+ nb = f->wnonblock == 1;
288
+ }
289
+ while (off < n) {
290
+ ssize_t put;
291
+ size_t want = n - off;
292
+ if (!nb) sp_io_wait_writable(f); /* frees the worker while the buffer is full */
293
+ if (f->is_sock) {
294
+ /* A datagram or seqpacket send is one message: shortening it would
295
+ deliver two where the caller wrote one, and would hide the EMSGSIZE
296
+ the kernel owes an over-long payload. Only a byte stream is capped. */
297
+ if (!f->wstream) {
298
+ int sotype = 0; socklen_t solen = sizeof sotype;
299
+ f->wstream = (getsockopt(fd, SOL_SOCKET, SO_TYPE, &sotype, &solen) == 0 &&
300
+ sotype == SOCK_STREAM) ? 1 : 2;
301
+ }
302
+ /* Linux honours MSG_DONTWAIT: the send returns what fit, or EAGAIN,
303
+ and the park below takes it from there, so the two extra syscalls
304
+ of the room query are not paid on the hot path there. Darwin and
305
+ the BSDs sleep despite the flag, and the query is what keeps them
306
+ out of the kernel. */
307
+ #if defined(__linux__)
308
+ long room = -1;
309
+ #else
310
+ long room = f->wstream == 1 ? sp_sock_room(fd) : -1;
311
+ #endif
312
+ /* No room: go back to the park above, which is where waiting belongs.
313
+ It cannot spin -- POLLOUT is raised only once the free space reaches
314
+ SO_SNDLOWAT (2048 by default), so a buffer this full does not report
315
+ the descriptor writable and the park really parks. */
316
+ if (room == 0) continue;
317
+ if (room > 0 && (size_t)room < want) want = (size_t)room;
318
+ put = send(fd, s + off, want, SP_MSG_DONTWAIT);
319
+ }
320
+ else {
321
+ put = write(fd, s + off, want);
322
+ }
323
+ if (put < 0) {
324
+ if (errno == EINTR) continue;
325
+ if ((nb || f->is_sock) && (errno == EAGAIN || errno == EWOULDBLOCK)) { sp_io_wait_writable(f); continue; }
326
+ sp_file_raise_errno("write", f->is_sock ? "socket" : "pipe");
327
+ }
328
+ off += (size_t)put;
329
+ }
330
+ return (sp_int)n;
331
+ }
332
+
333
+ /* Direct write to a regular file's descriptor, bypassing the stdio
334
+ buffer. IO#syswrite is unbuffered: the bytes must reach the file
335
+ before the next call returns, so there is nothing to flush and no
336
+ read/write switching hazard on the shared FILE*. Sockets and sync
337
+ parkable handles keep sp_sock_write, which already writes straight
338
+ to the descriptor. */
339
+ static sp_int sp_io_write_raw(sp_File *f, const char *s, size_t n) {
340
+ int fd = fileno(f->fp);
341
+ size_t off = 0;
342
+ /* Flush whatever stdio still holds: a previous buffered write would
343
+ otherwise land after this direct write and reorder the bytes. A
344
+ flush failure is the same error path as a write failure. */
345
+ if (fflush(f->fp) != 0) sp_file_raise_errno("write", "file");
346
+ while (off < n) {
347
+ ssize_t put = write(fd, s + off, n - off);
348
+ if (put < 0) {
349
+ if (errno == EINTR) continue;
350
+ sp_file_raise_errno("write", "file");
351
+ }
352
+ off += (size_t)put;
353
+ }
354
+ /* Sync the FILE* position with the descriptor: the raw write loop moved
355
+ the descriptor past where stdio thinks it is, and a zero-length write
356
+ is still a valid sync point. Without this, a subsequent ftello,
357
+ buffered read, or buffered write on the same stream would use the
358
+ stale stdio offset. The stream is set to the descriptor's own offset:
359
+ a relative seek (SEEK_CUR) counts from stdio's cached offset, which the
360
+ raw write did not move, and moved the descriptor back to it, so the
361
+ next syswrite overwrote this one. A descriptor with no offset (a pipe,
362
+ a terminal) has none to share, where the relative seek raised ESPIPE. */
363
+ off_t pos = lseek(fd, 0, SEEK_CUR);
364
+ if (pos >= 0 && fseeko(f->fp, pos, SEEK_SET) != 0) sp_file_raise_errno("write", "file");
365
+ return (sp_int)n;
366
+ }
367
+
368
+ /* Shared write core: `n` is the operand byte length (strlen for the
369
+ bare-literal-safe entry, sp_str_byte_len for the binary one). */
370
+ SP_NORETURN SP_COLD void sp_io_raise_closed(void) {
371
+ sp_raise_cls("IOError", "closed stream");
372
+ }
373
+
374
+ static sp_int sp_File_write_len(sp_File *f, const char *s, size_t n) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
375
+ if (f->is_sock) return sp_sock_write(f, s, n);
376
+ /* An IO.pipe write end is sync, so setvbuf left it unbuffered and stdio
377
+ holds nothing between calls: the raw loop is what fwrite would do anyway,
378
+ and it can park between attempts. Every other handle keeps stdio. */
379
+ if (f->sync_on && sp_io_parkable(f)) return sp_sock_write(f, s, n);
380
+ return (sp_int)fwrite(s, 1, n, f->fp);
381
+ }
382
+
383
+ sp_int sp_File_write(sp_File *f, const char *s) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
384
+ SP_IO_OPEN(f);
385
+ if (!s) return 0;
386
+ return sp_File_write_len(f, s, strlen(s));
387
+ }
388
+
389
+ /* Binary-safe write: sizes the operand with the header length, so an embedded
390
+ NUL reaches the descriptor instead of truncating the write (IO#read and
391
+ File.read already return such bytes; only the write side dropped them).
392
+ Reads s[-1], so it is for CODEGEN-emitted String values only -- the same
393
+ value class String#<< / #replace already size this way. Runtime-internal
394
+ callers and codegen's synthesized "" / "\n" literals are bare C literals
395
+ with no marker byte and must use the plain entry above. */
396
+ sp_int sp_File_write_bin(sp_File *f, const char *s) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
397
+ SP_IO_OPEN(f);
398
+ if (!s) return 0;
399
+ return sp_File_write_len(f, s, sp_str_byte_len(s));
400
+ }
401
+
402
+ /* IO#syswrite: the same write core as sp_File_write_bin, but the byte
403
+ length is passed in by the caller rather than read off the marker byte.
404
+ The codegen syswrite arm sizes the operand itself -- a String source's
405
+ length (sp_str_byte_len, so an embedded NUL reaches the descriptor) or a
406
+ converted value's length (sp_poly_to_s + strlen) -- and hands both the
407
+ pointer and the count to this entry, which is the single place the
408
+ socket/pipe/stream routing decision is made. A regular file writes
409
+ straight to the descriptor (sp_io_write_raw), bypassing the stdio
410
+ buffer: syswrite is unbuffered, so the bytes are on disk before the
411
+ call returns and a read back through the same or another handle sees
412
+ them without a flush. */
413
+ sp_int sp_File_syswrite(sp_File *f, const char *s, size_t n) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
414
+ SP_IO_OPEN(f);
415
+ if (!s) return 0;
416
+ if (f->is_sock) return sp_sock_write(f, s, n);
417
+ if (f->sync_on && sp_io_parkable(f)) return sp_sock_write(f, s, n);
418
+ return sp_io_write_raw(f, s, n);
419
+ }
420
+
421
+ sp_bool sp_File_tty_p(sp_File *f) {
422
+ SP_IO_OPEN(f);
423
+ return isatty(fileno(f->fp)) ? 1 : 0;
424
+ }
425
+
426
+ sp_int sp_File_fileno(sp_File *f) {
427
+ SP_IO_OPEN(f);
428
+ if (f->fno_plus1) return (sp_int)(f->fno_plus1 - 1);
429
+ return (sp_int)fileno(f->fp);
430
+ }
431
+
432
+ /* IO#winsize -> [rows, cols]. Queries the terminal; an OPEN non-tty
433
+ (pipe/file) has no size, so CRuby raises there, but returning [0, 0] keeps
434
+ the common "STDOUT.winsize" probe compiling and running without an
435
+ exception path. A closed handle is the macro's IOError, as everywhere. */
436
+ sp_IntArray *sp_File_winsize(sp_File *f) {
437
+ SP_IO_OPEN(f);
438
+ sp_int rows = 0, cols = 0;
439
+ struct winsize ws;
440
+ if (ioctl(fileno(f->fp), TIOCGWINSZ, &ws) == 0) { rows = ws.ws_row; cols = ws.ws_col; }
441
+ sp_IntArray *a = sp_IntArray_new();
442
+ sp_IntArray_push(a, rows);
443
+ sp_IntArray_push(a, cols);
444
+ return a;
445
+ }
446
+
447
+ /* IO#winsize= [rows, cols] or [rows, cols, xpixel, ypixel], as CRuby's
448
+ io/console: any other length is ArgumentError, and a handle that is not a
449
+ terminal raises the ioctl's Errno (ENOTTY) rather than answering quietly,
450
+ since a size that was not set is not something to carry on from. Answers
451
+ its argument, which is what an assignment yields. A pty master is the case
452
+ that needs it: the program sizes the terminal its child sees. */
453
+ sp_IntArray *sp_File_set_winsize(sp_File *f, sp_IntArray *size) {
454
+ SP_IO_OPEN(f);
455
+ sp_int n = size ? size->len : 0;
456
+ if (n != 2 && n != 4) {
457
+ char msg[96];
458
+ snprintf(msg, sizeof msg, "wrong number of arguments (given %lld, expected 2 or 4)", (long long)n);
459
+ sp_raise_cls("ArgumentError", msg);
460
+ }
461
+ const sp_int *v = size->data + size->start;
462
+ struct winsize ws;
463
+ memset(&ws, 0, sizeof ws);
464
+ ws.ws_row = (unsigned short)v[0];
465
+ ws.ws_col = (unsigned short)v[1];
466
+ if (n == 4) {
467
+ ws.ws_xpixel = (unsigned short)v[2];
468
+ ws.ws_ypixel = (unsigned short)v[3];
469
+ }
470
+ #ifdef TIOCSWINSZ
471
+ if (ioctl(fileno(f->fp), TIOCSWINSZ, &ws) != 0) {
472
+ int e = errno;
473
+ sp_raise_cls(sp_errno_class_name(e), strerror(e));
474
+ }
475
+ #else
476
+ /* a platform with no terminal-size ioctl (wasm32-wasi) */
477
+ (void)ws;
478
+ sp_raise_cls("NotImplementedError", "winsize= is not implemented on this platform");
479
+ #endif
480
+ return size;
481
+ }
482
+
483
+ /* The standard streams are singletons in static storage, not GC allocations.
484
+ Reads of $stdout / $stderr compile straight to these calls and only ever
485
+ dereference the result, so nothing scanned ever held one -- until
486
+ `$stderr = $stdout` stored it into a real global slot, which the generated
487
+ globals hook marks. sp_gc_mark decides what a pointer is from the byte
488
+ before it, finds whatever .bss happens to sit there, fabricates a header at
489
+ obj - sizeof(sp_gc_hdr) and calls the scan pointer read out of it (#3410).
490
+
491
+ Give them the tag the collector already reads as "static storage, leave it
492
+ alone", the same one a rodata string literal carries. The mark then returns
493
+ before it can walk a header that was never written. The eight-byte lead-in
494
+ puts that byte immediately before the struct; sp_File begins with a pointer,
495
+ so the compiler adds no padding of its own, and the assertion pins it. */
496
+ typedef struct { unsigned char lead[8]; sp_File f; } sp_StaticFile;
497
+ _Static_assert(offsetof(sp_StaticFile, f) == 8, "the skip tag must sit immediately before the sp_File");
498
+ #define SP_STATIC_FILE_LEAD { 0, 0, 0, 0, 0, 0, 0, 0xff }
499
+
500
+ sp_File *sp_io_stdout(void) {
501
+ static sp_StaticFile s = { SP_STATIC_FILE_LEAD, { NULL, "<STDOUT>", "w" } };
502
+ if (!s.f.fp) s.f.fp = stdout;
503
+ return &s.f;
504
+ }
505
+
506
+ sp_File *sp_io_stderr(void) {
507
+ static sp_StaticFile s = { SP_STATIC_FILE_LEAD, { NULL, "<STDERR>", "w" } };
508
+ if (!s.f.fp) s.f.fp = stderr;
509
+ return &s.f;
510
+ }
511
+
512
+ sp_File *sp_io_stdin(void) {
513
+ static sp_StaticFile s = { SP_STATIC_FILE_LEAD, { NULL, "<STDIN>", "r" } };
514
+ if (!s.f.fp) s.f.fp = stdin;
515
+ return &s.f;
516
+ }
517
+
518
+ sp_bool sp_io_frozen(sp_File *f) { return f ? (sp_bool)f->frozen : TRUE; }
519
+ sp_File *sp_io_freeze(sp_File *f) { if (f) f->frozen = 1; return f; }
520
+
521
+ /* Declared in sp_sched.h, which sp_io.c does not include (it is deliberately
522
+ self-contained); the single-threaded build gets the no-op definition. */
523
+ void sp_sched_ev_forget(int fd);
524
+
525
+ sp_int sp_File_close(sp_File *f) {
526
+ /* never fclose the shared stdout/stderr handles (sp_io_stdout/sp_io_stderr):
527
+ closing the process's standard streams would corrupt the singleton and any
528
+ later write through it. Closing them is a no-op. */
529
+ /* Take the descriptor out of the scheduler's readiness set BEFORE it stops
530
+ being ours. The registration is persistent, so this is the one place a
531
+ handle's fd dies where we can be exact; everything else that could strand
532
+ one -- a raw close behind our back, exec, a dup we did not make -- is left
533
+ to the self-healing arm, which re-adds on ENOENT (#4306). */
534
+ /* The handle reads as closed BEFORE the parked readers are woken and the
535
+ descriptor is closed, and what it carries from then on is the /dev/null
536
+ sentinel, not the freed FILE: a reader readied by the forget below
537
+ retries on another worker and raises on the flag; one already past its
538
+ check reads EOF from the sentinel instead of dereferencing NULL or the
539
+ connection the next accept gave that number (#4546). */
540
+ if (f && f->fp && !f->closed && f->fp != stdout && f->fp != stderr && f->fp != stdin) {
541
+ FILE *fp = f->fp;
542
+ int fd = fileno(fp);
543
+ f->closed = 1;
544
+ f->fp = sp_io_closed_sentinel();
545
+ sp_sched_ev_forget(fd);
546
+ /* autoclose=false on an IO that wraps the fd itself: flush and abandon
547
+ the FILE (see sp_File_fin); a for_fd wrapper holds a dup, so its
548
+ fclose never reaches the caller's descriptor (#4208) */
549
+ if (f->no_autoclose && !f->fno_plus1) { fflush(fp); return 0; }
550
+ fclose(fp);
551
+ return 0;
552
+ }
553
+ if (f && f->fp && !f->closed) sp_sched_ev_forget(fileno(f->fp));
554
+ return 0;
555
+ }
556
+
557
+ sp_bool sp_File_closed_p(sp_File *f) {
558
+ return !f || !f->fp || f->closed;
559
+ }
560
+ FILE *sp_io_closed_sentinel(void) {
561
+ static FILE *sentinel = NULL;
562
+ if (!sentinel) {
563
+ FILE *s = fopen("/dev/null", "r+");
564
+ if (!s) s = fopen("/dev/null", "r");
565
+ if (!s) sp_raise_cls("IOError", "cannot open /dev/null");
566
+ if (!SP_ATOMIC_CAS(&sentinel, &(FILE *){NULL}, s, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) fclose(s);
567
+ }
568
+ return sentinel;
569
+ }
570
+
571
+ /* The Ruby class a handle presents as, for the NoMethodError texts and #inspect.
572
+ The kind label a socket carries in ->mode is what tells the socket classes
573
+ apart; a path-backed handle is a File and everything else is an IO. */
574
+ const char *sp_io_kind_name(sp_File *f) {
575
+ /* a NULL handle IS nil (the readiness family answers nil on timeout), so it
576
+ names NilClass -- both for #class and for the NoMethodError texts */
577
+ if (!f) return SPL("NilClass");
578
+ if (f->is_sock && f->mode) {
579
+ if (strcmp(f->mode, "tcpserver") == 0) return SPL("TCPServer");
580
+ if (strcmp(f->mode, "tcp") == 0) return SPL("TCPSocket");
581
+ if (strcmp(f->mode, "udp") == 0) return SPL("UDPSocket");
582
+ if (strcmp(f->mode, "unix") == 0) return SPL("UNIXSocket");
583
+ if (strcmp(f->mode, "unixserver") == 0) return SPL("UNIXServer");
584
+ if (strcmp(f->mode, "socket") == 0) return SPL("Socket");
585
+ }
586
+ if (f->path && f->path[0] && f->path[0] != '<') return SPL("File");
587
+ return SPL("IO");
588
+ }
589
+
590
+ /* The builtin superclass chain for a handle's class, mirroring the one the
591
+ generated TU carries for class VALUES (sp_builtin_superclass). A handle
592
+ answers #is_a? from its kind, which is a runtime property, so the walk lives
593
+ here rather than in the emitted switch. io_super_name (src/analyze_util.c)
594
+ is the compiler's copy; keep the two the same. */
595
+ static const char *sp_io_super_of(const char *k) {
596
+ if (strcmp(k, "TCPServer") == 0) return SPL("TCPSocket");
597
+ if (strcmp(k, "TCPSocket") == 0) return SPL("IPSocket");
598
+ if (strcmp(k, "UDPSocket") == 0) return SPL("IPSocket");
599
+ if (strcmp(k, "IPSocket") == 0) return SPL("BasicSocket");
600
+ if (strcmp(k, "UNIXServer") == 0) return SPL("UNIXSocket");
601
+ if (strcmp(k, "UNIXSocket") == 0) return SPL("BasicSocket");
602
+ if (strcmp(k, "Socket") == 0) return SPL("BasicSocket");
603
+ if (strcmp(k, "BasicSocket") == 0) return SPL("IO");
604
+ if (strcmp(k, "File") == 0) return SPL("IO");
605
+ if (strcmp(k, "IO") == 0) return SPL("Object");
606
+ if (strcmp(k, "Object") == 0) return SPL("BasicObject");
607
+ return NULL;
608
+ }
609
+ sp_bool sp_io_is_a(sp_File *f, const char *cls) {SP_GC_ROOT(f);
610
+ if (!cls) return 0;
611
+ if (strcmp(cls, "Kernel") == 0) return 1; /* Object includes Kernel */
612
+ for (const char *k = sp_io_kind_name(f); k; k = sp_io_super_of(k))
613
+ if (strcmp(k, cls) == 0) return 1;
614
+ return 0;
615
+ }
616
+ int sp_io_pick_class(sp_File *f, const char *const *names, const int *idx) {SP_GC_ROOT(f);
617
+ if (!f) return 0x7fffffff;
618
+ if (f->mode && (strcmp(f->mode, "stat") == 0 || strcmp(f->mode, "lstat") == 0)) return 0x7fffffff;
619
+ for (const char *k = sp_io_kind_name(f); k; k = sp_io_super_of(k))
620
+ for (int i = 0; names[i]; i++)
621
+ if (strcmp(k, names[i]) == 0) return idx[i];
622
+ return 0x7fffffff;
623
+ }
624
+ sp_bool sp_io_instance_of(sp_File *f, const char *cls) {SP_GC_ROOT(f);
625
+ return cls && strcmp(sp_io_kind_name(f), cls) == 0;
626
+ }
627
+
628
+ /* Socket:: numeric constants. Their values differ between Linux and the BSDs
629
+ (SO_REUSEADDR is 2 on Linux, 0x0004 on macOS), so they are resolved here --
630
+ where the system headers are in scope -- rather than baked into the emitted
631
+ C as literals. Unknown names answer -1; the caller raises NameError. */
632
+ sp_int sp_sock_const(const char *n) {
633
+ if (!n) return -1;
634
+ struct { const char *n; sp_int v; } T[] = {
635
+ { "SOL_SOCKET", SOL_SOCKET }, { "IPPROTO_TCP", IPPROTO_TCP },
636
+ { "IPPROTO_IP", IPPROTO_IP }, { "IPPROTO_UDP", IPPROTO_UDP },
637
+ { "SO_REUSEADDR", SO_REUSEADDR }, { "SO_KEEPALIVE", SO_KEEPALIVE },
638
+ { "SO_BROADCAST", SO_BROADCAST }, { "SO_RCVBUF", SO_RCVBUF },
639
+ { "SO_SNDBUF", SO_SNDBUF }, { "SO_ERROR", SO_ERROR },
640
+ { "SO_LINGER", SO_LINGER }, { "SO_TYPE", SO_TYPE },
641
+ #ifdef SO_REUSEPORT
642
+ { "SO_REUSEPORT", SO_REUSEPORT },
643
+ #endif
644
+ { "TCP_NODELAY", TCP_NODELAY },
645
+ /* keepalive tuning and the timeout/credential options a client needs to
646
+ configure a long-lived connection (#3541). Each is guarded: the names
647
+ differ across platforms (macOS spells the idle timer TCP_KEEPALIVE). */
648
+ #ifdef TCP_KEEPIDLE
649
+ { "TCP_KEEPIDLE", TCP_KEEPIDLE },
650
+ #elif defined(TCP_KEEPALIVE)
651
+ { "TCP_KEEPIDLE", TCP_KEEPALIVE },
652
+ #endif
653
+ #ifdef TCP_KEEPALIVE
654
+ { "TCP_KEEPALIVE", TCP_KEEPALIVE },
655
+ #endif
656
+ #ifdef TCP_KEEPINTVL
657
+ { "TCP_KEEPINTVL", TCP_KEEPINTVL },
658
+ #endif
659
+ #ifdef TCP_KEEPCNT
660
+ { "TCP_KEEPCNT", TCP_KEEPCNT },
661
+ #endif
662
+ #ifdef TCP_MAXSEG
663
+ { "TCP_MAXSEG", TCP_MAXSEG },
664
+ #endif
665
+ #ifdef SO_RCVTIMEO
666
+ { "SO_RCVTIMEO", SO_RCVTIMEO },
667
+ #endif
668
+ #ifdef SO_SNDTIMEO
669
+ { "SO_SNDTIMEO", SO_SNDTIMEO },
670
+ #endif
671
+ #ifdef SO_RCVLOWAT
672
+ { "SO_RCVLOWAT", SO_RCVLOWAT },
673
+ #endif
674
+ #ifdef SO_SNDLOWAT
675
+ { "SO_SNDLOWAT", SO_SNDLOWAT },
676
+ #endif
677
+ #ifdef SO_OOBINLINE
678
+ { "SO_OOBINLINE", SO_OOBINLINE },
679
+ #endif
680
+ #ifdef SO_DONTROUTE
681
+ { "SO_DONTROUTE", SO_DONTROUTE },
682
+ #endif
683
+ #ifdef SO_ACCEPTCONN
684
+ { "SO_ACCEPTCONN", SO_ACCEPTCONN },
685
+ #endif
686
+ #ifdef IPPROTO_IPV6
687
+ { "IPPROTO_IPV6", IPPROTO_IPV6 },
688
+ #endif
689
+ #ifdef IPV6_V6ONLY
690
+ { "IPV6_V6ONLY", IPV6_V6ONLY },
691
+ #endif
692
+ #ifdef IP_TTL
693
+ { "IP_TTL", IP_TTL },
694
+ #endif
695
+ #ifdef SOCK_RAW
696
+ { "SOCK_RAW", SOCK_RAW },
697
+ #endif
698
+ #ifdef SOCK_SEQPACKET
699
+ { "SOCK_SEQPACKET", SOCK_SEQPACKET },
700
+ #endif
701
+ #ifdef AF_UNSPEC
702
+ { "AF_UNSPEC", AF_UNSPEC }, { "PF_UNSPEC", PF_UNSPEC },
703
+ #endif
704
+ #ifdef MSG_PEEK
705
+ { "MSG_PEEK", MSG_PEEK },
706
+ #endif
707
+ #ifdef MSG_WAITALL
708
+ { "MSG_WAITALL", MSG_WAITALL },
709
+ #endif
710
+ #ifdef MSG_DONTROUTE
711
+ { "MSG_DONTROUTE", MSG_DONTROUTE },
712
+ #endif
713
+ #ifdef MSG_OOB
714
+ { "MSG_OOB", MSG_OOB },
715
+ #endif
716
+ { "AF_INET", AF_INET }, { "AF_INET6", AF_INET6 }, { "AF_UNIX", AF_UNIX },
717
+ { "PF_INET", PF_INET }, { "PF_INET6", PF_INET6 }, { "PF_UNIX", PF_UNIX },
718
+ { "SOCK_STREAM", SOCK_STREAM }, { "SOCK_DGRAM", SOCK_DGRAM },
719
+ { "SHUT_RD", SHUT_RD }, { "SHUT_WR", SHUT_WR }, { "SHUT_RDWR", SHUT_RDWR },
720
+ { NULL, 0 }
721
+ };
722
+ for (int i = 0; T[i].n; i++) if (strcmp(T[i].n, n) == 0) return T[i].v;
723
+ return -1;
724
+ }
725
+
726
+ /* UDPSocket.new / UNIXSocket.new / UNIXServer.new -- each wraps its fd in the
727
+ same handle every other stream uses; the kind label is what tells them
728
+ apart. A UDP handle keeps its own datagram path, so it is not fdopen'd. */
729
+ sp_File *sp_sock_udp_new(sp_int family) {
730
+ extern int sp_net_udp_open(int family);
731
+ int fd = sp_net_udp_open((int)family);
732
+ if (fd < 0) sp_raise_cls("SocketError", "cannot create UDP socket");
733
+ sp_File *f = (sp_File *)sp_gc_alloc(sizeof(sp_File), sp_File_fin, sp_File_scan);
734
+ f->fp = fdopen(fd, "r+");
735
+ if (!f->fp) { close(fd); sp_raise_cls("IOError", "fdopen failed"); return NULL; }
736
+ f->path = NULL; f->mode = "udp"; f->lineno = 0; f->is_sock = 1; f->rev_lookup = !sp_sock_dnrl_default;
737
+ return f;
738
+ }
739
+ sp_File *sp_sock_unix_server(const char *path) {SP_GC_ROOT_STR(path);
740
+ extern int sp_net_unix_listen(const char *path, int backlog);
741
+ int fd = sp_net_unix_listen(path, 128);
742
+ if (fd < 0) sp_file_raise_errno("bind", path ? path : "");
743
+ sp_File *f = sp_io_fdopen_sock(fd, "unixserver");
744
+ return f;
745
+ }
746
+ sp_File *sp_sock_unix_connect(const char *path) {SP_GC_ROOT_STR(path);
747
+ extern int sp_net_unix_connect(const char *path);
748
+ int fd = sp_net_unix_connect(path);
749
+ if (fd < 0) sp_file_raise_errno("connect", path ? path : "");
750
+ return sp_io_fdopen_sock(fd, "unix");
751
+ }
752
+
753
+ /* BasicSocket#do_not_reverse_lookup and its writer: a socket's own, so a
754
+ File or a pipe answers NoMethodError, as CRuby does */
755
+ static void sp_sock_dnrl_check(sp_File *f, const char *name) {
756
+ if (!f) sp_nil_recv(name);
757
+ if (!f->is_sock)
758
+ sp_raise_cls("NoMethodError", sp_sprintf("undefined method '%s' for an instance of %s",
759
+ name, sp_io_kind_name(f)));
760
+ }
761
+ sp_bool sp_sock_dnrl(sp_File *f) {
762
+ sp_sock_dnrl_check(f, "do_not_reverse_lookup");
763
+ return !f->rev_lookup;
764
+ }
765
+ void sp_sock_set_dnrl(sp_File *f, sp_bool on) {
766
+ sp_sock_dnrl_check(f, "do_not_reverse_lookup=");
767
+ f->rev_lookup = !on;
768
+ }
769
+
770
+ const char *sp_sock_gethostname(void) {
771
+ extern int sp_net_gethostname(char *buf, int cap);
772
+ char buf[256];
773
+ if (sp_net_gethostname(buf, (int)sizeof buf) != 0) sp_file_raise_errno("gethostname", "");
774
+ return sp_str_from_bytes(buf, strlen(buf));
775
+ }
776
+ /* Socket.new(domain, type, protocol) -- a bare socket the bind/listen/connect
777
+ methods then drive. */
778
+ sp_File *sp_sock_new(sp_int domain, sp_int type, sp_int proto) {
779
+ extern int sp_net_socket(int domain, int type, int protocol);
780
+ int fd = sp_net_socket((int)domain, (int)type, (int)proto);
781
+ if (fd < 0) sp_file_raise_errno("socket", "");
782
+ sp_File *f = (sp_File *)sp_gc_alloc(sizeof(sp_File), sp_File_fin, sp_File_scan);
783
+ f->fp = fdopen(fd, "r+");
784
+ if (!f->fp) { close(fd); sp_raise_cls("IOError", "fdopen failed"); return NULL; }
785
+ /* Socket.new answers Socket, not the protocol-specific subclass CRuby uses
786
+ for TCPSocket.new -- the caller asked for the generic class. */
787
+ f->path = NULL; f->mode = "socket"; f->lineno = 0; f->is_sock = 1; f->rev_lookup = !sp_sock_dnrl_default;
788
+ return f;
789
+ }
790
+ /* Socket.pair / Socket.socketpair -> the two connected ends. */
791
+ sp_File *sp_sock_pair_end(sp_int domain, sp_int type, sp_int proto, sp_int which) {
792
+ extern int sp_net_socketpair(int domain, int type, int protocol, int fds[2]);
793
+ static int cached[2] = { -1, -1 };
794
+ if (which == 0) {
795
+ if (sp_net_socketpair((int)domain, (int)type, (int)proto, cached) != 0)
796
+ sp_file_raise_errno("socketpair", "");
797
+ }
798
+ int fd = cached[which == 0 ? 0 : 1];
799
+ if (fd < 0) sp_raise_cls("IOError", "socketpair");
800
+ (void)domain; (void)type;
801
+ return sp_io_fdopen_sock(fd, "socket");
802
+ }
803
+
804
+ /* Socket.getaddrinfo: one row per resolution, in CRuby's 7-element shape. */
805
+ /* A family or socktype argument of Socket.getaddrinfo: nil is 0
806
+ (unrestricted), an Integer as given, a String or Symbol by name with or
807
+ without its AF_ / SOCK_ prefix, as CRuby reads it. An unknown name is CRuby's
808
+ SocketError. */
809
+ sp_int sp_sock_addrinfo_hint(sp_RbVal v, sp_int is_family) {
810
+ if (v.tag == SP_TAG_NIL) return 0;
811
+ if (v.tag == SP_TAG_INT) return v.v.i;
812
+ const char *name = NULL;
813
+ if (v.tag == SP_TAG_STR) name = v.v.s;
814
+ else if (v.tag == SP_TAG_SYM && sp_sym_name_fn) name = sp_sym_name_fn((sp_sym)v.v.i);
815
+ if (!name) sp_raise_cls("TypeError", "no implicit conversion into Integer");
816
+ const char *prefix = is_family ? "AF_" : "SOCK_";
817
+ size_t pl = strlen(prefix);
818
+ if (strncmp(name, prefix, pl) == 0) name += pl;
819
+ if (is_family && strcmp(name, "UNSPEC") == 0) return 0;
820
+ char full[64];
821
+ snprintf(full, sizeof full, "%s%s", prefix, name);
822
+ sp_int r = sp_sock_const(full);
823
+ if (r < 0) sp_raise_cls("SocketError", is_family ? "unknown socket domain" : "unknown socket type");
824
+ return r;
825
+ }
826
+
827
+ /* Socket.getaddrinfo(host, port [, family [, socktype]]): a family or socktype
828
+ of 0 (nil) leaves it unrestricted, as in CRuby. */
829
+ sp_PolyArray *sp_sock_getaddrinfo(const char *host, sp_int port, sp_int family, sp_int socktype) {SP_GC_ROOT_STR(host);
830
+ extern int sp_net_getaddrinfo_at(const char *host, int port, int want_family, int socktype, int idx,
831
+ int *family, int *stype, int *proto,
832
+ char *ipbuf, int ipcap, int *port_out);
833
+ sp_PolyArray *out = sp_PolyArray_new();
834
+ SP_GC_ROOT(out);
835
+ for (int i = 0; i < 64; i++) {
836
+ int fam = 0, stype = 0, proto = 0, p = 0;
837
+ char ip[64];
838
+ if (sp_net_getaddrinfo_at(host, (int)port, (int)family, (int)socktype, i, &fam, &stype, &proto,
839
+ ip, (int)sizeof ip, &p) != 0) break;
840
+ const char *ips = sp_str_from_bytes(ip, strlen(ip));
841
+ sp_PolyArray *row = sp_PolyArray_new();
842
+ sp_PolyArray_push(row, sp_box_str(fam == AF_INET6 ? "AF_INET6" : "AF_INET"));
843
+ sp_PolyArray_push(row, sp_box_int((sp_int)p));
844
+ sp_PolyArray_push(row, sp_box_str(ips));
845
+ sp_PolyArray_push(row, sp_box_str(ips));
846
+ sp_PolyArray_push(row, sp_box_int((sp_int)fam));
847
+ sp_PolyArray_push(row, sp_box_int((sp_int)stype));
848
+ sp_PolyArray_push(row, sp_box_int((sp_int)proto));
849
+ sp_PolyArray_push(out, sp_box_poly_array(row));
850
+ }
851
+ return out;
852
+ }
853
+
854
+ /* Socket.sockaddr_in(port, host) / Socket.pack_sockaddr_in: the packed
855
+ sockaddr, as a byte string. It carries NUL bytes (a zero port octet, the
856
+ sin_zero padding, an address with a zero octet), so it is built with
857
+ sp_str_from_bytes rather than a C-string constructor -- the string this
858
+ returns is the one connect_nonblock(sockaddr) and bind take. */
859
+ const char *sp_sock_pack_sockaddr_in(sp_int port, const char *host) {SP_GC_ROOT_STR(host);
860
+ extern int sp_net_pack_sockaddr_in(const char *host, int port, void *out, int cap);
861
+ char buf[128];
862
+ int n = sp_net_pack_sockaddr_in(host, (int)port, buf, (int)sizeof buf);
863
+ if (n < 0)
864
+ sp_raise_cls("SocketError",
865
+ sp_sprintf("getaddrinfo: Name or service not known - %s", host ? host : ""));
866
+ return sp_str_from_bytes(buf, (size_t)n);
867
+ }
868
+
869
+ const char *sp_sock_pack_sockaddr_un(const char *path) {SP_GC_ROOT_STR(path);
870
+ extern int sp_net_pack_sockaddr_un(const char *path, void *out, int cap);
871
+ char buf[128 + 108];
872
+ int n = sp_net_pack_sockaddr_un(path, buf, (int)sizeof buf);
873
+ if (n < 0)
874
+ sp_raise_cls("ArgumentError",
875
+ sp_sprintf("too long unix socket path (%d bytes given)",
876
+ path ? (int)strlen(path) : 0));
877
+ return sp_str_from_bytes(buf, (size_t)n);
878
+ }
879
+
880
+ /* Addrinfo#to_sockaddr / #to_s -- the same packed form for an endpoint that is
881
+ already resolved, which is the whole reason the 1-arg connect exists: the
882
+ caller does not pay for the name lookup twice. AF_UNIX goes through the path
883
+ form; everything else is already numeric, so the getaddrinfo below is a
884
+ parse rather than a resolution. */
885
+ const char *sp_addrinfo_to_sockaddr(sp_Addrinfo *a) {SP_GC_ROOT(a);
886
+ if (!a) sp_raise_cls("SocketError", "no address");
887
+ if (a->afname && strcmp(a->afname, "AF_UNIX") == 0)
888
+ return sp_sock_pack_sockaddr_un(a->ip);
889
+ return sp_sock_pack_sockaddr_in(a->port, a->ip);
890
+ }
891
+
892
+ /* Socket.unpack_sockaddr_in -> [port, host]. The inverse, and what makes a
893
+ packed address inspectable from Ruby without byte hacks. */
894
+ sp_PolyArray *sp_sock_unpack_sockaddr_in(const char *sa) {SP_GC_ROOT_STR(sa);
895
+ extern int sp_net_unpack_sockaddr_in(const void *sa, int salen, char *ipbuf, int cap);
896
+ char ip[128];
897
+ int len = sa ? (int)sp_str_byte_len(sa) : 0;
898
+ int port = sp_net_unpack_sockaddr_in(sa, len, ip, (int)sizeof ip);
899
+ if (port < 0) sp_raise_cls("ArgumentError", "not an AF_INET/AF_INET6 sockaddr");
900
+ sp_PolyArray *out = sp_PolyArray_new();
901
+ SP_GC_ROOT(out);
902
+ sp_PolyArray_push(out, sp_box_int((sp_int)port));
903
+ sp_PolyArray_push(out, sp_box_str(sp_str_from_bytes(ip, strlen(ip))));
904
+ return out;
905
+ }
906
+
907
+ /* #local_address / #remote_address -> Addrinfo for this end / the peer. */
908
+ sp_Addrinfo *sp_sock_address(sp_File *f, sp_int peer) {SP_GC_ROOT(f);
909
+ extern sp_Addrinfo *sp_addrinfo_new(const char *ip, sp_int port, sp_int stype, sp_int is_unix);
910
+ extern int sp_net_sock_ip(int fd, int peer, char *ipbuf, int cap);
911
+ extern int sp_net_unix_path(int fd, int peer, char *buf, int cap);
912
+ if (!f || !f->is_sock)
913
+ sp_raise_cls("NoMethodError",
914
+ sp_sprintf("undefined method '%s' for an instance of %s",
915
+ peer ? "remote_address" : "local_address", sp_io_kind_name(f)));
916
+ SP_IO_OPEN(f);
917
+ int fd = fileno(f->fp);
918
+ const char *k = sp_io_kind_name(f);
919
+ int is_unix = (strcmp(k, "UNIXSocket") == 0 || strcmp(k, "UNIXServer") == 0);
920
+ char buf[256];
921
+ buf[0] = '\0';
922
+ if (is_unix) {
923
+ sp_net_unix_path(fd, (int)peer, buf, (int)sizeof buf);
924
+ return sp_addrinfo_new(buf, 0, SOCK_STREAM, 1);
925
+ }
926
+ int port = sp_net_sock_ip(fd, (int)peer, buf, (int)sizeof buf);
927
+ if (port < 0) { buf[0] = '\0'; port = 0; }
928
+ int stype = (strcmp(k, "UDPSocket") == 0) ? SOCK_DGRAM : SOCK_STREAM;
929
+ return sp_addrinfo_new(buf, (sp_int)port, stype, 0);
930
+ }
931
+
932
+ /* Only a socket answers the socket-specific methods; say which class the
933
+ receiver actually is when it does not. */
934
+ static void sp_sock_require(sp_File *f, const char *m) {SP_GC_ROOT(f);SP_GC_ROOT_STR(m);
935
+ if (!f || !f->is_sock)
936
+ sp_raise_cls("NoMethodError",
937
+ sp_sprintf("undefined method '%s' for an instance of %s", m, sp_io_kind_name(f)));
938
+ SP_IO_OPEN(f);
939
+ }
940
+ sp_int sp_sock_bind(sp_File *f, const char *host, sp_int port) {SP_GC_ROOT(f);SP_GC_ROOT_STR(host);
941
+ extern int sp_net_udp_bind(int fd, const char *host, int port);
942
+ sp_sock_require(f, "bind");
943
+ if (sp_net_udp_bind(fileno(f->fp), host, (int)port) != 0)
944
+ sp_file_raise_errno("bind", host ? host : "");
945
+ return 0;
946
+ }
947
+ sp_int sp_sock_connect(sp_File *f, const char *host, sp_int port) {SP_GC_ROOT(f);SP_GC_ROOT_STR(host);
948
+ extern int sp_net_udp_connect(int fd, const char *host, int port);
949
+ sp_sock_require(f, "connect");
950
+ if (sp_net_udp_connect(fileno(f->fp), host, (int)port) != 0)
951
+ sp_file_raise_errno("connect", host ? host : "");
952
+ return 0;
953
+ }
954
+ sp_int sp_sock_send(sp_File *f, const char *data, sp_int len, const char *host, sp_int port) {SP_GC_ROOT(f);SP_GC_ROOT_STR(data);SP_GC_ROOT_STR(host);
955
+ extern int sp_net_udp_send_to(int fd, const char *data, int len, const char *host, int port);
956
+ sp_sock_require(f, "send");
957
+ int n = sp_net_udp_send_to(fileno(f->fp), data, (int)len, host, (int)port);
958
+ if (n < 0) sp_file_raise_errno("send", host ? host : "");
959
+ return (sp_int)n;
960
+ }
961
+ static long sp_io_buffered(sp_File *f);
962
+ /* #recv reads one datagram (or up to `len` stream bytes) as a String;
963
+ #recvfrom pairs it with the sender's address, CRuby's 4-element form. */
964
+ const char *sp_sock_recv(sp_File *f, sp_int len) {SP_GC_ROOT(f);
965
+ extern int sp_net_udp_recv_from(int fd, char *buf, int cap, char *ipbuf, int ipcap, int *port_out);
966
+ sp_sock_require(f, "recv");
967
+ if (len <= 0) return sp_str_from_bytes("", 0);
968
+ /* bytes a read on the stream already pulled into stdio's buffer are the
969
+ peer's next ones: served first, as readpartial does, or recv stepped
970
+ over them (#7195) */
971
+ long pend = sp_io_buffered(f);
972
+ if (pend > 0) {
973
+ size_t want = (size_t)len < (size_t)pend ? (size_t)len : (size_t)pend;
974
+ char *r = sp_str_alloc(want);
975
+ size_t got = fread(r, 1, want, f->fp);
976
+ r[got] = 0;
977
+ sp_str_set_len(r, got);
978
+ return r;
979
+ }
980
+ /* park until the peer writes, as readpartial does: a close from another
981
+ thread then wakes this one with CRuby's IOError, where a recv(2) already
982
+ blocked in the kernel answered EBADF, or read a descriptor that was by
983
+ then someone else's (#7555) */
984
+ sp_io_wait_readable(f);
985
+ char *buf = (char *)malloc((size_t)len);
986
+ if (!buf) sp_raise_cls("NoMemoryError", "recv");
987
+ int n = sp_net_udp_recv_from(fileno(f->fp), buf, (int)len, NULL, 0, NULL);
988
+ if (n < 0) { free(buf); sp_file_raise_errno("recv", ""); }
989
+ /* a stream socket at EOF answers nil (Ruby 3.3 and later); an empty
990
+ datagram is still "" */
991
+ if (n == 0) {
992
+ int st = 0; socklen_t sl = sizeof st;
993
+ if (getsockopt(fileno(f->fp), SOL_SOCKET, SO_TYPE, &st, &sl) == 0 && st == SOCK_STREAM) { free(buf); return NULL; }
994
+ }
995
+ const char *s = sp_str_from_bytes(buf, (size_t)n);
996
+ free(buf);
997
+ return s;
998
+ }
999
+ /* Fills the caller's address slots; returns the payload. */
1000
+ const char *sp_sock_recvfrom(sp_File *f, sp_int len, const char **ip_out, sp_int *port_out) {SP_GC_ROOT(f);
1001
+ extern int sp_net_udp_recv_from(int fd, char *buf, int cap, char *ipbuf, int ipcap, int *port_out);
1002
+ sp_sock_require(f, "recvfrom");
1003
+ char ipbuf[64];
1004
+ int port = 0;
1005
+ if (len <= 0) { *ip_out = sp_str_from_bytes("", 0); *port_out = 0; return sp_str_from_bytes("", 0); }
1006
+ sp_io_wait_readable(f); /* as sp_sock_recv (#7555) */
1007
+ char *buf = (char *)malloc((size_t)len);
1008
+ if (!buf) sp_raise_cls("NoMemoryError", "recvfrom");
1009
+ int n = sp_net_udp_recv_from(fileno(f->fp), buf, (int)len, ipbuf, (int)sizeof ipbuf, &port);
1010
+ if (n < 0) { free(buf); sp_file_raise_errno("recvfrom", ""); }
1011
+ const char *s = sp_str_from_bytes(buf, (size_t)n);
1012
+ free(buf);
1013
+ *ip_out = sp_str_from_bytes(ipbuf, strlen(ipbuf));
1014
+ *port_out = (sp_int)port;
1015
+ return s;
1016
+ }
1017
+ sp_int sp_sock_shutdown(sp_File *f, sp_int how) {SP_GC_ROOT(f);
1018
+ extern int sp_net_shutdown(int fd, int how);
1019
+ sp_sock_require(f, "shutdown");
1020
+ if (sp_net_shutdown(fileno(f->fp), (int)how) != 0) sp_file_raise_errno("shutdown", "");
1021
+ return 0;
1022
+ }
1023
+ /* A setsockopt value as the int the option takes: an Integer, true/false as
1024
+ 1/0, or the option's packed bytes ([1].pack("i")), as CRuby accepts it. */
1025
+ sp_int sp_sock_optval(sp_RbVal v) {
1026
+ if (v.tag == SP_TAG_INT) return v.v.i;
1027
+ if (v.tag == SP_TAG_BOOL) return v.v.i != 0;
1028
+ if (v.tag == SP_TAG_STR && v.v.s) {
1029
+ int n = 0;
1030
+ if (sp_str_byte_len(v.v.s) != sizeof n)
1031
+ sp_raise_cls("ArgumentError", "only an int-sized packed option value is supported");
1032
+ memcpy(&n, v.v.s, sizeof n);
1033
+ return n;
1034
+ }
1035
+ sp_raise_cls("TypeError", "no implicit conversion into Integer");
1036
+ return 0;
1037
+ }
1038
+
1039
+ sp_int sp_sock_setsockopt(sp_File *f, sp_int level, sp_int opt, sp_int value) {SP_GC_ROOT(f);
1040
+ extern int sp_net_setsockopt_int(int fd, int level, int optname, int value);
1041
+ sp_sock_require(f, "setsockopt");
1042
+ if (sp_net_setsockopt_int(fileno(f->fp), (int)level, (int)opt, (int)value) != 0)
1043
+ sp_file_raise_errno("setsockopt", "");
1044
+ return 0;
1045
+ }
1046
+ sp_SockOpt *sp_sock_getsockopt(sp_File *f, sp_int level, sp_int opt) {SP_GC_ROOT(f);
1047
+ extern int sp_net_getsockopt_int(int fd, int level, int optname);
1048
+ extern sp_SockOpt *sp_sockopt_new(sp_int family, sp_int level, sp_int optname, sp_int value);
1049
+ extern int sp_net_fd_family(int fd);
1050
+ sp_sock_require(f, "getsockopt");
1051
+ int v = sp_net_getsockopt_int(fileno(f->fp), (int)level, (int)opt);
1052
+ return sp_sockopt_new((sp_int)sp_net_fd_family(fileno(f->fp)), level, opt, (sp_int)v);
1053
+ }
1054
+ sp_int sp_sock_listen(sp_File *f, sp_int backlog) {SP_GC_ROOT(f);
1055
+ sp_sock_require(f, "listen");
1056
+ if (listen(fileno(f->fp), (int)backlog) != 0) sp_file_raise_errno("listen", "");
1057
+ return 0;
1058
+ }
1059
+
1060
+ /* ---- the non-blocking readiness family ----
1061
+ Each call tries once on a non-blocking descriptor. Would-block is not an
1062
+ error: with `exception: false` the caller gets :wait_readable /
1063
+ :wait_writable, otherwise the matching IO::*Wait* exception, which carries
1064
+ both its Errno parent and the IO::Wait* module (see lib/sp_exc.c). */
1065
+ /* Bytes stdio already buffered for this handle. A #gets followed by a
1066
+ #read_nonblock must see them: the raw read(2) below goes straight to the
1067
+ descriptor and would skip the buffer. Same platform probe sp_io_wait_readable
1068
+ uses. */
1069
+ static long sp_io_buffered(sp_File *f) {
1070
+ #if defined(__GLIBC__)
1071
+ return (long)(f->fp->_IO_read_end - f->fp->_IO_read_ptr);
1072
+ #elif defined(__APPLE__)
1073
+ return (long)f->fp->_r;
1074
+ #else
1075
+ return (long)__freadahead(f->fp);
1076
+ #endif
1077
+ }
1078
+ /* O_NONBLOCK is set for the duration of one call and put back: leaving it on
1079
+ would make a later blocking #gets treat EAGAIN as EOF. */
1080
+ static int sp_io_nb_begin(sp_File *f) {
1081
+ int fd = fileno(f->fp);
1082
+ int fl = fcntl(fd, F_GETFL);
1083
+ if (fl >= 0 && !(fl & O_NONBLOCK)) fcntl(fd, F_SETFL, fl | O_NONBLOCK);
1084
+ return fl;
1085
+ }
1086
+ static void sp_io_nb_end(sp_File *f, int saved) {
1087
+ if (saved >= 0 && !(saved & O_NONBLOCK)) fcntl(fileno(f->fp), F_SETFL, saved);
1088
+ }
1089
+ static void sp_sock_nb_prepare(sp_File *f, const char *m) {SP_GC_ROOT(f);SP_GC_ROOT_STR(m);
1090
+ if (!f || !f->is_sock)
1091
+ sp_raise_cls("NoMethodError",
1092
+ sp_sprintf("undefined method '%s' for an instance of %s", m, sp_io_kind_name(f)));
1093
+ SP_IO_OPEN(f);
1094
+ }
1095
+ SP_NORETURN static void sp_sock_raise_wait(int writable, const char *op) {SP_GC_ROOT_STR(op);
1096
+ sp_raise_cls(writable ? "IO::EAGAINWaitWritable" : "IO::EAGAINWaitReadable",
1097
+ sp_sprintf("Resource temporarily unavailable - %s would block", op));
1098
+ }
1099
+ static int sp_sock_would_block(void) {
1100
+ return errno == EAGAIN || errno == EWOULDBLOCK;
1101
+ }
1102
+ /* accept_nonblock -> the new handle, or nil / the :wait_readable marker. */
1103
+ sp_File *sp_sock_accept_nb(sp_File *f, sp_bool exc) {SP_GC_ROOT(f);
1104
+ extern int sp_net_accept_nb(int sfd);
1105
+ sp_sock_nb_prepare(f, "accept_nonblock");
1106
+ int saved = sp_io_nb_begin(f);
1107
+ int fd = sp_net_accept_nb(fileno(f->fp));
1108
+ int e = errno;
1109
+ sp_io_nb_end(f, saved);
1110
+ errno = e;
1111
+ if (fd >= 0) return sp_io_fdopen_sock(fd, "tcp");
1112
+ if (sp_sock_would_block()) {
1113
+ if (!exc) return NULL;
1114
+ sp_sock_raise_wait(0, "accept");
1115
+ }
1116
+ sp_file_raise_errno("accept", "");
1117
+ }
1118
+ /* read_nonblock / recv_nonblock -> the bytes read, or nil when it would block
1119
+ and the caller asked for no exception. EOF is nil in CRuby only for
1120
+ `exception: false`; otherwise it is EOFError. */
1121
+ /* IO#readpartial / #sysread: at most n bytes, returning as soon as ANY are
1122
+ available. It used to be one fread of the full n, which is a different
1123
+ operation: fread keeps calling read(2) until it has n bytes or hits EOF. On
1124
+ a socket that blocks until the peer sends n or closes, so the read-then-write
1125
+ shape every HTTP server has -- read the request, write the response --
1126
+ deadlocks, both ends waiting for the other (#3379). Where libc returns a
1127
+ short fread instead of blocking, the same call left the stream in a state
1128
+ the following write did not survive.
1129
+
1130
+ Buffer first, exactly as sp_sock_read_nb does: whatever stdio already holds
1131
+ is data the peer has sent, and a raw read(2) would step over it, so a #gets
1132
+ before a #readpartial would lose bytes. Then a single BLOCKING read -- that
1133
+ is the only difference from the nonblocking sibling below.
1134
+
1135
+ Park first, like every other read entry point: the blocking read(2) sits on
1136
+ the OS worker, which never reaches a safepoint, so the next stop-the-world
1137
+ collection waited on it forever and one green thread idle in readpartial
1138
+ on a quiet socket froze every other thread's allocation (#4528). */
1139
+ const char *sp_File_readpartial_or_nil(sp_File *f, sp_int n) {SP_GC_ROOT(f);
1140
+ SP_IO_OPEN(f);
1141
+ if (n < 0) sp_raise_cls("EOFError", "end of file reached");
1142
+ if (n == 0) return sp_str_empty_binary();
1143
+ sp_io_wait_readable(f);
1144
+ char *r = sp_str_alloc((size_t)n);
1145
+ ssize_t got;
1146
+ long pend = sp_io_buffered(f);
1147
+ if (pend > 0) {
1148
+ size_t want = (size_t)n < (size_t)pend ? (size_t)n : (size_t)pend;
1149
+ got = (ssize_t)fread(r, 1, want, f->fp);
1150
+ }
1151
+ else {
1152
+ do { got = read(fileno(f->fp), r, (size_t)n); } while (got < 0 && errno == EINTR);
1153
+ if (got < 0) sp_file_raise_errno("read", "");
1154
+ }
1155
+ if (got == 0) return NULL;
1156
+ r[got] = 0;
1157
+ sp_str_set_len(r, (size_t)got);
1158
+ sp_str_mark_binary(r); /* readpartial / sysread answer ASCII-8BIT, as CRuby */
1159
+ return r;
1160
+ }
1161
+
1162
+ /* readpartial / sysread: EOFError at the end of the stream. */
1163
+ const char *sp_File_readpartial(sp_File *f, sp_int n) {
1164
+ const char *r = sp_File_readpartial_or_nil(f, n);
1165
+ if (!r) sp_raise_cls("EOFError", "end of file reached");
1166
+ return r;
1167
+ }
1168
+
1169
+ const char *sp_sock_read_nb(sp_File *f, sp_int len, sp_bool exc, sp_bool is_recv, sp_bool *eof) {SP_GC_ROOT(f);
1170
+ if (eof) *eof = 0;
1171
+ if (is_recv) sp_sock_nb_prepare(f, "recv_nonblock");
1172
+ else SP_IO_OPEN(f);
1173
+ /* the bytes read are BINARY, as CRuby's read_nonblock and recv answer them */
1174
+ if (len <= 0) { char *e = (char *)sp_str_from_bytes("", 0); sp_str_mark_binary(e); return e; }
1175
+ char *buf = (char *)malloc((size_t)len);
1176
+ if (!buf) sp_raise_cls("NoMemoryError", "read_nonblock");
1177
+ ssize_t n;
1178
+ long pend = sp_io_buffered(f);
1179
+ if (pend > 0) { /* serve what stdio already holds, as CRuby does */
1180
+ size_t want = (size_t)len < (size_t)pend ? (size_t)len : (size_t)pend;
1181
+ n = (ssize_t)fread(buf, 1, want, f->fp);
1182
+ }
1183
+ else {
1184
+ int saved = sp_io_nb_begin(f);
1185
+ do { n = read(fileno(f->fp), buf, (size_t)len); } while (n < 0 && errno == EINTR);
1186
+ int e = errno;
1187
+ sp_io_nb_end(f, saved);
1188
+ errno = e;
1189
+ }
1190
+ if (n > 0) { char *s = (char *)sp_str_from_bytes(buf, (size_t)n); free(buf); sp_str_mark_binary(s); return s; }
1191
+ if (n == 0) {
1192
+ free(buf);
1193
+ if (eof) *eof = 1;
1194
+ /* recv_nonblock answers nil at EOF in BOTH forms (Ruby 3.3 and later;
1195
+ it was "" before) -- it does not raise EOFError. read_nonblock is the
1196
+ one that tells them apart: nil for `exception: false`, EOFError
1197
+ otherwise. */
1198
+ if (is_recv) return NULL;
1199
+ if (!exc) return NULL; /* CRuby: nil at EOF */
1200
+ sp_raise_cls("EOFError", "end of file reached");
1201
+ }
1202
+ free(buf);
1203
+ if (sp_sock_would_block()) {
1204
+ if (!exc) return NULL;
1205
+ sp_sock_raise_wait(0, "read");
1206
+ }
1207
+ sp_file_raise_errno("read", "");
1208
+ }
1209
+ /* write_nonblock -> the byte count, or SP_INT_NIL when it would block.
1210
+ Paired like sp_File_write: the _bin entry sizes with the header length and
1211
+ is emitted only for a String value; this one stays strlen for bare
1212
+ literals (a poly operand reaches it through sp_poly_to_s, which answers
1213
+ static class/symbol names with no marker byte). */
1214
+ static sp_int sp_sock_write_nb_len(sp_File *f, const char *data, size_t len, sp_bool exc) {SP_GC_ROOT(f);SP_GC_ROOT_STR(data);
1215
+ ssize_t n;
1216
+ int saved = sp_io_nb_begin(f);
1217
+ do { n = write(fileno(f->fp), data ? data : "", len); } while (n < 0 && errno == EINTR);
1218
+ int we = errno;
1219
+ sp_io_nb_end(f, saved);
1220
+ errno = we;
1221
+ if (n >= 0) return (sp_int)n;
1222
+ if (sp_sock_would_block()) {
1223
+ if (!exc) return SP_INT_NIL;
1224
+ sp_sock_raise_wait(1, "write");
1225
+ }
1226
+ sp_file_raise_errno("write", "");
1227
+ }
1228
+ sp_int sp_sock_write_nb(sp_File *f, const char *data, sp_bool exc) {SP_GC_ROOT(f);SP_GC_ROOT_STR(data);
1229
+ SP_IO_OPEN(f);
1230
+ return sp_sock_write_nb_len(f, data, data ? strlen(data) : 0, exc);
1231
+ }
1232
+ sp_int sp_sock_write_nb_bin(sp_File *f, const char *data, sp_bool exc) {SP_GC_ROOT(f);SP_GC_ROOT_STR(data);
1233
+ SP_IO_OPEN(f);
1234
+ return sp_sock_write_nb_len(f, data, data ? sp_str_byte_len(data) : 0, exc);
1235
+ }
1236
+ /* connect_nonblock: an in-flight connect is IO::EINPROGRESSWaitWritable. */
1237
+ sp_int sp_sock_connect_nb(sp_File *f, const char *host, sp_int port, sp_bool exc) {SP_GC_ROOT(f);SP_GC_ROOT_STR(host);
1238
+ extern int sp_net_udp_connect(int fd, const char *host, int port);
1239
+ sp_sock_nb_prepare(f, "connect_nonblock");
1240
+ int saved = sp_io_nb_begin(f);
1241
+ int rc = sp_net_udp_connect(fileno(f->fp), host, (int)port);
1242
+ int ce = errno;
1243
+ sp_io_nb_end(f, saved);
1244
+ errno = ce;
1245
+ if (rc == 0) return 0;
1246
+ if (errno == EINPROGRESS || errno == EALREADY) {
1247
+ if (!exc) return SP_INT_NIL;
1248
+ sp_raise_cls("IO::EINPROGRESSWaitWritable", "operation in progress - connect(2) would block");
1249
+ }
1250
+ if (errno == EISCONN) return 0;
1251
+ sp_file_raise_errno("connect", host ? host : "");
1252
+ }
1253
+ /* connect_nonblock(sockaddr). The peer is already resolved into a packed
1254
+ struct sockaddr (the binary String returned by Socket.sockaddr_in or
1255
+ #to_sockaddr on an Addrinfo). connect(2) takes the raw bytes; we just
1256
+ pass them through. Same nonblocking lifecycle as sp_sock_connect_nb. */
1257
+ sp_int sp_sock_connect_nb_sa(sp_File *f, const char *sa, sp_int salen, sp_bool exc) {SP_GC_ROOT(f);SP_GC_ROOT_STR(sa);
1258
+ /* sp_sock_nb_prepare runs first: it checks the closed-socket and
1259
+ wrong-class guards and raises the matching exception (e.g. IOError
1260
+ "closed stream") before we touch the address. The sa/salen guard
1261
+ comes after, sets errno = EINVAL, and routes through the same
1262
+ errno->raise path every other socket op uses. */
1263
+ sp_sock_nb_prepare(f, "connect_nonblock");
1264
+ if (!sa || salen <= 0) {
1265
+ errno = EINVAL;
1266
+ sp_file_raise_errno("connect", "");
1267
+ }
1268
+ int saved = sp_io_nb_begin(f);
1269
+ int rc = connect(fileno(f->fp), (const struct sockaddr *)sa, (socklen_t)salen);
1270
+ int ce = errno;
1271
+ sp_io_nb_end(f, saved);
1272
+ errno = ce;
1273
+ if (rc == 0) return 0;
1274
+ if (errno == EINPROGRESS || errno == EALREADY) {
1275
+ if (!exc) return SP_INT_NIL;
1276
+ sp_raise_cls("IO::EINPROGRESSWaitWritable", "operation in progress - connect(2) would block");
1277
+ }
1278
+ /* Already connected answers 0, the same as the two-argument form above and
1279
+ the same as CRuby answers here -- measured on both, in both exception
1280
+ modes, including a second connect to a different address. */
1281
+ if (errno == EISCONN) return 0;
1282
+ sp_file_raise_errno("connect", "");
1283
+ return -1;
1284
+ }
1285
+
1286
+ /* TCPServer#accept: park cooperatively for a pending connection first -- a
1287
+ blocking accept would stall the whole green-thread scheduler -- then wrap the
1288
+ new descriptor. Only a socket handle answers it. */
1289
+ sp_File *sp_sock_accept(sp_File *f) {SP_GC_ROOT(f);
1290
+ extern int sp_net_accept(int sfd);
1291
+ if (!f || !f->is_sock)
1292
+ sp_raise_cls("NoMethodError",
1293
+ sp_sprintf("undefined method 'accept' for an instance of %s", sp_io_kind_name(f)));
1294
+ SP_IO_OPEN(f);
1295
+ sp_io_wait_readable(f);
1296
+ return sp_io_fdopen_sock(sp_net_accept(fileno(f->fp)), "tcp");
1297
+ }
1298
+
1299
+ /* `#<File:/etc/passwd>` for a path-backed handle, `#<IO:<STDOUT>>` for a
1300
+ standard stream. The path is what tells the two apart, the same way #class
1301
+ renders them (a stream's path is bracketed). */
1302
+ const char *sp_File_inspect(sp_File *f) {SP_GC_ROOT(f);
1303
+ /* A NULL handle IS nil -- the readiness family answers one on timeout -- so
1304
+ it inspects as nil, the way sp_io_kind_name already names it NilClass. It
1305
+ rendered as a closed IO instead, which reads wrong in a puts even though
1306
+ nil? and truthiness were right (#4316). A handle that is merely CLOSED is
1307
+ a different thing and keeps its own rendering below. */
1308
+ if (!f) return SPL("nil");
1309
+ const char *p = f->path ? f->path : "";
1310
+ const char *cls = (p[0] && p[0] != '<') ? "File" : "IO";
1311
+ if (!f->fp || f->closed) return p[0] ? sp_sprintf("#<%s:%s (closed)>", cls, p)
1312
+ : sp_sprintf("#<IO:(closed)>");
1313
+ /* a pipe end or a wrapped descriptor has no path: CRuby names the fd */
1314
+ if (!p[0]) return sp_sprintf("#<IO:fd %d>", fileno(f->fp));
1315
+ return sp_sprintf("#<%s:%s>", cls, p);
1316
+ }
1317
+
1318
+ static void sp_File_puts_n(sp_File *f, const char *s, size_t n) {
1319
+ SP_IO_OPEN(f);
1320
+ if (f->is_sock) {
1321
+ sp_sock_write(f, s, n);
1322
+ if (n == 0 || s[n - 1] != '\n') sp_sock_write(f, "\n", 1);
1323
+ return;
1324
+ }
1325
+ fwrite(s, 1, n, f->fp);
1326
+ if (n == 0 || s[n - 1] != '\n') fputc('\n', f->fp);
1327
+ }
1328
+ void sp_File_puts(sp_File *f, const char *s) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
1329
+ if (!s) { SP_IO_OPEN(f); return; }
1330
+ sp_File_puts_n(f, s, strlen(s));
1331
+ }
1332
+ /* Binary-safe puts, the pair of sp_File_write_bin: sizes the operand with the
1333
+ header length, so an embedded NUL reaches the descriptor and the trailing
1334
+ newline is decided by the real last byte. Reads s[-1], so it is for
1335
+ CODEGEN-emitted String values only -- the emitter picks it exactly where
1336
+ the write arm picks sp_File_write_bin. */
1337
+ void sp_File_puts_bin(sp_File *f, const char *s) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
1338
+ /* a nil String arrives as NULL and prints as `puts nil` does: a newline */
1339
+ if (!s) { sp_File_puts_n(f, "", 0); return; }
1340
+ sp_File_puts_n(f, s, sp_str_byte_len(s));
1341
+ }
1342
+
1343
+ void sp_File_print(sp_File *f, const char *s) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
1344
+ SP_IO_OPEN(f);
1345
+ if (!s) return;
1346
+ if (f->is_sock) { sp_sock_write(f, s, strlen(s)); return; }
1347
+ fputs(s, f->fp);
1348
+ }
1349
+
1350
+ sp_int sp_File_flush(sp_File *f) {
1351
+ SP_IO_OPEN(f);
1352
+ fflush(f->fp);
1353
+ return 0;
1354
+ }
1355
+
1356
+ sp_bool sp_File_eof_p(sp_File *f) {
1357
+ SP_IO_OPEN(f);
1358
+ sp_io_wait_readable(f);
1359
+ int c = fgetc(f->fp);
1360
+ if (c == EOF) return TRUE;
1361
+ ungetc(c, f->fp);
1362
+ return FALSE;
1363
+ }
1364
+
1365
+ sp_int sp_File_seek(sp_File *f, sp_int off, sp_int whence) {
1366
+ SP_IO_OPEN(f);
1367
+ /* whence uses the Ruby IO::SEEK_* values (0/1/2), mapped explicitly so we
1368
+ never depend on the platform's SEEK_SET/CUR/END numbering. fseeko/ftello
1369
+ take off_t rather than fseek's long, so offsets past 2GB survive even
1370
+ where long is 32-bit. */
1371
+ int w = (whence == 1) ? SEEK_CUR : (whence == 2) ? SEEK_END : SEEK_SET;
1372
+ /* a refused seek is the descriptor's error, not a -1 return: a negative
1373
+ absolute offset is Errno::EINVAL in CRuby, and a caller that rescues
1374
+ Errno read the -1 as a successful seek (#4623) */
1375
+ if (fseeko(f->fp, (off_t)off, w) != 0) sp_file_raise_errno("seek", f->path ? f->path : "");
1376
+ return 0;
1377
+ }
1378
+
1379
+ sp_int sp_File_tell(sp_File *f) {
1380
+ SP_IO_OPEN(f);
1381
+ return (sp_int)ftello(f->fp);
1382
+ }
1383
+
1384
+ sp_int sp_File_rewind(sp_File *f) {
1385
+ SP_IO_OPEN(f);
1386
+ rewind(f->fp);
1387
+ return 0;
1388
+ }
1389
+
1390
+ /* ---- File metadata predicates ----
1391
+ libc / WinAPI only, no spinel-string allocation and no shared mutable
1392
+ state, so they live here rather than inline in spinel_rt.h. */
1393
+ sp_bool sp_file_directory(const char *path) {
1394
+ struct stat st;
1395
+ return path && stat(path, &st) == 0 && S_ISDIR(st.st_mode);
1396
+ }
1397
+
1398
+ sp_bool sp_file_file(const char *path) {
1399
+ struct stat st;
1400
+ return path && stat(path, &st) == 0 && S_ISREG(st.st_mode);
1401
+ }
1402
+
1403
+ sp_bool sp_file_symlink(const char *path) {
1404
+ struct stat st;
1405
+ return path && lstat(path, &st) == 0 && S_ISLNK(st.st_mode);
1406
+ }
1407
+
1408
+ /* map errno to the matching Errno:: class (see sp_io.h) */
1409
+ SP_NORETURN void sp_file_raise_errno(const char *op, const char *path) {SP_GC_ROOT_STR(op);SP_GC_ROOT_STR(path);
1410
+ int e = errno; /* read once, before the formatting can touch it */
1411
+ sp_raise_cls(sp_errno_class_name(e),
1412
+ sp_sprintf("%s @ %s - %s", strerror(e), op, path ? path : ""));
1413
+ }
1414
+
1415
+ sp_bool sp_file_owned(const char *path) {
1416
+ struct stat st;
1417
+ return path && stat(path, &st) == 0 && st.st_uid == geteuid();
1418
+ }
1419
+ sp_bool sp_file_grpowned(const char *path) {
1420
+ struct stat st;
1421
+ return path && stat(path, &st) == 0 && st.st_gid == getegid();
1422
+ }
1423
+ sp_bool sp_file_setuid(const char *path) {
1424
+ struct stat st;
1425
+ return path && stat(path, &st) == 0 && (st.st_mode & S_ISUID) != 0;
1426
+ }
1427
+ sp_bool sp_file_setgid(const char *path) {
1428
+ struct stat st;
1429
+ return path && stat(path, &st) == 0 && (st.st_mode & S_ISGID) != 0;
1430
+ }
1431
+ sp_bool sp_file_sticky(const char *path) {
1432
+ struct stat st;
1433
+ return path && stat(path, &st) == 0 && (st.st_mode & S_ISVTX) != 0;
1434
+ }
1435
+ sp_bool sp_file_socket(const char *path) {
1436
+ struct stat st;
1437
+ return path && stat(path, &st) == 0 && S_ISSOCK(st.st_mode);
1438
+ }
1439
+ sp_bool sp_file_blockdev(const char *path) {
1440
+ struct stat st;
1441
+ return path && stat(path, &st) == 0 && S_ISBLK(st.st_mode);
1442
+ }
1443
+ sp_bool sp_file_chardev(const char *path) {
1444
+ struct stat st;
1445
+ return path && stat(path, &st) == 0 && S_ISCHR(st.st_mode);
1446
+ }
1447
+ /* world_readable? / world_writable?: the permission bits (0..0777) when the
1448
+ other-read / other-write bit is set, else nil (SP_INT_NIL) (#3005) */
1449
+ sp_int sp_file_world_readable(const char *path) {
1450
+ struct stat st;
1451
+ if (!(path && stat(path, &st) == 0 && (st.st_mode & S_IROTH))) return SP_INT_NIL;
1452
+ return (sp_int)(st.st_mode & 0777);
1453
+ }
1454
+ sp_int sp_file_world_writable(const char *path) {
1455
+ struct stat st;
1456
+ if (!(path && stat(path, &st) == 0 && (st.st_mode & S_IWOTH))) return SP_INT_NIL;
1457
+ return (sp_int)(st.st_mode & 0777);
1458
+ }
1459
+ sp_int sp_file_do_symlink(const char *oldp, const char *newp) {SP_GC_ROOT_STR(newp);
1460
+ if (symlink(oldp, newp) != 0) sp_file_raise_errno("symlink", newp);
1461
+ return 0;
1462
+ }
1463
+ sp_int sp_file_do_link(const char *oldp, const char *newp) {SP_GC_ROOT_STR(newp);
1464
+ if (link(oldp, newp) != 0) sp_file_raise_errno("link", newp);
1465
+ return 0;
1466
+ }
1467
+ sp_int sp_file_umask(sp_int mask, int have_arg) {
1468
+ if (have_arg) return (sp_int)umask((mode_t)mask);
1469
+ mode_t cur = umask(0); /* read is destructive; restore immediately */
1470
+ umask(cur);
1471
+ return (sp_int)cur;
1472
+ }
1473
+ sp_int sp_file_mkfifo(const char *path, sp_int mode) {SP_GC_ROOT_STR(path);
1474
+ if (mkfifo(path, (mode_t)mode) != 0) sp_file_raise_errno("mkfifo", path);
1475
+ return 0;
1476
+ }
1477
+ /* File.utime / File.lutime. The times arrive as a whole second and a
1478
+ nanosecond REMAINDER, not as a double: a Time carries nanoseconds and a
1479
+ timestamp near 2^31 seconds leaves a double about 100ns of resolution, so
1480
+ the fraction was lost long before utimes' microseconds were (#4635).
1481
+ utimensat takes the pair as it stands; the `l` form is the same call with
1482
+ AT_SYMLINK_NOFOLLOW, the way lstat is to stat. */
1483
+ static sp_int sp_file_utime_at(int64_t asec, int32_t ansec, int64_t msec, int32_t mnsec,
1484
+ const char *path, int flags, const char *who) {
1485
+ struct timespec ts[2];
1486
+ ts[0].tv_sec = (time_t)asec; ts[0].tv_nsec = (long)ansec;
1487
+ ts[1].tv_sec = (time_t)msec; ts[1].tv_nsec = (long)mnsec;
1488
+ if (utimensat(AT_FDCWD, path, ts, flags) != 0) sp_file_raise_errno(who, path);
1489
+ return 1;
1490
+ }
1491
+ sp_int sp_file_utime_ns(int64_t asec, int32_t ansec, int64_t msec, int32_t mnsec,
1492
+ const char *path) {SP_GC_ROOT_STR(path);
1493
+ return sp_file_utime_at(asec, ansec, msec, mnsec, path, 0, "utime");
1494
+ }
1495
+ sp_int sp_file_lutime_ns(int64_t asec, int32_t ansec, int64_t msec, int32_t mnsec,
1496
+ const char *path) {SP_GC_ROOT_STR(path);
1497
+ return sp_file_utime_at(asec, ansec, msec, mnsec, path, AT_SYMLINK_NOFOLLOW, "lutime");
1498
+ }
1499
+
1500
+ /* stat, not fopen: opening a FIFO for read blocks until a writer appears, so
1501
+ the old fopen probe hung File.exist? on a fresh mkfifo path (#3118). stat
1502
+ also answers true for directories, matching CRuby. */
1503
+ sp_bool sp_file_exist(const char *path) { struct stat st; return path && stat(path, &st) == 0; }
1504
+ /* unlink(2), not remove(3): remove would take a directory too, which
1505
+ File.delete refuses (EPERM here, EISDIR on Linux), as CRuby does. */
1506
+ void sp_file_path_check(const char *path) {
1507
+ if (!path) sp_raise_cls("TypeError", "no implicit conversion of nil into String");
1508
+ }
1509
+ void sp_file_delete(const char *path) {SP_GC_ROOT_STR(path);
1510
+ sp_file_path_check(path);
1511
+ if (unlink(path) != 0) sp_file_raise_errno("apply2files", path);
1512
+ }
1513
+ void sp_file_rename(const char *from, const char *to) {SP_GC_ROOT_STR(from);SP_GC_ROOT_STR(to);
1514
+ sp_file_path_check(from); sp_file_path_check(to);
1515
+ if (rename(from, to) != 0) {
1516
+ int e = errno;
1517
+ const char *pair = sp_sprintf("(%s, %s)", from, to);
1518
+ errno = e;
1519
+ sp_file_raise_errno("rb_file_s_rename", pair);
1520
+ }
1521
+ }
1522
+
1523
+ /* --- IO instance methods that ride the underlying fd (#3038) ------------- */
1524
+
1525
+ /* IO#readbyte: like #getbyte but EOFError at end of file. */
1526
+ sp_int sp_File_readbyte(sp_File *f) {
1527
+ SP_IO_OPEN(f);
1528
+ sp_io_wait_readable(f);
1529
+ int ch = fgetc(f->fp);
1530
+ if (ch == EOF) sp_raise_cls("EOFError", "end of file reached");
1531
+ return (sp_int)(unsigned char)ch;
1532
+ }
1533
+ /* IO#ungetbyte: push one byte back onto the read buffer; returns nil. */
1534
+ void sp_File_ungetbyte(sp_File *f, sp_int byte) {
1535
+ SP_IO_OPEN(f);
1536
+ ungetc((int)(unsigned char)byte, f->fp);
1537
+ }
1538
+ /* IO#binmode?: true after #binmode, or for a handle opened in binary mode.
1539
+ A socket is binary too, as CRuby's is (its encoding is BINARY). */
1540
+ sp_bool sp_File_binmode_p(sp_File *f) {
1541
+ SP_IO_OPEN(f);
1542
+ if (f->bin_flag || f->is_sock) return 1;
1543
+ return f->mode && strchr(f->mode, 'b') != NULL;
1544
+ }
1545
+ void sp_File_set_binmode(sp_File *f) { SP_IO_OPEN(f); f->bin_flag = 1; }
1546
+ /* The handle flags codegen used to read straight off the struct (#2792,
1547
+ #3131): a closed handle answers IOError for them as for everything else.
1548
+ #sync= keeps the flush a truthy value implies (#4229). */
1549
+ sp_int sp_File_lineno(sp_File *f) { SP_IO_OPEN(f); return f->lineno; }
1550
+ sp_int sp_File_set_lineno(sp_File *f, sp_int n) { SP_IO_OPEN(f); f->lineno = n; return n; }
1551
+ sp_bool sp_File_sync_p(sp_File *f) { SP_IO_OPEN(f); return (f->is_sock || f->sync_on) ? 1 : 0; }
1552
+ sp_bool sp_File_set_sync(sp_File *f, sp_bool on) {
1553
+ SP_IO_OPEN(f);
1554
+ f->sync_on = on ? 1 : 0;
1555
+ /* A sync handle is unbuffered, as in CRuby: every write reaches the
1556
+ descriptor before the call returns. Kernel#puts writes straight through
1557
+ stdio, so the flag alone did nothing for a file or a pipe -- a test that
1558
+ set `$stdout.sync = true` and was killed by the CI timeout left an EMPTY
1559
+ output, with no clue where it had hung. The stream is flushed first so
1560
+ the buffer swap loses nothing. */
1561
+ fflush(f->fp);
1562
+ if (f->fp != sp_io_closed_sentinel())
1563
+ setvbuf(f->fp, NULL, on ? _IONBF : (isatty(fileno(f->fp)) ? _IOLBF : _IOFBF), 0);
1564
+ return on;
1565
+ }
1566
+ sp_bool sp_File_autoclose_p(sp_File *f) { SP_IO_OPEN(f); return !f->no_autoclose; }
1567
+ void sp_File_set_autoclose(sp_File *f, sp_bool on) { SP_IO_OPEN(f); f->no_autoclose = !on; }
1568
+ /* IO#reopen(io): rebind this handle's descriptor onto the other stream. */
1569
+ sp_File *sp_File_reopen_io(sp_File *f, sp_File *other) {SP_GC_ROOT(f);SP_GC_ROOT(other); sp_gc_wb((void*)f);
1570
+ if (!f || !other) return f;
1571
+ /* a closed handle on either side is CRuby's IOError, not a quiet no-op */
1572
+ SP_IO_OPEN(f);
1573
+ SP_IO_OPEN(other);
1574
+ fflush(f->fp);
1575
+ fflush(other->fp);
1576
+ if (dup2(fileno(other->fp), fileno(f->fp)) < 0)
1577
+ sp_file_raise_errno("reopen", other->path ? other->path : "");
1578
+ f->path = other->path;
1579
+ f->mode = other->mode;
1580
+ f->lineno = 0;
1581
+ return f;
1582
+ }
1583
+ /* IO#close_on_exec? / #close_on_exec= via the FD_CLOEXEC descriptor flag. */
1584
+ sp_bool sp_File_close_on_exec_p(sp_File *f) {
1585
+ SP_IO_OPEN(f);
1586
+ int fl = fcntl(fileno(f->fp), F_GETFD);
1587
+ return fl >= 0 && (fl & FD_CLOEXEC) != 0;
1588
+ }
1589
+ void sp_File_set_close_on_exec(sp_File *f, sp_bool on) {
1590
+ SP_IO_OPEN(f);
1591
+ int fd = fileno(f->fp);
1592
+ int fl = fcntl(fd, F_GETFD);
1593
+ if (fl < 0) return;
1594
+ fcntl(fd, F_SETFD, on ? (fl | FD_CLOEXEC) : (fl & ~FD_CLOEXEC));
1595
+ }
1596
+ /* IO#fcntl(cmd, arg=0): the raw descriptor command. */
1597
+ sp_int sp_File_fcntl(sp_File *f, sp_int cmd, sp_int arg) {SP_GC_ROOT(f);
1598
+ SP_IO_OPEN(f);
1599
+ int r = fcntl(fileno(f->fp), (int)cmd, (long)arg);
1600
+ if (r < 0) sp_file_raise_errno("fcntl", f->path ? f->path : "");
1601
+ return (sp_int)r;
1602
+ }
1603
+ /* IO#pwrite(str, offset): write without moving the file position. */
1604
+ static sp_int sp_File_pwrite_n(sp_File *f, const char *s, size_t n, sp_int off) {
1605
+ SP_IO_OPEN(f);
1606
+ fflush(f->fp);
1607
+ ssize_t put = pwrite(fileno(f->fp), s ? s : "", n, (off_t)off);
1608
+ if (put < 0) sp_file_raise_errno("pwrite", f->path ? f->path : "");
1609
+ return (sp_int)put;
1610
+ }
1611
+ sp_int sp_File_pwrite(sp_File *f, const char *s, sp_int off) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
1612
+ return sp_File_pwrite_n(f, s, s ? strlen(s) : 0, off);
1613
+ }
1614
+ /* Binary-safe pwrite, the pair of sp_File_write_bin: sizes the operand with
1615
+ the header length, so an embedded NUL reaches the descriptor instead of
1616
+ cutting the write short. Reads s[-1], so it is for CODEGEN-emitted String
1617
+ values only -- the emitter picks it exactly where the write arm picks
1618
+ sp_File_write_bin. */
1619
+ sp_int sp_File_pwrite_bin(sp_File *f, const char *s, sp_int off) {SP_GC_ROOT(f);SP_GC_ROOT_STR(s);
1620
+ return sp_File_pwrite_n(f, s, s ? sp_str_byte_len(s) : 0, off);
1621
+ }
1622
+ /* IO#advise(sym, offset=0, len=0): a hint, and nil either way. POSIX
1623
+ fadvise is Linux-ish; where it is absent the hint is simply dropped. */
1624
+ void sp_File_advise(sp_File *f, const char *kind, sp_int off, sp_int len) {
1625
+ SP_IO_OPEN(f);
1626
+ #ifdef POSIX_FADV_NORMAL
1627
+ int fd = fileno(f->fp);
1628
+ int a = POSIX_FADV_NORMAL;
1629
+ if (!kind) return;
1630
+ if (!strcmp(kind, "sequential")) a = POSIX_FADV_SEQUENTIAL;
1631
+ else if (!strcmp(kind, "random")) a = POSIX_FADV_RANDOM;
1632
+ else if (!strcmp(kind, "willneed")) a = POSIX_FADV_WILLNEED;
1633
+ else if (!strcmp(kind, "dontneed")) a = POSIX_FADV_DONTNEED;
1634
+ else if (!strcmp(kind, "noreuse")) a = POSIX_FADV_NOREUSE;
1635
+ posix_fadvise(fd, (off_t)off, (off_t)len, a);
1636
+ #else
1637
+ (void)kind; (void)off; (void)len;
1638
+ #endif
1639
+ }
1640
+ /* IO#close_read / #close_write. A plain file is not duplex, so half-closing
1641
+ the side it does not have raises; half-closing the side it IS just closes
1642
+ the handle, which is what CRuby does. */
1643
+ void sp_File_close_half(sp_File *f, sp_bool reading) {SP_GC_ROOT(f);
1644
+ /* a closed File or pipe answers nil, as CRuby's IO#close_read and
1645
+ #close_write do, ahead of the duplex check; a closed socket raises, as
1646
+ BasicSocket's do */
1647
+ if (f && f->closed) {
1648
+ if (f->is_sock) sp_io_raise_closed();
1649
+ return;
1650
+ }
1651
+ const char *m = (f && f->mode) ? f->mode : "r";
1652
+ sp_bool writable = strchr(m, 'w') || strchr(m, 'a') || strchr(m, '+');
1653
+ sp_bool readable = strchr(m, 'r') || strchr(m, '+');
1654
+ if (reading ? !readable : !writable)
1655
+ sp_raise_cls("IOError", reading ? "closing non-duplex IO for reading"
1656
+ : "closing non-duplex IO for writing");
1657
+ if (reading ? writable : readable)
1658
+ sp_raise_cls("IOError", reading ? "closing non-duplex IO for reading"
1659
+ : "closing non-duplex IO for writing");
1660
+ if (f && f->fp && !f->closed) { FILE *fp = f->fp; f->closed = 1; f->fp = sp_io_closed_sentinel(); fclose(fp); }
1661
+ }
1662
+ /* IO#reopen(path, mode): rebind the handle to another file. */
1663
+ sp_File *sp_File_reopen(sp_File *f, const char *path, const char *mode) {SP_GC_ROOT(f);SP_GC_ROOT_STR(path);SP_GC_ROOT_STR(mode); sp_gc_wb((void*)f);
1664
+ if (!f) return f;
1665
+ /* a closed handle carries the shared sentinel, which must not be
1666
+ reopened: it gets a fresh stream */
1667
+ FILE *nf = (f->closed || !f->fp) ? fopen(path ? path : "", mode && mode[0] ? mode : "r")
1668
+ : freopen(path ? path : "", mode && mode[0] ? mode : "r", f->fp);
1669
+ if (!nf) sp_file_raise_errno("reopen", path ? path : "");
1670
+ f->fp = nf;
1671
+ f->closed = 0;
1672
+ f->path = path;
1673
+ f->mode = mode && mode[0] ? mode : "r";
1674
+ f->lineno = 0;
1675
+ return f;
1676
+ }