diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index f90baa699..8a2163634 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -30,7 +30,7 @@ General_Error :: enum u32 { Unsupported, } -Platform_Error :: enum i32 {None=0} +Platform_Error :: _Platform_Error Error :: union #shared_nil { General_Error, diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index 7f28d1c41..29815bf79 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -3,6 +3,8 @@ package os2 import "core:sys/linux" +_Platform_Error :: linux.Errno + @(rodata) _errno_strings := [linux.Errno]string{ .NONE = "", diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin index 7143557a6..9e3424a4a 100644 --- a/core/os/os2/errors_posix.odin +++ b/core/os/os2/errors_posix.odin @@ -4,6 +4,8 @@ package os2 import "core:sys/posix" +_Platform_Error :: posix.Errno + _error_string :: proc(errno: i32) -> string { return string(posix.strerror(posix.Errno(errno))) } diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6421d26ee..6748c1167 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -5,6 +5,8 @@ import "base:runtime" import "core:slice" import win32 "core:sys/windows" +_Platform_Error :: win32.System_Error + _error_string :: proc(errno: i32) -> string { e := win32.DWORD(errno) if e == 0 { @@ -68,4 +70,4 @@ _get_platform_error :: proc() -> Error { // fallthrough } return Platform_Error(err) -} \ No newline at end of file +} diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 60dc29189..40406bad5 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -576,10 +576,13 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { success_byte: [1]u8 linux.write(child_pipe_fds[WRITE], success_byte[:]) - if errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH}); errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - unreachable() + errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH}) + + // NOTE: we can't tell the parent about this failure because we already wrote the success byte. + // So if this happens the user will just see the process failed when they call process_wait. + + assert(errno != nil) + intrinsics.trap() } process.pid = int(pid) diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin index 8a27efce4..c9b67f199 100644 --- a/core/os/os2/process_posix.odin +++ b/core/os/os2/process_posix.odin @@ -3,9 +3,13 @@ package os2 import "base:runtime" -import "core:time" -import "core:sys/posix" +import "core:time" +import "core:strings" +import "core:path/filepath" + +import kq "core:sys/kqueue" +import "core:sys/posix" _exit :: proc "contextless" (code: int) -> ! { posix.exit(i32(code)) @@ -43,27 +47,309 @@ _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 + } + + TEMP_ALLOCATOR_GUARD() + + // search PATH if just a plain name is provided. + exe_builder := strings.builder_make(temp_allocator()) + exe_name := desc.command[0] + if strings.index_byte(exe_name, '/') < 0 { + path_env := get_env("PATH", temp_allocator()) + path_dirs := filepath.split_list(path_env, temp_allocator()) + + 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, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + continue + } else { + posix.close(exe_fd) + found = true + break + } + } + if !found { + // check in cwd to match windows behavior + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, desc.working_dir) + if len(desc.working_dir) > 0 && desc.working_dir[len(desc.working_dir)-1] != '/' { + strings.write_byte(&exe_builder, '/') + } + strings.write_string(&exe_builder, "./") + strings.write_string(&exe_builder, exe_name) + + // "hello/./world" is fine right? + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + } else { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + + 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) + } + + env: [^]cstring + if desc.env == nil { + // take this process's current environment + env = posix.environ + } else { + cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) + for env, i in desc.env { + cenv[i] = temp_cstring(env) + } + env = raw_data(cenv) + } + + READ :: 0 + WRITE :: 1 + + pipe: [2]posix.FD + if posix.pipe(&pipe) != .OK { + err = _get_platform_error() + return + } + defer posix.close(pipe[WRITE]) + defer posix.close(pipe[READ]) + + if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + + switch pid := posix.fork(); pid { + case -1: + err = _get_platform_error() + return + + case 0: + abort :: proc(parent_fd: posix.FD) -> ! { + #assert(len(posix.Errno) < max(u8)) + errno := u8(posix.errno()) + posix.write(parent_fd, &errno, 1) + runtime.trap() + } + + null := posix.open("/dev/null", {.RDWR}) + if null == -1 { abort(pipe[WRITE]) } + + 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 + + if posix.dup2(stderr, posix.STDERR_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdout, posix.STDOUT_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdin, posix.STDIN_FILENO ) == -1 { abort(pipe[WRITE]) } + + if cwd != nil { + if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) } + } + + ok := u8(0) + posix.write(pipe[WRITE], &ok, 1) + + res := posix.execve(strings.to_cstring(&exe_builder), raw_data(cmd), env) + + // NOTE: we can't tell the parent about this failure because we already wrote the success byte. + // So if this happens the user will just see the process failed when they call process_wait. + + assert(res == -1) + runtime.trap() + + case: + errno: posix.Errno + for { + errno_byte: u8 + switch posix.read(pipe[READ], &errno_byte, 1) { + case 1: + errno = posix.Errno(errno_byte) + case: + errno = posix.errno() + if errno == .EINTR { + continue + } else { + // If the read failed, something weird happened. Do not return the read + // error so the user knows to wait on it. + errno = nil + } + } + break + } + + if errno != nil { + // We can assume it trapped here. + + for { + info: posix.siginfo_t + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 && posix.errno() == .EINTR { + continue + } + break + } + + err = errno + return + } + + process.pid = int(pid) + process, _ = _process_open(int(pid), {}) + return + } } _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { - err = .Unsupported + process_state.pid = process.pid + + _process_handle_still_valid(process) or_return + + // timeout > 0 = use kqueue to wait (with a timeout) on process exit + // timeout == 0 = use waitid with WNOHANG so it returns immediately + // timeout > 0 = use waitid without WNOHANG so it waits indefinitely + // + // at the end use waitid to actually reap the process and get it's status + + if timeout > 0 { + timeout := timeout + + queue := kq.kqueue() or_return + defer posix.close(queue) + + changelist, eventlist: [1]kq.KEvent + + changelist[0] = { + ident = uintptr(process.pid), + filter = .Proc, + flags = { .Add }, + fflags = { + fproc = { .Exit }, + }, + } + + for { + start := time.tick_now() + n, kerr := kq.kevent(queue, changelist[:], eventlist[:], &{ + tv_sec = posix.time_t(timeout / time.Second), + tv_nsec = i64(timeout % time.Second), + }) + if kerr == .EINTR { + timeout -= time.tick_since(start) + continue + } else if kerr != nil { + err = kerr + return + } else if n == 0 { + err = .Timeout + _process_state_update_times(process, &process_state) + return + } else { + _process_state_update_times(process, &process_state) + break + } + } + } else { + flags := posix.Wait_Flags{.EXITED, .NOWAIT} + if timeout == 0 { + flags += {.NOHANG} + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, flags) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + _process_state_update_times(process, &process_state) + + if info.si_signo == nil { + assert(timeout == 0) + err = .Timeout + return + } + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + switch info.si_code.chld { + case: unreachable() + case .CONTINUED, .STOPPED: unreachable() + case .EXITED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = process_state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = false + } + 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) { + _process_handle_still_valid(process) or_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..ace7a4fa2 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -8,6 +8,7 @@ import "core:bytes" import "core:sys/darwin" import "core:sys/posix" import "core:sys/unix" +import "core:time" foreign import lib "system:System.framework" @@ -254,3 +255,58 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) return } + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(pid), .V0, &rusage); ret != 0 { + err = _get_platform_error() + return + } + + // XOR fold the UUID so it fits the handle, I think this is enough to verify pid uniqueness. + #assert(size_of(uintptr) == size_of(u64)) + a := intrinsics.unaligned_load((^u64)(&rusage.ri_uuid)) + b := intrinsics.unaligned_load((^u64)(&rusage.ri_uuid[8])) + process.handle = uintptr(a ~ b) + + process.pid = int(pid) + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return _get_platform_error() + } + + // XOR fold the UUID so it fits the handle, I think this is enough to verify pid uniqueness. + #assert(size_of(uintptr) == size_of(u64)) + a := intrinsics.unaligned_load((^u64)(&rusage.ri_uuid)) + b := intrinsics.unaligned_load((^u64)(&rusage.ri_uuid[8])) + handle := uintptr(a ~ b) + + if p.handle != handle { + return posix.Errno.ESRCH + } + + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return + } + + // NOTE(laytan): I have no clue if this is correct, the output seems correct comparing it with `time`'s output. + HZ :: 20000000 + + state.user_time = ( + (time.Duration(rusage.ri_user_time) / HZ * time.Second) + + time.Duration(rusage.ri_user_time % HZ)) + state.system_time = ( + (time.Duration(rusage.ri_system_time) / HZ * time.Second) + + time.Duration(rusage.ri_system_time % HZ)) + + return +} diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin index ce84b1012..02f78b9ac 100644 --- a/core/os/os2/process_posix_other.odin +++ b/core/os/os2/process_posix_other.odin @@ -13,3 +13,16 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) err = .Unsupported return } + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + return +} diff --git a/core/sys/darwin/proc.odin b/core/sys/darwin/proc.odin index 8f7eb1fcb..fa5391f6f 100644 --- a/core/sys/darwin/proc.odin +++ b/core/sys/darwin/proc.odin @@ -12,6 +12,7 @@ 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 --- + proc_pid_rusage :: proc(pid: posix.pid_t, flavor: Pid_Rusage_Flavor, buffer: rawptr) -> i32 --- } MAXCOMLEN :: 16 @@ -166,3 +167,26 @@ PID_Info_Flavor :: enum i32 { } PIDPATHINFO_MAXSIZE :: 4*posix.PATH_MAX + +Pid_Rusage_Flavor :: enum i32 { + V0, + V1, + V2, + V3, + V4, + V5, +} + +rusage_info_v0 :: struct { + ri_uuid: [16]u8, + ri_user_time: u64, + ri_system_time: u64, + ri_pkg_idle_wkups: u64, + ri_interrupt_wkups: u64, + ri_pageins: u64, + ri_wired_size: u64, + ri_resident_size: u64, + ri_phys_footprint: u64, + ri_proc_start_abstime: u64, + ri_proc_exit_abstime: u64, +} diff --git a/core/sys/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin new file mode 100644 index 000000000..27d1ecaae --- /dev/null +++ b/core/sys/kqueue/kqueue.odin @@ -0,0 +1,256 @@ +//+build darwin, netbsd, openbsd, freebsd +package kqueue + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +import "base:intrinsics" + +import "core:c" +import "core:sys/posix" + +KQ :: posix.FD + +kqueue :: proc() -> (kq: KQ, err: posix.Errno) { + kq = _kqueue() + if kq == -1 { + err = posix.errno() + } + return +} + +kevent :: proc(kq: KQ, change_list: []KEvent, event_list: []KEvent, timeout: ^posix.timespec) -> (n_events: c.int, err: posix.Errno) { + n_events = _kevent( + kq, + raw_data(change_list), + c.int(len(change_list)), + raw_data(event_list), + c.int(len(event_list)), + timeout, + ) + if n_events == -1 { + err = posix.errno() + } + return +} + +Flag :: enum _Flags_Backing { + Add = log2(0x0001), // Add event to kq (implies .Enable). + Delete = log2(0x0002), // Delete event from kq. + Enable = log2(0x0004), // Enable event. + Disable = log2(0x0008), // Disable event (not reported). + One_Shot = log2(0x0010), // Only report one occurrence. + Clear = log2(0x0020), // Clear event state after reporting. + Receipt = log2(0x0040), // Force immediate event output. + Dispatch = log2(0x0080), // Disable event after reporting. + + Error = log2(0x4000), // Error, data contains errno. + EOF = log2(0x8000), // EOF detected. +} +Flags :: bit_set[Flag; _Flags_Backing] + +Filter :: enum _Filter_Backing { + Read = _FILTER_READ, // Check for read availability on the file descriptor. + Write = _FILTER_WRITE, // Check for write availability on the file descriptor. + AIO = _FILTER_AIO, // Attached to AIO requests. + VNode = _FILTER_VNODE, // Check for changes to the subject file. + Proc = _FILTER_PROC, // Check for changes to the subject process. + Signal = _FILTER_SIGNAL, // Check for signals delivered to the process. + Timer = _FILTER_TIMER, // Timers. +} + +RW_Flag :: enum u32 { + Low_Water_Mark = log2(0x00000001), +} +RW_Flags :: bit_set[RW_Flag; u32] + +VNode_Flag :: enum u32 { + Delete = log2(0x00000001), // Deleted. + Write = log2(0x00000002), // Contents changed. + Extend = log2(0x00000004), // Size increased. + Attrib = log2(0x00000008), // Attributes changed. + Link = log2(0x00000010), // Link count changed. + Rename = log2(0x00000020), // Renamed. + Revoke = log2(0x00000040), // Access was revoked. +} +VNode_Flags :: bit_set[VNode_Flag; u32] + +Proc_Flag :: enum u32 { + Exit = log2(0x80000000), // Process exited. + Fork = log2(0x40000000), // Process forked. + Exec = log2(0x20000000), // Process exec'd. + Signal = log2(0x08000000), // Shared with `Filter.Signal`. +} +Proc_Flags :: bit_set[Proc_Flag; u32] + +Timer_Flag :: enum u32 { + Seconds = log2(0x00000001), // Data is seconds. + USeconds = log2(0x00000002), // Data is microseconds. + NSeconds = log2(_NOTE_NSECONDS), // Data is nanoseconds. + Absolute = log2(_NOTE_ABSOLUTE), // Absolute timeout. +} +Timer_Flags :: bit_set[Timer_Flag; u32] + +when ODIN_OS == .Darwin { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000004 + _NOTE_ABSOLUTE :: 0x00000008 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: c.long /* intptr_t */, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + } + +} else when ODIN_OS == .FreeBSD { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000004 + _NOTE_ABSOLUTE :: 0x00000008 + + KEvent :: struct { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + // Extensions. + ext: [4]u64, + } +} else when ODIN_OS == .NetBSD { + + _Filter_Backing :: distinct u32 + _Flags_Backing :: distinct u32 + + _FILTER_READ :: 0 + _FILTER_WRITE :: 1 + _FILTER_AIO :: 2 + _FILTER_VNODE :: 3 + _FILTER_PROC :: 4 + _FILTER_SIGNAL :: 5 + _FILTER_TIMER :: 6 + + _NOTE_NSECONDS :: 0x00000003 + _NOTE_ABSOLUTE :: 0x00000010 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + // Extensions. + ext: [4]u64, + } +} else when ODIN_OS == .OpenBSD { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000003 + _NOTE_ABSOLUTE :: 0x00000010 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + } +} + +@(private) +log2 :: intrinsics.constant_log2 + +foreign lib { + @(link_name="kqueue") + _kqueue :: proc() -> KQ --- + @(link_name="kevent") + _kevent :: proc(kq: KQ, change_list: [^]KEvent, n_changes: c.int, event_list: [^]KEvent, n_events: c.int, timeout: ^posix.timespec) -> c.int --- +}