process_linux.odin 21 KB

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