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