mirror of
https://github.com/odin-lang/Odin.git
synced 2026-02-17 08:34:08 +00:00
333 lines
8.5 KiB
Odin
333 lines
8.5 KiB
Odin
#+private
|
|
package os
|
|
|
|
import "base:runtime"
|
|
import "base:intrinsics"
|
|
|
|
import "core:bytes"
|
|
import "core:c"
|
|
import "core:sys/darwin"
|
|
import "core:sys/posix"
|
|
import "core:sys/unix"
|
|
import "core:time"
|
|
|
|
foreign import libc "system:System"
|
|
foreign import pthread "system:System"
|
|
|
|
foreign libc {
|
|
sysctl :: proc "c" (
|
|
name: [^]i32, namelen: u32,
|
|
oldp: rawptr, oldlenp: ^uint,
|
|
newp: rawptr, newlen: uint,
|
|
) -> posix.result ---
|
|
|
|
@(link_name="sysctlbyname")
|
|
_sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
|
|
}
|
|
|
|
_get_current_thread_id :: proc "contextless" () -> int {
|
|
tid: u64
|
|
// NOTE(Oskar): available from OSX 10.6 and iOS 3.2.
|
|
// For older versions there is `syscall(SYS_thread_selfid)`, but not really
|
|
// the same thing apparently.
|
|
foreign pthread { pthread_threadid_np :: proc "c" (rawptr, ^u64) -> c.int --- }
|
|
pthread_threadid_np(nil, &tid)
|
|
return int(tid)
|
|
}
|
|
|
|
_get_processor_core_count :: proc() -> int {
|
|
count : int = 0
|
|
count_size := size_of(count)
|
|
if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 {
|
|
if count > 0 {
|
|
return count
|
|
}
|
|
}
|
|
|
|
return 1
|
|
}
|
|
|
|
_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.
|
|
if .Priority in selection {
|
|
info: darwin.proc_taskallinfo
|
|
ret := darwin.proc_pidinfo(posix.pid_t(pid), .TASKALLINFO, 0, &info, size_of(info))
|
|
if ret > 0 {
|
|
assert(ret == size_of(info))
|
|
ppid = info.pbsd.pbi_ppid
|
|
prio = info.ptinfo.pti_priority
|
|
uid = info.pbsd.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
|
|
}
|
|
|
|
|
|
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
|
info.pid = pid
|
|
|
|
// 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, mprio, 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 prio, has_prio := mprio.?; has_prio && .Priority in selection {
|
|
info.priority = int(prio)
|
|
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 := runtime.make_aligned([]byte, length, 4, temp_allocator)
|
|
if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
|
|
if err == nil {
|
|
err = _get_platform_error()
|
|
|
|
// Looks like EINVAL is returned here if you don't have permission.
|
|
if err == Platform_Error(posix.Errno.EINVAL) {
|
|
err = .Permission_Denied
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
|
|
|
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
|
|
}
|
|
|
|
_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
|
|
}
|
|
|
|
// Using the start time as the handle, there is no pidfd or anything on Darwin.
|
|
// There is a uuid, but once a process becomes a zombie it changes...
|
|
process.handle = uintptr(rusage.ri_proc_start_abstime)
|
|
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()
|
|
}
|
|
|
|
handle := uintptr(rusage.ri_proc_start_abstime)
|
|
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
|
|
}
|