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