Coverage Report

Created: 2018-07-03 15:31

/home/travis/build/MoarVM/MoarVM/src/io/syncsocket.c
Line
Count
Source (jump to first uncovered line)
1
#include "moar.h"
2
3
#ifdef _WIN32
4
    #include <winsock2.h>
5
    #include <ws2tcpip.h>
6
    #include <io.h>
7
8
    typedef SOCKET Socket;
9
    #define sa_family_t unsigned int
10
    #define isatty _isatty
11
#else
12
    #include "unistd.h"
13
    #include <sys/socket.h>
14
    #include <sys/un.h>
15
16
    typedef int Socket;
17
0
    #define closesocket close
18
#endif
19
20
#if defined(_MSC_VER)
21
#define snprintf _snprintf
22
#endif
23
24
/* Assumed maximum packet size. If ever changing this to something beyond a
25
 * 16-bit number, then make sure to change the receive offsets in the data
26
 * structure below. */
27
0
#define PACKET_SIZE 65535
28
29
/* Error handling varies between POSIX and WinSock. */
30
MVM_NO_RETURN static void throw_error(MVMThreadContext *tc, int r, char *operation) MVM_NO_RETURN_ATTRIBUTE;
31
#ifdef _WIN32
32
    #define MVM_IS_SOCKET_ERROR(x) ((x) == SOCKET_ERROR)
33
    static void throw_error(MVMThreadContext *tc, int r, char *operation) {
34
        int error = WSAGetLastError();
35
        LPTSTR error_string = NULL;
36
        if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
37
                NULL, error, 0, (LPTSTR)&error_string, 0, NULL) == 0) {
38
            /* Couldn't get error string; throw with code. */
39
            MVM_exception_throw_adhoc(tc, "Could not %s: error code %d", operation, error);
40
        }
41
        MVM_exception_throw_adhoc(tc, "Could not %s: %s", operation, error_string);
42
    }
43
#else
44
0
    #define MVM_IS_SOCKET_ERROR(x) ((x) < 0)
45
0
    static void throw_error(MVMThreadContext *tc, int r, char *operation) {
46
0
        MVM_exception_throw_adhoc(tc, "Could not %s: %s", operation, strerror(errno));
47
0
    }
48
#endif
49
50
 /* Data that we keep for a socket-based handle. */
