Coverage Report

Created: 2017-04-15 07:07

/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
15
#  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
1
static int mkdir_p(MVMThreadContext *tc, char *pathname, MVMint64 mode) {
37
1
    char *p = pathname, ch;
38
1
    uv_fs_t req;
39
1
#endif
40
1
    int created = 0;
41
1
42
15
    for (;; ++p)
43
16
        if (!*p || IS_SLASH(*p)) {
44
1
            ch = *p;
45
1
            *p  = '\0';
46
1
#ifdef _WIN32
47
            if (CreateDirectoryW(pathname, NULL)) {
48
                created = 1;
49
            }
50
#else
51
1
            if (uv_fs_stat(tc->loop, &req, pathname, NULL) <= 0) {
52
1
                if (mkdir(pathname, mode) != -1) {
53
1
                    created = 1;
54
1
                }
55
1
            }
56
1
#endif
57
1
            if (!(*p = ch)) break;
58
1
        }
59
1
60
1
    if (!created) return -1;
61
1
62
1
    return 0;
63
1
}
64
65
/* Create a directory recursively. */
66
1
void MVM_dir_mkdir(MVMThreadContext *tc, MVMString *path, MVMint64 mode) {
67
1
    char * const pathname = MVM_string_utf8_c8_encode_C_string(tc, path);
68
1
69
1
#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
1
105
1
    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
1
111
1
    MVM_free(pathname);
112
1
#endif
113
1
}
114
115
/* Remove a directory recursively. */
116
1
void MVM_dir_rmdir(MVMThreadContext *tc, MVMString *path) {
117
1
    char * const pathname = MVM_string_utf8_c8_encode_C_string(tc, path);
118
1
    uv_fs_t req;
119
1
120
1
    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
1
125
1
    MVM_free(pathname);
126
1
}
127
128
/* Get the current working directory. */
129
9
MVMString * MVM_dir_cwd(MVMThreadContext *tc) {
130
9
#ifdef _WIN32
131
    char path[MAX_PATH];
132
    size_t max_path = MAX_PATH;
133
    int r;
134
#else
135
9
    char path[PATH_MAX];
136
9
    size_t max_path = PATH_MAX;
137
9
    int r;
138
9
#endif
139
9
140
9
    if ((r = uv_cwd(path, (size_t *)&max_path)) < 0) {
141
0
        MVM_exception_throw_adhoc(tc, "chdir failed: %s", uv_strerror(r));
142
0
    }
143
9
144
9
    return MVM_string_utf8_c8_decode(tc, tc->instance->VMString, path, strlen(path));
145
9
}
146
147
/* Change directory. */
148
4
void MVM_dir_chdir(MVMThreadContext *tc, MVMString *dir) {
149
4
    char * const dirstring = MVM_string_utf8_c8_encode_C_string(tc, dir);
150
4
151
4
    if (uv_chdir((const char *)dirstring) != 0) {
152
0
        int chdir_error = errno;
153
0
        MVM_free(dirstring);
154
0
        MVM_exception_throw_adhoc(tc, "chdir failed: %s", uv_strerror(chdir_error));
155
0
    }
156
4
157
4
    MVM_free(dirstring);
158
4
}
159
160
/* Structure to keep track of directory iteration state. */
161
typedef struct {
162
#ifdef _WIN32
163
    wchar_t *dir_name;
164
    HANDLE   dir_handle;
165
#else
166
    DIR     *dir_handle;
167
#endif
168
    MVMuint8 encoding;
169
} MVMIODirIter;
170
171
/* Sets the encoding used for reading the directory listing. */
172
0
static void set_encoding(MVMThreadContext *tc, MVMOSHandle *h, MVMint64 encoding) {
173
0
    MVMIODirIter *data = (MVMIODirIter *)h->body.data;
174
0
    data->encoding = encoding;
175
0
}
176
177
/* Frees data associated with the directory handle. */
178
0
static void gc_free(MVMThreadContext *tc, MVMObject *h, void *d) {
179
0
    MVMIODirIter *data = (MVMIODirIter *)d;
180
0
    if (data) {
181
0
#ifdef _WIN32
182
        if (data->dir_name)
183
            MVM_free(data->dir_name);
184
185
        if (data->dir_handle)
186
            FindClose(data->dir_handle);
187
#else
188
0
        if (data->dir_handle)
189
0
            closedir(data->dir_handle);
190
0
#endif
191
0
        MVM_free(data);
192
0
    }
193
0
}
194
195
/* Ops table for directory iterator; it all works off special ops, so almost
196
 * no entries. */
