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,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
|
+
}
|