Coverage Report

Created: 2018-07-03 15:31

/home/travis/build/MoarVM/MoarVM/src/io/dirops.c
Line
Count
Source (jump to first uncovered line)
1
#include "moar.h"
2
#ifndef _WIN32
3
#include <dirent.h>
4
#endif
5
6
#ifdef _WIN32
7
#  define IS_SLASH(c)     ((c) == L'\\' || (c) == L'/')
8
#else
9
102
#  define IS_SLASH(c)     ((c) == '/')
10
#endif
11
12
#ifdef _WIN32
13
static wchar_t * UTF8ToUnicode(char *str)
14
{
15
     const int         len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
16
     wchar_t * const result = (wchar_t *)MVM_malloc(len * sizeof(wchar_t));
17
18
     MultiByteToWideChar(CP_UTF8, 0, str, -1, result, len);
19
20
     return result;
21
}
22
23
static char * UnicodeToUTF8(const wchar_t *str)
24
{
25
     const int       len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
26
     char * const result = (char *)MVM_malloc(len * sizeof(char));
27
28
     WideCharToMultiByte(CP_UTF8, 0, str, -1, result, len, NULL, NULL);
29
30
     return result;
31
}
32
33
static int mkdir_p(MVMThreadContext *tc, wchar_t *pathname, MVMint64 mode) {
34
    wchar_t *p = pathname, ch;
35
#else
36
4
static int mkdir_p(MVMThreadContext *tc, char *pathname, MVMint64 mode) {
37
4
    char *p = pathname, ch;
38
4
    uv_fs_t req;
39
4
#endif
40
4
    int created = 0;
41
4
42
102
    for (;; ++p)
43
106
        if (!*p || IS_SLASH(*p)) {
44
9
            ch = *p;
45
9
            *p  = '\0';
46
9
#ifdef _WIN32
47
            if (CreateDirectoryW(pathname, NULL)) {
48
                created = 1;
49
            }
50
#else
51
9
            if (uv_fs_stat(tc->loop, &req, pathname, NULL) <= 0) {
52
9
                if (mkdir(pathname, mode) != -1) {
53
8
                    created = 1;
54
8
                }
55
9
            }
56
9
#endif
57
9
            if (!(*p = ch)) break;
58
9
        }
59
4
60
4
    if (!created) return -1;
61
4
62
3
    return 0;
63
4
}
64
65
/* Create a directory recursively. */
66
4
void MVM_dir_mkdir(MVMThreadContext *tc, MVMString *path, MVMint64 mode) {
67
4
    char * const pathname = MVM_string_utf8_c8_encode_C_string(tc, path);
68
4
69
4
#ifdef _WIN32
70
    /* Must using UTF8ToUnicode for supporting CJK Windows file name. */
71
    wchar_t *wpathname = UTF8ToUnicode(pathname);
72
    int str_len = wcslen(wpathname);
73
    MVM_free(pathname);
74
75
    if (str_len > MAX_PATH) {
76
        wchar_t  abs_dirname[4096]; /* 4096 should be enough for absolute path */
77
        wchar_t *lpp_part;
78
79
        /* You cannot use the "\\?\" prefix with a relative path,
80
         * relative paths are always limited to a total of MAX_PATH characters.
81
         * see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx */
82
        if (!GetFullPathNameW(wpathname, 4096, abs_dirname, &lpp_part)) {
83
            MVM_free(wpathname);
84
            MVM_exception_throw_adhoc(tc, "Directory path is wrong: %d", GetLastError());
85
        }
86
87
        MVM_free(wpathname);
88
89
        str_len  = wcslen(abs_dirname);
90
        wpathname = (wchar_t *)MVM_malloc((str_len + 4) * sizeof(wchar_t));
91
        wcscpy(wpathname, L"\\\\?\\");
92
        wcscat(wpathname, abs_dirname);
93
    }
94
95
    if (mkdir_p(tc, wpathname, mode) == -1) {
96
        DWORD error = GetLastError();
97
        if (error != ERROR_ALREADY_EXISTS) {
98
            MVM_free(wpathname);
99
            MVM_exception_throw_adhoc(tc, "Failed to mkdir: %d", error);
100
        }
101
    }
102
    MVM_free(wpathname);
103
#else
104
4
105
4
    if (mkdir_p(tc, pathname, mode) == -1 && errno != EEXIST) {
106
0
        int mkdir_error = errno;
107
0
        MVM_free(pathname);
108
0
        MVM_exception_throw_adhoc(tc, "Failed to mkdir: %d", mkdir_error);
109
0
    }
110
4
111
4
    MVM_free(pathname);
112
4
#endif
113
4
}
114
115
/* Remove a directory recursively. */
116
8
void MVM_dir_rmdir(MVMThreadContext *tc, MVMString *path) {
117
8
    char * const pathname = MVM_string_utf8_c8_encode_C_string(tc, path);
118
8
    uv_fs_t req;
119
8
120
8
    if(uv_fs_rmdir(tc->loop, &req, pathname, NULL) < 0 ) {
121
0
        MVM_free(pathname);
122
0
        MVM_exception_throw_adhoc(tc, "Failed to rmdir: %s", uv_strerror(req.result));
123
0
    }
124
8
125
8
    MVM_free(pathname);
126
8
}
127
128
/* Get the current working directory. */
129
7
MVMString * MVM_dir_cwd(MVMThreadContext *tc) {
130
7
#ifdef _WIN32
131
    char path[MAX_PATH];
132
    size_t max_path = MAX_PATH;
133
    int r;
134
#else
135
7
    char path[PATH_MAX];
136
7
    size_t max_path = PATH_MAX;
137
7
    int r;
138
7
#endif
139
7
140
7
    if ((r = uv_cwd(path, (size_t *)&max_path)) < 0) {
141
0
        MVM_exception_throw_adhoc(tc, "Failed to determine cwd: %s", uv_strerror(r));
142
0
    }
143
7
144
7
    return MVM_string_utf8_c8_decode(tc, tc->instance->VMString, path, strlen(path));
145
7
}
146
4
int MVM_dir_chdir_C_string(MVMThreadContext *tc, const char *dirstring) {
147
4
    return uv_chdir(dirstring);
148
4
}
149
/* Change directory. */
150
4
void MVM_dir_chdir(MVMThreadContext *tc, MVMString *dir) {
151
4
    const char *dirstring = MVM_string_utf8_c8_encode_C_string(tc, dir);
152
4
    int chdir_error = MVM_dir_chdir_C_string(tc, dirstring);
153
4
    MVM_free((void*)dirstring);
154
4
    if (chdir_error) {
155
0
        MVM_exception_throw_adhoc(tc, "chdir failed: %s", uv_strerror(chdir_error));
156
0
    }
157
4
}
158
159
/* Structure to keep track of directory iteration state. */
160
typedef struct {
161
#ifdef _WIN32
162
    wchar_t *dir_name;
163
    HANDLE   dir_handle;
164
#else
165
    DIR     *dir_handle;
166
#endif
167
} MVMIODirIter;
168
169
/* Frees data associated with the directory handle. */
170
0
static void gc_free(MVMThreadContext *tc, MVMObject *h, void *d) {
171
0
    MVMIODirIter *data = (MVMIODirIter *)d;
172
0
    if (data) {
173
0
#ifdef _WIN32
174
        if (data->dir_name)
175
            MVM_free(data->dir_name);
176
177
        if (data->dir_handle)
178
            FindClose(data->dir_handle);
179
#else
180
0
        if (data->dir_handle)
181
0
            closedir(data->dir_handle);
182
0
#endif
183
0
        MVM_free(data);
184
0
    }
185
0
}
186
187
/* Ops table for directory iterator; it all works off special ops, so no entries. */
188
static const MVMIOOps op_table = {
189
    NULL,
190
    NULL,
191
    NULL,
192
    NULL,
193
    NULL,
194
    NULL,
195
    NULL,
196
    NULL,
197
    NULL,
198
    NULL,
199
    NULL,
200
    NULL,
201
    NULL,
202
    gc_free
203
};
204
205
/* Open a filehandle, returning a handle. */
206
2
MVMObject * MVM_dir_open(MVMThreadContext *tc, MVMString *dirname) {
207
2
    MVMOSHandle  * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIO);
208
2
    MVMIODirIter * const data   = MVM_calloc(1, sizeof(MVMIODirIter));
209
2
#ifdef _WIN32
210
    char *name;
211
    int str_len;
212
    wchar_t *wname;
213
    wchar_t *dir_name;
214
215
    name  = MVM_string_utf8_c8_encode_C_string(tc, dirname);
216
    wname = UTF8ToUnicode(name);
217
    MVM_free(name);
218
219
    str_len = wcslen(wname);
220
221
    if (str_len > MAX_PATH - 2) { // the length of later appended '\*' is 2
222
        wchar_t  abs_dirname[4096]; /* 4096 should be enough for absolute path */
223
        wchar_t *lpp_part;
224
225
        /* You cannot use the "\\?\" prefix with a relative path,
226
         * relative paths are always limited to a total of MAX_PATH characters.
227
         * see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx */
228
        if (!GetFullPathNameW(wname, 4096, abs_dirname, &lpp_part)) {
229
            MVM_free(wname);
230
            MVM_exception_throw_adhoc(tc, "Directory path is wrong: %d", GetLastError());
231
        }
232
        MVM_free(wname);
233
234
        str_len  = wcslen(abs_dirname);
235
        dir_name = (wchar_t *)MVM_malloc((str_len + 7) * sizeof(wchar_t));
236
        wcscpy(dir_name, L"\\\\?\\");
237
        wcscat(dir_name, abs_dirname);
238
    } else {
239
        dir_name = (wchar_t *)MVM_malloc((str_len + 3) * sizeof(wchar_t));
240
        wcscpy(dir_name, wname);
241
        MVM_free(wname);
242
    }
243
244
    wcscat(dir_name, L"\\*");     /* Three characters are for the "\*" plus NULL appended.
245
                                   * see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365200%28v=vs.85%29.aspx */
246
247
    data->dir_name   = dir_name;
248
    data->dir_handle = INVALID_HANDLE_VALUE;
249
250
#else
251
2
    char * const dir_name = MVM_string_utf8_c8_encode_C_string(tc, dirname);
252
2
    DIR * const dir_handle = opendir(dir_name);
253
2
    int opendir_error = errno;
254
2
    MVM_free(dir_name);
255
2
256
2
    if (!dir_handle)
257
0
        MVM_exception_throw_adhoc(tc, "Failed to open dir: %d", opendir_error);
258
2
259
2
    data->dir_handle = dir_handle;
260
2
#endif
261
2
262
2
    result->body.ops  = &op_table;
263
2
    result->body.data = data;
264
2
265
2
    return (MVMObject *)result;
266
2
}
267
268
/* Casts to a handle, checking it's a directory handle along the way. */
269
8
static MVMOSHandle * get_dirhandle(MVMThreadContext *tc, MVMObject *oshandle, const char *msg) {
270
8
    MVMOSHandle *handle = (MVMOSHandle *)oshandle;
271
8
    if (REPR(oshandle)->ID != MVM_REPR_ID_MVMOSHandle)
272
0
        MVM_exception_throw_adhoc(tc, "%s requires an object with REPR MVMOSHandle (got %s with REPR %s)", msg, MVM_6model_get_debug_name(tc, (MVMObject *)handle), REPR(handle)->name);
273
8
    if (handle->body.ops != &op_table)
274
0
        MVM_exception_throw_adhoc(tc, "%s got incorrect kind of handle", msg);
275
8
    return handle;
276
8
}
277
278
/* Reads a directory entry from a directory. */
279
6
MVMString * MVM_dir_read(MVMThreadContext *tc, MVMObject *oshandle) {
280
6
    MVMOSHandle  *handle = get_dirhandle(tc, oshandle, "readdir");
281
6
    MVMIODirIter *data   = (MVMIODirIter *)handle->body.data;
282
6
#ifdef _WIN32
283
    MVMString *result;
284
    TCHAR dir[MAX_PATH];
285
    WIN32_FIND_DATAW ffd;
286
    char *dir_str;
287
288
    if (data->dir_handle == INVALID_HANDLE_VALUE) {
289
        HANDLE hFind = FindFirstFileW(data->dir_name, &ffd);
290
291
        if (hFind == INVALID_HANDLE_VALUE) {
292
            MVM_exception_throw_adhoc(tc, "read from dirhandle failed: %d", GetLastError());
293
        }
294
295
        data->dir_handle = hFind;
296
        dir_str = UnicodeToUTF8(ffd.cFileName);
297
        result = MVM_string_utf8_c8_decode(tc, tc->instance->VMString, dir_str, strlen(dir_str));
298
        MVM_free(dir_str);
299
        return result;
300
    }
301
    else if (FindNextFileW(data->dir_handle, &ffd) != 0)  {
302
        dir_str = UnicodeToUTF8(ffd.cFileName);
303
        result  = MVM_string_decode(tc, tc->instance->VMString, dir_str, strlen(dir_str),
304
                                    MVM_encoding_type_utf8_c8);
305
        MVM_free(dir_str);
306
        return result;
307
    } else {
308
        return tc->instance->str_consts.empty;
309
    }
310
#else
311
6
312
6
    struct dirent *entry;
313
6
    errno = 0; /* must reset errno so we won't check old errno */
314
6
315
6
    if (!data->dir_handle) {
316
1
        MVM_exception_throw_adhoc(tc, "Cannot read a closed dir handle.");
317
1
    }
318
6
319
6
    entry = readdir(data->dir_handle);
320
6
321
6
    if (errno == 0) {
322
5
        MVMString *ret = (entry == NULL)
323
1
                       ? tc->instance->str_consts.empty
324
4
                       : MVM_string_decode(tc, tc->instance->VMString, entry->d_name,
325
4
                               strlen(entry->d_name), MVM_encoding_type_utf8_c8);
326
5
        return ret;
327
5
    }
328
6
329
1
    MVM_exception_throw_adhoc(tc, "Failed to read dirhandle: %d", errno);
330
1
#endif
331
1
}
332
333
2
void MVM_dir_close(MVMThreadContext *tc, MVMObject *oshandle) {
334
2
    MVMOSHandle  *handle = get_dirhandle(tc, oshandle, "readdir");
335
2
    MVMIODirIter *data   = (MVMIODirIter *)handle->body.data;
336
2
337
2
#ifdef _WIN32
338
    if (data->dir_name) {
339
        MVM_free(data->dir_name);
340
        data->dir_name = NULL;
341
    }
342
343
    if (!FindClose(data->dir_handle))
344
        MVM_exception_throw_adhoc(tc, "Failed to close dirhandle: %d", GetLastError());
345
    data->dir_handle = NULL;
346
#else
347
2
    if (closedir(data->dir_handle) == -1)
348
0
        MVM_exception_throw_adhoc(tc, "Failed to close dirhandle: %d", errno);
349
2
    data->dir_handle = NULL;
350
2
#endif
351
2
}