Coverage Report

Created: 2018-07-03 15:31

/home/travis/build/MoarVM/MoarVM/src/io/syncfile.c
Line
Count
Source (jump to first uncovered line)
1
#include "moar.h"
2
#include "platform/io.h"
3
4
#ifndef _WIN32
5
#include <sys/types.h>
6
#include <unistd.h>
7
215
#define DEFAULT_MODE 0x01B6
8
typedef struct stat STAT;
9
#else
10
#include <fcntl.h>
11
#include <errno.h>
12
#define O_CREAT  _O_CREAT
13
#define O_RDONLY _O_RDONLY
14
#define O_WRONLY _O_WRONLY
15
#define O_TRUNC  _O_TRUNC
16
#define O_EXCL   _O_EXCL
17
#define O_RDWR   _O_RDWR
18
#define DEFAULT_MODE _S_IWRITE
19
#define open _open
20
#define close _close
21
#define read _read
22
#define write _write
23
#define isatty _isatty
24
#define ftruncate _chsize
25
#define fstat _fstat
26
typedef struct _stat STAT;
27
#endif
28
29
/* Data that we keep for a file-based handle. */
30
typedef struct {
31
    /* File descriptor. */
32
    int fd;
33
34
    /* Is it seekable? */
35
    short seekable;
36
37
    /* Is it known to be writable? */
38
    short known_writable;
39
40
    /* How many bytes have we read/written? Used to fake tell on handles that
41
     * are not seekable. */
42
    MVMint64 byte_position;
43
44
    /* Did read already report EOF? */
45
    int eof_reported;
46
47
    /* Output buffer, for buffered output. */
48
    char *output_buffer;
49
50
    /* Size of the output buffer, for buffered output; 0 if not buffering. */
51
    size_t output_buffer_size;
52
53
    /* How much of the output buffer has been used so far. */
54
    size_t output_buffer_used;
55
} MVMIOFileData;
56
57
/* Checks if the file is a TTY. */
58
1
static MVMint64 is_tty(MVMThreadContext *tc, MVMOSHandle *h) {
59
1
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
60
1
    return isatty(data->fd);
61
1
}
62
63
/* Gets the file descriptor. */
64
0
static MVMint64 mvm_fileno(MVMThreadContext *tc, MVMOSHandle *h) {
65
0
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
66
0
    return (MVMint64)data->fd;
67
0
}
68
69
/* Performs a write, either because a buffer filled or because we are not
70
 * buffering output. */
71
13.3k
static void perform_write(MVMThreadContext *tc, MVMIOFileData *data, char *buf, MVMint64 bytes) {
72
13.3k
    MVMint64 bytes_written = 0;
73
13.3k
    MVM_gc_mark_thread_blocked(tc);
74
26.6k
    while (bytes > 0) {
75
13.3k
        int r;
76
13.3k
        do {
77
13.3k
            r = write(data->fd, buf, (int)bytes);
78
13.3k
        } while (r == -1 && errno == EINTR);
79
13.3k
        if (r == -1) {
80
0
            int save_errno = errno;
81
0
            MVM_gc_mark_thread_unblocked(tc);
82
0
            MVM_exception_throw_adhoc(tc, "Failed to write bytes to filehandle: %s",
83
0
                strerror(save_errno));
84
0
        }
85
13.3k
        bytes_written += r;
86
13.3k
        buf += r;
87
13.3k
        bytes -= r;
88
13.3k
    }
89
13.3k
    MVM_gc_mark_thread_unblocked(tc);
90
13.3k
    data->byte_position += bytes_written;
91
13.3k
    data->known_writable = 1;
92
13.3k
}
93
94
/* Flushes any existing output buffer and clears use back to 0. */
95
902
static void flush_output_buffer(MVMThreadContext *tc, MVMIOFileData *data) {
96
902
    if (data->output_buffer_used) {
97
0
        perform_write(tc, data, data->output_buffer, data->output_buffer_used);
98
0
        data->output_buffer_used = 0;
99
0
    }
100
902
}
101
102
/* Seek to the specified position in the file. */
103
6
static void seek(MVMThreadContext *tc, MVMOSHandle *h, MVMint64 offset, MVMint64 whence) {
104
6
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
105
6
    if (!data->seekable)
106
0
        MVM_exception_throw_adhoc(tc, "It is not possible to seek this kind of handle");
107
6
    flush_output_buffer(tc, data);
108
6
    if (MVM_platform_lseek(data->fd, offset, whence) == -1)
109
2
        MVM_exception_throw_adhoc(tc, "Failed to seek in filehandle: %d", errno);
110
6
}
111
112
/* Get current position in the file. */
113
9
static MVMint64 mvm_tell(MVMThreadContext *tc, MVMOSHandle *h) {
114
9
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
115
9
    flush_output_buffer(tc, data);
116
9
    if (data->seekable) {
117
9
        MVMint64 r;
118
9
        if ((r = MVM_platform_lseek(data->fd, 0, SEEK_CUR)) == -1)
119
0
            MVM_exception_throw_adhoc(tc, "Failed to tell in filehandle: %d", errno);
120
9
        return r;
121
9
    }
122
0
    else {
123
0
        return data->byte_position;
124
0
    }
125
9
}
126
127
/* Reads the specified number of bytes into the supplied buffer, returning
128
 * the number actually read. */