197
static const MVMIOEncodable encodable = { set_encoding };
198
static const MVMIOOps op_table = {
199
    NULL,
200
    &encodable,
201
    NULL,
202
    NULL,
203
    NULL,
204
    NULL,
205
    NULL,
206
    NULL,
207
    NULL,
208
    NULL,
209
    NULL,
210
    NULL,
211
    NULL,
212
    gc_free
213
};
214
215
/* Open a filehandle, returning a handle. */
216
0
MVMObject * MVM_dir_open(MVMThreadContext *tc, MVMString *dirname) {
217
0
    MVMOSHandle  * const result = (MVMOSHandle *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTIO);
218
0
    MVMIODirIter * const data   = MVM_calloc(1, sizeof(MVMIODirIter));
219
0
#ifdef _WIN32
220
    char *name;
221
    int str_len;
222
    wchar_t *wname;
223
    wchar_t *dir_name;
224
225
    name  = MVM_string_utf8_c8_encode_C_string(tc, dirname);
226
    wname = UTF8ToUnicode(name);
227
    MVM_free(name);
228
229
    str_len = wcslen(wname);
230
231
    if (str_len > MAX_PATH - 2) { // the length of later appended '\*' is 2
232
        wchar_t  abs_dirname[4096]; /* 4096 should be enough for absolute path */
233
        wchar_t *lpp_part;
234
235
        /* You cannot use the "\\?\" prefix with a relative path,
236
         * relative paths are always limited to a total of MAX_PATH characters.
237
         * see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx */
238
        if (!GetFullPathNameW(wname, 4096, abs_dirname, &lpp_part)) {
239
            MVM_free(wname);
240
            MVM_exception_throw_adhoc(tc, "Directory path is wrong: %d", GetLastError());
241
        }
242
        MVM_free(wname);
243
244
        str_len  = wcslen(abs_dirname);
245
        dir_name = (wchar_t *)MVM_malloc((str_len + 7) * sizeof(wchar_t));
246
        wcscpy(dir_name, L"\\\\?\\");
247
        wcscat(dir_name, abs_dirname);
248
    } else {
249
        dir_name = (wchar_t *)MVM_malloc((str_len + 3) * sizeof(wchar_t));
250
        wcscpy(dir_name, wname);
251
        MVM_free(wname);
252
    }
253
254
    wcscat(dir_name, L"\\*");     /* Three characters are for the "\*" plus NULL appended.
255
                                   * see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365200%28v=vs.85%29.aspx */
256
257
    data->dir_name   = dir_name;
258
    data->dir_handle = INVALID_HANDLE_VALUE;
259
260
#else
261
0
    char * const dir_name = MVM_string_utf8_c8_encode_C_string(tc, dirname);
262
0
    DIR * const dir_handle = opendir(dir_name);
263
0
    int opendir_error = errno;
264
0
    MVM_free(dir_name);
265
0
266
0
    if (!dir_handle)
267
0
        MVM_exception_throw_adhoc(tc, "Failed to open dir: %d", opendir_error);
268
0
269
0
    data->dir_handle = dir_handle;
270
0
#endif
271
0
272
0
    data->encoding = MVM_encoding_type_utf8_c8;
273
0
    result->body.ops  = &op_table;
274
0
    result->body.data = data;
275
0
276
0
    return (MVMObject *)result;
277
0
}
278
279
/* Casts to a handle, checking it's a directory handle along the way. */
280
0
static MVMOSHandle * get_dirhandle(MVMThreadContext *tc, MVMObject *oshandle, const char *msg) {
281
0
    MVMOSHandle *handle = (MVMOSHandle *)oshandle;
282
0
    if (REPR(oshandle)->ID != MVM_REPR_ID_MVMOSHandle)
283
0
        MVM_exception_throw_adhoc(tc, "%s requires an object with REPR MVMOSHandle (got %s with REPR %s)", msg, STABLE(handle)->debug_name, REPR(handle)->name);
284
0
    if (handle->body.ops != &op_table)
285
0
        MVM_exception_throw_adhoc(tc, "%s got incorrect kind of handle", msg);
286
0
    return handle;
287
0
}
288
289
/* Reads a directory entry from a directory. */
290
0
MVMString * MVM_dir_read(MVMThreadContext *tc, MVMObject *oshandle) {
291
0
    MVMOSHandle  *handle = get_dirhandle(tc, oshandle, "readdir");
292
0
    MVMIODirIter *data   = (MVMIODirIter *)handle->body.data;
293
0
#ifdef _WIN32
294
    MVMString *result;
295
    TCHAR dir[MAX_PATH];
296
    WIN32_FIND_DATAW ffd;
297
    char *dir_str;
298
299
    if (data->dir_handle == INVALID_HANDLE_VALUE) {
300
        HANDLE hFind = FindFirstFileW(data->dir_name, &ffd);
301
302
        if (hFind == INVALID_HANDLE_VALUE) {
303
            MVM_exception_throw_adhoc(tc, "read from dirhandle failed: %d", GetLastError());
304
        }
305
306
        data->dir_handle = hFind;
307
        dir_str = UnicodeToUTF8(ffd.cFileName);
308
        result = MVM_string_utf8_c8_decode(tc, tc->instance->VMString, dir_str, strlen(dir_str));
309
        MVM_free(dir_str);
310
        return result;
311
    }
312
    else if (FindNextFileW(data->dir_handle, &ffd) != 0)  {
313
        dir_str = UnicodeToUTF8(ffd.cFileName);
314
        result  = MVM_string_decode(tc, tc->instance->VMString, dir_str, strlen(dir_str),
315
                                    data->encoding);
316
        MVM_free(dir_str);
317
        return result;
318
    } else {
319
        return tc->instance->str_consts.empty;
320
    }
321
#else
322
0
323
0
    struct dirent *entry;
324
0
    errno = 0; /* must reset errno so we won't check old errno */
325
0
326
0
    entry = readdir(data->dir_handle);
327
0
328
0
    if (errno == 0) {
329
0
        MVMString *ret = (entry == NULL)
330
0
                       ? tc->instance->str_consts.empty
331
0
                       : MVM_string_decode(tc, tc->instance->VMString, entry->d_name, strlen(entry->d_name), data->encoding);
332
0
        return ret;
333
0
    }
334
0
335
0
    MVM_exception_throw_adhoc(tc, "Failed to read dirhandle: %d", errno);
336
0
#endif
337
0
}
338
339
0
void MVM_dir_close(MVMThreadContext *tc, MVMObject *oshandle) {
340
0
    MVMOSHandle  *handle = get_dirhandle(tc, oshandle, "readdir");
341
0
    MVMIODirIter *data   = (MVMIODirIter *)handle->body.data;
342
0
343
0
#ifdef _WIN32
344
    if (data->dir_name) {
345
        MVM_free(data->dir_name);
346
        data->dir_name = NULL;
347
    }
348
349
    if (!FindClose(data->dir_handle))
350
        MVM_exception_throw_adhoc(tc, "Failed to close dirhandle: %d", GetLastError());
351
    data->dir_handle = NULL;
352
#else
353
0
    if (closedir(data->dir_handle) == -1)
354
0
        MVM_exception_throw_adhoc(tc, "Failed to close dirhandle: %d", errno);
355
0
    data->dir_handle = NULL;
356
0
#endif
357
0
}