Coverage Report

Created: 2018-07-03 15:31

/home/travis/build/MoarVM/MoarVM/src/io/fileops.c
Line
Count
Source (jump to first uncovered line)
1
#include "moar.h"
2
3
#ifndef _WIN32
4
#include <sys/types.h>
5
#include <unistd.h>
6
#define DEFAULT_MODE 0x01B6
7
#else
8
#include <fcntl.h>
9
#define O_CREAT  _O_CREAT
10
#define O_RDONLY _O_RDONLY
11
#define O_WRONLY _O_WRONLY
12
#define O_TRUNC  _O_TRUNC
13
#define DEFAULT_MODE _S_IWRITE /* work around sucky libuv defaults */
14
#endif
15
16
180
static uv_stat_t file_info(MVMThreadContext *tc, MVMString *filename, MVMint32 use_lstat) {
17
180
    char * const a = MVM_string_utf8_c8_encode_C_string(tc, filename);
18
180
    uv_fs_t req;
19
180
20
180
    if ((use_lstat
21
13
      ? uv_fs_lstat(tc->loop, &req, a, NULL)
22
167
      :  uv_fs_stat(tc->loop, &req, a, NULL)
23
0
    ) < 0) {
24
0
        MVM_free(a);
25
0
        MVM_exception_throw_adhoc(tc, "Failed to stat file: %s", uv_strerror(req.result));
26
0
    }
27
180
28
180
    MVM_free(a);
29
180
    return req.statbuf;
30
180
}
31
32
182
MVMint64 MVM_file_stat(MVMThreadContext *tc, MVMString *filename, MVMint64 status, MVMint32 use_lstat) {
33
182
    MVMint64 r = -1;
34
182
35
182
    switch (status) {
36
182
37
16
        case MVM_STAT_EXISTS:             r = MVM_file_exists(tc, filename, use_lstat); break;
38
16
39
1
        case MVM_STAT_FILESIZE: {
40
1
                char * const a = MVM_string_utf8_c8_encode_C_string(tc, filename);
41
1
                uv_fs_t req;
42
1
43
1
                if ((use_lstat
44
0
                  ? uv_fs_lstat(tc->loop, &req, a, NULL)
45
1
                  :  uv_fs_stat(tc->loop, &req, a, NULL)
46
0
                ) < 0) {
47
0
                    MVM_free(a);
48
0
                    MVM_exception_throw_adhoc(tc, "Failed to stat file: %s", uv_strerror(req.result));
49
0
                }
50
1
                MVM_free(a);
51
1
52
1
                r = req.statbuf.st_size;
53
1
                break;
54
16
            }
55
16
56
146
        case MVM_STAT_ISDIR:              r = (file_info(tc, filename, use_lstat).st_mode & S_IFMT) == S_IFDIR; break;
57
16
58
2
        case MVM_STAT_ISREG:              r = (file_info(tc, filename, use_lstat).st_mode & S_IFMT) == S_IFREG; break;
59
16
60
0
        case MVM_STAT_ISDEV: {
61
0
            const int mode = file_info(tc, filename, use_lstat).st_mode;
62
0
#ifdef _WIN32
63
            r = mode & S_IFMT == S_IFCHR;
64
#else
65
0
            r = (mode & S_IFMT) == S_IFCHR || (mode & S_IFMT) == S_IFBLK;
66
0
#endif
67
0
            break;
68
16
        }
69
16
70
0
        case MVM_STAT_CREATETIME:         r = file_info(tc, filename, use_lstat).st_birthtim.tv_sec; break;
71
16
72
3
        case MVM_STAT_ACCESSTIME:         r = file_info(tc, filename, use_lstat).st_atim.tv_sec; break;
73
16
74
3
        case MVM_STAT_MODIFYTIME:         r = file_info(tc, filename, use_lstat).st_mtim.tv_sec; break;
75
16
76
3
        case MVM_STAT_CHANGETIME:         r = file_info(tc, filename, use_lstat).st_ctim.tv_sec; break;
77
16
78
16
/*        case MVM_STAT_BACKUPTIME:         r = -1; break;  */
79
16
80
0
        case MVM_STAT_UID:                r = file_info(tc, filename, use_lstat).st_uid; break;
81
16
82
0
        case MVM_STAT_GID:                r = file_info(tc, filename, use_lstat).st_gid; break;
83
16
84
4
        case MVM_STAT_ISLNK:              r = (file_info(tc, filename, 1).st_mode & S_IFMT) == S_IFLNK; break;
85
16
86
2
        case MVM_STAT_PLATFORM_DEV:       r = file_info(tc, filename, use_lstat).st_dev; break;
87
16
88
2
        case MVM_STAT_PLATFORM_INODE:     r = file_info(tc, filename, use_lstat).st_ino; break;
89
16
90
0
        case MVM_STAT_PLATFORM_MODE:      r = file_info(tc, filename, use_lstat).st_mode; break;
91
16
92
0
        case MVM_STAT_PLATFORM_NLINKS:    r = file_info(tc, filename, use_lstat).st_nlink; break;
93
16
94
0
        case MVM_STAT_PLATFORM_DEVTYPE:   r = file_info(tc, filename, use_lstat).st_rdev; break;
95
16
96
0
        case MVM_STAT_PLATFORM_BLOCKSIZE: r = file_info(tc, filename, use_lstat).st_blksize; break;
97
16
98
0
        case MVM_STAT_PLATFORM_BLOCKS:    r = file_info(tc, filename, use_lstat).st_blocks; break;
99
16
100
0
        default: break;
101
182
    }
102
182
103
182
    return r;
104
182
}
105
106
15
MVMnum64 MVM_file_time(MVMThreadContext *tc, MVMString *filename, MVMint64 status, MVMint32 use_lstat) {
107
15
    uv_stat_t statbuf = file_info(tc, filename, use_lstat);
108
15
    uv_timespec_t ts;
109
15
110
15
    switch(status) {
111
0
        case MVM_STAT_CREATETIME: ts = statbuf.st_birthtim; break;
112
5
        case MVM_STAT_MODIFYTIME: ts = statbuf.st_mtim; break;
113
5
        case MVM_STAT_ACCESSTIME: ts = statbuf.st_atim; break;
114
5
        case MVM_STAT_CHANGETIME: ts = statbuf.st_ctim; break;
115
0
        default: return -1;
116
15
    }
117
15
118
15
    return ts.tv_sec + 1e-9 * (MVMnum64)ts.tv_nsec;
119
15
}
120
121
/* copy a file from one to another */
122
2
void MVM_file_copy(MVMThreadContext *tc, MVMString *src, MVMString * dest) {
123
2
    char * const a = MVM_string_utf8_c8_encode_C_string(tc, src);
124
2
    char * const b = MVM_string_utf8_c8_encode_C_string(tc, dest);
125
2
    uv_fs_t req;
126
2
127
2
    if(uv_fs_copyfile(tc->loop, &req, a, b, 0, NULL) < 0) {
128
0
        MVM_free(a);
129
0
        MVM_free(b);
130
0
        MVM_exception_throw_adhoc(tc, "Failed to copy file: %s", uv_strerror(req.result));
131
0
    }
132
2
133
2
    MVM_free(a);
134
2
    MVM_free(b);
135
2
}
136
137
/* rename one file to another. */
138
2
void MVM_file_rename(MVMThreadContext *tc, MVMString *src, MVMString *dest) {
139
2
    char * const a = MVM_string_utf8_c8_encode_C_string(tc, src);
140
2
    char * const b = MVM_string_utf8_c8_encode_C_string(tc, dest);
141
2
    uv_fs_t req;
142
2
143
2
    if(uv_fs_rename(tc->loop, &req, a, b, NULL) < 0 ) {
144
0
        MVM_free(a);
145
0
        MVM_free(b);
146
0
        MVM_exception_throw_adhoc(tc, "Failed to rename file: %s", uv_strerror(req.result));
147
0
    }
148
2
149
2
    MVM_free(a);
150
2
    MVM_free(b);
151
2
}
152
153
21
void MVM_file_delete(MVMThreadContext *tc, MVMString *f) {
154
21
    uv_fs_t req;
155
21
    char * const a = MVM_string_utf8_c8_encode_C_string(tc, f);
156
21
157
21
#ifdef _WIN32
158
    const int r = MVM_platform_unlink(a);
159
160
    if( r < 0 && errno != ENOENT) {
161
        MVM_free(a);
162
        MVM_exception_throw_adhoc(tc, "Failed to delete file: %d", errno);
163
    }
164
165
#else
166
21
    const int r = uv_fs_unlink(tc->loop, &req, a, NULL);
167
21
168
21
    if( r < 0 && r != UV_ENOENT) {
169
0
        MVM_free(a);
170
0
        MVM_exception_throw_adhoc(tc, "Failed to delete file: %s", uv_strerror(req.result));
171
0
    }
172
21
173
21
#endif
174
21
    MVM_free(a);
175
21
}
176
177
0
void MVM_file_chmod(MVMThreadContext *tc, MVMString *f, MVMint64 flag) {
178
0
    char * const a = MVM_string_utf8_c8_encode_C_string(tc, f);
179
0
    uv_fs_t req;
180
0
181
0
    if(uv_fs_chmod(tc->loop, &req, a, flag, NULL) < 0 ) {
182
0
        MVM_free(a);
183
0
        MVM_exception_throw_adhoc(tc, "Failed to set permissions on path: %s", uv_strerror(req.result));
184
0
    }
185
0
186
0
    MVM_free(a);
187
0
}
188
189
16
MVMint64 MVM_file_exists(MVMThreadContext *tc, MVMString *f, MVMint32 use_lstat) {
190
16
    uv_fs_t req;
191
16
    char * const a = MVM_string_utf8_c8_encode_C_string(tc, f);
192
16
    const MVMint64 result = (use_lstat
193
4
      ? uv_fs_lstat(tc->loop, &req, a, NULL)
194
12
      :  uv_fs_stat(tc->loop, &req, a, NULL)
195
9
    ) < 0 ? 0 : 1;
196
16
197
16
    MVM_free(a);
198
16
199
16
    return result;
200
16
}
201
202
#ifdef _WIN32
203
#define FILE_IS(name, rwx) \
204
    MVMint64 MVM_file_is ## name (MVMThreadContext *tc, MVMString *filename, MVMint32 use_lstat) { \