129
387
static MVMint64 read_bytes(MVMThreadContext *tc, MVMOSHandle *h, char **buf_out, MVMint64 bytes) {
130
387
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
131
387
    char *buf = MVM_malloc(bytes);
132
387
    unsigned int interval_id = MVM_telemetry_interval_start(tc, "syncfile.read_to_buffer");
133
387
    MVMint32 bytes_read;
134
387
#ifdef _WIN32
135
    /* Can only perform relatively small reads from a Windows console;
136
     * trying to do larger ones gives back ENOMEM, most likely due to
137
     * limitations of the Windows console subsystem. */
138
    if (bytes > 16387 && _isatty(data->fd))
139
        bytes = 16387;
140
#endif
141
387
    flush_output_buffer(tc, data);
142
387
    do {
143
387
        MVM_gc_mark_thread_blocked(tc);
144
387
        bytes_read = read(data->fd, buf, bytes);
145
387
        MVM_gc_mark_thread_unblocked(tc);
146
387
    } while(bytes_read == -1 && errno == EINTR);
147
387
    if (bytes_read  == -1) {
148
0
        int save_errno = errno;
149
0
        MVM_free(buf);
150
0
        MVM_exception_throw_adhoc(tc, "Reading from filehandle failed: %s",
151
0
            strerror(save_errno));
152
0
    }
153
387
    *buf_out = buf;
154
387
    MVM_telemetry_interval_annotate(bytes_read, interval_id, "read this many bytes");
155
387
    MVM_telemetry_interval_stop(tc, interval_id, "syncfile.read_to_buffer");
156
387
    data->byte_position += bytes_read;
157
387
    if (bytes_read == 0 && bytes != 0)
158
194
        data->eof_reported = 1;
159
387
    return bytes_read;
160
387
}
161
162
/* Checks if the end of file has been reached. */
163
16
static MVMint64 mvm_eof(MVMThreadContext *tc, MVMOSHandle *h) {
164
16
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
165
16
    if (data->seekable) {
166
16
        MVMint64 seek_pos;
167
16
        STAT statbuf;
168
16
        if (fstat(data->fd, &statbuf) == -1)
169
0
            MVM_exception_throw_adhoc(tc, "Failed to stat file descriptor: %s",
170
0
                strerror(errno));
171
16
        if ((seek_pos = MVM_platform_lseek(data->fd, 0, SEEK_CUR)) == -1)
172
0
            MVM_exception_throw_adhoc(tc, "Failed to seek in filehandle: %d", errno);
173
16
        /* For some special files, like those in /proc, the file size is 0,
174
16
         * so in those cases, fall back to eof_reported flag to detect EOF. */
175
16
        return statbuf.st_size
176
16
             ? statbuf.st_size <= seek_pos : data->eof_reported;
177
16
    }
178
0
    else {
179
0
        return data->eof_reported;
180
0
    }
181
16
}
182
183
/* Sets the output buffer size; if <= 0, means no buffering. Flushes any
184
 * existing buffer before changing. */