51
typedef struct {
52
    /* The socket handle (file descriptor on POSIX, SOCKET on Windows). */
53
    Socket handle;
54
55
    /* Buffer of the last received packet of data, and start/end pointers
56
     * into the data. */
57
    char *last_packet;
58
    MVMuint16 last_packet_start;
59
    MVMuint16 last_packet_end;
60
61
    /* Did we reach EOF yet? */
62
    MVMint32 eof;
63
64
    /* ID for instrumentation. */
65
    unsigned int interval_id;
66
} MVMIOSyncSocketData;
67
68
/* Read a packet worth of data into the last packet buffer. */
69
0
static void read_one_packet(MVMThreadContext *tc, MVMIOSyncSocketData *data) {
70
0
    unsigned int interval_id = MVM_telemetry_interval_start(tc, "syncsocket.read_one_packet");
71
0
    int r;
72
0
    data->last_packet = MVM_malloc(PACKET_SIZE);
73
0
    do {
74
0
        MVM_gc_mark_thread_blocked(tc);
75
0
        r = recv(data->handle, data->last_packet, PACKET_SIZE, 0);
76
0
        MVM_gc_mark_thread_unblocked(tc);
77
0
    } while(r == -1 && errno == EINTR);
78
0
    MVM_telemetry_interval_stop(tc, interval_id, "syncsocket.read_one_packet");
79
0
    if (MVM_IS_SOCKET_ERROR(r) || r == 0) {
80
0
        MVM_free(data->last_packet);
81
0
        data->last_packet = NULL;
82
0
        if (r != 0)
83
0
            throw_error(tc, r, "receive data from socket");
84
0
    }
85
0
    else {
86
0
        data->last_packet_start = 0;
87
0
        data->last_packet_end = r;
88
0
    }
89
0
}
90
91
0
MVMint64 socket_read_bytes(MVMThreadContext *tc, MVMOSHandle *h, char **buf, MVMint64 bytes) {
92
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
93
0
    char *use_last_packet = NULL;
94
0
    MVMuint16 use_last_packet_start, use_last_packet_end;
95
0
96
0
    /* If at EOF, nothing more to do. */
97
0
    if (data->eof) {
98
0
        *buf = NULL;
99
0
        return 0;
100
0
    }
101
0
102
0
    /* See if there's anything in the packet buffer. */
103
0
    if (data->last_packet) {
104
0
        MVMuint16 last_remaining = data->last_packet_end - data->last_packet_start;
105
0
        if (bytes <= last_remaining) {
106
0
            /* There's enough, and it's sufficient for the request. Extract it
107
0
             * and return, discarding the last packet buffer if we drain it. */
108
0
            *buf = MVM_malloc(bytes);
109
0
            memcpy(*buf, data->last_packet + data->last_packet_start, bytes);
110
0
            if (bytes == last_remaining) {
111
0
                MVM_free(data->last_packet);
112
0
                data->last_packet = NULL;
113
0
            }
114
0
            else {
115
0
                data->last_packet_start += bytes;
116
0
            }
117
0
            return bytes;
118
0
        }
119
0
        else {
120
0
            /* Something, but not enough. Take the last packet for use, then
121
0
             * we'll read another one. */
122
0
            use_last_packet = data->last_packet;
123
0
            use_last_packet_start = data->last_packet_start;
124
0
            use_last_packet_end = data->last_packet_end;
125
0
            data->last_packet = NULL;
126
0
        }
127
0
    }
128
0
129
0
    /* If we get here, we need to read another packet. */
130
0
    read_one_packet(tc, data);
131
0
132
0
    /* Now assemble the result. */
133
0
    if (data->last_packet && use_last_packet) {
134
0
        /* Need to assemble it from two places. */
135
0
        MVMuint32 last_available = use_last_packet_end - use_last_packet_start;
136
0
        MVMuint32 available = last_available + data->last_packet_end;
137
0
        if (bytes > available)
138
0
            bytes = available;
139
0
        *buf = MVM_malloc(bytes);
140
0
        memcpy(*buf, use_last_packet + use_last_packet_start, last_available);
141
0
        memcpy(*buf + last_available, data->last_packet, bytes - last_available);
142
0
        if (bytes == available) {
143
0
            /* We used all of the just-read packet. */
144
0
            MVM_free(data->last_packet);
145
0
            data->last_packet = NULL;
146
0
        }
147
0
        else {
148
0
            /* Still something left in the just-read packet for next time. */
149
0
            data->last_packet_start += bytes - last_available;
150
0
        }
151
0
    }
152
0
    else if (data->last_packet) {
153
0
        /* Only data from the just-read packet. */
154
0
        if (bytes >= data->last_packet_end) {
155
0
            /* We need all of it, so no copying needed, just hand it back. */
156
0
            *buf = data->last_packet;
157
0
            bytes = data->last_packet_end;
158
0
            data->last_packet = NULL;
159
0
        }
160
0
        else {
161
0
            /* Only need some of it. */
162
0
            *buf = MVM_malloc(bytes);
163
0
            memcpy(*buf, data->last_packet, bytes);
164
0
            data->last_packet_start += bytes;
165
0
        }
166
0
    }
167
0
    else if (use_last_packet) {
168
0
        /* Nothing read this time, so at the end. Drain previous packet data
169
0
         * and mark EOF. */
170
0
        bytes = use_last_packet_end - use_last_packet_start;
171
0
        *buf = MVM_malloc(bytes);
172
0
        memcpy(*buf, use_last_packet + use_last_packet_start, bytes);
173
0
        data->eof = 1;
174
0
    }
175
0
    else {
176
0
        /* Nothing to hand back; at EOF. */
177
0
        *buf = NULL;
178
0
        bytes = 0;
179
0
        data->eof = 1;
180
0
    }
181
0
182
0
    return bytes;
183
0
}
184
185
/* Checks if EOF has been reached on the incoming data. */
186
0
MVMint64 socket_eof(MVMThreadContext *tc, MVMOSHandle *h) {
187
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
188
0
    return data->eof;
189
0
}
190
191
0
void socket_flush(MVMThreadContext *tc, MVMOSHandle *h, MVMint32 sync) {
192
0
    /* A no-op for sockets; we don't buffer. */
193
0
}
194
195
0
void socket_truncate(MVMThreadContext *tc, MVMOSHandle *h, MVMint64 bytes) {
196
0
    MVM_exception_throw_adhoc(tc, "Cannot truncate a socket");
197
0
}
198
199
/* Writes the specified bytes to the stream. */
200
0
MVMint64 socket_write_bytes(MVMThreadContext *tc, MVMOSHandle *h, char *buf, MVMint64 bytes) {
201
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
202
0
    MVMint64 sent = 0;
203
0
    unsigned int interval_id;
204
0
205
0
    interval_id = MVM_telemetry_interval_start(tc, "syncsocket.write_bytes");
206
0
    MVM_gc_mark_thread_blocked(tc);
207
0
    while (bytes > 0) {
208
0
        int r;
209
0
        do {
210
0
            r = send(data->handle, buf, (int)bytes, 0);
211
0
        } while(r == -1 && errno == EINTR);
212
0
        if (MVM_IS_SOCKET_ERROR(r)) {
213
0
            MVM_gc_mark_thread_unblocked(tc);
214
0
            MVM_telemetry_interval_stop(tc, interval_id, "syncsocket.write_bytes");
215
0
            throw_error(tc, r, "send data to socket");
216
0
        }
217
0
        sent += r;
218
0
        buf += r;
219
0
        bytes -= r;
220
0
    }
221
0
    MVM_gc_mark_thread_unblocked(tc);
222
0
    MVM_telemetry_interval_annotate(bytes, interval_id, "written this many bytes");
223
0
    MVM_telemetry_interval_stop(tc, interval_id, "syncsocket.write_bytes");
224
0
    return bytes;
225
0
}
226
227
0
static MVMint64 do_close(MVMThreadContext *tc, MVMIOSyncSocketData *data) {
228
0
    if (data->handle) {
229
0
        closesocket(data->handle);
230
0
        data->handle = 0;
231
0
    }
232
0
    return 0;
233
0
}
234
0
static MVMint64 close_socket(MVMThreadContext *tc, MVMOSHandle *h) {
235
0
    return do_close(tc, (MVMIOSyncSocketData *)h->body.data);
236
0
}
237
238
0
static void gc_free(MVMThreadContext *tc, MVMObject *h, void *d) {
239
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)d;
240
0
    do_close(tc, data);