205
        if (!MVM_file_exists(tc, filename, use_lstat)) \
206
            return 0; \
207
        else { \
208
            uv_stat_t statbuf = file_info(tc, filename, use_lstat); \
209
            MVMint64 r = (statbuf.st_mode & S_I ## rwx ); \
210
            return r ? 1 : 0; \
211
        } \
212
    }
213
FILE_IS(readable, READ)
214
FILE_IS(writable, WRITE)
215
MVMint64 MVM_file_isexecutable(MVMThreadContext *tc, MVMString *filename, MVMint32 use_lstat) {
216
    if (!MVM_file_exists(tc, filename, use_lstat))
217
        return 0;
218
    else {
219
        MVMint64 r = 0;
220
        uv_stat_t statbuf = file_info(tc, filename, use_lstat);
221
        if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
222
            return 1;
223
        else {
224
            /* true if fileext is in PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC */
225
            MVMString *dot = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, ".");
226
            MVMROOT(tc, dot, {
227
                MVMint64 n = MVM_string_index_from_end(tc, filename, dot, 0);
228
                if (n >= 0) {
229
                    MVMString *fileext = MVM_string_substring(tc, filename, n, -1);
230
                    char *ext  = MVM_string_utf8_c8_encode_C_string(tc, fileext);
231
                    char *pext = getenv("PATHEXT");
232
                    int plen   = strlen(pext);
233
                    int i;
234
                    for (i = 0; i < plen; i++) {
235
                        if (0 == stricmp(ext, pext++)) {
236
                             r = 1;
237
                             break;
238
                        }
239
                    }
240
                    MVM_free(ext);
241
                    MVM_free(pext);
242
                }
243
            });
244
        }
245
        return r;
246
    }