185
0
static void set_buffer_size(MVMThreadContext *tc, MVMOSHandle *h, MVMint64 size) {
186
0
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
187
0
188
0
    /* Flush and clear up any existing output buffer. */
189
0
    flush_output_buffer(tc, data);
190
0
    MVM_free(data->output_buffer);
191
0
192
0
    /* Set up new buffer if needed. */
193
0
    if (size > 0) {
194
0
        data->output_buffer_size = size;
195
0
        data->output_buffer = MVM_malloc(size);
196
0
    }
197
0
    else {
198
0
        data->output_buffer_size = 0;
199
0
        data->output_buffer = NULL;
200
0
    }
201
0
}
202
203
/* Writes the specified bytes to the file handle. */
204
13.3k
static MVMint64 write_bytes(MVMThreadContext *tc, MVMOSHandle *h, char *buf, MVMint64 bytes) {
205
13.3k
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
206
13.3k
    if (data->output_buffer_size && data->known_writable) {
207
0
        /* If we can't fit it on the end of the buffer, flush the buffer. */
208
0
        if (data->output_buffer_used + bytes > data->output_buffer_size)
209
0
            flush_output_buffer(tc, data);
210
0
211
0
        /* If we can fit it in the buffer now, memcpy it there, and we're
212
0
         * done. */
213
0
        if (bytes < data->output_buffer_size) {
214
0
            memcpy(data->output_buffer + data->output_buffer_used, buf, bytes);
215
0
            data->output_buffer_used += bytes;
216
0
            return bytes;
217
0
        }
218
0
    }
219
13.3k
    perform_write(tc, data, buf, bytes);
220
13.3k
    return bytes;
221
13.3k
}
222
223
/* Flushes the file handle. */
224
288
static void flush(MVMThreadContext *tc, MVMOSHandle *h, MVMint32 sync){
225
288
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
226
288
    flush_output_buffer(tc, data);
227
288
    if (sync) {
228
0
        if (MVM_platform_fsync(data->fd) == -1) {
229
0
            /* If this is something that can't be flushed, we let that pass. */
230
0
            if (errno != EROFS && errno != EINVAL
231
0
#ifdef WSL_BASH_ON_WIN
232
               && ! (errno == EIO && ! data->seekable)
233
               /* Bash on Win10 doesn't seem to handle TTYs right when flushing:
234
                * https://github.com/borgbackup/borg/issues/1961#issuecomment-335653560 */
235
#endif
236
0
            )
237
0
                MVM_exception_throw_adhoc(tc, "Failed to flush filehandle: %s", strerror(errno));
238
0
        }
239
0
    }
240
288
}
241
242
/* Truncates the file handle. */
243
0
static void truncatefh(MVMThreadContext *tc, MVMOSHandle *h, MVMint64 bytes) {
244
0
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
245
0
    if (ftruncate(data->fd, bytes) == -1)
246
0
        MVM_exception_throw_adhoc(tc, "Failed to truncate filehandle: %s", strerror(errno));
247
0
}
248
249
/* Closes the file. */
250
212
static MVMint64 closefh(MVMThreadContext *tc, MVMOSHandle *h) {
251
212
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
252
212
    if (data->fd != -1) {
253
212
        int r;
254
212
        flush_output_buffer(tc, data);
255
212
        MVM_free(data->output_buffer);
256
212
        data->output_buffer = NULL;
257
212
        r = close(data->fd);
258
212
        data->fd = -1;
259
212
        if (r == -1)
260
0
            MVM_exception_throw_adhoc(tc, "Failed to close filehandle: %s", strerror(errno));
261
212
    }
262
212
    return 0;
263
212
}
264
265
/* Locks a file. */
266
0
static MVMint64 lock(MVMThreadContext *tc, MVMOSHandle *h, MVMint64 flag) {
267
0
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
268
0
269
0
#ifdef _WIN32
270
271
    const DWORD len = 0xffffffff;
272
    const HANDLE hf = (HANDLE)_get_osfhandle(data->fd);
273
    OVERLAPPED offset;
274
275
    if (hf == INVALID_HANDLE_VALUE) {
276
        MVM_exception_throw_adhoc(tc, "Failed to lock filehandle: bad file descriptor");
277
    }
278
279
    flag = ((flag & MVM_FILE_FLOCK_NONBLOCK) ? LOCKFILE_FAIL_IMMEDIATELY : 0)
280
          + ((flag & MVM_FILE_FLOCK_TYPEMASK) == MVM_FILE_FLOCK_SHARED
281
                                       ? 0 : LOCKFILE_EXCLUSIVE_LOCK);
282
283
    memset (&offset, 0, sizeof(offset));
284
    MVM_gc_mark_thread_blocked(tc);
285
    if (LockFileEx(hf, flag, 0, len, len, &offset)) {
286
        MVM_gc_mark_thread_unblocked(tc);
287
        return 1;
288
    }
289
    MVM_gc_mark_thread_unblocked(tc);
290
291
    MVM_exception_throw_adhoc(tc, "Failed to lock filehandle: %d", GetLastError());
292
293
    return 0;
294
295
#else
296
0
297
0
    struct flock l;
298
0
    ssize_t r;
299
0
    int fc;
300
0
    const int fd = data->fd;
301
0
302
0
    l.l_whence = SEEK_SET;
303
0
    l.l_start = 0;
304
0
    l.l_len = 0;
305
0
306
0
    if ((flag & MVM_FILE_FLOCK_TYPEMASK) == MVM_FILE_FLOCK_SHARED)
307
0
        l.l_type = F_RDLCK;
308
0
    else
309
0
        l.l_type = F_WRLCK;
310
0
311
0
    fc = (flag & MVM_FILE_FLOCK_NONBLOCK) ? F_SETLK : F_SETLKW;
312
0
313
0
    do {
314
0
        MVM_gc_mark_thread_blocked(tc);
315
0
        r = fcntl(fd, fc, &l);
316
0
        MVM_gc_mark_thread_unblocked(tc);
317
0
    } while (r == -1 && errno == EINTR);
318
0
319
0
    if (r == -1) {
320
0
        MVM_exception_throw_adhoc(tc, "Failed to lock filehandle: %d", errno);
321
0
    }
322
0
323
0
    return 1;
324
0
#endif
325
0
}
326
327
/* Unlocks a file. */
328
0
static void unlock(MVMThreadContext *tc, MVMOSHandle *h) {
329
0
    MVMIOFileData *data = (MVMIOFileData *)h->body.data;
330
0
331
0
#ifdef _WIN32
332
333
    const DWORD len = 0xffffffff;
334
    const HANDLE hf = (HANDLE)_get_osfhandle(data->fd);
335
    OVERLAPPED offset;
336
337
    if (hf == INVALID_HANDLE_VALUE) {
338
        MVM_exception_throw_adhoc(tc, "Failed to seek in filehandle: bad file descriptor");
339
    }
340
341
    memset (&offset, 0, sizeof(offset));
342
    MVM_gc_mark_thread_blocked(tc);
343
    if (UnlockFileEx(hf, 0, len, len, &offset)) {
344
        MVM_gc_mark_thread_unblocked(tc);
345
        return;
346
    }
347
    MVM_gc_mark_thread_unblocked(tc);
348
349
    MVM_exception_throw_adhoc(tc, "Failed to unlock filehandle: %d", GetLastError());
350
#else
351
0
352
0
    struct flock l;
353
0
    ssize_t r;
354
0
    const int fd = data->fd;
355
0
356
0
    l.l_whence = SEEK_SET;
357
0
    l.l_start = 0;
358
0
    l.l_len = 0;
359
0
    l.l_type = F_UNLCK;
360
0
361
0
    do {
362
0
        MVM_gc_mark_thread_blocked(tc);
363
0
        r = fcntl(fd, F_SETLKW, &l);
364
0
        MVM_gc_mark_thread_unblocked(tc);
365
0
    } while (r == -1 && errno == EINTR);
366
0
367
0
    if (r == -1) {
368
0
        MVM_exception_throw_adhoc(tc, "Failed to unlock filehandle: %d", errno);
369
0
    }
370
0
#endif
371
0
}
372
373
/* Frees data associated with the handle. */
374
85
static void gc_free(MVMThreadContext *tc, MVMObject *h, void *d) {
375
85
    MVMIOFileData *data = (MVMIOFileData *)d;
376
85
    if (data) {
377
85
        MVM_free(data->output_buffer);
378
85
        MVM_free(data);
379
85
    }
380
85
}
381
382
/* IO ops table, populated with functions. */
383
static const MVMIOClosable      closable      = { closefh };
384
static const MVMIOSyncReadable  sync_readable = { read_bytes, mvm_eof };
385
static const MVMIOSyncWritable  sync_writable = { write_bytes, flush, truncatefh };
386
static const MVMIOSeekable      seekable      = { seek, mvm_tell };
387
static const MVMIOLockable      lockable      = { lock, unlock };
388
static const MVMIOIntrospection introspection = { is_tty, mvm_fileno };
389
390
static const MVMIOOps op_table = {
391
    &closable,
392
    &sync_readable,
393
    &sync_writable,
394
    NULL,
395
    NULL,
396
    NULL,
397
    &seekable,
398
    NULL,
399
    NULL,
400
    &lockable,
401
    &introspection,
402
    &set_buffer_size,
403
    NULL,
404
    gc_free
405
};
406
407
/* Builds POSIX flag from mode string. */
408
215
static int resolve_open_mode(int *flag, const char *cp) {
409
215
    switch (*cp++) {
410
193
        case 'r': *flag = O_RDONLY; break;
411
0
        case '-': *flag = O_WRONLY; break;
412
0
        case '+': *flag = O_RDWR;   break;
413
193
414
193
        /* alias for "-c" or "-ct" if by itself */
415
22
        case 'w':
416
21
        *flag = *cp ? O_WRONLY | O_CREAT : O_WRONLY | O_CREAT | O_TRUNC;
417
22
        break;
418
193
419
0
        default:
420
0
        return 0;
421
215
    }
422
215
423
216
    for (;;) switch (*cp++) {
424
215
        case 0:
425
215
        return 1;
426
215
427
1
        case 'a': *flag |= O_APPEND; break;
428
0
        case 'c': *flag |= O_CREAT;  break;
429
0
        case 't': *flag |= O_TRUNC;  break;
430
0
        case 'x': *flag |= O_EXCL;   break;
431
215
432
0
        default:
433
0
        return 0;
434
216
    }
435
215
}
436
437
/* Opens a file, returning a synchronous file handle. */
438
215
MVMObject * MVM_file_open_fh(MVMThreadContext *tc, MVMString *filename, MVMString *mode) {
439
215
    char * const fname = MVM_string_utf8_c8_encode_C_string(tc, filename);
440
215
    int fd;
441
215
    int flag;
442
215
    STAT statbuf;
443
215
444
215
    /* Resolve mode description to flags. */
445
215
    char * const fmode  = MVM_string_utf8_encode_C_string(tc, mode);
446
215
    if (!resolve_open_mode(&flag, fmode)) {
447
0
        char *waste[] = { fname, fmode, NULL };
448
0
        MVM_exception_throw_adhoc_free(tc, waste,
449
0
            "Invalid open mode for file %s: %s", fname, fmode);
450
0
    }
451
215
    MVM_free(fmode);
452
215
453
215
    /* Try to open the file. */
454
215
#ifdef _WIN32
455
    flag |= _O_BINARY;
456
#endif
457
215
    if ((fd = open((const char *)fname, flag, DEFAULT_MODE)) == -1) {
458
1
        char *waste[] = { fname, NULL };
459
1
        const char *err = strerror(errno);
460
1
        MVM_exception_throw_adhoc_free(tc, waste, "Failed to open file %s: %s", fname, err);
461
1
    }
462
215
463
215
    /*  Check that we didn't open a directory by accident.
464
215
        If fstat fails, just move on: Most of the documented error cases should
465
215
        already have triggered when opening the file, and we can't do anything
466
215
        about the others; a failure also does not necessarily imply that the
467
215
        file descriptor cannot be used for reading/writing. */
468
215
    if (fstat(fd, &statbuf) == 0 && (statbuf.st_mode & S_IFMT) == S_IFDIR) {
469
0
        char *waste[] = { fname, NULL };
470
0
        if (close(fd) == -1) {
471
0
            const char *err = strerror(errno);
472
0
            MVM_exception_throw_adhoc_free(tc, waste,
473
0
                "Tried to open directory %s, which we failed to close: %s",
474
0
                fname, err);
475
0
        }
476
0
        MVM_exception_throw_adhoc_free(tc, waste, "Tried to open directory %s", fname);
477
0
    }
478
215
479
215
    /* Set up handle. */
480
215
    MVM_free(fname);
481
215
    {
482
215
        MVMIOFileData * const data   = MVM_calloc(1, sizeof(MVMIOFileData));
483
215
        MVMOSHandle   * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc,
484
215
            tc->instance->boot_types.BOOTIO);
485
215
        data->fd          = fd;
486
215
        data->seekable    = MVM_platform_is_fd_seekable(fd);
487
215
        result->body.ops  = &op_table;
488
215
        result->body.data = data;
489
215
        return (MVMObject *)result;
490
215
    }
491
215
}
492
493
/* Opens a file, returning a synchronous file handle. */
494
432
MVMObject * MVM_file_handle_from_fd(MVMThreadContext *tc, int fd) {
495
432
    MVMOSHandle   * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIO);
496
432
    MVMIOFileData * const data   = MVM_calloc(1, sizeof(MVMIOFileData));
497
432
    data->fd          = fd;
498
432
    data->seekable    = MVM_platform_is_fd_seekable(fd);
499
432
    result->body.ops  = &op_table;
500
432
    result->body.data = data;
501
432
#ifdef _WIN32
502
    _setmode(fd, _O_BINARY);
503
#endif
504
432
    return (MVMObject *)result;
505
432
}