241
0
    MVM_free(data);
242
0
}
243
244
0
static size_t get_struct_size_for_family(sa_family_t family) {
245
0
    switch (family) {
246
0
        case AF_INET6:
247
0
            return sizeof(struct sockaddr_in6);
248
0
        case AF_INET:
249
0
            return sizeof(struct sockaddr_in);
250
0
#ifndef _WIN32
251
0
        case AF_UNIX:
252
0
            return sizeof(struct sockaddr_un);
253
0
#endif
254
0
        default:
255
0
            return sizeof(struct sockaddr);
256
0
    }
257
0
}
258
259
/* This function may return any type of sockaddr e.g. sockaddr_un, sockaddr_in or sockaddr_in6
260
 * It shouldn't be a problem with general code as long as the port number is kept below the int16 limit: 65536
261
 * After this it defines the family which may spawn non internet sockaddr's
262
 * The family can be extracted by (port >> 16) & USHORT_MAX
263
 *
264
 * Currently supported families:
265
 *
266
 * AF_UNSPEC = 1
267
 *   Unspecified, in most cases should be equal to AF_INET or AF_INET6
268
 *
269
 * AF_UNIX = 1
270
 *   Unix domain socket, will spawn a sockaddr_un which will use the given host as path
271
 *   e.g: MVM_io_resolve_host_name(tc, "/run/moarvm.sock", 1 << 16)
272
 *   will spawn an unix domain socket on /run/moarvm.sock
273
 *
274
 * AF_INET = 2
275
 *   IPv4 socket
276
 *
277
 * AF_INET6 = 10
278
 *   IPv6 socket
279
 */
