module os #include #include #include #include #include #include #include #insert "@VEXEROOT/vlib/os/execute_capture_nix.h" // short_path is a Windows-only helper that returns the DOS 8.3 short path. // On non-Windows platforms it simply returns the given path unchanged, // so that callers guarded by `$if windows { ... }` type-check on other targets. pub fn short_path(path string) string { return path } // path_separator is the platform specific separator string, used between the folders and filenames in a path. It is '/' on POSIX, and '\\' on Windows. pub const path_separator = '/' // path_delimiter is the platform specific delimiter string, used between the paths in environment variables like PATH. It is ':' on POSIX, and ';' on Windows. pub const path_delimiter = ':' // path_devnull is a platform-specific file path of the null device. // It is '/dev/null' on POSIX, and r'\\.\nul' on Windows. pub const path_devnull = '/dev/null' const executable_suffixes = [''] const stdin_value = 0 const stdout_value = 1 const stderr_value = 2 // (Must be realized in Syscall) (Must be specified) // ref: http://www.ccfit.nsu.ru/~deviv/courses/unix/unix/ng7c229.html pub const s_ifmt = 0xF000 // type of file pub const s_ifdir = 0x4000 // directory pub const s_ifreg = 0x8000 // regular file pub const s_iflnk = 0xa000 // link pub const s_isuid = 0o4000 // SUID pub const s_isgid = 0o2000 // SGID pub const s_isvtx = 0o1000 // Sticky pub const s_irusr = 0o0400 // Read by owner pub const s_iwusr = 0o0200 // Write by owner pub const s_ixusr = 0o0100 // Execute by owner pub const s_irgrp = 0o0040 // Read by group pub const s_iwgrp = 0o0020 // Write by group pub const s_ixgrp = 0o0010 // Execute by group pub const s_iroth = 0o0004 // Read by others pub const s_iwoth = 0o0002 // Write by others pub const s_ixoth = 0o0001 fn C.utime(&char, &C.utimbuf) i32 fn C.uname(name &C.utsname) i32 fn C.symlink(&char, &char) i32 fn C.readlink(&char, &char, i32) i32 fn C.link(&char, &char) i32 fn C.gethostname(&char, i32) i32 // Note: not available on Android fn C.getlogin_r(&char, int) int fn C.getlogin() &char fn C.getppid() i32 fn C.getgid() i32 fn C.getegid() i32 fn C.v_os_execute_capture_start(cmd &char, child_pid &int, read_fd &int) int fn C.v_os_exec_capture_start(argv &&char, child_pid &int, read_fd &int) int enum GlobMatch { exact ends_with starts_with start_and_ends_with contains any } fn glob_match(dir string, pattern string, next_pattern string, mut matches []string) []string { mut subdirs := []string{} if is_file(dir) { return subdirs } mut files := ls(dir) or { return subdirs } mut mode := GlobMatch.exact mut pat := pattern if pat == '*' { mode = GlobMatch.any if next_pattern != pattern && next_pattern != '' { for file in files { if is_dir('${dir}/${file}') { subdirs << '${dir}/${file}' } } return subdirs } } if pat == '**' { files = walk_ext(dir, '') pat = next_pattern } if pat.starts_with('*') { mode = .ends_with pat = pat[1..] } if pat.ends_with('*') { mode = if mode == .ends_with { GlobMatch.contains } else { GlobMatch.starts_with } pat = pat[..pat.len - 1] } if pat.contains('*') { mode = .start_and_ends_with } for file in files { mut fpath := file f := if file.contains(path_separator) { pathwalk := file.split(path_separator) pathwalk[pathwalk.len - 1] } else { fpath = if dir == '.' { file } else { '${dir}/${file}' } file } if f in ['.', '..'] || f == '' { continue } hit := match mode { .any { true } .exact { f == pat } .starts_with { f.starts_with(pat) } .ends_with { f.ends_with(pat) } .start_and_ends_with { p := pat.split('*') f.starts_with(p[0]) && f.ends_with(p[1]) } .contains { f.contains(pat) } } if hit { if is_dir(fpath) { subdirs << fpath if next_pattern == pattern && next_pattern != '' { matches << '${fpath}${path_separator}' } } else { matches << fpath } } } return subdirs } fn native_glob_pattern(pattern string, mut matches []string) ! { steps := pattern.split(path_separator) cwd := if pattern.starts_with(path_separator) { path_separator } else { '.' } mut subdirs := [cwd] for i := 0; i < steps.len; i++ { step := steps[i] step2 := if i + 1 == steps.len { step } else { steps[i + 1] } if step == '' { continue } if is_dir('${cwd}${path_separator}${step}') { dd := if cwd == '/' { step } else { if cwd == '.' || cwd == '' { step } else { if step == '.' || step == '/' { cwd } else { '${cwd}/${step}' } } } if i + 1 != steps.len { if dd !in subdirs { subdirs << dd } } } mut subs := []string{} for sd in subdirs { d := if cwd == '/' { sd } else { if cwd == '.' || cwd == '' { sd } else { if sd == '.' || sd == '/' { cwd } else { '${cwd}/${sd}' } } } subs << glob_match(d.replace('//', '/'), step, step2, mut matches) } subdirs = subs.clone() } } // utime changes the access and modification times of the inode specified by path. // It returns POSIX error message, if it can not do so. pub fn utime(path string, actime i64, modtime i64) ! { u := C.utimbuf{actime, modtime} if C.utime(&char(path.str), &u) != 0 { return error_with_code(posix_get_error_msg(C.errno), C.errno) } } // uname returns information about the platform on which the program is running. // For example: // os.Uname{ // sysname: 'Linux' // nodename: 'nemesis' // release: '5.15.0-57-generic' // version: '#63~20.04.1-Ubuntu SMP Wed Nov 30 13:40:16 UTC 2022' // machine: 'x86_64' // } // where the fields have the following meaning: // sysname is the name of this implementation of the operating system // nodename is the name of this node within an implementation-dependent communications network // release is the current release level of this implementation // version is the current version level of this release // machine is the name of the hardware type, on which the system is running // See also https://pubs.opengroup.org/onlinepubs/7908799/xsh/sysutsname.h.html pub fn uname() Uname { mut u := Uname{} unsafe { d := &C.utsname(malloc_noscan(int(sizeof(C.utsname)))) if C.uname(d) == 0 { u.sysname = cstring_to_vstring(d.sysname) u.nodename = cstring_to_vstring(d.nodename) u.release = cstring_to_vstring(d.release) u.version = cstring_to_vstring(d.version) u.machine = cstring_to_vstring(d.machine) } free(d) } return u } // hostname returns the hostname (system's DNS name) or POSIX error message if the hostname call fails. pub fn hostname() !string { mut hstnme := '' size := 256 buf := unsafe { &char(malloc_noscan(size)) } if C.gethostname(buf, size) == 0 { hstnme = unsafe { cstring_to_vstring(buf) } unsafe { free(buf) } return hstnme } return error(posix_get_error_msg(C.errno)) } // loginname returns the name of the user logged in on the controlling terminal of the process. // It returns a POSIX error message, if the getlogin call fails. pub fn loginname() !string { x := C.getlogin() if !isnil(x) { return unsafe { cstring_to_vstring(x) } } return error(posix_get_error_msg(C.errno)) } // ls returns ![]string of the files and dirs in the given `path` ( os.ls uses C.readdir ). Symbolic links are returned to be files. For recursive list see os.walk functions. // See also: `os.walk`, `os.walk_ext`, `os.is_dir`, `os.is_file` // Example: // ``` // entries := os.ls(os.home_dir()) or { [] } // for entry in entries { // if os.is_dir(os.join_path(os.home_dir(), entry)) { // println('dir: ${entry}') // } else { // println('file: ${entry}') // } // } // ``` @[manualfree] pub fn ls(path string) ![]string { if path == '' { return error('ls() expects a folder, not an empty string') } mut res := []string{cap: 50} dir := unsafe { C.opendir(&char(path.str)) } if isnil(dir) { return error_posix(msg: 'ls() couldnt open dir "${path}"') } mut ent := &C.dirent(unsafe { nil }) for { ent = C.readdir(dir) if isnil(ent) { break } unsafe { bptr := &u8(&ent.d_name[0]) // vfmt off if bptr[0] == 0 || (bptr[0] == `.` && bptr[1] == 0) || (bptr[0] == `.` && bptr[1] == `.` && bptr[2] == 0) { continue } res << tos_clone(bptr) // vfmt on } } C.closedir(dir) return res } // mkdir creates a new directory with the specified path. pub fn mkdir(path string, params MkdirParams) ! { if path == '.' { return } apath := real_path(path) r := unsafe { C.mkdir(&char(apath.str), params.mode) } if r == -1 { return error(posix_get_error_msg(C.errno)) } } // execute starts the specified command, waits for it to complete, and returns its output. pub fn execute(cmd string) Result { mut pid := 0 mut read_fd := -1 v_os_execute_lock() rc := C.v_os_execute_capture_start(&char(cmd.str), &pid, &read_fd) v_os_execute_unlock() if rc != 0 { return Result{ exit_code: -1 output: 'exec("${cmd}") failed' } } soutput := fd_slurp(read_fd).join('') fd_close(read_fd) mut status := 0 for { C.errno = 0 if C.waitpid(pid, &status, 0) != -1 { break } if C.errno == C.EINTR { continue } return Result{ exit_code: -1 output: soutput } } exit_code, _ := posix_wait4_to_exit_status(status) return Result{ exit_code: exit_code output: soutput } } // exec starts the specified command with arguments, waits for it to complete, and returns its output. pub fn exec(args []string) Result { if args.len == 0 { return Result{ exit_code: -1 output: 'exec requires at least one argument' } } mut cargs := []&char{cap: args.len + 1} for arg in args { cargs << &char(arg.str) } cargs << &char(unsafe { nil }) mut pid := 0 mut read_fd := -1 v_os_execute_lock() rc := C.v_os_exec_capture_start(cargs.data, &pid, &read_fd) v_os_execute_unlock() if rc != 0 { return Result{ exit_code: -1 output: 'exec("${args[0]}") failed' } } soutput := fd_slurp(read_fd).join('') fd_close(read_fd) mut status := 0 for { C.errno = 0 if C.waitpid(pid, &status, 0) != -1 { break } if C.errno == C.EINTR { continue } return Result{ exit_code: -1 output: soutput } } exit_code, _ := posix_wait4_to_exit_status(status) return Result{ exit_code: exit_code output: soutput } } // raw_execute does the same as `execute` on Unix platforms. // On Windows raw_execute starts the specified command, waits for it to complete, and returns its output. // It's marked as `unsafe` to help emphasize the problems that may arise by allowing, for example, // user provided escape sequences. @[unsafe] pub fn raw_execute(cmd string) Result { return execute(cmd) } // symlink creates a symbolic link named link_name, which points to target. // It returns a POSIX error message, if it can not do so. pub fn symlink(target string, link_name string) ! { res := C.symlink(&char(target.str), &char(link_name.str)) if res == 0 { return } return error(posix_get_error_msg(C.errno)) } // readlink reads the target of a symbolic link. // It returns a POSIX error message if it can not do so. // // Note that the target of a symbolic link can be any string: // it is often used to point to another path, but the target is not guaranteed // to resolve as a path, nor to point to a path that exists. @[manualfree] pub fn readlink(path string) !string { // Use a region of stack to get information into; we'll return new memory of more precise size later. mut buf := [max_path_buffer_size]u8{} // readlink returns the number of bytes written into buf, or -1 for errors. res := C.readlink(&char(path.str), &char(&buf[0]), max_path_buffer_size) if res < 0 { return last_error() } // Common case: we got a complete read into our buffer on the stack. // In this case, copy the data into a new heap-allocated string that's right-sized // (we can't return memory from our stack). if res < max_path_buffer_size { return unsafe { (&buf[0]).vstring_with_len(res).clone() } } // If the number of bytes read wasn't less than as many as we said we'd accept: that means we might not have gotten a complete read. // In this case, we have to start doing heap allocations, increasingly large, and simply check until we get a complete one. // Whenever we do succeed: we'll return a string that refers to a subset of that possibly excessively sized buffer, // because we're already on the heap and returning it is valid; and because allocating a new buffer just // to save some resident memory is usually a poor trade of spending of time just to reclaim a very minor amount of space. mut size := max_path_buffer_size for { size *= 2 mut buf2 := unsafe { &char(malloc_noscan(size)) } res2 := C.readlink(&char(path.str), buf2, size) if res2 < 0 { return last_error() } if res2 < size { unsafe { buf2[res2] = 0 return cstring_to_vstring(buf2) } } unsafe { free(buf2) } // and then loop around to try again with a larger one. } return error('${@METHOD} unreachable code') } // link creates a new link (also known as a hard link) to an existing file. // It returns a POSIX error message, if it can not do so. pub fn link(origin string, target string) ! { res := C.link(&char(origin.str), &char(target.str)) if res == 0 { return } return error(posix_get_error_msg(C.errno)) } // get_error_msg return error code representation in string. pub fn get_error_msg(code int) string { return posix_get_error_msg(code) } pub fn (mut f File) close() { if !f.is_opened { return } f.is_opened = false cfile := f.cfile f.cfile = unsafe { nil } C.fflush(cfile) C.fclose(cfile) } fn C.mkstemp(stemplate &u8) i32 // ensure_folder_is_writable checks that `folder` exists, and is writable to the process // by creating an empty file in it, then deleting it. @[manualfree] pub fn ensure_folder_is_writable(folder string) ! { if !exists(folder) { return error_with_code('`${folder}` does not exist', 1) } if !is_dir(folder) { return error_with_code('`${folder}` is not a folder', 2) } tmp_perm_check := join_path_single(folder, 'XXXXXX') defer { unsafe { tmp_perm_check.free() } } unsafe { x := C.mkstemp(&char(tmp_perm_check.str)) if -1 == x { return error_with_code('folder `${folder}` is not writable', 3) } C.close(x) } rm(tmp_perm_check)! } // getpid returns the process ID (PID) of the calling process. @[inline] pub fn getpid() int { return C.getpid() } // getppid returns the process ID of the parent of the calling process. @[inline] pub fn getppid() int { return C.getppid() } // getuid returns the real user ID of the calling process. @[inline] pub fn getuid() int { return C.getuid() } // geteuid returns the effective user ID of the calling process. @[inline] pub fn geteuid() int { return C.geteuid() } // getgid returns the real group ID of the calling process. @[inline] pub fn getgid() int { return C.getgid() } // getegid returns the effective group ID of the calling process. @[inline] pub fn getegid() int { return C.getegid() } // Turns the given bit on or off, depending on the `enable` parameter. pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) { mut new_mode := u32(0) if s := stat(path_s) { new_mode = s.mode } match enable { true { new_mode |= mode } false { new_mode &= (0o7777 - mode) } } C.chmod(&char(path_s.str), int(new_mode)) } // get_long_path has no meaning for *nix, but has for windows, where `c:\folder\some~1` for example // can be the equivalent of `c:\folder\some spa ces`. On *nix, it just returns a copy of the input path. fn get_long_path(path string) !string { return path } fn C.sysconf(name i32) i64 // page_size returns the page size in bytes. pub fn page_size() int { return int(C.sysconf(C._SC_PAGESIZE)) } struct C.statvfs { f_bsize usize f_blocks usize f_bfree usize f_bavail usize } // disk_usage returns disk usage of `path`. @[manualfree] pub fn disk_usage(path string) !DiskUsage { mpath := if path == '' { '.' } else { path } defer { unsafe { mpath.free() } } mut vfs := C.statvfs{} ret := unsafe { C.statvfs(&char(mpath.str), &vfs) } if ret == -1 { return error('cannot get disk usage of path') } f_bsize := u64(vfs.f_bsize) f_blocks := u64(vfs.f_blocks) f_bavail := u64(vfs.f_bavail) f_bfree := u64(vfs.f_bfree) return DiskUsage{ total: f_bsize * f_blocks available: f_bsize * f_bavail used: f_bsize * (f_blocks - f_bfree) } }