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