280
0
struct sockaddr * MVM_io_resolve_host_name(MVMThreadContext *tc, MVMString *host, MVMint64 port) {
281
0
    char *host_cstr = MVM_string_utf8_encode_C_string(tc, host);
282
0
    struct sockaddr *dest;
283
0
    int error;
284
0
    struct addrinfo *result;
285
0
    char port_cstr[8];
286
0
    unsigned short family = (port >> 16) & USHRT_MAX;
287
0
    struct addrinfo hints;
288
0
289
0
#ifndef _WIN32
290
0
    /* AF_UNIX = 1 */
291
0
    if (family == AF_UNIX) {
292
0
        struct sockaddr_un *result_un = MVM_malloc(sizeof(struct sockaddr_un));
293
0
294
0
        if (strlen(host_cstr) > 107) {
295
0
            MVM_free(result_un);
296
0
            MVM_free(host_cstr);
297
0
            MVM_exception_throw_adhoc(tc, "Socket path can only be maximal 107 characters long");
298
0
        }
299
0
300
0
        result_un->sun_family = AF_UNIX;
301
0
        strcpy(result_un->sun_path, host_cstr);
302
0
        MVM_free(host_cstr);
303
0
304
0
        return (struct sockaddr *)result_un;
305
0
    }
306
0
#endif
307
0
308
0
    hints.ai_family = family;
309
0
    hints.ai_socktype = 0;
310
0
    hints.ai_flags = AI_PASSIVE;
311
0
    hints.ai_protocol = 0;
312
0
    hints.ai_addrlen = 0;
313
0
    hints.ai_addr = NULL;
314
0
    hints.ai_canonname = NULL;
315
0
    hints.ai_next = NULL;
316
0
317
0
    snprintf(port_cstr, 8, "%d", (int)port);
318
0
319
0
    MVM_gc_mark_thread_blocked(tc);
320
0
    error = getaddrinfo(host_cstr, port_cstr, &hints, &result);
321
0
    MVM_gc_mark_thread_unblocked(tc);
322
0
    if (error == 0) {
323
0
        size_t size = get_struct_size_for_family(result->ai_addr->sa_family);
324
0
        MVM_free(host_cstr);
325
0
        dest = MVM_malloc(size);
326
0
        memcpy(dest, result->ai_addr, size);
327
0
    }
328
0
    else {
329
0
        char *waste[] = { host_cstr, NULL };
330
0
        MVM_exception_throw_adhoc_free(tc, waste, "Failed to resolve host name '%s' with family %d. Error: '%s'",
331
0
                                       host_cstr, family, gai_strerror(error));
332
0
    }
333
0
    freeaddrinfo(result);
334
0
335
0
    return dest;
336
0
}
337
338
/* Establishes a connection. */
339
0
static void socket_connect(MVMThreadContext *tc, MVMOSHandle *h, MVMString *host, MVMint64 port) {
340
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
341
0
    unsigned int interval_id;
342
0
343
0
    interval_id = MVM_telemetry_interval_start(tc, "syncsocket connect");
344
0
    if (!data->handle) {
345
0
        struct sockaddr *dest = MVM_io_resolve_host_name(tc, host, port);
346
0
        int r;
347
0
348
0
        Socket s = socket(dest->sa_family , SOCK_STREAM , 0);
349
0
        if (MVM_IS_SOCKET_ERROR(s)) {
350
0
            MVM_free(dest);
351
0
            MVM_telemetry_interval_stop(tc, interval_id, "syncsocket connect");
352
0
            throw_error(tc, s, "create socket");
353
0
        }
354
0
355
0
        do {
356
0
            MVM_gc_mark_thread_blocked(tc);
357
0
            r = connect(s, dest, (socklen_t)get_struct_size_for_family(dest->sa_family));
358
0
            MVM_gc_mark_thread_unblocked(tc);
359
0
        } while(r == -1 && errno == EINTR);
360
0
        MVM_free(dest);
361
0
        if (MVM_IS_SOCKET_ERROR(r)) {
362
0
            MVM_telemetry_interval_stop(tc, interval_id, "syncsocket connect");
363
0
            throw_error(tc, s, "connect socket");
364
0
        }
365
0
366
0
        data->handle = s;
367
0
    }
368
0
    else {
369
0
        MVM_telemetry_interval_stop(tc, interval_id, "syncsocket didn't connect");
370
0
        MVM_exception_throw_adhoc(tc, "Socket is already bound or connected");
371
0
    }
372
0
}
373
374
0
static void socket_bind(MVMThreadContext *tc, MVMOSHandle *h, MVMString *host, MVMint64 port, MVMint32 backlog) {
375
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
376
0
    if (!data->handle) {
377
0
        struct sockaddr *dest = MVM_io_resolve_host_name(tc, host, port);
378
0
        int r;
379
0
380
0
        Socket s = socket(dest->sa_family , SOCK_STREAM , 0);
381
0
        if (MVM_IS_SOCKET_ERROR(s)) {
382
0
            MVM_free(dest);
383
0
            throw_error(tc, s, "create socket");
384
0
        }
385
0
386
0
        /* On POSIX, we set the SO_REUSEADDR option, which allows re-use of
387
0
         * a port in TIME_WAIT state (modulo many hair details). Oringinally,
388
0
         * MoarVM used libuv, which does this automatically on non-Windows.
389
0
         * We have tests with bring up a server, then take it down, and then
390
0
         * bring another up on the same port, and we get test failures due
391
0
         * to racing to re-use the port without this. */
392
0
#ifndef _WIN32
393
0
        {
394
0
            int one = 1;
395
0
            setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
396
0
        }
397
0
#endif
398
0
399
0
        r = bind(s, dest, (socklen_t)get_struct_size_for_family(dest->sa_family));
400
0
        MVM_free(dest);
401
0
        if (MVM_IS_SOCKET_ERROR(r))
402
0
            throw_error(tc, s, "bind socket");
403
0
404
0
        r = listen(s, (int)backlog);
405
0
        if (MVM_IS_SOCKET_ERROR(r))
406
0
            throw_error(tc, s, "start listening on socket");
407
0
408
0
        data->handle = s;
409
0
    }
410
0
    else {
411
0
        MVM_exception_throw_adhoc(tc, "Socket is already bound or connected");
412
0
    }
413
0
}
414
415
0
MVMint64 socket_getport(MVMThreadContext *tc, MVMOSHandle *h) {
416
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
417
0
418
0
    struct sockaddr_storage name;
419
0
    int error;
420
0
    socklen_t len = sizeof(struct sockaddr_storage);
421
0
    MVMint64 port = 0;
422
0
423
0
    error = getsockname(data->handle, (struct sockaddr *) &name, &len);
424
0
425
0
    if (error != 0)
426
0
        MVM_exception_throw_adhoc(tc, "Failed to getsockname: %s", strerror(errno));
427
0
428
0
    switch (name.ss_family) {
429
0
        case AF_INET6:
430
0
            port = ntohs((*( struct sockaddr_in6 *) &name).sin6_port);
431
0
            break;
432
0
        case AF_INET:
433
0
            port = ntohs((*( struct sockaddr_in *) &name).sin_port);
434
0
            break;
435
0
    }
436
0
437
0
    return port;
438
0
}
439
440
0
static MVMint64 socket_is_tty(MVMThreadContext *tc, MVMOSHandle *h) {
441
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
442
0
    return isatty(data->handle);
443
0
}
444
445
0
static MVMint64 socket_handle(MVMThreadContext *tc, MVMOSHandle *h) {
446
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
447
0
    return (MVMint64)data->handle;
448
0
}
449
450
static MVMObject * socket_accept(MVMThreadContext *tc, MVMOSHandle *h);
451
452
/* IO ops table, populated with functions. */
453
static const MVMIOClosable      closable      = { close_socket };
454
static const MVMIOSyncReadable  sync_readable = { socket_read_bytes,
455
                                                  socket_eof };
