process_linux.odin 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. #+build linux
  2. #+private file
  3. package os2
  4. import "base:runtime"
  5. import "base:intrinsics"
  6. import "core:time"
  7. import "core:slice"
  8. import "core:strings"
  9. import "core:strconv"
  10. import "core:sys/linux"
  11. PIDFD_UNASSIGNED :: ~uintptr(0)
  12. @(private="package")
  13. _exit :: proc "contextless" (code: int) -> ! {
  14. linux.exit_group(i32(code))
  15. }
  16. @(private="package")
  17. _get_uid :: proc() -> int {
  18. return int(linux.getuid())
  19. }
  20. @(private="package")
  21. _get_euid :: proc() -> int {
  22. return int(linux.geteuid())
  23. }
  24. @(private="package")
  25. _get_gid :: proc() -> int {
  26. return int(linux.getgid())
  27. }
  28. @(private="package")
  29. _get_egid :: proc() -> int {
  30. return int(linux.getegid())
  31. }
  32. @(private="package")
  33. _get_pid :: proc() -> int {
  34. return int(linux.getpid())
  35. }
  36. @(private="package")
  37. _get_ppid :: proc() -> int {
  38. return int(linux.getppid())
  39. }
  40. @(private="package")
  41. _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
  42. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  43. dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
  44. #partial switch errno {
  45. case .NONE:
  46. // okay
  47. case .ENOTDIR:
  48. err = .Invalid_Dir
  49. return
  50. case .ENOENT:
  51. err = .Not_Exist
  52. return
  53. case:
  54. err = _get_platform_error(errno)
  55. return
  56. }
  57. defer linux.close(dir_fd)
  58. dynamic_list := make([dynamic]int, temp_allocator) or_return
  59. buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
  60. loop: for {
  61. buflen: int
  62. buflen, errno = linux.getdents(dir_fd, buf[:])
  63. #partial switch errno {
  64. case .EINVAL:
  65. resize(&buf, len(buf) * 2)
  66. continue loop
  67. case .NONE:
  68. if buflen == 0 { break loop }
  69. case:
  70. return {}, _get_platform_error(errno)
  71. }
  72. offset: int
  73. for d in linux.dirent_iterate_buf(buf[:buflen], &offset) {
  74. d_name_str := linux.dirent_name(d)
  75. if pid, ok := strconv.parse_int(d_name_str); ok {
  76. append(&dynamic_list, pid)
  77. }
  78. }
  79. }
  80. list, err = slice.clone(dynamic_list[:], allocator)
  81. return
  82. }
  83. @(private="package")
  84. _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  85. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  86. info.pid = pid
  87. // Use this to make cstrings without copying.
  88. path_backing: [48]u8
  89. path_builder := strings.builder_from_bytes(path_backing[:])
  90. strings.write_string(&path_builder, "/proc/")
  91. strings.write_int(&path_builder, pid)
  92. proc_fd, errno := linux.open(strings.to_cstring(&path_builder) or_return, _OPENDIR_FLAGS)
  93. if errno != .NONE {
  94. err = _get_platform_error(errno)
  95. return
  96. }
  97. defer linux.close(proc_fd)
  98. username_if: if .Username in selection {
  99. s: linux.Stat
  100. if errno = linux.fstat(proc_fd, &s); errno != .NONE {
  101. err = _get_platform_error(errno)
  102. break username_if
  103. }
  104. passwd_bytes: []u8
  105. passwd_err: Error
  106. passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
  107. if passwd_err != nil {
  108. err = passwd_err
  109. break username_if
  110. }
  111. passwd := string(passwd_bytes)
  112. for len(passwd) > 0 {
  113. n := strings.index_byte(passwd, ':')
  114. if n < 0 {
  115. break
  116. }
  117. username := passwd[:n]
  118. passwd = passwd[n+1:]
  119. // skip password field
  120. passwd = passwd[strings.index_byte(passwd, ':') + 1:]
  121. n = strings.index_byte(passwd, ':')
  122. if uid, ok := strconv.parse_int(passwd[:n]); ok && uid == int(s.uid) {
  123. info.username = strings.clone(username, allocator) or_return
  124. info.fields += {.Username}
  125. break
  126. } else if !ok {
  127. err = .Invalid_File
  128. break username_if
  129. }
  130. eol := strings.index_byte(passwd, '\n')
  131. if eol < 0 {
  132. break
  133. }
  134. passwd = passwd[eol + 1:]
  135. }
  136. }
  137. cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} {
  138. strings.builder_reset(&path_builder)
  139. strings.write_string(&path_builder, "/proc/")
  140. strings.write_int(&path_builder, pid)
  141. strings.write_string(&path_builder, "/cmdline")
  142. cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
  143. if cmdline_err != nil || len(cmdline_bytes) == 0 {
  144. err = cmdline_err
  145. break cmdline_if
  146. }
  147. cmdline := string(cmdline_bytes)
  148. terminator := strings.index_byte(cmdline, 0)
  149. assert(terminator > 0)
  150. // command_line_exec := cmdline[:terminator]
  151. // Still need cwd if the execution on the command line is relative.
  152. cwd: string
  153. cwd_err: Error
  154. if .Working_Dir in selection {
  155. strings.builder_reset(&path_builder)
  156. strings.write_string(&path_builder, "/proc/")
  157. strings.write_int(&path_builder, pid)
  158. strings.write_string(&path_builder, "/cwd")
  159. cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
  160. if cwd_err == nil && .Working_Dir in selection {
  161. info.working_dir = strings.clone(cwd, allocator) or_return
  162. info.fields += {.Working_Dir}
  163. } else if cwd_err != nil {
  164. err = cwd_err
  165. break cmdline_if
  166. }
  167. }
  168. if selection & {.Command_Line, .Command_Args} != {} {
  169. // skip to first arg
  170. //cmdline = cmdline[terminator + 1:]
  171. command_line_builder: strings.Builder
  172. command_args_list: [dynamic]string
  173. if .Command_Line in selection {
  174. command_line_builder = strings.builder_make(allocator) or_return
  175. info.fields += {.Command_Line}
  176. }
  177. for i := 0; len(cmdline) > 0; i += 1 {
  178. if terminator = strings.index_byte(cmdline, 0); terminator < 0 {
  179. break
  180. }
  181. if .Command_Line in selection {
  182. if i > 0 {
  183. strings.write_byte(&command_line_builder, ' ')
  184. }
  185. strings.write_string(&command_line_builder, cmdline[:terminator])
  186. }
  187. if .Command_Args in selection {
  188. if i == 1 {
  189. command_args_list = make([dynamic]string, allocator) or_return
  190. info.fields += {.Command_Args}
  191. }
  192. if i > 0 {
  193. arg := strings.clone(cmdline[:terminator], allocator) or_return
  194. append(&command_args_list, arg) or_return
  195. }
  196. }
  197. cmdline = cmdline[terminator + 1:]
  198. }
  199. info.command_line = strings.to_string(command_line_builder)
  200. info.command_args = command_args_list[:]
  201. }
  202. }
  203. stat_if: if selection & {.PPid, .Priority} != {} {
  204. strings.builder_reset(&path_builder)
  205. strings.write_string(&path_builder, "/proc/")
  206. strings.write_int(&path_builder, pid)
  207. strings.write_string(&path_builder, "/stat")
  208. proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
  209. if stat_err != nil {
  210. err = stat_err
  211. break stat_if
  212. }
  213. if len(proc_stat_bytes) <= 0 {
  214. break stat_if
  215. }
  216. // Skip to the first field after the executable name
  217. stats: string
  218. if start := strings.last_index_byte(string(proc_stat_bytes), ')'); start != -1 {
  219. stats = string(proc_stat_bytes[start + 2:])
  220. } else {
  221. break stat_if
  222. }
  223. // NOTE: index 0 corresponds to field 3 (state) from `man 5 proc_pid_stat`
  224. // because we skipped passed the executable name above.
  225. Fields :: enum {
  226. State,
  227. PPid,
  228. PGrp,
  229. Session,
  230. Tty_Nr,
  231. TpGid,
  232. Flags,
  233. MinFlt,
  234. CMinFlt,
  235. MajFlt,
  236. CMajFlt,
  237. UTime,
  238. STime,
  239. CUTime,
  240. CSTime,
  241. Priority,
  242. Nice,
  243. //... etc,
  244. }
  245. stat_fields := strings.split(stats, " ", temp_allocator) or_return
  246. if len(stat_fields) <= int(Fields.Nice) {
  247. break stat_if
  248. }
  249. if .PPid in selection {
  250. if ppid, ok := strconv.parse_int(stat_fields[Fields.PPid]); ok {
  251. info.ppid = ppid
  252. info.fields += {.PPid}
  253. } else {
  254. err = .Invalid_File
  255. break stat_if
  256. }
  257. }
  258. if .Priority in selection {
  259. if nice, ok := strconv.parse_int(stat_fields[Fields.Nice]); ok {
  260. info.priority = nice
  261. info.fields += {.Priority}
  262. } else {
  263. err = .Invalid_File
  264. break stat_if
  265. }
  266. }
  267. }
  268. if .Executable_Path in selection {
  269. /*
  270. NOTE(Jeroen):
  271. The old version returned the wrong executable path for things like `bash` or `sh`,
  272. for whom `/proc/<pid>/cmdline` will just report "bash" or "sh",
  273. resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there.
  274. Thanks to Yawning for suggesting `/proc/self/exe`.
  275. */
  276. strings.builder_reset(&path_builder)
  277. strings.write_string(&path_builder, "/proc/")
  278. strings.write_int(&path_builder, pid)
  279. strings.write_string(&path_builder, "/exe")
  280. if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
  281. info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
  282. info.fields += {.Executable_Path}
  283. } else {
  284. err = exe_err
  285. }
  286. }
  287. if .Environment in selection {
  288. strings.builder_reset(&path_builder)
  289. strings.write_string(&path_builder, "/proc/")
  290. strings.write_int(&path_builder, pid)
  291. strings.write_string(&path_builder, "/environ")
  292. if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
  293. env := string(env_bytes)
  294. env_list := make([dynamic]string, allocator) or_return
  295. for len(env) > 0 {
  296. terminator := strings.index_byte(env, 0)
  297. if terminator <= 0 {
  298. break
  299. }
  300. e := strings.clone(env[:terminator], allocator) or_return
  301. append(&env_list, e) or_return
  302. env = env[terminator + 1:]
  303. }
  304. info.environment = env_list[:]
  305. info.fields += {.Environment}
  306. } else if err == nil {
  307. err = env_err
  308. }
  309. }
  310. return
  311. }
  312. @(private="package")
  313. _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  314. return _process_info_by_pid(process.pid, selection, allocator)
  315. }
  316. @(private="package")
  317. _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  318. return _process_info_by_pid(get_pid(), selection, allocator)
  319. }
  320. @(private="package")
  321. _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err: Error) {
  322. process.pid = pid
  323. process.handle = PIDFD_UNASSIGNED
  324. pidfd, errno := linux.pidfd_open(linux.Pid(pid), {})
  325. if errno == .ENOSYS {
  326. return process, .Unsupported
  327. }
  328. if errno != .NONE {
  329. return process, _get_platform_error(errno)
  330. }
  331. process.handle = uintptr(pidfd)
  332. return
  333. }
  334. @(private="package")
  335. _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
  336. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  337. if len(desc.command) == 0 {
  338. return process, .Invalid_Command
  339. }
  340. dir_fd := linux.AT_FDCWD
  341. errno: linux.Errno
  342. if desc.working_dir != "" {
  343. dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
  344. if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
  345. return process, _get_platform_error(errno)
  346. }
  347. }
  348. defer if desc.working_dir != "" {
  349. linux.close(dir_fd)
  350. }
  351. // search PATH if just a plain name is provided
  352. exe_path: cstring
  353. executable_name := desc.command[0]
  354. if strings.index_byte(executable_name, '/') < 0 {
  355. path_env := get_env("PATH", temp_allocator)
  356. path_dirs := split_path_list(path_env, temp_allocator) or_return
  357. exe_builder := strings.builder_make(temp_allocator) or_return
  358. found: bool
  359. for dir in path_dirs {
  360. strings.builder_reset(&exe_builder)
  361. strings.write_string(&exe_builder, dir)
  362. strings.write_byte(&exe_builder, '/')
  363. strings.write_string(&exe_builder, executable_name)
  364. exe_path = strings.to_cstring(&exe_builder) or_return
  365. if linux.access(exe_path, linux.X_OK) == .NONE {
  366. found = true
  367. break
  368. }
  369. }
  370. if !found {
  371. // check in cwd to match windows behavior
  372. strings.builder_reset(&exe_builder)
  373. strings.write_string(&exe_builder, "./")
  374. strings.write_string(&exe_builder, executable_name)
  375. exe_path = strings.to_cstring(&exe_builder) or_return
  376. if linux.access(exe_path, linux.X_OK) != .NONE {
  377. return process, .Not_Exist
  378. }
  379. }
  380. } else {
  381. exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
  382. if linux.access(exe_path, linux.X_OK) != .NONE {
  383. return process, .Not_Exist
  384. }
  385. }
  386. // args and environment need to be a list of cstrings
  387. // that are terminated by a nil pointer.
  388. cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
  389. for command, i in desc.command {
  390. cargs[i] = clone_to_cstring(command, temp_allocator) or_return
  391. }
  392. // Use current process' environment if description didn't provide it.
  393. env: [^]cstring
  394. if desc.env == nil {
  395. // take this process's current environment
  396. env = raw_data(export_cstring_environment(temp_allocator))
  397. } else {
  398. cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
  399. for env, i in desc.env {
  400. cenv[i] = clone_to_cstring(env, temp_allocator) or_return
  401. }
  402. env = &cenv[0]
  403. }
  404. child_pipe_fds: [2]linux.Fd
  405. if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE {
  406. return process, _get_platform_error(errno)
  407. }
  408. defer linux.close(child_pipe_fds[READ])
  409. // TODO: This is the traditional textbook implementation with fork.
  410. // A more efficient implementation with vfork:
  411. //
  412. // 1. retrieve signal handlers
  413. // 2. block all signals
  414. // 3. allocate some stack space
  415. // 4. vfork (waits for child exit or execve); In child:
  416. // a. set child signal handlers
  417. // b. set up any necessary pipes
  418. // c. execve
  419. // 5. restore signal handlers
  420. //
  421. pid: linux.Pid
  422. if pid, errno = linux.fork(); errno != .NONE {
  423. linux.close(child_pipe_fds[WRITE])
  424. return process, _get_platform_error(errno)
  425. }
  426. STDIN :: linux.Fd(0)
  427. STDOUT :: linux.Fd(1)
  428. STDERR :: linux.Fd(2)
  429. READ :: 0
  430. WRITE :: 1
  431. if pid == 0 {
  432. // in child process now
  433. write_errno_to_parent_and_abort :: proc(parent_fd: linux.Fd, errno: linux.Errno) -> ! {
  434. error_byte: [1]u8 = { u8(errno) }
  435. linux.write(parent_fd, error_byte[:])
  436. linux.exit(126)
  437. }
  438. stdin_fd: linux.Fd
  439. stdout_fd: linux.Fd
  440. stderr_fd: linux.Fd
  441. if desc.stdin != nil {
  442. stdin_fd = linux.Fd(fd(desc.stdin))
  443. } else {
  444. stdin_fd, errno = linux.open("/dev/null", {})
  445. if errno != .NONE {
  446. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  447. }
  448. }
  449. write_devnull: linux.Fd = -1
  450. if desc.stdout != nil {
  451. stdout_fd = linux.Fd(fd(desc.stdout))
  452. } else {
  453. write_devnull, errno = linux.open("/dev/null", {.WRONLY})
  454. if errno != .NONE {
  455. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  456. }
  457. stdout_fd = write_devnull
  458. }
  459. if desc.stderr != nil {
  460. stderr_fd = linux.Fd(fd(desc.stderr))
  461. } else {
  462. if write_devnull < 0 {
  463. write_devnull, errno = linux.open("/dev/null", {.WRONLY})
  464. if errno != .NONE {
  465. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  466. }
  467. }
  468. stderr_fd = write_devnull
  469. }
  470. if _, errno = linux.dup2(stdin_fd, STDIN); errno != .NONE {
  471. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  472. }
  473. if _, errno = linux.dup2(stdout_fd, STDOUT); errno != .NONE {
  474. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  475. }
  476. if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE {
  477. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  478. }
  479. if dir_fd != linux.AT_FDCWD {
  480. if errno = linux.fchdir(dir_fd); errno != .NONE {
  481. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  482. }
  483. }
  484. errno = linux.execveat(dir_fd, exe_path, &cargs[0], env)
  485. assert(errno != nil)
  486. write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
  487. }
  488. linux.close(child_pipe_fds[WRITE])
  489. process.pid = int(pid)
  490. child_byte: [1]u8
  491. errno = .EINTR
  492. for errno == .EINTR {
  493. _, errno = linux.read(child_pipe_fds[READ], child_byte[:])
  494. }
  495. // If the read failed, something weird happened. Do not return the read
  496. // error so the user knows to wait on it.
  497. if errno == .NONE {
  498. child_errno := linux.Errno(child_byte[0])
  499. if child_errno != .NONE {
  500. // We can assume it trapped here.
  501. _reap_terminated(process)
  502. process.pid = 0
  503. return process, _get_platform_error(child_errno)
  504. }
  505. }
  506. process, _ = process_open(int(pid))
  507. return
  508. }
  509. _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
  510. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  511. stat_path_buf: [48]u8
  512. path_builder := strings.builder_from_bytes(stat_path_buf[:])
  513. strings.write_string(&path_builder, "/proc/")
  514. strings.write_int(&path_builder, int(state.pid))
  515. strings.write_string(&path_builder, "/stat")
  516. stat_buf: []u8
  517. stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
  518. if err != nil {
  519. return
  520. }
  521. // ')' will be the end of the executable name (item 2)
  522. idx := strings.last_index_byte(string(stat_buf), ')')
  523. stats := string(stat_buf[idx + 2:])
  524. // utime and stime are the 14 and 15th items, respectively, and we are
  525. // currently on item 3. Skip 11 items here.
  526. for _ in 0..<11 {
  527. stats = stats[strings.index_byte(stats, ' ') + 1:]
  528. }
  529. idx = strings.index_byte(stats, ' ')
  530. utime_str := stats[:idx]
  531. stats = stats[idx + 1:]
  532. stime_str := stats[:strings.index_byte(stats, ' ')]
  533. utime, stime: int
  534. ok: bool
  535. if utime, ok = strconv.parse_int(utime_str, 10); !ok {
  536. return .Invalid_File
  537. }
  538. if stime, ok = strconv.parse_int(stime_str, 10); !ok {
  539. return .Invalid_File
  540. }
  541. // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms
  542. state.user_time = time.Duration(utime) * 10 * time.Millisecond
  543. state.system_time = time.Duration(stime) * 10 * time.Millisecond
  544. return
  545. }
  546. _reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error) {
  547. state.pid = process.pid
  548. _process_state_update_times(&state)
  549. info: linux.Sig_Info
  550. errno := linux.Errno.EINTR
  551. for errno == .EINTR {
  552. errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED}, nil)
  553. }
  554. err = _get_platform_error(errno)
  555. switch linux.Sig_Child_Code(info.code) {
  556. case .NONE, .CONTINUED, .STOPPED:
  557. unreachable()
  558. case .EXITED:
  559. state.exited = true
  560. state.exit_code = int(info.status)
  561. state.success = state.exit_code == 0
  562. case .KILLED, .DUMPED, .TRAPPED:
  563. state.exited = true
  564. state.exit_code = int(info.status)
  565. state.success = false
  566. }
  567. return
  568. }
  569. _timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
  570. timeout := timeout
  571. process_state.pid = process.pid
  572. pidfd := linux.Fd(process.handle)
  573. pollfd: [1]linux.Poll_Fd = {
  574. {
  575. fd = pidfd,
  576. events = {.IN},
  577. },
  578. }
  579. start_tick := time.tick_now()
  580. mask: bit_set[0..<64; u64]
  581. mask += { int(linux.Signal.SIGCHLD) - 1 }
  582. sigchld_set := transmute(linux.Sig_Set)(mask)
  583. info: linux.Sig_Info
  584. for {
  585. if timeout <= 0 {
  586. _process_state_update_times(&process_state)
  587. err = .Timeout
  588. return
  589. }
  590. ts: linux.Time_Spec = {
  591. time_sec = uint(timeout / time.Second),
  592. time_nsec = uint(timeout % time.Second),
  593. }
  594. n, errno := linux.ppoll(pollfd[:], &ts, &sigchld_set)
  595. if errno != .NONE {
  596. if errno == .EINTR {
  597. timeout -= time.tick_since(start_tick)
  598. start_tick = time.tick_now()
  599. continue
  600. }
  601. return process_state, _get_platform_error(errno)
  602. }
  603. if n == 0 { // timeout with no events
  604. _process_state_update_times(&process_state)
  605. err = .Timeout
  606. return
  607. }
  608. if errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE {
  609. return process_state, _get_platform_error(errno)
  610. }
  611. if info.signo == .SIGCHLD {
  612. break
  613. }
  614. timeout -= time.tick_since(start_tick)
  615. start_tick = time.tick_now()
  616. }
  617. // _reap_terminated for pidfd
  618. {
  619. _process_state_update_times(&process_state)
  620. errno := linux.Errno.EINTR
  621. for errno == .EINTR {
  622. errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED}, nil)
  623. }
  624. err = _get_platform_error(errno)
  625. switch linux.Sig_Child_Code(info.code) {
  626. case .NONE, .CONTINUED, .STOPPED:
  627. unreachable()
  628. case .EXITED:
  629. process_state.exited = true
  630. process_state.exit_code = int(info.status)
  631. process_state.success = process_state.exit_code == 0
  632. case .KILLED, .DUMPED, .TRAPPED:
  633. process_state.exited = true
  634. process_state.exit_code = int(info.status)
  635. process_state.success = false
  636. }
  637. }
  638. return
  639. }
  640. _timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
  641. timeout := timeout
  642. process_state.pid = process.pid
  643. mask: bit_set[0..<64; u64]
  644. mask += { int(linux.Signal.SIGCHLD) - 1 }
  645. sigchld_set := transmute(linux.Sig_Set)(mask)
  646. start_tick := time.tick_now()
  647. org_sigset: linux.Sig_Set
  648. errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset)
  649. if errno != .NONE {
  650. return process_state, _get_platform_error(errno)
  651. }
  652. defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil)
  653. // In case there was a signal handler on SIGCHLD, avoid race
  654. // condition by checking wait first.
  655. info: linux.Sig_Info
  656. errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WNOWAIT, .WEXITED, .WNOHANG}, nil)
  657. for errno != .NONE || info.code == 0 || info.pid != linux.Pid(process.pid) {
  658. if timeout <= 0 {
  659. _process_state_update_times(&process_state)
  660. err = .Timeout
  661. return
  662. }
  663. ts: linux.Time_Spec = {
  664. time_sec = uint(timeout / time.Second),
  665. time_nsec = uint(timeout % time.Second),
  666. }
  667. _, errno = linux.rt_sigtimedwait(&sigchld_set, &info, &ts)
  668. #partial switch errno {
  669. case .EAGAIN: // timeout
  670. _process_state_update_times(&process_state)
  671. err = .Timeout
  672. return
  673. case .EINTR:
  674. timeout -= time.tick_since(start_tick)
  675. start_tick = time.tick_now()
  676. case .EINVAL:
  677. return process_state, _get_platform_error(errno)
  678. }
  679. }
  680. return _reap_terminated(process)
  681. }
  682. @(private="package")
  683. _process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) {
  684. if timeout > 0 {
  685. if process.handle == PIDFD_UNASSIGNED {
  686. return _timed_wait_on_pid(process, timeout)
  687. } else {
  688. return _timed_wait_on_handle(process, timeout)
  689. }
  690. }
  691. process_state: Process_State = {
  692. pid = process.pid,
  693. }
  694. errno: linux.Errno
  695. options: linux.Wait_Options = {.WEXITED}
  696. if timeout == 0 {
  697. options += {.WNOHANG}
  698. }
  699. info: linux.Sig_Info
  700. errno = .EINTR
  701. for errno == .EINTR {
  702. errno = linux.waitid(.PID, linux.Id(process.pid), &info, options + {.WNOWAIT}, nil)
  703. }
  704. if errno == .EAGAIN || (errno == .NONE && info.signo != .SIGCHLD) {
  705. _process_state_update_times(&process_state)
  706. return process_state, .Timeout
  707. }
  708. if errno != .NONE {
  709. return process_state, _get_platform_error(errno)
  710. }
  711. return _reap_terminated(process)
  712. }
  713. @(private="package")
  714. _process_close :: proc(process: Process) -> Error {
  715. if process.handle == 0 || process.handle == PIDFD_UNASSIGNED {
  716. return nil
  717. }
  718. pidfd := linux.Fd(process.handle)
  719. return _get_platform_error(linux.close(pidfd))
  720. }
  721. @(private="package")
  722. _process_kill :: proc(process: Process) -> Error {
  723. return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGKILL))
  724. }