123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866 |
- //+build linux
- //+private file
- package os2
- import "base:runtime"
- import "base:intrinsics"
- import "core:time"
- import "core:slice"
- import "core:strings"
- import "core:strconv"
- import "core:sys/linux"
- import "core:path/filepath"
- PIDFD_UNASSIGNED :: ~uintptr(0)
- @(private="package")
- _exit :: proc "contextless" (code: int) -> ! {
- linux.exit_group(i32(code))
- }
- @(private="package")
- _get_uid :: proc() -> int {
- return int(linux.getuid())
- }
- @(private="package")
- _get_euid :: proc() -> int {
- return int(linux.geteuid())
- }
- @(private="package")
- _get_gid :: proc() -> int {
- return int(linux.getgid())
- }
- @(private="package")
- _get_egid :: proc() -> int {
- return int(linux.getegid())
- }
- @(private="package")
- _get_pid :: proc() -> int {
- return int(linux.getpid())
- }
- @(private="package")
- _get_ppid :: proc() -> int {
- return int(linux.getppid())
- }
- @(private="package")
- _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
- #partial switch errno {
- case .NONE:
- // okay
- case .ENOTDIR:
- err = .Invalid_Dir
- return
- case .ENOENT:
- err = .Not_Exist
- return
- case:
- err = _get_platform_error(errno)
- return
- }
- defer linux.close(dir_fd)
- dynamic_list := make([dynamic]int, temp_allocator()) or_return
- buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return
- loop: for {
- buflen: int
- buflen, errno = linux.getdents(dir_fd, buf[:])
- #partial switch errno {
- case .EINVAL:
- resize(&buf, len(buf) * 2)
- continue loop
- case .NONE:
- if buflen == 0 { break loop }
- case:
- return {}, _get_platform_error(errno)
- }
- offset: int
- for d in linux.dirent_iterate_buf(buf[:buflen], &offset) {
- d_name_str := linux.dirent_name(d)
- if pid, ok := strconv.parse_int(d_name_str); ok {
- append(&dynamic_list, pid)
- }
- }
- }
- list, err = slice.clone(dynamic_list[:], allocator)
- return
- }
- @(private="package")
- _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- info.pid = pid
- // Use this to make cstrings without copying.
- path_backing: [48]u8
- path_builder := strings.builder_from_bytes(path_backing[:])
- strings.write_string(&path_builder, "/proc/")
- strings.write_int(&path_builder, pid)
- proc_fd, errno := linux.open(strings.to_cstring(&path_builder), _OPENDIR_FLAGS)
- if errno != .NONE {
- err = _get_platform_error(errno)
- return
- }
- defer linux.close(proc_fd)
- username_if: if .Username in selection {
- s: linux.Stat
- if errno = linux.fstat(proc_fd, &s); errno != .NONE {
- err = _get_platform_error(errno)
- break username_if
- }
- passwd_bytes: []u8
- passwd_err: Error
- passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator())
- if passwd_err != nil {
- err = passwd_err
- break username_if
- }
- passwd := string(passwd_bytes)
- for len(passwd) > 0 {
- n := strings.index_byte(passwd, ':')
- if n < 0 {
- break
- }
- username := passwd[:n]
- passwd = passwd[n+1:]
- // skip password field
- passwd = passwd[strings.index_byte(passwd, ':') + 1:]
- n = strings.index_byte(passwd, ':')
- if uid, ok := strconv.parse_int(passwd[:n]); ok && uid == int(s.uid) {
- info.username = strings.clone(username, allocator) or_return
- info.fields += {.Username}
- break
- } else if !ok {
- err = .Invalid_File
- break username_if
- }
- eol := strings.index_byte(passwd, '\n')
- if eol < 0 {
- break
- }
- passwd = passwd[eol + 1:]
- }
- }
- cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} {
- strings.builder_reset(&path_builder)
- strings.write_string(&path_builder, "/proc/")
- strings.write_int(&path_builder, pid)
- strings.write_string(&path_builder, "/cmdline")
- cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator())
- if cmdline_err != nil || len(cmdline_bytes) == 0 {
- err = cmdline_err
- break cmdline_if
- }
- cmdline := string(cmdline_bytes)
- terminator := strings.index_byte(cmdline, 0)
- assert(terminator > 0)
- command_line_exec := cmdline[:terminator]
- // Still need cwd if the execution on the command line is relative.
- cwd: string
- cwd_err: Error
- if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') {
- strings.builder_reset(&path_builder)
- strings.write_string(&path_builder, "/proc/")
- strings.write_int(&path_builder, pid)
- strings.write_string(&path_builder, "/cwd")
- cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder), temp_allocator()) // allowed to fail
- if cwd_err == nil && .Working_Dir in selection {
- info.working_dir = strings.clone(cwd, allocator) or_return
- info.fields += {.Working_Dir}
- } else if cwd_err != nil {
- err = cwd_err
- break cmdline_if
- }
- }
- if .Executable_Path in selection {
- if cmdline[0] == '/' {
- info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return
- info.fields += {.Executable_Path}
- } else if cwd_err == nil {
- info.executable_path = filepath.join({ cwd, cmdline[:terminator] }, allocator) or_return
- info.fields += {.Executable_Path}
- } else {
- break cmdline_if
- }
- }
- if selection & {.Command_Line, .Command_Args} != {} {
- // skip to first arg
- //cmdline = cmdline[terminator + 1:]
- command_line_builder: strings.Builder
- command_args_list: [dynamic]string
- if .Command_Line in selection {
- command_line_builder = strings.builder_make(allocator) or_return
- info.fields += {.Command_Line}
- }
- for i := 0; len(cmdline) > 0; i += 1 {
- if terminator = strings.index_byte(cmdline, 0); terminator < 0 {
- break
- }
- if .Command_Line in selection {
- if i > 0 {
- strings.write_byte(&command_line_builder, ' ')
- }
- strings.write_string(&command_line_builder, cmdline[:terminator])
- }
- if .Command_Args in selection {
- if i == 1 {
- command_args_list = make([dynamic]string, allocator) or_return
- info.fields += {.Command_Args}
- }
- if i > 0 {
- arg := strings.clone(cmdline[:terminator], allocator) or_return
- append(&command_args_list, arg) or_return
- }
- }
- cmdline = cmdline[terminator + 1:]
- }
- info.command_line = strings.to_string(command_line_builder)
- info.command_args = command_args_list[:]
- }
- }
- stat_if: if selection & {.PPid, .Priority} != {} {
- strings.builder_reset(&path_builder)
- strings.write_string(&path_builder, "/proc/")
- strings.write_int(&path_builder, pid)
- strings.write_string(&path_builder, "/stat")
- proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator())
- if stat_err != nil {
- err = stat_err
- break stat_if
- }
- if len(proc_stat_bytes) <= 0 {
- break stat_if
- }
- // Skip to the first field after the executable name
- stats: string
- if start := strings.last_index_byte(string(proc_stat_bytes), ')'); start != -1 {
- stats = string(proc_stat_bytes[start + 2:])
- } else {
- break stat_if
- }
- // NOTE: index 0 corresponds to field 3 (state) from `man 5 proc_pid_stat`
- // because we skipped passed the executable name above.
- Fields :: enum {
- State,
- PPid,
- PGrp,
- Session,
- Tty_Nr,
- TpGid,
- Flags,
- MinFlt,
- CMinFlt,
- MajFlt,
- CMajFlt,
- UTime,
- STime,
- CUTime,
- CSTime,
- Priority,
- Nice,
- //... etc,
- }
- stat_fields := strings.split(stats, " ", temp_allocator()) or_return
- if len(stat_fields) <= int(Fields.Nice) {
- break stat_if
- }
- if .PPid in selection {
- if ppid, ok := strconv.parse_int(stat_fields[Fields.PPid]); ok {
- info.ppid = ppid
- info.fields += {.PPid}
- } else {
- err = .Invalid_File
- break stat_if
- }
- }
- if .Priority in selection {
- if nice, ok := strconv.parse_int(stat_fields[Fields.Nice]); ok {
- info.priority = nice
- info.fields += {.Priority}
- } else {
- err = .Invalid_File
- break stat_if
- }
- }
- }
- if .Environment in selection {
- strings.builder_reset(&path_builder)
- strings.write_string(&path_builder, "/proc/")
- strings.write_int(&path_builder, pid)
- strings.write_string(&path_builder, "/environ")
- if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()); env_err == nil {
- env := string(env_bytes)
- env_list := make([dynamic]string, allocator) or_return
- for len(env) > 0 {
- terminator := strings.index_byte(env, 0)
- if terminator <= 0 {
- break
- }
- e := strings.clone(env[:terminator], allocator) or_return
- append(&env_list, e) or_return
- env = env[terminator + 1:]
- }
- info.environment = env_list[:]
- info.fields += {.Environment}
- } else if err == nil {
- err = env_err
- }
- }
- return
- }
- @(private="package")
- _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)
- }
- @(private="package")
- _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)
- }
- @(private="package")
- _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err: Error) {
- process.pid = pid
- process.handle = PIDFD_UNASSIGNED
- pidfd, errno := linux.pidfd_open(linux.Pid(pid), {})
- if errno == .ENOSYS {
- return process, .Unsupported
- }
- if errno != .NONE {
- return process, _get_platform_error(errno)
- }
- process.handle = uintptr(pidfd)
- return
- }
- @(private="package")
- _Sys_Process_Attributes :: struct {}
- @(private="package")
- _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
- has_executable_permissions :: proc(fd: linux.Fd) -> bool {
- backing: [48]u8
- b := strings.builder_from_bytes(backing[:])
- strings.write_string(&b, "/proc/self/fd/")
- strings.write_int(&b, int(fd))
- return linux.access(strings.to_cstring(&b), linux.X_OK) == .NONE
- }
- TEMP_ALLOCATOR_GUARD()
- if len(desc.command) == 0 {
- return process, .Invalid_Command
- }
- dir_fd := linux.AT_FDCWD
- errno: linux.Errno
- if desc.working_dir != "" {
- dir_cstr := temp_cstring(desc.working_dir) or_return
- if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
- return process, _get_platform_error(errno)
- }
- }
- defer if desc.working_dir != "" {
- linux.close(dir_fd)
- }
- // search PATH if just a plain name is provided
- exe_fd: linux.Fd
- executable_name := desc.command[0]
- if strings.index_byte(executable_name, '/') < 0 {
- path_env := get_env("PATH", temp_allocator())
- path_dirs := filepath.split_list(path_env, temp_allocator()) or_return
- exe_builder := strings.builder_make(temp_allocator()) or_return
- found: bool
- for dir in path_dirs {
- strings.builder_reset(&exe_builder)
- strings.write_string(&exe_builder, dir)
- strings.write_byte(&exe_builder, '/')
- strings.write_string(&exe_builder, executable_name)
- exe_path := strings.to_cstring(&exe_builder)
- if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE {
- continue
- }
- if !has_executable_permissions(exe_fd) {
- linux.close(exe_fd)
- continue
- }
- found = true
- break
- }
- if !found {
- // check in cwd to match windows behavior
- strings.builder_reset(&exe_builder)
- strings.write_string(&exe_builder, "./")
- strings.write_string(&exe_builder, executable_name)
- exe_path := strings.to_cstring(&exe_builder)
- if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE {
- return process, .Not_Exist
- }
- if !has_executable_permissions(exe_fd) {
- linux.close(exe_fd)
- return process, .Permission_Denied
- }
- }
- } else {
- exe_path := temp_cstring(executable_name) or_return
- if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE {
- return process, _get_platform_error(errno)
- }
- if !has_executable_permissions(exe_fd) {
- linux.close(exe_fd)
- return process, .Permission_Denied
- }
- }
- // At this point, we have an executable.
- defer linux.close(exe_fd)
- // args and environment need to be a list of cstrings
- // that are terminated by a nil pointer.
- cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return
- for command, i in desc.command {
- cargs[i] = temp_cstring(command) or_return
- }
- // Use current process' environment if description didn't provide it.
- env: [^]cstring
- if desc.env == nil {
- // take this process's current environment
- env = raw_data(export_cstring_environment(temp_allocator()))
- } else {
- cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return
- for env, i in desc.env {
- cenv[i] = temp_cstring(env) or_return
- }
- env = &cenv[0]
- }
- child_pipe_fds: [2]linux.Fd
- if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE {
- return process, _get_platform_error(errno)
- }
- defer linux.close(child_pipe_fds[READ])
- // TODO: This is the traditional textbook implementation with fork.
- // A more efficient implementation with vfork:
- //
- // 1. retrieve signal handlers
- // 2. block all signals
- // 3. allocate some stack space
- // 4. vfork (waits for child exit or execve); In child:
- // a. set child signal handlers
- // b. set up any necessary pipes
- // c. execve
- // 5. restore signal handlers
- //
- pid: linux.Pid
- if pid, errno = linux.fork(); errno != .NONE {
- linux.close(child_pipe_fds[WRITE])
- return process, _get_platform_error(errno)
- }
- STDIN :: linux.Fd(0)
- STDOUT :: linux.Fd(1)
- STDERR :: linux.Fd(2)
- READ :: 0
- WRITE :: 1
- if pid == 0 {
- // in child process now
- write_errno_to_parent_and_abort :: proc(parent_fd: linux.Fd, errno: linux.Errno) -> ! {
- error_byte: [1]u8 = { u8(errno) }
- linux.write(parent_fd, error_byte[:])
- intrinsics.trap()
- }
- stdin_fd: linux.Fd
- stdout_fd: linux.Fd
- stderr_fd: linux.Fd
- if desc.stdin != nil {
- stdin_fd = linux.Fd(fd(desc.stdin))
- } else {
- stdin_fd, errno = linux.open("/dev/null", {})
- if errno != .NONE {
- write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
- }
- }
- write_devnull: linux.Fd = -1
- if desc.stdout != nil {
- stdout_fd = linux.Fd(fd(desc.stdout))
- } else {
- write_devnull, errno = linux.open("/dev/null", {.WRONLY})
- if errno != .NONE {
- write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
- }
- stdout_fd = write_devnull
- }
- if desc.stderr != nil {
- stderr_fd = linux.Fd(fd(desc.stderr))
- } else {
- if write_devnull < 0 {
- write_devnull, errno = linux.open("/dev/null", {.WRONLY})
- if errno != .NONE {
- write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
- }
- }
- stderr_fd = write_devnull
- }
- if _, errno = linux.dup2(stdin_fd, STDIN); errno != .NONE {
- write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
- }
- if _, errno = linux.dup2(stdout_fd, STDOUT); errno != .NONE {
- write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
- }
- if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE {
- write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
- }
- errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH})
- assert(errno != nil)
- write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
- }
- linux.close(child_pipe_fds[WRITE])
- process.pid = int(pid)
- child_byte: [1]u8
- errno = .EINTR
- for errno == .EINTR {
- _, errno = linux.read(child_pipe_fds[READ], child_byte[:])
- }
- // If the read failed, something weird happened. Do not return the read
- // error so the user knows to wait on it.
- if errno == .NONE {
- child_errno := linux.Errno(child_byte[0])
- if child_errno != .NONE {
- // We can assume it trapped here.
- _reap_terminated(process)
- process.pid = 0
- return process, _get_platform_error(child_errno)
- }
- }
- process, _ = process_open(int(pid))
- return
- }
- _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
- TEMP_ALLOCATOR_GUARD()
- stat_path_buf: [48]u8
- path_builder := strings.builder_from_bytes(stat_path_buf[:])
- strings.write_string(&path_builder, "/proc/")
- strings.write_int(&path_builder, int(state.pid))
- strings.write_string(&path_builder, "/stat")
- stat_buf: []u8
- stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator())
- if err != nil {
- return
- }
- // ')' will be the end of the executable name (item 2)
- idx := strings.last_index_byte(string(stat_buf), ')')
- stats := string(stat_buf[idx + 2:])
- // utime and stime are the 14 and 15th items, respectively, and we are
- // currently on item 3. Skip 11 items here.
- for _ in 0..<11 {
- stats = stats[strings.index_byte(stats, ' ') + 1:]
- }
- idx = strings.index_byte(stats, ' ')
- utime_str := stats[:idx]
- stats = stats[idx + 1:]
- stime_str := stats[:strings.index_byte(stats, ' ')]
- utime, stime: int
- ok: bool
- if utime, ok = strconv.parse_int(utime_str, 10); !ok {
- return .Invalid_File
- }
- if stime, ok = strconv.parse_int(stime_str, 10); !ok {
- return .Invalid_File
- }
- // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms
- state.user_time = time.Duration(utime) * 10 * time.Millisecond
- state.system_time = time.Duration(stime) * 10 * time.Millisecond
- return
- }
- _reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error) {
- state.pid = process.pid
- _process_state_update_times(&state)
- info: linux.Sig_Info
- errno := linux.Errno.EINTR
- for errno == .EINTR {
- errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED}, nil)
- }
- err = _get_platform_error(errno)
- switch linux.Sig_Child_Code(info.code) {
- case .NONE, .CONTINUED, .STOPPED:
- unreachable()
- case .EXITED:
- state.exited = true
- state.exit_code = int(info.status)
- state.success = state.exit_code == 0
- case .KILLED, .DUMPED, .TRAPPED:
- state.exited = true
- state.exit_code = int(info.status)
- state.success = false
- }
- return
- }
- _timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
- timeout := timeout
- process_state.pid = process.pid
- pidfd := linux.Fd(process.handle)
- pollfd: [1]linux.Poll_Fd = {
- {
- fd = pidfd,
- events = {.IN},
- },
- }
- start_tick := time.tick_now()
- mask: bit_set[0..<64; u64]
- mask += { int(linux.Signal.SIGCHLD) - 1 }
- sigchld_set := transmute(linux.Sig_Set)(mask)
- info: linux.Sig_Info
- for {
- if timeout <= 0 {
- _process_state_update_times(&process_state)
- err = .Timeout
- return
- }
- ts: linux.Time_Spec = {
- time_sec = uint(timeout / time.Second),
- time_nsec = uint(timeout % time.Second),
- }
- n, errno := linux.ppoll(pollfd[:], &ts, &sigchld_set)
- if errno != .NONE {
- if errno == .EINTR {
- timeout -= time.tick_since(start_tick)
- start_tick = time.tick_now()
- continue
- }
- return process_state, _get_platform_error(errno)
- }
- if n == 0 { // timeout with no events
- _process_state_update_times(&process_state)
- err = .Timeout
- return
- }
- if errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE {
- return process_state, _get_platform_error(errno)
- }
- if info.signo == .SIGCHLD {
- break
- }
- timeout -= time.tick_since(start_tick)
- start_tick = time.tick_now()
- }
- // _reap_terminated for pidfd
- {
- _process_state_update_times(&process_state)
- errno := linux.Errno.EINTR
- for errno == .EINTR {
- errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED}, nil)
- }
- err = _get_platform_error(errno)
- switch linux.Sig_Child_Code(info.code) {
- case .NONE, .CONTINUED, .STOPPED:
- unreachable()
- case .EXITED:
- process_state.exited = true
- process_state.exit_code = int(info.status)
- process_state.success = process_state.exit_code == 0
- case .KILLED, .DUMPED, .TRAPPED:
- process_state.exited = true
- process_state.exit_code = int(info.status)
- process_state.success = false
- }
- }
- return
- }
- _timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
- timeout := timeout
- process_state.pid = process.pid
- mask: bit_set[0..<64; u64]
- mask += { int(linux.Signal.SIGCHLD) - 1 }
- sigchld_set := transmute(linux.Sig_Set)(mask)
- start_tick := time.tick_now()
- org_sigset: linux.Sig_Set
- errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset)
- if errno != .NONE {
- return process_state, _get_platform_error(errno)
- }
- defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil)
- // In case there was a signal handler on SIGCHLD, avoid race
- // condition by checking wait first.
- info: linux.Sig_Info
- errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WNOWAIT, .WEXITED, .WNOHANG}, nil)
- for errno != .NONE || info.code == 0 || info.pid != linux.Pid(process.pid) {
- if timeout <= 0 {
- _process_state_update_times(&process_state)
- err = .Timeout
- return
- }
- ts: linux.Time_Spec = {
- time_sec = uint(timeout / time.Second),
- time_nsec = uint(timeout % time.Second),
- }
- _, errno = linux.rt_sigtimedwait(&sigchld_set, &info, &ts)
- #partial switch errno {
- case .EAGAIN: // timeout
- _process_state_update_times(&process_state)
- err = .Timeout
- return
- case .EINTR:
- timeout -= time.tick_since(start_tick)
- start_tick = time.tick_now()
- case .EINVAL:
- return process_state, _get_platform_error(errno)
- }
- }
- return _reap_terminated(process)
- }
- @(private="package")
- _process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) {
- if timeout > 0 {
- if process.handle == PIDFD_UNASSIGNED {
- return _timed_wait_on_pid(process, timeout)
- } else {
- return _timed_wait_on_handle(process, timeout)
- }
- }
- process_state: Process_State = {
- pid = process.pid,
- }
- errno: linux.Errno
- options: linux.Wait_Options = {.WEXITED}
- if timeout == 0 {
- options += {.WNOHANG}
- }
- info: linux.Sig_Info
- errno = .EINTR
- for errno == .EINTR {
- errno = linux.waitid(.PID, linux.Id(process.pid), &info, options + {.WNOWAIT}, nil)
- }
- if errno == .EAGAIN || (errno == .NONE && info.signo != .SIGCHLD) {
- _process_state_update_times(&process_state)
- return process_state, .Timeout
- }
- if errno != .NONE {
- return process_state, _get_platform_error(errno)
- }
- return _reap_terminated(process)
- }
- @(private="package")
- _process_close :: proc(process: Process) -> Error {
- if process.handle == 0 || process.handle == PIDFD_UNASSIGNED {
- return nil
- }
- pidfd := linux.Fd(process.handle)
- return _get_platform_error(linux.close(pidfd))
- }
- @(private="package")
- _process_kill :: proc(process: Process) -> Error {
- return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGKILL))
- }
|