1 | module os |
2 | |
3 | import strings |
4 | |
5 | #include <sys/stat.h> // #include <signal.h> |
6 | #include <errno.h> |
7 | |
8 | $if freebsd { |
9 | #include <sys/sysctl.h> |
10 | } |
11 | |
12 | pub const ( |
13 | args = []string{} |
14 | ) |
15 | |
16 | fn C.readdir(voidptr) &C.dirent |
17 | |
18 | fn C.readlink(pathname &char, buf &char, bufsiz usize) int |
19 | |
20 | fn C.getline(voidptr, voidptr, voidptr) int |
21 | |
22 | fn C.sigaction(int, voidptr, int) int |
23 | |
24 | fn C.open(&char, int, ...int) int |
25 | |
26 | fn C._wopen(&u16, int, ...int) int |
27 | |
28 | fn C.fdopen(fd int, mode &char) &C.FILE |
29 | |
30 | fn C.ferror(stream &C.FILE) int |
31 | |
32 | fn C.feof(stream &C.FILE) int |
33 | |
34 | fn C.CopyFile(&u16, &u16, bool) int |
35 | |
36 | // fn C.lstat(charptr, voidptr) u64 |
37 | |
38 | fn C._wstat64(&u16, voidptr) u64 |
39 | |
40 | fn C.chown(&char, int, int) int |
41 | |
42 | fn C.ftruncate(voidptr, u64) int |
43 | |
44 | fn C._chsize_s(voidptr, u64) int |
45 | |
46 | // read_bytes returns all bytes read from file in `path`. |
47 | [manualfree] |
48 | pub fn read_bytes(path string) ![]u8 { |
49 | mut fp := vfopen(path, 'rb')! |
50 | defer { |
51 | C.fclose(fp) |
52 | } |
53 | fsize := find_cfile_size(fp)! |
54 | if fsize == 0 { |
55 | mut sb := slurp_file_in_builder(fp)! |
56 | return unsafe { sb.reuse_as_plain_u8_array() } |
57 | } |
58 | mut res := []u8{len: fsize} |
59 | nr_read_elements := int(C.fread(res.data, 1, fsize, fp)) |
60 | if nr_read_elements == 0 && fsize > 0 { |
61 | return error('fread failed') |
62 | } |
63 | res.trim(nr_read_elements) |
64 | return res |
65 | } |
66 | |
67 | fn find_cfile_size(fp &C.FILE) !int { |
68 | // NB: Musl's fseek returns -1 for virtual files, while Glibc's fseek returns 0 |
69 | cseek := C.fseek(fp, 0, C.SEEK_END) |
70 | raw_fsize := C.ftell(fp) |
71 | if raw_fsize != 0 && cseek != 0 { |
72 | return error('fseek failed') |
73 | } |
74 | if cseek != 0 && raw_fsize < 0 { |
75 | return error('ftell failed') |
76 | } |
77 | len := int(raw_fsize) |
78 | // For files > 2GB, C.ftell can return values that, when cast to `int`, can result in values below 0. |
79 | if i64(len) < raw_fsize { |
80 | return error('int(${raw_fsize}) cast results in ${len}') |
81 | } |
82 | C.rewind(fp) |
83 | return len |
84 | } |
85 | |
86 | const buf_size = 4096 |
87 | |
88 | // slurp_file_in_builder reads an entire file into a strings.Builder chunk by chunk, without relying on its file size. |
89 | // It is intended for reading 0 sized files, or a dynamic files in a virtual filesystem like /proc/cpuinfo. |
90 | // For these, we can not allocate all memory in advance (since we do not know the final size), and so we have no choice |
91 | // but to read the file in `buf_size` chunks. |
92 | [manualfree] |
93 | fn slurp_file_in_builder(fp &C.FILE) !strings.Builder { |
94 | buf := [os.buf_size]u8{} |
95 | mut sb := strings.new_builder(os.buf_size) |
96 | for { |
97 | mut read_bytes := fread(&buf[0], 1, os.buf_size, fp) or { |
98 | if err is Eof { |
99 | break |
100 | } |
101 | unsafe { sb.free() } |
102 | return err |
103 | } |
104 | unsafe { sb.write_ptr(&buf[0], read_bytes) } |
105 | } |
106 | return sb |
107 | } |
108 | |
109 | // read_file reads the file in `path` and returns the contents. |
110 | [manualfree] |
111 | pub fn read_file(path string) !string { |
112 | mode := 'rb' |
113 | mut fp := vfopen(path, mode)! |
114 | defer { |
115 | C.fclose(fp) |
116 | } |
117 | allocate := find_cfile_size(fp)! |
118 | if allocate == 0 { |
119 | mut sb := slurp_file_in_builder(fp)! |
120 | res := sb.str() |
121 | unsafe { sb.free() } |
122 | return res |
123 | } |
124 | unsafe { |
125 | mut str := malloc_noscan(allocate + 1) |
126 | nelements := int(C.fread(str, 1, allocate, fp)) |
127 | is_eof := int(C.feof(fp)) |
128 | is_error := int(C.ferror(fp)) |
129 | if is_eof == 0 && is_error != 0 { |
130 | free(str) |
131 | return error('fread failed') |
132 | } |
133 | str[nelements] = 0 |
134 | if nelements == 0 { |
135 | // It is highly likely that the file was a virtual file from |
136 | // /sys or /proc, with information generated on the fly, so |
137 | // fsize was not reliably reported. Using vstring() here is |
138 | // slower (it calls strlen internally), but will return more |
139 | // consistent results. |
140 | // For example reading from /sys/class/sound/card0/id produces |
141 | // a `PCH\n` string, but fsize is 4096, and otherwise you would |
142 | // get a V string with .len = 4096 and .str = "PCH\n\\000". |
143 | return str.vstring() |
144 | } |
145 | return str.vstring_with_len(nelements) |
146 | } |
147 | } |
148 | |
149 | // ***************************** OS ops ************************ |
150 | // |
151 | // truncate changes the size of the file located in `path` to `len`. |
152 | // Note that changing symbolic links on Windows only works as admin. |
153 | pub fn truncate(path string, len u64) ! { |
154 | fp := $if windows { |
155 | C._wopen(path.to_wide(), o_wronly | o_trunc, 0) |
156 | } $else { |
157 | C.open(&char(path.str), o_wronly | o_trunc, 0) |
158 | } |
159 | if fp < 0 { |
160 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
161 | } |
162 | defer { |
163 | C.close(fp) |
164 | } |
165 | $if windows { |
166 | if C._chsize_s(fp, len) != 0 { |
167 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
168 | } |
169 | } $else { |
170 | if C.ftruncate(fp, len) != 0 { |
171 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
172 | } |
173 | } |
174 | } |
175 | |
176 | fn eprintln_unknown_file_size() { |
177 | eprintln('os.file_size() Cannot determine file-size: ' + posix_get_error_msg(C.errno)) |
178 | } |
179 | |
180 | // file_size returns the size of the file located in `path`. |
181 | // If an error occurs it returns 0. |
182 | // Note that use of this on symbolic links on Windows returns always 0. |
183 | pub fn file_size(path string) u64 { |
184 | mut s := C.stat{} |
185 | unsafe { |
186 | $if x64 { |
187 | $if windows { |
188 | mut swin := C.__stat64{} |
189 | if C._wstat64(path.to_wide(), voidptr(&swin)) != 0 { |
190 | eprintln_unknown_file_size() |
191 | return 0 |
192 | } |
193 | return swin.st_size |
194 | } $else { |
195 | if C.stat(&char(path.str), &s) != 0 { |
196 | eprintln_unknown_file_size() |
197 | return 0 |
198 | } |
199 | return u64(s.st_size) |
200 | } |
201 | } |
202 | $if x32 { |
203 | $if debug { |
204 | eprintln('Using os.file_size() on 32bit systems may not work on big files.') |
205 | } |
206 | $if windows { |
207 | if C._wstat(path.to_wide(), voidptr(&s)) != 0 { |
208 | eprintln_unknown_file_size() |
209 | return 0 |
210 | } |
211 | return u64(s.st_size) |
212 | } $else { |
213 | if C.stat(&char(path.str), &s) != 0 { |
214 | eprintln_unknown_file_size() |
215 | return 0 |
216 | } |
217 | return u64(s.st_size) |
218 | } |
219 | } |
220 | } |
221 | return 0 |
222 | } |
223 | |
224 | // rename_dir renames the folder from `src` to `dst`. |
225 | // Use mv to move or rename a file in a platform independent manner. |
226 | pub fn rename_dir(src string, dst string) ! { |
227 | $if windows { |
228 | w_src := src.replace('/', '\\') |
229 | w_dst := dst.replace('/', '\\') |
230 | ret := C._wrename(w_src.to_wide(), w_dst.to_wide()) |
231 | if ret != 0 { |
232 | return error_with_code('failed to rename ${src} to ${dst}', int(ret)) |
233 | } |
234 | } $else { |
235 | ret := C.rename(&char(src.str), &char(dst.str)) |
236 | if ret != 0 { |
237 | return error_with_code('failed to rename ${src} to ${dst}', ret) |
238 | } |
239 | } |
240 | } |
241 | |
242 | // rename renames the file or folder from `src` to `dst`. |
243 | // Use mv to move or rename a file in a platform independent manner. |
244 | pub fn rename(src string, dst string) ! { |
245 | mut rdst := dst |
246 | if is_dir(rdst) { |
247 | rdst = join_path_single(rdst.trim_right(path_separator), file_name(src.trim_right(path_separator))) |
248 | } |
249 | $if windows { |
250 | w_src := src.replace('/', '\\') |
251 | w_dst := rdst.replace('/', '\\') |
252 | ret := C._wrename(w_src.to_wide(), w_dst.to_wide()) |
253 | if ret != 0 { |
254 | return error_with_code('failed to rename ${src} to ${dst}', int(ret)) |
255 | } |
256 | } $else { |
257 | ret := C.rename(&char(src.str), &char(rdst.str)) |
258 | if ret != 0 { |
259 | return error_with_code('failed to rename ${src} to ${dst}', ret) |
260 | } |
261 | } |
262 | } |
263 | |
264 | // cp copies files or folders from `src` to `dst`. |
265 | pub fn cp(src string, dst string) ! { |
266 | $if windows { |
267 | w_src := src.replace('/', '\\') |
268 | w_dst := dst.replace('/', '\\') |
269 | if C.CopyFile(w_src.to_wide(), w_dst.to_wide(), false) == 0 { |
270 | result := C.GetLastError() |
271 | return error_with_code('failed to copy ${src} to ${dst}', int(result)) |
272 | } |
273 | } $else { |
274 | fp_from := C.open(&char(src.str), C.O_RDONLY, 0) |
275 | if fp_from < 0 { // Check if file opened |
276 | return error_with_code('cp: failed to open ${src}', int(fp_from)) |
277 | } |
278 | fp_to := C.open(&char(dst.str), C.O_WRONLY | C.O_CREAT | C.O_TRUNC, C.S_IWUSR | C.S_IRUSR) |
279 | if fp_to < 0 { // Check if file opened (permissions problems ...) |
280 | C.close(fp_from) |
281 | return error_with_code('cp (permission): failed to write to ${dst} (fp_to: ${fp_to})', |
282 | int(fp_to)) |
283 | } |
284 | // TODO use defer{} to close files in case of error or return. |
285 | // Currently there is a C-Error when building. |
286 | mut buf := [1024]u8{} |
287 | mut count := 0 |
288 | for { |
289 | count = C.read(fp_from, &buf[0], sizeof(buf)) |
290 | if count == 0 { |
291 | break |
292 | } |
293 | if C.write(fp_to, &buf[0], count) < 0 { |
294 | C.close(fp_to) |
295 | C.close(fp_from) |
296 | return error_with_code('cp: failed to write to ${dst}', int(-1)) |
297 | } |
298 | } |
299 | from_attr := C.stat{} |
300 | unsafe { |
301 | C.stat(&char(src.str), &from_attr) |
302 | } |
303 | if C.chmod(&char(dst.str), from_attr.st_mode) < 0 { |
304 | C.close(fp_to) |
305 | C.close(fp_from) |
306 | return error_with_code('failed to set permissions for ${dst}', int(-1)) |
307 | } |
308 | C.close(fp_to) |
309 | C.close(fp_from) |
310 | } |
311 | } |
312 | |
313 | // vfopen returns an opened C file, given its path and open mode. |
314 | // Note: os.vfopen is useful for compatibility with C libraries, that expect `FILE *`. |
315 | // If you write pure V code, os.create or os.open are more convenient. |
316 | pub fn vfopen(path string, mode string) !&C.FILE { |
317 | if path.len == 0 { |
318 | return error('vfopen called with ""') |
319 | } |
320 | mut fp := unsafe { nil } |
321 | $if windows { |
322 | fp = C._wfopen(path.to_wide(), mode.to_wide()) |
323 | } $else { |
324 | fp = C.fopen(&char(path.str), &char(mode.str)) |
325 | } |
326 | if isnil(fp) { |
327 | return error('failed to open file "${path}"') |
328 | } else { |
329 | return fp |
330 | } |
331 | } |
332 | |
333 | // fileno returns the file descriptor of an opened C file. |
334 | pub fn fileno(cfile voidptr) int { |
335 | $if windows { |
336 | return C._fileno(cfile) |
337 | } $else { |
338 | mut cfile_casted := &C.FILE(0) // FILE* cfile_casted = 0; |
339 | cfile_casted = cfile |
340 | // Required on FreeBSD/OpenBSD/NetBSD as stdio.h defines fileno(..) with a macro |
341 | // that performs a field access on its argument without casting from void*. |
342 | return C.fileno(cfile_casted) |
343 | } |
344 | } |
345 | |
346 | // vpopen system starts the specified command, waits for it to complete, and returns its code. |
347 | fn vpopen(path string) voidptr { |
348 | // *C.FILE { |
349 | $if windows { |
350 | mode := 'rb' |
351 | wpath := path.to_wide() |
352 | return C._wpopen(wpath, mode.to_wide()) |
353 | } $else { |
354 | cpath := path.str |
355 | return C.popen(&char(cpath), c'r') |
356 | } |
357 | } |
358 | |
359 | fn posix_wait4_to_exit_status(waitret int) (int, bool) { |
360 | $if windows { |
361 | return waitret, false |
362 | } $else { |
363 | mut ret := 0 |
364 | mut is_signaled := true |
365 | // (see man system, man 2 waitpid: C macro WEXITSTATUS section) |
366 | if C.WIFEXITED(waitret) { |
367 | ret = C.WEXITSTATUS(waitret) |
368 | is_signaled = false |
369 | } else if C.WIFSIGNALED(waitret) { |
370 | ret = C.WTERMSIG(waitret) |
371 | is_signaled = true |
372 | } |
373 | return ret, is_signaled |
374 | } |
375 | } |
376 | |
377 | // posix_get_error_msg return error code representation in string. |
378 | pub fn posix_get_error_msg(code int) string { |
379 | ptr_text := C.strerror(code) // voidptr? |
380 | if ptr_text == 0 { |
381 | return '' |
382 | } |
383 | return unsafe { tos3(ptr_text) } |
384 | } |
385 | |
386 | // vpclose will close a file pointer opened with `vpopen`. |
387 | fn vpclose(f voidptr) int { |
388 | $if windows { |
389 | return C._pclose(f) |
390 | } $else { |
391 | ret, _ := posix_wait4_to_exit_status(C.pclose(f)) |
392 | return ret |
393 | } |
394 | } |
395 | |
396 | // system works like `exec`, but only returns a return code. |
397 | pub fn system(cmd string) int { |
398 | // if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { |
399 | // TODO remove panic |
400 | // panic(';, &&, || and \\n are not allowed in shell commands') |
401 | // } |
402 | mut ret := 0 |
403 | $if windows { |
404 | // overcome bug in system & _wsystem (cmd) when first char is quote `"` |
405 | wcmd := if cmd.len > 1 && cmd[0] == `"` && cmd[1] != `"` { '"${cmd}"' } else { cmd } |
406 | unsafe { |
407 | ret = C._wsystem(wcmd.to_wide()) |
408 | } |
409 | } $else { |
410 | $if ios { |
411 | unsafe { |
412 | arg := [c'/bin/sh', c'-c', &u8(cmd.str), 0] |
413 | pid := 0 |
414 | ret = C.posix_spawn(&pid, c'/bin/sh', 0, 0, arg.data, 0) |
415 | status := 0 |
416 | ret = C.waitpid(pid, &status, 0) |
417 | if C.WIFEXITED(status) { |
418 | ret = C.WEXITSTATUS(status) |
419 | } |
420 | } |
421 | } $else { |
422 | unsafe { |
423 | ret = C.system(&char(cmd.str)) |
424 | } |
425 | } |
426 | } |
427 | if ret == -1 { |
428 | print_c_errno() |
429 | } |
430 | $if !windows { |
431 | pret, is_signaled := posix_wait4_to_exit_status(ret) |
432 | if is_signaled { |
433 | println('Terminated by signal ${ret:2d} (' + sigint_to_signal_name(pret) + ')') |
434 | } |
435 | ret = pret |
436 | } |
437 | return ret |
438 | } |
439 | |
440 | // exists returns true if `path` (file or directory) exists. |
441 | pub fn exists(path string) bool { |
442 | $if windows { |
443 | p := path.replace('/', '\\') |
444 | return C._waccess(p.to_wide(), f_ok) != -1 |
445 | } $else { |
446 | return C.access(&char(path.str), f_ok) != -1 |
447 | } |
448 | } |
449 | |
450 | // is_executable returns `true` if `path` is executable. |
451 | // Warning: `is_executable()` is known to cause a TOCTOU vulnerability when used incorrectly |
452 | // (for more information: https://github.com/vlang/v/blob/master/vlib/os/README.md) |
453 | pub fn is_executable(path string) bool { |
454 | $if windows { |
455 | // Note: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess?view=vs-2019 |
456 | // i.e. there is no X bit there, the modes can be: |
457 | // 00 Existence only |
458 | // 02 Write-only |
459 | // 04 Read-only |
460 | // 06 Read and write |
461 | p := real_path(path) |
462 | return exists(p) && (p.ends_with('.exe') || p.ends_with('.bat') || p.ends_with('.cmd')) |
463 | } |
464 | $if solaris { |
465 | statbuf := C.stat{} |
466 | unsafe { |
467 | if C.stat(&char(path.str), &statbuf) != 0 { |
468 | return false |
469 | } |
470 | } |
471 | return (int(statbuf.st_mode) & (s_ixusr | s_ixgrp | s_ixoth)) != 0 |
472 | } |
473 | return C.access(&char(path.str), x_ok) != -1 |
474 | } |
475 | |
476 | // is_writable returns `true` if `path` is writable. |
477 | // Warning: `is_writable()` is known to cause a TOCTOU vulnerability when used incorrectly |
478 | // (for more information: https://github.com/vlang/v/blob/master/vlib/os/README.md) |
479 | [manualfree] |
480 | pub fn is_writable(path string) bool { |
481 | $if windows { |
482 | p := path.replace('/', '\\') |
483 | wp := p.to_wide() |
484 | res := C._waccess(wp, w_ok) != -1 |
485 | unsafe { free(wp) } // &u16 |
486 | unsafe { p.free() } |
487 | return res |
488 | } $else { |
489 | return C.access(&char(path.str), w_ok) != -1 |
490 | } |
491 | } |
492 | |
493 | // is_readable returns `true` if `path` is readable. |
494 | // Warning: `is_readable()` is known to cause a TOCTOU vulnerability when used incorrectly |
495 | // (for more information: https://github.com/vlang/v/blob/master/vlib/os/README.md) |
496 | [manualfree] |
497 | pub fn is_readable(path string) bool { |
498 | $if windows { |
499 | p := path.replace('/', '\\') |
500 | wp := p.to_wide() |
501 | res := C._waccess(wp, r_ok) != -1 |
502 | unsafe { free(wp) } // &u16 |
503 | unsafe { p.free() } |
504 | return res |
505 | } $else { |
506 | return C.access(&char(path.str), r_ok) != -1 |
507 | } |
508 | } |
509 | |
510 | // rm removes file in `path`. |
511 | pub fn rm(path string) ! { |
512 | mut rc := 0 |
513 | $if windows { |
514 | rc = C._wremove(path.to_wide()) |
515 | } $else { |
516 | rc = C.remove(&char(path.str)) |
517 | } |
518 | if rc == -1 { |
519 | return error('Failed to remove "${path}": ' + posix_get_error_msg(C.errno)) |
520 | } |
521 | // C.unlink(path.cstr()) |
522 | } |
523 | |
524 | // rmdir removes a specified directory. |
525 | pub fn rmdir(path string) ! { |
526 | $if windows { |
527 | rc := C.RemoveDirectory(path.to_wide()) |
528 | if !rc { |
529 | // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya - 0 == false, is failure |
530 | return error('Failed to remove "${path}": ' + posix_get_error_msg(C.errno)) |
531 | } |
532 | } $else { |
533 | rc := C.rmdir(&char(path.str)) |
534 | if rc == -1 { |
535 | return error(posix_get_error_msg(C.errno)) |
536 | } |
537 | } |
538 | } |
539 | |
540 | // print_c_errno will print the current value of `C.errno`. |
541 | fn print_c_errno() { |
542 | e := C.errno |
543 | se := unsafe { tos_clone(&u8(C.strerror(e))) } |
544 | println('errno=${e} err=${se}') |
545 | } |
546 | |
547 | // get_raw_line returns a one-line string from stdin along with '\n' if there is any. |
548 | pub fn get_raw_line() string { |
549 | $if windows { |
550 | unsafe { |
551 | max_line_chars := 256 |
552 | buf := malloc_noscan(max_line_chars * 2) |
553 | h_input := C.GetStdHandle(C.STD_INPUT_HANDLE) |
554 | mut bytes_read := u32(0) |
555 | if is_atty(0) > 0 { |
556 | x := C.ReadConsole(h_input, buf, max_line_chars * 2, &bytes_read, 0) |
557 | if !x { |
558 | return tos(buf, 0) |
559 | } |
560 | return string_from_wide2(&u16(buf), int(bytes_read)) |
561 | } |
562 | mut offset := 0 |
563 | for { |
564 | pos := buf + offset |
565 | res := C.ReadFile(h_input, pos, 1, &u32(&bytes_read), 0) |
566 | if !res && offset == 0 { |
567 | return tos(buf, 0) |
568 | } |
569 | if !res || bytes_read == 0 { |
570 | break |
571 | } |
572 | if *pos == `\n` { |
573 | offset++ |
574 | break |
575 | } |
576 | offset++ |
577 | } |
578 | return buf.vstring_with_len(offset) |
579 | } |
580 | } $else { |
581 | max := usize(0) |
582 | buf := &char(0) |
583 | nr_chars := unsafe { C.getline(&buf, &max, C.stdin) } |
584 | str := unsafe { tos(&u8(buf), if nr_chars < 0 { 0 } else { nr_chars }) } |
585 | ret := str.clone() |
586 | unsafe { |
587 | if nr_chars > 0 && buf != 0 { |
588 | C.free(buf) |
589 | } |
590 | } |
591 | return ret |
592 | } |
593 | } |
594 | |
595 | // get_raw_stdin will get the raw input from stdin. |
596 | pub fn get_raw_stdin() []u8 { |
597 | $if windows { |
598 | unsafe { |
599 | block_bytes := 512 |
600 | mut old_size := block_bytes |
601 | mut buf := malloc_noscan(block_bytes) |
602 | h_input := C.GetStdHandle(C.STD_INPUT_HANDLE) |
603 | mut bytes_read := 0 |
604 | mut offset := 0 |
605 | for { |
606 | pos := buf + offset |
607 | res := C.ReadFile(h_input, pos, block_bytes, &u32(&bytes_read), 0) |
608 | offset += bytes_read |
609 | if !res { |
610 | break |
611 | } |
612 | new_size := offset + block_bytes + (block_bytes - bytes_read) |
613 | buf = realloc_data(buf, old_size, new_size) |
614 | old_size = new_size |
615 | } |
616 | return array{ |
617 | element_size: 1 |
618 | data: voidptr(buf) |
619 | len: offset |
620 | cap: offset |
621 | } |
622 | } |
623 | } $else { |
624 | max := usize(0) |
625 | buf := &char(0) |
626 | nr_chars := unsafe { C.getline(&buf, &max, C.stdin) } |
627 | return array{ |
628 | element_size: 1 |
629 | data: voidptr(buf) |
630 | len: if nr_chars < 0 { 0 } else { nr_chars } |
631 | cap: int(max) |
632 | } |
633 | } |
634 | } |
635 | |
636 | // read_file_array reads an array of `T` values from file `path`. |
637 | pub fn read_file_array[T](path string) []T { |
638 | a := T{} |
639 | tsize := int(sizeof(a)) |
640 | // prepare for reading, get current file size |
641 | mut fp := vfopen(path, 'rb') or { return []T{} } |
642 | C.fseek(fp, 0, C.SEEK_END) |
643 | fsize := C.ftell(fp) |
644 | C.rewind(fp) |
645 | // read the actual data from the file |
646 | len := fsize / tsize |
647 | allocate := int(fsize) |
648 | // On some systems C.ftell can return values in the 64-bit range |
649 | // that, when cast to `int`, can result in values below 0. |
650 | if i64(allocate) < fsize { |
651 | panic('${fsize} cast to int results in ${int(fsize)})') |
652 | } |
653 | buf := unsafe { |
654 | malloc_noscan(allocate) |
655 | } |
656 | nread := C.fread(buf, tsize, len, fp) |
657 | C.fclose(fp) |
658 | return unsafe { |
659 | array{ |
660 | element_size: tsize |
661 | data: buf |
662 | len: int(nread) |
663 | cap: int(len) |
664 | } |
665 | } |
666 | } |
667 | |
668 | // executable returns the path name of the executable that started the current |
669 | // process. |
670 | [manualfree] |
671 | pub fn executable() string { |
672 | mut result := [max_path_buffer_size]u8{} |
673 | $if windows { |
674 | pu16_result := unsafe { &u16(&result[0]) } |
675 | len := C.GetModuleFileName(0, pu16_result, 512) |
676 | // determine if the file is a windows symlink |
677 | attrs := C.GetFileAttributesW(pu16_result) |
678 | is_set := attrs & 0x400 // FILE_ATTRIBUTE_REPARSE_POINT |
679 | if is_set != 0 { // it's a windows symlink |
680 | // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 |
681 | file := C.CreateFile(pu16_result, 0x80000000, 1, 0, 3, 0x80, 0) |
682 | if file != voidptr(-1) { |
683 | defer { |
684 | C.CloseHandle(file) |
685 | } |
686 | final_path := [max_path_buffer_size]u8{} |
687 | // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew |
688 | final_len := C.GetFinalPathNameByHandleW(file, unsafe { &u16(&final_path[0]) }, |
689 | max_path_buffer_size, 0) |
690 | if final_len < u32(max_path_buffer_size) { |
691 | sret := unsafe { string_from_wide2(&u16(&final_path[0]), int(final_len)) } |
692 | defer { |
693 | unsafe { sret.free() } |
694 | } |
695 | // remove '\\?\' from beginning (see link above) |
696 | sret_slice := sret[4..] |
697 | res := sret_slice.clone() |
698 | return res |
699 | } else { |
700 | eprintln('os.executable() saw that the executable file path was too long') |
701 | } |
702 | } |
703 | } |
704 | res := unsafe { string_from_wide2(pu16_result, int(len)) } |
705 | return res |
706 | } |
707 | $if macos { |
708 | pid := C.getpid() |
709 | ret := proc_pidpath(pid, &result[0], max_path_len) |
710 | if ret <= 0 { |
711 | eprintln('os.executable() failed at calling proc_pidpath with pid: ${pid} . proc_pidpath returned ${ret} ') |
712 | return executable_fallback() |
713 | } |
714 | res := unsafe { tos_clone(&result[0]) } |
715 | return res |
716 | } |
717 | $if freebsd { |
718 | bufsize := usize(max_path_buffer_size) |
719 | mib := [1, // CTL_KERN |
720 | 14, // KERN_PROC |
721 | 12, // KERN_PROC_PATHNAME |
722 | -1] |
723 | unsafe { C.sysctl(mib.data, mib.len, &result[0], &bufsize, 0, 0) } |
724 | res := unsafe { tos_clone(&result[0]) } |
725 | return res |
726 | } |
727 | $if netbsd { |
728 | count := C.readlink(c'/proc/curproc/exe', &char(&result[0]), max_path_len) |
729 | if count < 0 { |
730 | eprintln('os.executable() failed at reading /proc/curproc/exe to get exe path') |
731 | return executable_fallback() |
732 | } |
733 | res := unsafe { tos_clone(&result[0]) } |
734 | return res |
735 | } |
736 | $if dragonfly { |
737 | count := C.readlink(c'/proc/curproc/file', &char(&result[0]), max_path_len) |
738 | if count < 0 { |
739 | eprintln('os.executable() failed at reading /proc/curproc/file to get exe path') |
740 | return executable_fallback() |
741 | } |
742 | res := unsafe { tos_clone(&result[0]) } |
743 | return res |
744 | } |
745 | $if linux { |
746 | count := C.readlink(c'/proc/self/exe', &char(&result[0]), max_path_len) |
747 | if count < 0 { |
748 | eprintln('os.executable() failed at reading /proc/self/exe to get exe path') |
749 | return executable_fallback() |
750 | } |
751 | res := unsafe { tos_clone(&result[0]) } |
752 | return res |
753 | } |
754 | // "Sadly there is no way to get the full path of the executed file in OpenBSD." |
755 | $if openbsd { |
756 | } |
757 | $if solaris { |
758 | } |
759 | $if haiku { |
760 | } |
761 | return executable_fallback() |
762 | } |
763 | |
764 | // is_dir returns a `bool` indicating whether the given `path` is a directory. |
765 | pub fn is_dir(path string) bool { |
766 | $if windows { |
767 | w_path := path.replace('/', '\\') |
768 | attr := C.GetFileAttributesW(w_path.to_wide()) |
769 | if attr == u32(C.INVALID_FILE_ATTRIBUTES) { |
770 | return false |
771 | } |
772 | if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 { |
773 | return true |
774 | } |
775 | return false |
776 | } $else { |
777 | statbuf := C.stat{} |
778 | if unsafe { C.stat(&char(path.str), &statbuf) } != 0 { |
779 | return false |
780 | } |
781 | // ref: https://code.woboq.org/gcc/include/sys/stat.h.html |
782 | val := int(statbuf.st_mode) & s_ifmt |
783 | return val == s_ifdir |
784 | } |
785 | } |
786 | |
787 | // is_link returns a boolean indicating whether `path` is a link. |
788 | // Warning: `is_link()` is known to cause a TOCTOU vulnerability when used incorrectly |
789 | // (for more information: https://github.com/vlang/v/blob/master/vlib/os/README.md) |
790 | pub fn is_link(path string) bool { |
791 | $if windows { |
792 | path_ := path.replace('/', '\\') |
793 | attr := C.GetFileAttributesW(path_.to_wide()) |
794 | return int(attr) != int(C.INVALID_FILE_ATTRIBUTES) && (attr & 0x400) != 0 |
795 | } $else { |
796 | statbuf := C.stat{} |
797 | if C.lstat(&char(path.str), &statbuf) != 0 { |
798 | return false |
799 | } |
800 | return int(statbuf.st_mode) & s_ifmt == s_iflnk |
801 | } |
802 | } |
803 | |
804 | struct PathKind { |
805 | mut: |
806 | is_dir bool |
807 | is_link bool |
808 | } |
809 | |
810 | fn kind_of_existing_path(path string) PathKind { |
811 | mut res := PathKind{} |
812 | $if windows { |
813 | attr := C.GetFileAttributesW(path.to_wide()) |
814 | if attr != u32(C.INVALID_FILE_ATTRIBUTES) { |
815 | if (int(attr) & C.FILE_ATTRIBUTE_DIRECTORY) != 0 { |
816 | res.is_dir = true |
817 | } |
818 | if (int(attr) & 0x400) != 0 { |
819 | res.is_link = true |
820 | } |
821 | } |
822 | } $else { |
823 | statbuf := C.stat{} |
824 | // ref: https://code.woboq.org/gcc/include/sys/stat.h.html |
825 | res_stat := unsafe { C.lstat(&char(path.str), &statbuf) } |
826 | if res_stat == 0 { |
827 | kind := (int(statbuf.st_mode) & s_ifmt) |
828 | if kind == s_ifdir { |
829 | res.is_dir = true |
830 | } |
831 | if kind == s_iflnk { |
832 | res.is_link = true |
833 | } |
834 | } |
835 | } |
836 | return res |
837 | } |
838 | |
839 | // chdir changes the current working directory to the new directory in `path`. |
840 | pub fn chdir(path string) ! { |
841 | ret := $if windows { C._wchdir(path.to_wide()) } $else { C.chdir(&char(path.str)) } |
842 | if ret == -1 { |
843 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
844 | } |
845 | } |
846 | |
847 | // getwd returns the absolute path of the current directory. |
848 | [manualfree] |
849 | pub fn getwd() string { |
850 | unsafe { |
851 | buf := [max_path_buffer_size]u8{} |
852 | $if windows { |
853 | if C._wgetcwd(&u16(&buf[0]), max_path_len) == 0 { |
854 | return '' |
855 | } |
856 | res := string_from_wide(&u16(&buf[0])) |
857 | return res |
858 | } $else { |
859 | if C.getcwd(&char(&buf[0]), max_path_len) == 0 { |
860 | return '' |
861 | } |
862 | res := tos_clone(&buf[0]) |
863 | return res |
864 | } |
865 | } |
866 | } |
867 | |
868 | // real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved. |
869 | // See http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html |
870 | // Also https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html |
871 | // and https://insanecoding.blogspot.com/2007/11/implementing-realpath-in-c.html |
872 | // Note: this particular rabbit hole is *deep* ... |
873 | [manualfree] |
874 | pub fn real_path(fpath string) string { |
875 | mut fullpath := [max_path_buffer_size]u8{} |
876 | mut res := '' |
877 | $if windows { |
878 | pu16_fullpath := unsafe { &u16(&fullpath[0]) } |
879 | // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 |
880 | // use C.CreateFile(fpath.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0) instead of get_file_handle |
881 | // try to open the file to get symbolic link path |
882 | fpath_wide := fpath.to_wide() |
883 | defer { |
884 | unsafe { free(voidptr(fpath_wide)) } |
885 | } |
886 | file := C.CreateFile(fpath_wide, 0x80000000, 1, 0, 3, 0x80, 0) |
887 | if file != voidptr(-1) { |
888 | defer { |
889 | C.CloseHandle(file) |
890 | } |
891 | // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew |
892 | final_len := C.GetFinalPathNameByHandleW(file, pu16_fullpath, max_path_buffer_size, |
893 | 0) |
894 | if final_len < u32(max_path_buffer_size) { |
895 | rt := unsafe { string_from_wide2(pu16_fullpath, int(final_len)) } |
896 | srt := rt[4..] |
897 | unsafe { res.free() } |
898 | res = srt.clone() |
899 | } else { |
900 | eprintln('os.real_path() saw that the file path was too long') |
901 | unsafe { res.free() } |
902 | return fpath.clone() |
903 | } |
904 | } else { |
905 | // if it is not a file C.CreateFile doesn't gets a file handle, use GetFullPath instead |
906 | ret := C.GetFullPathName(fpath_wide, max_path_len, pu16_fullpath, 0) |
907 | if ret == 0 { |
908 | // TODO: check errors if path len is not enough |
909 | unsafe { res.free() } |
910 | return fpath.clone() |
911 | } |
912 | unsafe { res.free() } |
913 | res = unsafe { string_from_wide(pu16_fullpath) } |
914 | } |
915 | } $else { |
916 | ret := &char(C.realpath(&char(fpath.str), &char(&fullpath[0]))) |
917 | if ret == 0 { |
918 | unsafe { res.free() } |
919 | return fpath.clone() |
920 | } |
921 | // Note: fullpath is much larger (usually ~4KB), than what C.realpath will |
922 | // actually fill in the vast majority of the cases => it pays to copy the |
923 | // resulting string from that buffer, to a shorter one, and then free the |
924 | // 4KB fullpath buffer. |
925 | unsafe { res.free() } |
926 | res = unsafe { tos_clone(&fullpath[0]) } |
927 | } |
928 | unsafe { normalize_drive_letter(res) } |
929 | return res |
930 | } |
931 | |
932 | [direct_array_access; manualfree; unsafe] |
933 | fn normalize_drive_letter(path string) { |
934 | $if !windows { |
935 | return |
936 | } |
937 | // normalize_drive_letter is needed, because |
938 | // a path like c:\nv\.bin (note the small `c`) in %PATH, |
939 | // is NOT recognized by cmd.exe (and probably other programs too)... |
940 | // Capital drive letters do work fine. |
941 | if path.len > 2 && path[0] >= `a` && path[0] <= `z` && path[1] == `:` |
942 | && path[2] == path_separator[0] { |
943 | unsafe { |
944 | x := &path.str[0] |
945 | (*x) = *x - 32 |
946 | } |
947 | } |
948 | } |
949 | |
950 | // fork will fork the current system process and return the pid of the fork. |
951 | pub fn fork() int { |
952 | mut pid := -1 |
953 | $if !windows { |
954 | pid = C.fork() |
955 | } |
956 | $if windows { |
957 | panic('os.fork not supported in windows') // TODO |
958 | } |
959 | return pid |
960 | } |
961 | |
962 | // wait blocks the calling process until one of its child processes exits or a signal is received. |
963 | // After child process terminates, parent continues its execution after wait system call instruction. |
964 | pub fn wait() int { |
965 | mut pid := -1 |
966 | $if !windows { |
967 | $if !emscripten ? { |
968 | pid = C.wait(0) |
969 | } |
970 | } |
971 | $if windows { |
972 | panic('os.wait not supported in windows') // TODO |
973 | } |
974 | return pid |
975 | } |
976 | |
977 | // file_last_mod_unix returns the "last modified" time stamp of file in `path`. |
978 | pub fn file_last_mod_unix(path string) i64 { |
979 | attr := C.stat{} |
980 | // # struct stat attr; |
981 | unsafe { C.stat(&char(path.str), &attr) } |
982 | // # stat(path.str, &attr); |
983 | return i64(attr.st_mtime) |
984 | // # return attr.st_mtime ; |
985 | } |
986 | |
987 | // flush will flush the stdout buffer. |
988 | pub fn flush() { |
989 | C.fflush(C.stdout) |
990 | } |
991 | |
992 | // chmod change file access attributes of `path` to `mode`. |
993 | // Octals like `0o600` can be used. |
994 | pub fn chmod(path string, mode int) ! { |
995 | if C.chmod(&char(path.str), mode) != 0 { |
996 | return error_with_code('chmod failed: ' + posix_get_error_msg(C.errno), C.errno) |
997 | } |
998 | } |
999 | |
1000 | // chown changes the owner and group attributes of `path` to `owner` and `group`. |
1001 | pub fn chown(path string, owner int, group int) ! { |
1002 | $if windows { |
1003 | return error('os.chown() not implemented for Windows') |
1004 | } $else { |
1005 | if C.chown(&char(path.str), owner, group) != 0 { |
1006 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
1007 | } |
1008 | } |
1009 | } |
1010 | |
1011 | // open_append tries to open a file from a given path. |
1012 | // If successfull, it and returns a `File` for appending. |
1013 | pub fn open_append(path string) !File { |
1014 | mut file := File{} |
1015 | $if windows { |
1016 | wpath := path.replace('/', '\\').to_wide() |
1017 | mode := 'ab' |
1018 | file = File{ |
1019 | cfile: C._wfopen(wpath, mode.to_wide()) |
1020 | } |
1021 | } $else { |
1022 | cpath := path.str |
1023 | file = File{ |
1024 | cfile: C.fopen(&char(cpath), c'ab') |
1025 | } |
1026 | } |
1027 | if isnil(file.cfile) { |
1028 | return error('failed to create(append) file "${path}"') |
1029 | } |
1030 | file.is_opened = true |
1031 | return file |
1032 | } |
1033 | |
1034 | // execvp - loads and executes a new child process, *in place* of the current process. |
1035 | // The child process executable is located in `cmdpath`. |
1036 | // The arguments, that will be passed to it are in `args`. |
1037 | // Note: this function will NOT return when successfull, since |
1038 | // the child process will take control over execution. |
1039 | pub fn execvp(cmdpath string, cmdargs []string) ! { |
1040 | mut cargs := []&char{} |
1041 | cargs << &char(cmdpath.str) |
1042 | for i in 0 .. cmdargs.len { |
1043 | cargs << &char(cmdargs[i].str) |
1044 | } |
1045 | cargs << &char(0) |
1046 | mut res := int(0) |
1047 | $if windows { |
1048 | res = C._execvp(&char(cmdpath.str), cargs.data) |
1049 | } $else { |
1050 | res = C.execvp(&char(cmdpath.str), cargs.data) |
1051 | } |
1052 | if res == -1 { |
1053 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
1054 | } |
1055 | |
1056 | // just in case C._execvp returned ... that happens on windows ... |
1057 | exit(res) |
1058 | } |
1059 | |
1060 | // execve - loads and executes a new child process, *in place* of the current process. |
1061 | // The child process executable is located in `cmdpath`. |
1062 | // The arguments, that will be passed to it are in `args`. |
1063 | // You can pass environment variables to through `envs`. |
1064 | // Note: this function will NOT return when successfull, since |
1065 | // the child process will take control over execution. |
1066 | pub fn execve(cmdpath string, cmdargs []string, envs []string) ! { |
1067 | mut cargv := []&char{} |
1068 | mut cenvs := []&char{} |
1069 | cargv << &char(cmdpath.str) |
1070 | for i in 0 .. cmdargs.len { |
1071 | cargv << &char(cmdargs[i].str) |
1072 | } |
1073 | for i in 0 .. envs.len { |
1074 | cenvs << &char(envs[i].str) |
1075 | } |
1076 | cargv << &char(0) |
1077 | cenvs << &char(0) |
1078 | mut res := int(0) |
1079 | $if windows { |
1080 | res = C._execve(&char(cmdpath.str), cargv.data, cenvs.data) |
1081 | } $else { |
1082 | res = C.execve(&char(cmdpath.str), cargv.data, cenvs.data) |
1083 | } |
1084 | // Note: normally execve does not return at all. |
1085 | // If it returns, then something went wrong... |
1086 | if res == -1 { |
1087 | return error_with_code(posix_get_error_msg(C.errno), C.errno) |
1088 | } |
1089 | } |
1090 | |
1091 | // is_atty returns 1 if the `fd` file descriptor is open and refers to a terminal |
1092 | pub fn is_atty(fd int) int { |
1093 | $if windows { |
1094 | mut mode := u32(0) |
1095 | osfh := voidptr(C._get_osfhandle(fd)) |
1096 | C.GetConsoleMode(osfh, voidptr(&mode)) |
1097 | return int(mode) |
1098 | } $else { |
1099 | return C.isatty(fd) |
1100 | } |
1101 | } |
1102 | |
1103 | // write_file_array writes the data in `buffer` to a file in `path`. |
1104 | pub fn write_file_array(path string, buffer array) ! { |
1105 | mut f := create(path)! |
1106 | unsafe { f.write_full_buffer(buffer.data, usize(buffer.len * buffer.element_size))! } |
1107 | f.close() |
1108 | } |
1109 | |
1110 | pub fn glob(patterns ...string) ![]string { |
1111 | mut matches := []string{} |
1112 | for pattern in patterns { |
1113 | native_glob_pattern(pattern, mut matches)! |
1114 | } |
1115 | matches.sort() |
1116 | return matches |
1117 | } |
1118 | |
1119 | pub fn last_error() IError { |
1120 | $if windows { |
1121 | code := int(C.GetLastError()) |
1122 | msg := get_error_msg(code) |
1123 | return error_with_code(msg, code) |
1124 | } $else { |
1125 | code := C.errno |
1126 | msg := posix_get_error_msg(code) |
1127 | return error_with_code(msg, code) |
1128 | } |
1129 | } |