Browse Source

posix: start on process API

Laytan Laats 1 year ago
parent
commit
142bda2804
2 changed files with 183 additions and 11 deletions
  1. 151 11
      core/os/os2/process_posix.odin
  2. 32 0
      core/os/os2/process_posix_darwin.odin

+ 151 - 11
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)
 	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 {}
 _Sys_Process_Attributes :: struct {}
 
 
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 _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) {
 _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
 	return
 }
 }
 
 
 _process_close :: proc(process: Process) -> Error {
 _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
 }
 }

+ 32 - 0
core/os/os2/process_posix_darwin.odin

@@ -19,6 +19,8 @@ foreign lib {
 	) -> posix.result ---
 	) -> posix.result ---
 }
 }
 
 
+import "core:fmt"
+
 _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
 _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) {
 	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.
 		// 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
 	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
+}