process_posix_darwin.odin 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #+private
  2. package os2
  3. import "base:runtime"
  4. import "base:intrinsics"
  5. import "core:bytes"
  6. import "core:sys/darwin"
  7. import "core:sys/posix"
  8. import "core:sys/unix"
  9. import "core:time"
  10. foreign import lib "system:System"
  11. foreign lib {
  12. sysctl :: proc(
  13. name: [^]i32, namelen: u32,
  14. oldp: rawptr, oldlenp: ^uint,
  15. newp: rawptr, newlen: uint,
  16. ) -> posix.result ---
  17. }
  18. _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  19. get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, prio: Maybe(i32), uid: posix.uid_t, ok: bool) {
  20. // Short info is enough and requires less permissions if the priority isn't requested.
  21. if .Priority in selection {
  22. info: darwin.proc_taskallinfo
  23. ret := darwin.proc_pidinfo(posix.pid_t(pid), .TASKALLINFO, 0, &info, size_of(info))
  24. if ret > 0 {
  25. assert(ret == size_of(info))
  26. ppid = info.pbsd.pbi_ppid
  27. prio = info.ptinfo.pti_priority
  28. uid = info.pbsd.pbi_uid
  29. ok = true
  30. return
  31. }
  32. }
  33. // Try short info, requires less permissions, but doesn't give a `nice`.
  34. psinfo: darwin.proc_bsdshortinfo
  35. ret := darwin.proc_pidinfo(posix.pid_t(pid), .SHORTBSDINFO, 0, &psinfo, size_of(psinfo))
  36. if ret > 0 {
  37. assert(ret == size_of(psinfo))
  38. ppid = psinfo.pbsi_ppid
  39. uid = psinfo.pbsi_uid
  40. ok = true
  41. }
  42. return
  43. }
  44. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  45. info.pid = pid
  46. // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
  47. // 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.
  48. pidinfo: {
  49. if selection & {.PPid, .Priority, .Username } != {} {
  50. ppid, mprio, uid, ok := get_pidinfo(pid, selection)
  51. if !ok {
  52. if err == nil {
  53. err = _get_platform_error()
  54. }
  55. break pidinfo
  56. }
  57. if .PPid in selection {
  58. info.ppid = int(ppid)
  59. info.fields += {.PPid}
  60. }
  61. if prio, has_prio := mprio.?; has_prio && .Priority in selection {
  62. info.priority = int(prio)
  63. info.fields += {.Priority}
  64. }
  65. if .Username in selection {
  66. pw := posix.getpwuid(uid)
  67. if pw == nil {
  68. if err == nil {
  69. err = _get_platform_error()
  70. }
  71. break pidinfo
  72. }
  73. info.username = clone_string(string(pw.pw_name), allocator) or_return
  74. info.fields += {.Username}
  75. }
  76. }
  77. }
  78. if .Working_Dir in selection {
  79. pinfo: darwin.proc_vnodepathinfo
  80. ret := darwin.proc_pidinfo(posix.pid_t(pid), .VNODEPATHINFO, 0, &pinfo, size_of(pinfo))
  81. if ret > 0 {
  82. assert(ret == size_of(pinfo))
  83. info.working_dir = clone_string(string(cstring(raw_data(pinfo.pvi_cdir.vip_path[:]))), allocator) or_return
  84. info.fields += {.Working_Dir}
  85. } else if err == nil {
  86. err = _get_platform_error()
  87. }
  88. }
  89. if .Executable_Path in selection {
  90. buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = ---
  91. ret := darwin.proc_pidpath(posix.pid_t(pid), raw_data(buffer[:]), len(buffer))
  92. if ret > 0 {
  93. info.executable_path = clone_string(string(buffer[:ret]), allocator) or_return
  94. info.fields += {.Executable_Path}
  95. } else if err == nil {
  96. err = _get_platform_error()
  97. }
  98. }
  99. args: if selection & { .Command_Line, .Command_Args, .Environment } != {} {
  100. mib := []i32{
  101. unix.CTL_KERN,
  102. unix.KERN_PROCARGS2,
  103. i32(pid),
  104. }
  105. length: uint
  106. if sysctl(raw_data(mib), 3, nil, &length, nil, 0) != .OK {
  107. if err == nil {
  108. err = _get_platform_error()
  109. }
  110. break args
  111. }
  112. buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
  113. if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
  114. if err == nil {
  115. err = _get_platform_error()
  116. // Looks like EINVAL is returned here if you don't have permission.
  117. if err == Platform_Error(posix.Errno.EINVAL) {
  118. err = .Permission_Denied
  119. }
  120. }
  121. break args
  122. }
  123. buf = buf[:length]
  124. if len(buf) < 4 {
  125. break args
  126. }
  127. // Layout isn't really documented anywhere, I deduced it to be:
  128. // i32 - argc
  129. // cstring - command name (skipped)
  130. // [^]byte - couple of 0 bytes (skipped)
  131. // [^]cstring - argv (up to argc entries)
  132. // [^]cstring - key=value env entries until the end (many intermittent 0 bytes and entries without `=` we skip here too)
  133. argc := (^i32)(raw_data(buf))^
  134. buf = buf[size_of(i32):]
  135. {
  136. command_line: [dynamic]byte
  137. command_line.allocator = allocator
  138. argv: [dynamic]string
  139. argv.allocator = allocator
  140. defer if err != nil {
  141. for arg in argv { delete(arg, allocator) }
  142. delete(argv)
  143. delete(command_line)
  144. }
  145. _, _ = bytes.split_iterator(&buf, {0})
  146. buf = bytes.trim_left(buf, {0})
  147. first_arg := true
  148. for arg in bytes.split_iterator(&buf, {0}) {
  149. if .Command_Line in selection {
  150. if !first_arg {
  151. append(&command_line, ' ') or_return
  152. }
  153. append(&command_line, ..arg) or_return
  154. }
  155. if .Command_Args in selection {
  156. sarg := clone_string(string(arg), allocator) or_return
  157. append(&argv, sarg) or_return
  158. }
  159. first_arg = false
  160. argc -= 1
  161. if argc == 0 {
  162. break
  163. }
  164. }
  165. if .Command_Line in selection {
  166. info.command_line = string(command_line[:])
  167. info.fields += {.Command_Line}
  168. }
  169. if .Command_Args in selection {
  170. info.command_args = argv[:]
  171. info.fields += {.Command_Args}
  172. }
  173. }
  174. if .Environment in selection {
  175. environment: [dynamic]string
  176. environment.allocator = allocator
  177. defer if err != nil {
  178. for entry in environment { delete(entry, allocator) }
  179. delete(environment)
  180. }
  181. for entry in bytes.split_iterator(&buf, {0}) {
  182. if bytes.index_byte(entry, '=') > -1 {
  183. sentry := clone_string(string(entry), allocator) or_return
  184. append(&environment, sentry) or_return
  185. }
  186. }
  187. info.environment = environment[:]
  188. info.fields += {.Environment}
  189. }
  190. }
  191. // Fields were requested that we didn't add.
  192. if err == nil && selection - info.fields != {} {
  193. err = .Unsupported
  194. }
  195. return
  196. }
  197. _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
  198. ret := darwin.proc_listallpids(nil, 0)
  199. if ret < 0 {
  200. err = _get_platform_error()
  201. return
  202. }
  203. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  204. buffer := make([]i32, ret, temp_allocator)
  205. ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
  206. if ret < 0 {
  207. err = _get_platform_error()
  208. return
  209. }
  210. list = make([]int, ret, allocator) or_return
  211. #no_bounds_check for &entry, i in list {
  212. entry = int(buffer[i])
  213. }
  214. return
  215. }
  216. _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
  217. rusage: darwin.rusage_info_v0
  218. if ret := darwin.proc_pid_rusage(posix.pid_t(pid), .V0, &rusage); ret != 0 {
  219. err = _get_platform_error()
  220. return
  221. }
  222. // Using the start time as the handle, there is no pidfd or anything on Darwin.
  223. // There is a uuid, but once a process becomes a zombie it changes...
  224. process.handle = uintptr(rusage.ri_proc_start_abstime)
  225. process.pid = int(pid)
  226. return
  227. }
  228. _process_handle_still_valid :: proc(p: Process) -> Error {
  229. rusage: darwin.rusage_info_v0
  230. if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 {
  231. return _get_platform_error()
  232. }
  233. handle := uintptr(rusage.ri_proc_start_abstime)
  234. if p.handle != handle {
  235. return posix.Errno.ESRCH
  236. }
  237. return nil
  238. }
  239. _process_state_update_times :: proc(p: Process, state: ^Process_State) {
  240. rusage: darwin.rusage_info_v0
  241. if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 {
  242. return
  243. }
  244. // NOTE(laytan): I have no clue if this is correct, the output seems correct comparing it with `time`'s output.
  245. HZ :: 20000000
  246. state.user_time = (
  247. (time.Duration(rusage.ri_user_time) / HZ * time.Second) +
  248. time.Duration(rusage.ri_user_time % HZ))
  249. state.system_time = (
  250. (time.Duration(rusage.ri_system_time) / HZ * time.Second) +
  251. time.Duration(rusage.ri_system_time % HZ))
  252. return
  253. }