diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin index 8a27efce4..fc6e135d8 100644 --- a/core/os/os2/process_posix.odin +++ b/core/os/os2/process_posix.odin @@ -43,27 +43,167 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime 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 + if len(desc.command) == 0 { + err = .Invalid_Path + return + } + + cwd: cstring; if desc.working_dir != "" { + cwd = temp_cstring(desc.working_dir) + } + + cmd := make([]cstring, len(desc.command)+1, temp_allocator()) + for part, i in desc.command { + cmd[i] = temp_cstring(part) + } + + switch pid := posix.fork(); pid { + case -1: + err = _get_platform_error() + return + + case 0: + // NOTE(laytan): would need to use execvp and look up the command in the PATH. + assert(len(desc.env) == 0, "unimplemented: process_start with env") + + null := posix.open("/dev/null", { .RDWR, .CLOEXEC }) + assert(null != -1) // TODO: Does this happen/need to be handled? + + stderr := (^File_Impl)(desc.stderr.impl).fd if desc.stderr != nil else null + stdout := (^File_Impl)(desc.stdout.impl).fd if desc.stdout != nil else null + stdin := (^File_Impl)(desc.stdin.impl).fd if desc.stdin != nil else null + + posix.dup2(stderr, posix.STDERR_FILENO) + posix.dup2(stdout, posix.STDOUT_FILENO) + posix.dup2(stdin, posix.STDIN_FILENO ) + + // NOTE(laytan): is this how we should handle these? + // Maybe we can try to `stat` the cwd in the parent before forking? + // Does that mean no other errors could happen in chdir? + // How about execvp? + + if cwd != nil { + if posix.chdir(cwd) != .OK { + posix.exit(i32(posix.errno())) // TODO: handle, or is it fine this way? + } + } + + posix.execvp(cmd[0], raw_data(cmd)) + posix.exit(i32(posix.errno())) // TODO: handle, or is it fine this way? + + case: + fmt.println("returning") + process, _ = _process_open(int(pid), {}) + process.pid = int(pid) + return + } } +import "core:fmt" +import "core:nbio/kqueue" + _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { - err = .Unsupported + process_state.pid = process.pid + + if !process_posix_handle_still_valid(process) { + err = Platform_Error(posix.Errno.ESRCH) + return + } + + // prev := posix.signal(.SIGALRM, proc "c" (_: posix.Signal) { + // context = runtime.default_context() + // fmt.println("alarm") + // }) + // defer posix.signal(.SIGALRM, prev) + // + // posix.alarm(u32(time.duration_seconds(timeout))) + // defer posix.alarm(0) + + // TODO: if there's no timeout, don't set up a kqueue. + + // TODO: if timeout is 0, don't set up a kqueue and use NO_HANG. + + kq, qerr := kqueue.kqueue() + if qerr != nil { + err = Platform_Error(qerr) + return + } + + changelist, eventlist: [1]kqueue.KEvent + + changelist[0] = { + ident = uintptr(process.pid), + filter = .Proc, + flags = { .Add }, + fflags = { + fproc = 0x80000000, + }, + } + + // NOTE: could this be interrupted which means it should be looped and subtracting the timeout on EINTR. + + n, eerr := kqueue.kevent(kq, changelist[:], eventlist[:], &{ + seconds = i64(timeout / time.Second), + nanoseconds = i64(timeout % time.Second), + }) + if eerr != nil { + err = Platform_Error(eerr) + return + } + + if n == 0 { + err = .Timeout + + // TODO: populate the time fields. + + return + } + + // NOTE(laytan): should this be looped untill WIFEXITED/WIFSIGNALED? + + status: i32 + wpid := posix.waitpid(posix.pid_t(process.pid), &status, {}) + if wpid == -1 { + err = _get_platform_error() + return + } + + process_state.exited = true + + // TODO: populate times + + switch { + case posix.WIFEXITED(status): + fmt.printfln("child exited, status=%v", posix.WEXITSTATUS(status)) + process_state.exit_code = int(posix.WEXITSTATUS(status)) + process_state.success = true + case posix.WIFSIGNALED(status): + fmt.printfln("child killed (signal %v)", posix.WTERMSIG(status)) + process_state.exit_code = int(posix.WTERMSIG(status)) + process_state.success = false + case: + fmt.panicf("unexpected status (%x)", status) + } + return } _process_close :: proc(process: Process) -> Error { - return .Unsupported + return nil } -_process_kill :: proc(process: Process) -> Error { - return .Unsupported +_process_kill :: proc(process: Process) -> (err: Error) { + if !process_posix_handle_still_valid(process) { + err = Platform_Error(posix.Errno.ESRCH) + return + } + + if posix.kill(posix.pid_t(process.pid), .SIGKILL) != .OK { + err = _get_platform_error() + } + + return } diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 4fbb84886..5558ef00a 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -19,6 +19,8 @@ foreign lib { ) -> posix.result --- } +import "core:fmt" + _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, prio: Maybe(i32), uid: posix.uid_t, ok: bool) { // Short info is enough and requires less permissions if the priority isn't requested. @@ -254,3 +256,33 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) return } + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + + // NOTE(laytan): pids can get reused, and afaik posix/macos doesn't have a unique identifier + // for a specific process execution, next best thing to me is checking the time the process + // started as some extra "uniqueness". We could also hash a bunch of the fields in this info. + + // This incidentally also checks if the pid is actually valid so that's nice. + + pinfo: darwin.proc_bsdinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .BSDINFO, 0, &pinfo, size_of(pinfo)) + if ret <= 0 { + err = _get_platform_error() + return + } + + assert(ret == size_of(pinfo)) + process = { int(pid), uintptr(pinfo.pbi_start_tvusec) } + return +} + +process_posix_handle_still_valid :: proc(p: Process) -> bool { + pinfo: darwin.proc_bsdinfo + ret := darwin.proc_pidinfo(posix.pid_t(p.pid), .BSDINFO, 0, &pinfo, size_of(pinfo)) + if ret <= 0 { + return false + } + + return uintptr(pinfo.pbi_start_tvusec) == p.handle +}