247
}
248
#else
249
#define FILE_IS(name, rwx) \
250
0
    MVMint64 MVM_file_is ## name (MVMThreadContext *tc, MVMString *filename, MVMint32 use_lstat) { \
251
0
        if (!MVM_file_exists(tc, filename, use_lstat)) \
252
0
            return 0; \
253
0
        else { \
254
0
            uv_stat_t statbuf = file_info(tc, filename, use_lstat); \
255
0
            MVMint64 r = (statbuf.st_mode & S_I ## rwx ## OTH) \
256
0
                      || (statbuf.st_uid == geteuid() && (statbuf.st_mode & S_I ## rwx ## USR)) \
257
0
                      || (statbuf.st_uid == getegid() && (statbuf.st_mode & S_I ## rwx ## GRP)); \
258
0
            return r ? 1 : 0; \
259
0
        } \
260
0
    }
Unexecuted instantiation: MVM_file_isreadable
Unexecuted instantiation: MVM_file_iswritable
Unexecuted instantiation: MVM_file_isexecutable
261
FILE_IS(readable, R)
262
FILE_IS(writable, W)
263
FILE_IS(executable, X)
264
#endif
265
266
/* Get a MoarVM file handle representing one of the standard streams */
267
432
MVMObject * MVM_file_get_stdstream(MVMThreadContext *tc, MVMint32 descriptor) {
268
432
    return MVM_file_handle_from_fd(tc, descriptor);
269
432
}
270
271
/* Takes a filename and prepends any --libpath value we have, if it's not an
272
 * absolute path. */
273
1.44k
MVMString * MVM_file_in_libpath(MVMThreadContext *tc, MVMString *orig) {
274
1.44k
    const char **lib_path = tc->instance->lib_path;
275
1.44k
    MVM_gc_root_temp_push(tc, (MVMCollectable **)&orig);
276
1.44k
    if (lib_path) {
277
1.44k
        /* We actually have a lib_path to consider. See if the filename is
278
1.44k
         * absolute (XXX wants a platform abstraction, and doing better). */
279
1.44k
        char *orig_cstr = MVM_string_utf8_c8_encode_C_string(tc, orig);
280
1.44k
        int  absolute   = orig_cstr[0] == '/' || orig_cstr[0] == '\\' ||
281
1.44k
                          (orig_cstr[1] == ':' && orig_cstr[2] == '\\');
282
1.44k
        if (absolute) {
283
0
            /* Nothing more to do; we have an absolute path. */
284
0
            MVM_free(orig_cstr);
285
0
            MVM_gc_root_temp_pop(tc); /* orig */
286
0
            return orig;
287
0
        }
288
1.44k
        else {
289
1.44k
            MVMString *result = NULL;
290
1.44k
            int lib_path_i = 0;
291
1.44k
            MVM_gc_root_temp_push(tc, (MVMCollectable **)&result);
292
1.44k
            while (lib_path[lib_path_i]) {
293
0
                /* Concatenate libpath with filename. */
294
0
                size_t lib_path_len = strlen(lib_path[lib_path_i]);
295
0
                size_t orig_len     = strlen(orig_cstr);
296
0
                int    need_sep     = lib_path[lib_path_i][lib_path_len - 1] != '/' &&
297
0
                                      lib_path[lib_path_i][lib_path_len - 1] != '\\';
298
0
                int    new_len      = lib_path_len + (need_sep ? 1 : 0) + orig_len;
299
0
                char * new_path     = MVM_malloc(new_len);
300
0
                memcpy(new_path, lib_path[lib_path_i], lib_path_len);
301
0
                if (need_sep) {
302
0
                    new_path[lib_path_len] = '/';
303
0
                    memcpy(new_path + lib_path_len + 1, orig_cstr, orig_len);
304
0
                }
305
0
                else {
306
0
                    memcpy(new_path + lib_path_len, orig_cstr, orig_len);
307
0
                }
308
0
                result = MVM_string_utf8_c8_decode(tc, tc->instance->VMString, new_path, new_len);
309
0
                MVM_free(new_path);
310
0
                if (!MVM_file_exists(tc, result, 1))
311
0
                    result = orig;
312
0
                else {
313
0
                    MVM_free(orig_cstr);
314
0
                    MVM_gc_root_temp_pop_n(tc, 2); /* orig and result */
315
0
                    return result;
316
0
                }
317
0
                lib_path_i++;
318
0
            }
319
1.44k
            if (!result || !MVM_file_exists(tc, result, 1))
320
1.44k
                result = orig;
321
1.44k
            MVM_free(orig_cstr);
322
1.44k
            MVM_gc_root_temp_pop_n(tc, 2); /* orig and result */
323
1.44k
            return result;
324
1.44k
        }
325
1.44k
    }
326
0
    else {
327
0
        /* No libpath, so just hand back the original name. */
328
0
        MVM_gc_root_temp_pop(tc); /* orig */
329
0
        return orig;
330
0
    }
331
1.44k
}
332
333
1
void MVM_file_link(MVMThreadContext *tc, MVMString *oldpath, MVMString *newpath) {
334
1
    uv_fs_t req;
335
1
    char * const oldpath_s = MVM_string_utf8_c8_encode_C_string(tc, oldpath);
336
1
    char * const newpath_s = MVM_string_utf8_c8_encode_C_string(tc, newpath);
337
1
338
1
    if (uv_fs_link(tc->loop, &req, oldpath_s, newpath_s, NULL)) {
339
0
        MVM_free(oldpath_s);
340
0
        MVM_free(newpath_s);
341
0
        MVM_exception_throw_adhoc(tc, "Failed to link file: %s", uv_strerror(req.result));
342
0
    }
343
1
344
1
    MVM_free(oldpath_s);
345
1
    MVM_free(newpath_s);
346
1
}
347
348
3
void MVM_file_symlink(MVMThreadContext *tc, MVMString *oldpath, MVMString *newpath) {
349
3
    uv_fs_t req;
350
3
    char * const oldpath_s = MVM_string_utf8_c8_encode_C_string(tc, oldpath);
351
3
    char * const newpath_s = MVM_string_utf8_c8_encode_C_string(tc, newpath);
352
3
353
3
    if (uv_fs_symlink(tc->loop, &req, oldpath_s, newpath_s, 0, NULL)) {
354
0
        MVM_free(oldpath_s);
355
0
        MVM_free(newpath_s);
356
0
        MVM_exception_throw_adhoc(tc, "Failed to symlink file: %s", uv_strerror(req.result));
357
0
    }
358
3
359
3
    MVM_free(oldpath_s);
360
3
    MVM_free(newpath_s);
361
3
}
362
363
1
MVMString * MVM_file_readlink(MVMThreadContext *tc, MVMString *path) {
364
1
    uv_fs_t req;
365
1
    MVMString *result;
366
1
367
1
    char * const path_s = MVM_string_utf8_c8_encode_C_string(tc, path);
368
1
    if (uv_fs_readlink(tc->loop, &req, path_s, NULL) < 0) {
369
0
        MVM_free(path_s);
370
0
        MVM_exception_throw_adhoc(tc, "Failed to readlink file: %s", uv_strerror(req.result));
371
0
    }
372
1
373
1
    MVM_free(path_s);
374
1
    result = MVM_string_utf8_c8_decode(tc, tc->instance->VMString, req.ptr, strlen(req.ptr));
375
1
    MVM_free(req.ptr);
376
1
377
1
    return result;
378
1
}