/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 | } |