v / vlib / os
Raw file | 1129 loc (1059 sloc) | 29.6 KB | Latest commit hash 3aeb6179b
1module os
2
3import 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
12pub const (
13 args = []string{}
14)
15
16fn C.readdir(voidptr) &C.dirent
17
18fn C.readlink(pathname &char, buf &char, bufsiz usize) int
19
20fn C.getline(voidptr, voidptr, voidptr) int
21
22fn C.sigaction(int, voidptr, int) int
23
24fn C.open(&char, int, ...int) int
25
26fn C._wopen(&u16, int, ...int) int
27
28fn C.fdopen(fd int, mode &char) &C.FILE
29
30fn C.ferror(stream &C.FILE) int
31
32fn C.feof(stream &C.FILE) int
33
34fn C.CopyFile(&u16, &u16, bool) int
35
36// fn C.lstat(charptr, voidptr) u64
37
38fn C._wstat64(&u16, voidptr) u64
39
40fn C.chown(&char, int, int) int
41
42fn C.ftruncate(voidptr, u64) int
43
44fn C._chsize_s(voidptr, u64) int
45
46// read_bytes returns all bytes read from file in `path`.
47[manualfree]
48pub 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
67fn 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
86const 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]
93fn 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]
111pub 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.
153pub 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
176fn 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.
183pub 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.
226pub 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.
244pub 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`.
265pub 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.
316pub 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.
334pub 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.
347fn 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
359fn 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.
378pub 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`.
387fn 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.
397pub 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.
441pub 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)
453pub 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]
480pub 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]
497pub 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`.
511pub 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.
525pub 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`.
541fn 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.
548pub 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.
596pub 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`.
637pub 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]
671pub 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.
765pub 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)
790pub 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
804struct PathKind {
805mut:
806 is_dir bool
807 is_link bool
808}
809
810fn 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`.
840pub 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]
849pub 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]
874pub 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]
933fn 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.
951pub 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.
964pub 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`.
978pub 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.
988pub 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.
994pub 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`.
1001pub 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.
1013pub 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.
1039pub 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.
1066pub 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
1092pub 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`.
1104pub 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
1110pub 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
1119pub 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}