456
static const MVMIOSyncWritable  sync_writable = { socket_write_bytes,
457
                                                  socket_flush,
458
                                                  socket_truncate };
459
static const MVMIOSockety             sockety = { socket_connect,
460
                                                  socket_bind,
461
                                                  socket_accept,
462
                                                  socket_getport };
463
static const MVMIOIntrospection introspection = { socket_is_tty,
464
                                                  socket_handle };
465
466
static const MVMIOOps op_table = {
467
    &closable,
468
    &sync_readable,
469
    &sync_writable,
470
    NULL,
471
    NULL,
472
    NULL,
473
    NULL,
474
    &sockety,
475
    NULL,
476
    NULL,
477
    &introspection,
478
    NULL,
479
    NULL,
480
    gc_free
481
};
482
483
0
static MVMObject * socket_accept(MVMThreadContext *tc, MVMOSHandle *h) {
484
0
    MVMIOSyncSocketData *data = (MVMIOSyncSocketData *)h->body.data;
485
0
    Socket s;
486
0
487
0
    unsigned int interval_id = MVM_telemetry_interval_start(tc, "syncsocket accept");
488
0
    do {
489
0
        MVM_gc_mark_thread_blocked(tc);
490
0
        s = accept(data->handle, NULL, NULL);
491
0
        MVM_gc_mark_thread_unblocked(tc);
492
0
    } while(s == -1 && errno == EINTR);
493
0
    if (MVM_IS_SOCKET_ERROR(s)) {
494
0
        MVM_telemetry_interval_stop(tc, interval_id, "syncsocket accept failed");
495
0
        throw_error(tc, s, "accept socket connection");
496
0
    }
497
0
    else {
498
0
        MVMOSHandle * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc,
499
0
                tc->instance->boot_types.BOOTIO);
500
0
        MVMIOSyncSocketData * const data = MVM_calloc(1, sizeof(MVMIOSyncSocketData));
501
0
        data->handle = s;
502
0
        result->body.ops  = &op_table;
503
0
        result->body.data = data;
504
0
        MVM_telemetry_interval_stop(tc, interval_id, "syncsocket accept succeeded");
505
0
        return (MVMObject *)result;
506
0
    }
507
0
}
508
509
0
MVMObject * MVM_io_socket_create(MVMThreadContext *tc, MVMint64 listen) {
510
0
    MVMOSHandle         * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIO);
511
0
    MVMIOSyncSocketData * const data   = MVM_calloc(1, sizeof(MVMIOSyncSocketData));
512
0
    result->body.ops  = &op_table;
513
0
    result->body.data = data;
514
0
    return (MVMObject *)result;
515
0
}
516
517
0
MVMString * MVM_io_get_hostname(MVMThreadContext *tc) {
518
0
    char hostname[65];
519
0
    gethostname(hostname, 65);
520
0
    return MVM_string_ascii_decode_nt(tc, tc->instance->VMString, hostname);
521
0
}