From a4d459f6517d23e6dc2129112e99a14ef158a45e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 21:31:30 +0200 Subject: [PATCH] os2: initial implementation for Darwin&BSDs, process API is only thing incomplete --- core/os/os2/allocators.odin | 1 - core/os/os2/dir_posix.odin | 71 ++++ core/os/os2/env_posix.odin | 78 +++++ core/os/os2/errors_posix.odin | 30 ++ core/os/os2/file_posix.odin | 446 ++++++++++++++++++++++++++ core/os/os2/heap_posix.odin | 97 ++++++ core/os/os2/internal_util.odin | 2 +- core/os/os2/path_posix.odin | 127 ++++++++ core/os/os2/pipe_posix.odin | 37 +++ core/os/os2/process.odin | 22 +- core/os/os2/process_posix.odin | 69 ++++ core/os/os2/process_posix_darwin.odin | 251 +++++++++++++++ core/os/os2/process_posix_other.odin | 15 + core/os/os2/stat.odin | 4 +- core/os/os2/stat_linux.odin | 2 + core/os/os2/stat_posix.odin | 113 +++++++ core/os/os2/stat_windows.odin | 8 + core/os/os2/temp_file.odin | 2 +- core/os/os2/temp_file_posix.odin | 20 ++ core/sys/darwin/proc.odin | 142 ++++++++ core/sys/posix/unistd.odin | 9 +- core/sys/unix/sysctl_darwin.odin | 4 +- core/sys/unix/sysctl_freebsd.odin | 2 + 23 files changed, 1535 insertions(+), 17 deletions(-) create mode 100644 core/os/os2/dir_posix.odin create mode 100644 core/os/os2/env_posix.odin create mode 100644 core/os/os2/errors_posix.odin create mode 100644 core/os/os2/file_posix.odin create mode 100644 core/os/os2/heap_posix.odin create mode 100644 core/os/os2/path_posix.odin create mode 100644 core/os/os2/pipe_posix.odin create mode 100644 core/os/os2/process_posix.odin create mode 100644 core/os/os2/process_posix_darwin.odin create mode 100644 core/os/os2/process_posix_other.odin create mode 100644 core/os/os2/stat_posix.odin create mode 100644 core/os/os2/temp_file_posix.odin create mode 100644 core/sys/darwin/proc.odin diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index ef73809b1..0ab3adfb2 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -61,4 +61,3 @@ TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime. global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT return tmp, loc } - diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin new file mode 100644 index 000000000..69cf7930f --- /dev/null +++ b/core/os/os2/dir_posix.odin @@ -0,0 +1,71 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:sys/posix" + +@(private) +_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + n := n + if n == 0 { + return + } + + impl := (^File_Impl)(f) + + dir := posix.fdopendir(impl.fd) + if dir == nil { + err = _get_platform_error() + return + } + defer posix.closedir(dir) + + dfiles: [dynamic]File_Info + dfiles.allocator = allocator + defer if err != nil { + file_info_slice_delete(dfiles[:], allocator) + } + + for { + posix.set_errno(.NONE) + entry := posix.readdir(dir) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + err = _get_platform_error() + return + } else { + break + } + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + + stat: posix.stat_t + if posix.fstatat(posix.dirfd(dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK { + err = _get_platform_error() + return + } + + fullpath := concatenate({impl.name, "/", string(cname)}, allocator) or_return + fi := internal_stat(stat, fullpath) + append(&dfiles, fi) or_return + + n -= 1 + if n == 0 { + break + } + } + + files = dfiles[:] + return +} diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin new file mode 100644 index 000000000..d7dc6d55a --- /dev/null +++ b/core/os/os2/env_posix.odin @@ -0,0 +1,78 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if key == "" { + return + } + + assert(!is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + cval := posix.getenv(ckey) + if cval == nil { + return + } + + found = true + value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? + + return +} + +_set_env :: proc(key, value: string) -> (ok: bool) { + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + cval := strings.clone_to_cstring(key, temp_allocator()) + + ok = posix.setenv(ckey, cval, true) == .OK + return +} + +_unset_env :: proc(key: string) -> (ok: bool) { + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + + ok = posix.unsetenv(ckey) == .OK + return +} + +// NOTE(laytan): clearing the env is weird, why would you ever do that? + +_clear_env :: proc() { + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + key := strings.truncate_to_byte(string(entry), '=') + _unset_env(key) + } +} + +_environ :: proc(allocator: runtime.Allocator) -> (environ: []string) { + n := 0 + for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} + + err: runtime.Allocator_Error + if environ, err = make([]string, n, allocator); err != nil { + // NOTE(laytan): is the environment empty or did allocation fail, how does the user know? + return + } + + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + if environ[i], err = strings.clone(string(entry), allocator); err != nil { + // NOTE(laytan): is the entire environment returned or did allocation fail, how does the user know? + return + } + } + + return +} + + diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin new file mode 100644 index 000000000..7143557a6 --- /dev/null +++ b/core/os/os2/errors_posix.odin @@ -0,0 +1,30 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" + +_error_string :: proc(errno: i32) -> string { + return string(posix.strerror(posix.Errno(errno))) +} + +_get_platform_error :: proc() -> Error { + #partial switch errno := posix.errno(); errno { + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory + case: + return Platform_Error(errno) + } +} diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin new file mode 100644 index 000000000..b77824843 --- /dev/null +++ b/core/os/os2/file_posix.odin @@ -0,0 +1,446 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:io" +import "core:c" +import "core:time" +import "core:sys/posix" + +// Most implementations will EINVAL at some point when doing big writes. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + cname: cstring, + fd: posix.FD, +} + +@(init) +init_std_files :: proc() { + // NOTE: is this (paths) also the case on non darwin? + stdin = _new_file(posix.STDIN_FILENO, "/dev/stdin") + stdout = _new_file(posix.STDOUT_FILENO, "/dev/stdout") + stderr = _new_file(posix.STDERR_FILENO, "/dev/stdout") +} + +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + sys_flags := posix.O_Flags{.NOCTTY, .CLOEXEC} + + if .Write in flags { + if .Read in flags { + sys_flags += {.RDWR} + } else { + sys_flags += {.WRONLY} + } + } + + if .Append in flags { sys_flags += {.APPEND} } + if .Create in flags { sys_flags += {.CREAT} } + if .Excl in flags { sys_flags += {.EXCL} } + if .Sync in flags { sys_flags += {.DSYNC} } + if .Trunc in flags { sys_flags += {.TRUNC} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) + if fd < 0 { + err = _get_platform_error() + return + } + + return _new_file(uintptr(fd), name), nil +} + +_new_file :: proc(handle: uintptr, name: string) -> ^File { + if name == "" || handle == ~uintptr(0) { + return nil + } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + crname := posix.realpath(cname, nil) + assert(crname != nil) + rname := string(crname) + + f := __new_file(posix.FD(handle)) + impl := (^File_Impl)(f.impl) + impl.name = rname + impl.cname = crname + + return f +} + +__new_file :: proc(handle: posix.FD) -> ^File { + impl := new(File_Impl, file_allocator()) + impl.file.impl = impl + impl.fd = posix.FD(handle) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if f == nil { return nil } + + if posix.close(f.fd) != .OK { + err = _get_platform_error() + } + + delete(f.cname, file_allocator()) + posix.free(f.cname) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> posix.FD { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + return -1 +} + +_name :: proc(f: ^File) -> string { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).name + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + if posix.fsync(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + if posix.ftruncate(__fd(f), posix.off_t(size)) != .OK { + return _get_platform_error() + } + return nil +} + +_remove :: proc(name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.remove(cname) != 0 { + return _get_platform_error() + } + return nil +} + +_rename :: proc(old_path, new_path: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_path) + cnew := temp_cstring(new_path) + if posix.rename(cold, cnew) != 0 { + return _get_platform_error() + } + return nil +} + +_link :: proc(old_name, new_name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_name) + cnew := temp_cstring(new_name) + if posix.link(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_symlink :: proc(old_name, new_name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_name) + cnew := temp_cstring(new_name) + if posix.symlink(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + assert(!is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + buf: [dynamic]byte + buf.allocator = allocator + defer if err != nil { delete(buf) } + + // Loop this because the file might've grown between lstat() and readlink(). + for { + stat: posix.stat_t + if posix.lstat(cname, &stat) != .OK { + err = _get_platform_error() + return + } + + bufsiz := int(stat.st_size + 1 if stat.st_size > 0 else posix.PATH_MAX) + + if bufsiz == len(buf) { + bufsiz *= 2 + } + + // Overflow. + if bufsiz <= 0 { + err = Platform_Error(posix.Errno.E2BIG) + return + } + + resize(&buf, bufsiz) or_return + + size := posix.readlink(cname, raw_data(buf), uint(bufsiz)) + if size < 0 { + err = _get_platform_error() + return + } + + // File has probably grown between lstat() and readlink(). + if size == bufsiz { + continue + } + + s = string(buf[:size]) + return + } +} + +_chdir :: proc(name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chdir(cname) != .OK { + return _get_platform_error() + } + return nil +} + +_fchdir :: proc(f: ^File) -> Error { + if posix.fchdir(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchmod :: proc(f: ^File, mode: int) -> Error { + if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_chmod :: proc(name: string, mode: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + if posix.fchown(__fd(f), posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + times := [2]posix.timeval{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(atime._nsec%1e9/1000), /* microseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(mtime._nsec%1e9/1000), /* microseconds */ + }, + } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + if posix.utimes(cname, ×) != .OK { + return _get_platform_error() + } + return nil +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + times := [2]posix.timespec{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_nsec = c.long(atime._nsec%1e9), /* nanoseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_nsec = c.long(mtime._nsec%1e9), /* nanoseconds */ + }, + } + + if posix.futimens(__fd(f), ×) != .OK { + return _get_platform_error() + } + return nil +} + +_exists :: proc(path: string) -> bool { + TEMP_ALLOCATOR_GUARD() + cpath := temp_cstring(path) + return posix.access(cpath) == .OK +} + +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File_Impl)(stream_data) + fd := f.fd + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := uint(min(len(p), MAX_RW)) + n = i64(posix.read(fd, raw_data(p), to_read)) + switch { + case n == 0: + err = .EOF + case n < 0: + err = .Unknown + } + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := uint(min(len(p), MAX_RW)) + n = i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset))) + switch { + case n == 0: + err = .EOF + case n < 0: + err = .Unknown + } + return + + case .Write: + p := p + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.write(fd, raw_data(p), to_write)); n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + } + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.pwrite(fd, raw_data(p), to_write, posix.off_t(offset))); n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + offset += _n + } + } + return + + case .Seek: + #assert(int(posix.Whence.SET) == int(io.Seek_From.Start)) + #assert(int(posix.Whence.CUR) == int(io.Seek_From.Current)) + #assert(int(posix.Whence.END) == int(io.Seek_From.End)) + + n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence))) + if n < 0 { + err = .Unknown + } + return + + case .Size: + stat: posix.stat_t + if posix.fstat(fd, &stat) != .OK { + err = .Unknown + return + } + + n = i64(stat.st_size) + return + + case .Flush: + ferr := _sync(&f.file) + err = error_to_io_error(ferr) + return + + case .Close, .Destroy: + ferr := _close(f) + err = error_to_io_error(ferr) + return + + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + + case: + return 0, .Empty + } +} diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin new file mode 100644 index 000000000..eb543d383 --- /dev/null +++ b/core/os/os2/heap_posix.odin @@ -0,0 +1,97 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:intrinsics" + +import "core:mem" +import "core:sys/posix" + + +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, zero_memory: bool, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { + assert(size > 0) + + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ + allocated_mem = posix.realloc(original_old_ptr, uint(space)+size_of(rawptr)) + } else if zero_memory { + allocated_mem = posix.calloc(1, uint(space)+size_of(rawptr)) + } else { + allocated_mem = posix.malloc(uint(space)+size_of(rawptr)) + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space || allocated_mem == nil { + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem + + return mem.byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + posix.free(mem.ptr_offset((^rawptr)(p), -1)^) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory: bool) -> (new_memory: []byte, err: mem.Allocator_Error) { + assert(p != nil) + + new_memory = aligned_alloc(new_size, new_alignment, false, p) or_return + + if zero_memory && new_size > old_size { + new_region := raw_data(new_memory[old_size:]) + intrinsics.mem_zero(new_region, new_size - old_size) + } + return + } + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + return aligned_alloc(size, alignment, mode == .Alloc) + + case .Free: + aligned_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize, .Resize_Non_Zeroed: + if old_memory == nil { + return aligned_alloc(size, alignment, mode == .Resize) + } + return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize) + + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil +} diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index f7a38f3f1..ed65ab7e0 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -43,7 +43,7 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri } @(require_results) -temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) { +temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error { return clone_to_cstring(s, temp_allocator()) } diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin new file mode 100644 index 000000000..1e59afdc1 --- /dev/null +++ b/core/os/os2/path_posix.odin @@ -0,0 +1,127 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" +import "core:path/filepath" + +import "core:sys/posix" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { + return _get_platform_error() + } + return nil +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + TEMP_ALLOCATOR_GUARD() + + if exists(path) { + return .Exist + } + + clean_path := filepath.clean(path, temp_allocator()) + return internal_mkdir_all(clean_path, perm) + + internal_mkdir_all :: proc(path: string, perm: int) -> Error { + a, _ := filepath.split(path) + if a != path { + if len(a) > 1 && a[len(a)-1] == '/' { + a = a[:len(a)-1] + } + internal_mkdir_all(a, perm) or_return + } + + err := _mkdir(path, perm) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cpath := temp_cstring(path) + + dir := posix.opendir(cpath) + if dir == nil { + return _get_platform_error() + } + + for { + posix.set_errno(.NONE) + entry := posix.readdir(dir) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + return _get_platform_error() + } else { + break + } + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + + fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) + if entry.d_type == .DIR { + _remove_all(fullpath[:len(fullpath)-1]) + } else { + if posix.unlink(cstring(raw_data(fullpath))) != .OK { + return _get_platform_error() + } + } + } + + if posix.rmdir(cpath) != .OK { + return _get_platform_error() + } + posix.closedir(dir) + return nil +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + assert(!is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() + + buf: [dynamic]byte + buf.allocator = temp_allocator() + size := uint(posix.PATH_MAX) + + cwd: cstring + for ; cwd == nil; size *= 2 { + resize(&buf, size) + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if cwd == nil && posix.errno() != .ERANGE { + err = _get_platform_error() + return + } + } + + return clone_string(string(cwd), allocator) +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + cdir := temp_cstring(dir) + if posix.chdir(cdir) != .OK { + err = _get_platform_error() + } + return +} diff --git a/core/os/os2/pipe_posix.odin b/core/os/os2/pipe_posix.odin new file mode 100644 index 000000000..62d6fc9fb --- /dev/null +++ b/core/os/os2/pipe_posix.odin @@ -0,0 +1,37 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" +import "core:strings" + +_pipe :: proc() -> (r, w: ^File, err: Error) { + fds: [2]posix.FD + if posix.pipe(&fds) != .OK { + err = _get_platform_error() + return + } + + r = __new_file(fds[0]) + ri := (^File_Impl)(r.impl) + + rname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&rname, "/dev/fd/") + strings.write_int(&rname, int(fds[0])) + ri.name = strings.to_string(rname) + ri.cname = strings.to_cstring(&rname) + + w = __new_file(fds[1]) + wi := (^File_Impl)(w.impl) + + wname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&wname, "/dev/fd/") + strings.write_int(&wname, int(fds[1])) + wi.name = strings.to_string(wname) + wi.cname = strings.to_cstring(&wname) + + return +} + diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 3f3e64668..f7a542276 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -14,7 +14,7 @@ TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration wi */ args := get_args() -@(private="file", require_results) +@(private="file") get_args :: proc() -> []string { result := make([]string, len(runtime.args__), heap_allocator()) for rt_arg, i in runtime.args__ { @@ -131,6 +131,8 @@ Process_Info_Field :: enum { Working_Dir, } +ALL_INFO :: Process_Info_Fields{.Executable_Path, .PPid, .Priority, .Command_Line, .Command_Args, .Environment, .Username, .Working_Dir} + /* Contains information about the process as obtained by the `process_info()` procedure. @@ -166,8 +168,8 @@ Process_Info :: struct { a process given by `pid`. Use `free_process_info` to free the memory allocated by this procedure. In - case the function returns an error all temporary allocations would be freed - and as such, calling `free_process_info()` is not needed. + case the function returns an error it may only have been an error for one part + of the information and you would still need to call it to free the other parts. **Note**: The resulting information may or may contain the fields specified by the `selection` parameter. Always check whether the returned @@ -187,8 +189,8 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: the `process` parameter. Use `free_process_info` to free the memory allocated by this procedure. In - case the function returns an error, all temporary allocations would be freed - and as such, calling `free_process_info` is not needed. + case the function returns an error it may only have been an error for one part + of the information and you would still need to call it to free the other parts. **Note**: The resulting information may or may contain the fields specified by the `selection` parameter. Always check whether the returned @@ -206,9 +208,9 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, This procedure obtains the information, specified by `selection` parameter about the currently running process. - Use `free_process_info` to free the memory allocated by this function. In - case this function returns an error, all temporary allocations would be - freed and as such calling `free_process_info()` is not needed. + Use `free_process_info` to free the memory allocated by this procedure. In + case the function returns an error it may only have been an error for one part + of the information and you would still need to call it to free the other parts. **Note**: The resulting information may or may contain the fields specified by the `selection` parameter. Always check whether the returned @@ -239,12 +241,16 @@ process_info :: proc { free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { delete(pi.executable_path, allocator) delete(pi.command_line, allocator) + for a in pi.command_args { + delete(a, allocator) + } delete(pi.command_args, allocator) for s in pi.environment { delete(s, allocator) } delete(pi.environment, allocator) delete(pi.working_dir, allocator) + delete(pi.username, allocator) } /* diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin new file mode 100644 index 000000000..8a27efce4 --- /dev/null +++ b/core/os/os2/process_posix.odin @@ -0,0 +1,69 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" +import "core:time" + +import "core:sys/posix" + +_exit :: proc "contextless" (code: int) -> ! { + posix.exit(i32(code)) +} + +_get_uid :: proc() -> int { + return int(posix.getuid()) +} + +_get_euid :: proc() -> int { + return int(posix.geteuid()) +} + +_get_gid :: proc() -> int { + return int(posix.getgid()) +} + +_get_egid :: proc() -> int { + return int(posix.getegid()) +} + +_get_pid :: proc() -> int { + return int(posix.getpid()) +} + +_get_ppid :: proc() -> int { + return int(posix.getppid()) +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(process.pid, selection, allocator) +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(_get_pid(), selection, allocator) +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_Sys_Process_Attributes :: struct {} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + err = .Unsupported + return +} + +_process_close :: proc(process: Process) -> Error { + return .Unsupported +} + +_process_kill :: proc(process: Process) -> Error { + return .Unsupported +} diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin new file mode 100644 index 000000000..f5816d693 --- /dev/null +++ b/core/os/os2/process_posix_darwin.odin @@ -0,0 +1,251 @@ +//+private +package os2 + +import "base:runtime" +import "base:intrinsics" + +import "core:bytes" +import "core:sys/darwin" +import "core:sys/posix" +import "core:sys/unix" + +foreign import lib "system:System.framework" + +foreign lib { + sysctl :: proc( + name: [^]i32, namelen: u32, + oldp: rawptr, oldlenp: ^uint, + newp: rawptr, newlen: uint, + ) -> posix.result --- +} + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = pid + + get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, nice: Maybe(i32), uid: posix.uid_t, ok: bool) { + // Short info is enough and requires less permissions if the priority isn't requested. + if .Priority in selection { + pinfo: darwin.proc_bsdinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .BSDINFO, 0, &pinfo, size_of(pinfo)) + if ret > 0 { + assert(ret == size_of(pinfo)) + ppid = pinfo.pbi_ppid + nice = pinfo.pbi_nice + uid = pinfo.pbi_uid + ok = true + return + } + } + + // Try short info, requires less permissions, but doesn't give a `nice`. + psinfo: darwin.proc_bsdshortinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .SHORTBSDINFO, 0, &psinfo, size_of(psinfo)) + if ret > 0 { + assert(ret == size_of(psinfo)) + ppid = psinfo.pbsi_ppid + uid = psinfo.pbsi_uid + ok = true + } + + return + } + + // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), + // other errors usually mean other parts of the info could be retrieved though, so in those cases we keep trying to get the other information. + + pidinfo: { + if selection >= {.PPid, .Priority, .Username } { + ppid, mnice, uid, ok := get_pidinfo(pid, selection) + if !ok { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + if .PPid in selection { + info.ppid = int(ppid) + info.fields += {.PPid} + } + + if nice, has_nice := mnice.?; has_nice && .Priority in selection { + info.priority = int(nice) + info.fields += {.Priority} + } + + if .Username in selection { + pw := posix.getpwuid(uid) + if pw == nil { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + info.username = clone_string(string(pw.pw_name), allocator) or_return + info.fields += {.Username} + } + } + } + + if .Working_Dir in selection { + pinfo: darwin.proc_vnodepathinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .VNODEPATHINFO, 0, &pinfo, size_of(pinfo)) + if ret > 0 { + assert(ret == size_of(pinfo)) + info.working_dir = clone_string(string(cstring(raw_data(pinfo.pvi_cdir.vip_path[:]))), allocator) or_return + info.fields += {.Working_Dir} + } else if err == nil { + err = _get_platform_error() + } + } + + if .Executable_Path in selection { + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.pid_t(pid), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + info.executable_path = clone_string(string(buffer[:ret]), allocator) or_return + info.fields += {.Executable_Path} + } else if err == nil { + err = _get_platform_error() + } + } + + args: if selection >= { .Command_Line, .Command_Args, .Environment } { + mib := []i32{ + unix.CTL_KERN, + unix.KERN_PROCARGS2, + i32(pid), + } + length: uint + if sysctl(raw_data(mib), 3, nil, &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + } + break args + } + + buf := make([]byte, length, temp_allocator()) + if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + } + break args + } + + buf = buf[:length] + + if len(buf) < 4 { + break args + } + + // Layout isn't really documented anywhere, I deduced it to be: + // i32 - argc + // cstring - command name (skipped) + // [^]byte - couple of 0 bytes (skipped) + // [^]cstring - argv (up to argc entries) + // [^]cstring - key=value env entries until the end (many intermittent 0 bytes and entries without `=` we skip here too) + + argc := (^i32)(raw_data(buf))^ + buf = buf[size_of(i32):] + + { + command_line: [dynamic]byte + command_line.allocator = allocator + + argv: [dynamic]string + argv.allocator = allocator + + defer if err != nil { + for arg in argv { delete(arg, allocator) } + delete(argv) + delete(command_line) + } + + _, _ = bytes.split_iterator(&buf, {0}) + buf = bytes.trim_left(buf, {0}) + + first_arg := true + for arg in bytes.split_iterator(&buf, {0}) { + if .Command_Line in selection { + if !first_arg { + append(&command_line, ' ') or_return + } + append(&command_line, ..arg) or_return + } + + if .Command_Args in selection { + sarg := clone_string(string(arg), allocator) or_return + append(&argv, sarg) or_return + } + + first_arg = false + argc -= 1 + if argc == 0 { + break + } + } + + if .Command_Line in selection { + info.command_line = string(command_line[:]) + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = argv[:] + info.fields += {.Command_Args} + } + } + + if .Environment in selection { + environment: [dynamic]string + environment.allocator = allocator + + defer if err != nil { + for entry in environment { delete(entry, allocator) } + delete(environment) + } + + for entry in bytes.split_iterator(&buf, {0}) { + if bytes.index_byte(entry, '=') > -1 { + sentry := clone_string(string(entry), allocator) or_return + append(&environment, sentry) or_return + } + } + + info.environment = environment[:] + info.fields += {.Environment} + } + } + + // Fields were requested that we didn't add. + if err == nil && selection - info.fields != {} { + err = .Unsupported + } + + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + ret := darwin.proc_listallpids(nil, 0) + if ret < 0 { + err = _get_platform_error() + return + } + + assert(!is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() + + buffer := make([]i32, ret, temp_allocator()) + ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) + if ret < 0 { + err = _get_platform_error() + return + } + + list = make([]int, ret, allocator) or_return + #no_bounds_check for &entry, i in list { + entry = int(buffer[i]) + } + + return +} diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin new file mode 100644 index 000000000..ce84b1012 --- /dev/null +++ b/core/os/os2/process_posix_other.odin @@ -0,0 +1,15 @@ +//+private +//+build netbsd, openbsd, freebsd +package os2 + +import "base:runtime" + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + err = .Unsupported + return +} diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index b3ca47be3..b53ebb3ab 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -30,8 +30,8 @@ file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: } file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { - for i := len(infos)-1; i >= 0; i -= 1 { - file_info_delete(infos[i], allocator) + #reverse for info in infos { + file_info_delete(info, allocator) } delete(infos, allocator) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 6ccac1be0..2355a09f0 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -48,6 +48,7 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return @@ -60,6 +61,7 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err } _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin new file mode 100644 index 000000000..3ffdefc6a --- /dev/null +++ b/core/os/os2/stat_posix.odin @@ -0,0 +1,113 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" +import "core:path/filepath" +import "core:sys/posix" +import "core:time" + +internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + fi.name = filepath.base(fi.fullpath) + + fi.inode = u64(stat.st_ino) + fi.size = i64(stat.st_size) + + fi.mode = int(transmute(posix._mode_t)(stat.st_mode - posix._S_IFMT)) + + fi.type = .Undetermined + switch { + case posix.S_ISBLK(stat.st_mode): + fi.type = .Block_Device + case posix.S_ISCHR(stat.st_mode): + fi.type = .Character_Device + case posix.S_ISDIR(stat.st_mode): + fi.type = .Directory + case posix.S_ISFIFO(stat.st_mode): + fi.type = .Named_Pipe + case posix.S_ISLNK(stat.st_mode): + fi.type = .Symlink + case posix.S_ISREG(stat.st_mode): + fi.type = .Regular + case posix.S_ISSOCK(stat.st_mode): + fi.type = .Socket + } + + fi.creation_time = timespec_time(stat.st_birthtimespec) + fi.modification_time = timespec_time(stat.st_mtim) + fi.access_time = timespec_time(stat.st_atim) + + timespec_time :: proc(t: posix.timespec) -> time.Time { + return time.Time{_nsec = i64(t.tv_sec) * 1e9 + i64(t.tv_nsec)} + } + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat: posix.stat_t + if posix.fstat(impl.fd, &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + assert(!is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) or_return + + rcname := posix.realpath(cname) + + stat: posix.stat_t + if posix.stat(rcname, &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath := clone_string(string(rcname), allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + assert(!is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) or_return + + rcname := posix.realpath(cname) + + stat: posix.stat_t + if posix.lstat(rcname, &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath := clone_string(string(rcname), allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 5e66507be..d0b7c45a7 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -44,6 +44,8 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path if name == "" { name = "." } + + assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() p := win32_utf8_to_utf16(name, temp_allocator()) or_return @@ -127,7 +129,10 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin if n == 0 { return "", _get_platform_error() } + + assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() + buf := make([]u16, max(n, 260)+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_from_buf(buf[:n], allocator) @@ -143,7 +148,10 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { if n == 0 { return nil, _get_platform_error() } + + assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() + buf := make([]u16, max(n, 260)+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_strip_prefix(buf[:n]), nil diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 467775e89..5ca4e1453 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -10,7 +10,7 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? // Opens the file for reading and writing, with 0o666 permissions, and returns the new `^File`. // The filename is generated by taking a pattern, and adding a randomized string to the end. // If the pattern includes an "*", the random string replaces the last "*". -// If `dir` is an empty tring, `temp_directory()` will be used. +// If `dir` is an empty string, `temp_directory()` will be used. // // The caller must `close` the file once finished with. @(require_results) diff --git a/core/os/os2/temp_file_posix.odin b/core/os/os2/temp_file_posix.odin new file mode 100644 index 000000000..67ec4d3e8 --- /dev/null +++ b/core/os/os2/temp_file_posix.odin @@ -0,0 +1,20 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +@(require) +import "core:sys/posix" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + if tmp, ok := _lookup_env("TMPDIR", allocator); ok { + return tmp, nil + } + + when #defined(posix.P_tmpdir) { + return clone_string(posix.P_tmpdir, allocator) + } + + return clone_string("/tmp/", allocator) +} diff --git a/core/sys/darwin/proc.odin b/core/sys/darwin/proc.odin new file mode 100644 index 000000000..a49383670 --- /dev/null +++ b/core/sys/darwin/proc.odin @@ -0,0 +1,142 @@ +package darwin + +import "base:intrinsics" + +import "core:sys/posix" + +foreign import lib "system:System.framework" + +// Incomplete bindings to the proc API on MacOS, add to when needed. + +foreign lib { + proc_pidinfo :: proc(pid: posix.pid_t, flavor: PID_Info_Flavor, arg: i64, buffer: rawptr, buffersize: i32) -> i32 --- + proc_pidpath :: proc(pid: posix.pid_t, buffer: [^]byte, buffersize: u32) -> i32 --- + proc_listallpids :: proc(buffer: [^]i32, buffersize: i32) -> i32 --- +} + +MAXCOMLEN :: 16 + +proc_bsdinfo :: struct { + pbi_flags: PBI_Flags, + pbi_status: u32, + pbi_xstatus: u32, + pbi_pid: u32, + pbi_ppid: u32, + pbi_uid: posix.uid_t, + pbi_gid: posix.gid_t, + pbi_ruid: posix.uid_t, + pbi_rgid: posix.gid_t, + pbi_svuid: posix.uid_t, + pbi_svgid: posix.gid_t, + rfu_1: u32, + pbi_comm: [MAXCOMLEN]byte `fmt:"s,0"`, + pbi_name: [2 * MAXCOMLEN]byte `fmt:"s,0"`, + pbi_nfiles: u32, + pbi_pgid: u32, + pbi_pjobc: u32, + e_tdev: u32, + e_tpgid: u32, + pbi_nice: i32, + pbi_start_tvsec: u64, + pbi_start_tvusec: u64, +} + +proc_bsdshortinfo :: struct { + pbsi_pid: u32, + pbsi_ppid: u32, + pbsi_pgid: u32, + pbsi_status: u32, + pbsi_comm: [MAXCOMLEN]byte `fmt:"s,0"`, + pbsi_flags: PBI_Flags, + pbsi_uid: posix.uid_t, + pbsi_gid: posix.gid_t, + pbsi_ruid: posix.uid_t, + pbsi_rgid: posix.gid_t, + pbsi_svuid: posix.uid_t, + pbsi_svgid: posix.gid_t, + pbsi_rfu: u32, +} + +proc_vnodepathinfo :: struct { + pvi_cdir: vnode_info_path, + pvi_rdir: vnode_info_path, +} + +vnode_info_path :: struct { + vip_vi: vnode_info, + vip_path: [posix.PATH_MAX]byte, +} + +vnode_info :: struct { + vi_stat: vinfo_stat, + vi_type: i32, + vi_pad: i32, + vi_fsid: fsid_t, +} + +vinfo_stat :: struct { + vst_dev: u32, + vst_mode: u16, + vst_nlink: u16, + vst_ino: u64, + vst_uid: posix.uid_t, + vst_gid: posix.gid_t, + vst_atime: i64, + vst_atimensec: i64, + vst_mtime: i64, + vst_mtimensec: i64, + vst_ctime: i64, + vst_ctimensec: i64, + vst_birthtime: i64, + vst_birthtimensec: i64, + vst_size: posix.off_t, + vst_blocks: i64, + vst_blksize: i32, + vst_flags: u32, + vst_gen: u32, + vst_rdev: u32, + vst_qspare: [2]i64, +} + +fsid_t :: distinct [2]i32 + +PBI_Flag_Bits :: enum u32 { + SYSTEM = intrinsics.constant_log2(0x0001), + TRACED = intrinsics.constant_log2(0x0002), + INEXIT = intrinsics.constant_log2(0x0004), + PWAIT = intrinsics.constant_log2(0x0008), + LP64 = intrinsics.constant_log2(0x0010), + SLEADER = intrinsics.constant_log2(0x0020), + CTTY = intrinsics.constant_log2(0x0040), + CONTROLT = intrinsics.constant_log2(0x0080), + THCWD = intrinsics.constant_log2(0x0100), + PC_THROTTLE = intrinsics.constant_log2(0x0200), + PC_SUSP = intrinsics.constant_log2(0x0400), + PC_KILL = intrinsics.constant_log2(0x0600), + PA_THROTTLE = intrinsics.constant_log2(0x0800), + PA_SUSP = intrinsics.constant_log2(0x1000), + PA_PSUGID = intrinsics.constant_log2(0x2000), + EXEC = intrinsics.constant_log2(0x4000), +} +PBI_Flags :: bit_set[PBI_Flag_Bits; u32] + +PID_Info_Flavor :: enum i32 { + LISTFDS = 1, + TASKALLINFO, + BSDINFO, + TASKINFO, + THREADINFO, + LISTTHREADS, + REGIONINFO, + REGIONPATHINFO, + VNODEPATHINFO, + THREADPATHINFO, + PATHINFO, + WORKQUEUEINFO, + SHORTBSDINFO, + LISTFILEPORTS, + THREADID64INFO, + RUSAGE, +} + +PIDPATHINFO_MAXSIZE :: 4*posix.PATH_MAX diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin index 8e08860b4..6ed9e5d11 100644 --- a/core/sys/posix/unistd.odin +++ b/core/sys/posix/unistd.odin @@ -211,7 +211,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]] */ - _exit :: proc(status: c.int) --- + _exit :: proc(status: c.int) -> ! --- /* The exec family of functions shall replace the current process image with a new process image. @@ -416,8 +416,11 @@ foreign lib { } cwd = posix.getcwd(raw_data(buf), len(buf)) - if errno := posix.errno(); cwd == nil && errno != .ERANGE { - fmt.panicf("getcwd failure: %v", posix.strerror(errno)) + if cwd == nil { + errno := posix.errno() + if errno != .ERANGE { + fmt.panicf("getcwd failure: %v", posix.strerror(errno)) + } } } diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 14d3c113a..92222bdfe 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -67,6 +67,8 @@ CTL_KERN :: 1 KERN_VERSION :: 4 // Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:darwin-8020.121.3~4/RELEASE_X86_64 KERN_OSRELDATE :: 26 // i32: OS release date KERN_OSVERSION :: 65 // Build number, e.g. 21F79 + KERN_PROCARGS :: 38 + KERN_PROCARGS2 :: 49 CTL_VM :: 2 CTL_VFS :: 3 CTL_NET :: 4 @@ -82,4 +84,4 @@ CTL_HW :: 6 HW_AVAILCPU :: 25 /* int: number of available CPUs */ CTL_MACHDEP :: 7 -CTL_USER :: 8 \ No newline at end of file +CTL_USER :: 8 diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index 35c5db02c..8ca40ef1b 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -23,6 +23,8 @@ CTL_KERN :: 1 KERN_OSRELEASE :: 2 KERN_OSREV :: 3 KERN_VERSION :: 4 + KERN_PROC :: 14 + KERN_PROC_PATHNAME :: 12 CTL_VM :: 2 CTL_VFS :: 3 CTL_NET :: 4