1use std::os::unix::io::RawFd;
13use std::time::{Duration, Instant};
14
15use crate::CoreError;
16use crate::error::syscall_ret;
17use crate::fd::Fd;
18use crate::io::DrainState;
19use crate::reactor::Reactor;
20use libc::{
21 O_CLOEXEC, O_NONBLOCK, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG, pid_t, pipe2, waitpid,
22};
23
24mod exec;
25mod posix;
26mod fork;
27
28use exec::ExecContext;
29use fork::spawn_fork_internal;
30use posix::spawn_posix_internal;
31
32unsafe extern "C" {
33 pub(crate) static mut environ: *mut *mut libc::c_char;
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub enum CancelPolicy {
39 #[default]
41 None,
42 Graceful,
44 Kill,
46}
47
48#[derive(Debug, Clone, Copy, Default)]
50pub struct ProcessGroup {
51 pub leader: Option<pid_t>,
53 pub isolated: bool,
55}
56
57impl ProcessGroup {
58 pub fn new(leader: Option<pid_t>, isolated: bool) -> Self {
60 Self { leader, isolated }
61 }
62}
63
64#[inline(always)]
65fn errno() -> i32 {
66 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
67}
68
69#[inline(always)]
72fn make_pipe() -> Result<(Fd, Fd), CoreError> {
73 let mut fds = [0; 2];
74 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC | O_NONBLOCK) };
75 syscall_ret(r, "pipe2")?;
76 Ok((Fd::new(fds[0], "pipe2")?, Fd::new(fds[1], "pipe2")?))
77}
78
79fn make_cloexec_pipe() -> Result<(RawFd, RawFd), CoreError> {
80 let mut fds = [0; 2];
81 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC) };
82 syscall_ret(r, "pipe2")?;
83 Ok((fds[0], fds[1]))
84}
85
86struct Pipes {
87 stdin_r: Option<Fd>,
88 stdin_w: Option<Fd>,
89 stdout_r: Option<Fd>,
90 stdout_w: Option<Fd>,
91 stderr_r: Option<Fd>,
92 stderr_w: Option<Fd>,
93}
94
95impl Pipes {
96 fn new(in_buf: Option<&[u8]>, out: bool, err: bool) -> Result<Self, CoreError> {
97 let (stdin_r, stdin_w) = if in_buf.is_some() {
98 let (r, w) = make_pipe()?;
99 (Some(r), Some(w))
100 } else {
101 (None, None)
102 };
103
104 let (stdout_r, stdout_w) = if out {
105 let (r, w) = make_pipe()?;
106 (Some(r), Some(w))
107 } else {
108 (None, None)
109 };
110
111 let (stderr_r, stderr_w) = if err {
112 let (r, w) = make_pipe()?;
113 (Some(r), Some(w))
114 } else {
115 (None, None)
116 };
117
118 Ok(Self {
119 stdin_r,
120 stdin_w,
121 stdout_r,
122 stdout_w,
123 stderr_r,
124 stderr_w,
125 })
126 }
127
128 #[inline(always)]
129 fn close_all(&mut self) {
130 self.stdin_r.take();
131 self.stdin_w.take();
132 self.stdout_r.take();
133 self.stdout_w.take();
134 self.stderr_r.take();
135 self.stderr_w.take();
136 }
137}
138
139#[derive(Debug, PartialEq, Eq)]
141pub enum ExitStatus {
142 Exited(i32),
144 Signaled(i32),
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum SpawnBackend {
151 PosixSpawn,
153 Fork,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Default)]
162pub enum SpawnFdPolicy {
163 #[default]
165 CloexecOnly,
166 CloseFrom3,
169 Allowlist(Vec<RawFd>),
176}
177
178#[inline(always)]
179fn decode_status(status: i32) -> ExitStatus {
180 if WIFEXITED(status) {
181 ExitStatus::Exited(WEXITSTATUS(status))
182 } else if WIFSIGNALED(status) {
183 ExitStatus::Signaled(WTERMSIG(status))
184 } else {
185 ExitStatus::Exited(-1)
186 }
187}
188
189pub struct Process {
197 pid: pid_t,
198}
199
200impl Process {
201 pub fn new(pid: pid_t) -> Self {
203 Self { pid }
204 }
205
206 pub fn pid(&self) -> pid_t {
208 self.pid
209 }
210
211 pub fn wait_step(&self) -> Result<Option<ExitStatus>, CoreError> {
217 loop {
218 let mut status = 0;
219 let r = unsafe { waitpid(self.pid, &mut status, libc::WNOHANG) };
220 if r == 0 {
221 return Ok(None);
222 }
223 if r < 0 {
224 let e = errno();
225 if e == libc::EINTR {
226 continue;
227 }
228 return Err(CoreError::sys(e, "waitpid_step"));
229 }
230 return Ok(Some(decode_status(status)));
231 }
232 }
233
234 pub fn wait_blocking(&self) -> Result<ExitStatus, CoreError> {
239 loop {
240 let mut status = 0;
241 let r = unsafe { waitpid(self.pid, &mut status, 0) };
242 if r < 0 {
243 let e = errno();
244 if e == libc::EINTR {
245 continue;
246 }
247 return Err(CoreError::sys(e, "waitpid_blocking"));
248 }
249 return Ok(decode_status(status));
250 }
251 }
252
253 pub fn kill(&self, sig: i32) -> Result<(), CoreError> {
260 let r = unsafe { libc::kill(self.pid, sig) };
261 if r < 0 {
262 let e = errno();
263 if e == libc::ESRCH {
264 return Ok(());
265 }
266 syscall_ret(-1, "kill")?;
267 }
268 Ok(())
269 }
270
271 pub fn kill_pgroup(&self, sig: i32) -> Result<(), CoreError> {
278 self.kill_group(self.pid, sig)
279 }
280
281 pub fn kill_group(&self, pgid: pid_t, sig: i32) -> Result<(), CoreError> {
290 let r = unsafe { libc::kill(-pgid, sig) };
291 if r < 0 {
292 let e = errno();
293 if e == libc::ESRCH {
294 return Ok(());
295 }
296 syscall_ret(-1, "kill_group")?;
297 }
298 Ok(())
299 }
300}
301
302#[derive(Clone)]
304pub struct SpawnOptions {
305 ctx: ExecContext,
306 stdin: Option<Box<[u8]>>,
307 capture_stdout: bool,
308 capture_stderr: bool,
309 wait: bool,
310 pgroup: ProcessGroup,
311 max_output: usize,
312 timeout_ms: Option<u32>,
313 kill_grace_ms: u32,
314 cancel: CancelPolicy,
315 backend: SpawnBackend,
316 fd_policy: SpawnFdPolicy,
317 early_exit: Option<fn(&[u8]) -> bool>,
318}
319
320impl SpawnOptions {
321 pub fn builder(argv: Vec<String>, backend: SpawnBackend) -> SpawnOptionsBuilder {
323 SpawnOptionsBuilder::new(argv, backend)
324 }
325
326 pub fn run(self) -> Result<Output, CoreError> {
328 spawn(self)
329 }
330}
331
332#[derive(Clone)]
334pub struct SpawnOptionsBuilder {
335 argv: Vec<String>,
336 env: Option<Vec<String>>,
337 cwd: Option<String>,
338 stdin: Option<Box<[u8]>>,
339 capture_stdout: bool,
340 capture_stderr: bool,
341 wait: bool,
342 pgroup: ProcessGroup,
343 max_output: usize,
344 timeout_ms: Option<u32>,
345 kill_grace_ms: u32,
346 cancel: CancelPolicy,
347 backend: SpawnBackend,
348 fd_policy: SpawnFdPolicy,
349 early_exit: Option<fn(&[u8]) -> bool>,
350}
351
352impl SpawnOptionsBuilder {
353 pub fn new(argv: Vec<String>, backend: SpawnBackend) -> Self {
355 Self {
356 argv,
357 env: None,
358 cwd: None,
359 stdin: None,
360 capture_stdout: false,
361 capture_stderr: false,
362 wait: true,
363 pgroup: ProcessGroup::default(),
364 max_output: 1024 * 1024,
365 timeout_ms: None,
366 kill_grace_ms: 2000,
367 cancel: CancelPolicy::Kill,
368 backend,
369 fd_policy: SpawnFdPolicy::default(),
370 early_exit: None,
371 }
372 }
373
374 pub fn env(mut self, env: Vec<String>) -> Self {
376 self.env = Some(env);
377 self
378 }
379
380 pub fn cwd(mut self, cwd: String) -> Self {
382 self.cwd = Some(cwd);
383 self
384 }
385
386 pub fn stdin(mut self, data: impl Into<Box<[u8]>>) -> Self {
388 self.stdin = Some(data.into());
389 self
390 }
391
392 pub fn capture_stdout(mut self) -> Self {
394 self.capture_stdout = true;
395 self
396 }
397
398 pub fn capture_stderr(mut self) -> Self {
400 self.capture_stderr = true;
401 self
402 }
403
404 pub fn wait(mut self, wait: bool) -> Self {
406 self.wait = wait;
407 self
408 }
409
410 pub fn pgroup(mut self, pgroup: ProcessGroup) -> Self {
412 self.pgroup = pgroup;
413 self
414 }
415
416 pub fn max_output(mut self, max: usize) -> Self {
421 self.max_output = max;
422 self
423 }
424
425 pub fn timeout_ms(mut self, ms: u32) -> Self {
427 self.timeout_ms = Some(ms);
428 self
429 }
430
431 pub fn kill_grace_ms(mut self, ms: u32) -> Self {
433 self.kill_grace_ms = ms;
434 self
435 }
436
437 pub fn cancel(mut self, policy: CancelPolicy) -> Self {
439 self.cancel = policy;
440 self
441 }
442
443 pub fn fd_policy(mut self, policy: SpawnFdPolicy) -> Self {
445 self.fd_policy = policy;
446 self
447 }
448
449 pub fn early_exit(mut self, callback: fn(&[u8]) -> bool) -> Self {
451 self.early_exit = Some(callback);
452 self
453 }
454
455 pub fn build(self) -> Result<SpawnOptions, CoreError> {
457 let ctx = ExecContext::new(self.argv, self.env, self.cwd)?;
458 Ok(SpawnOptions {
459 ctx,
460 stdin: self.stdin,
461 capture_stdout: self.capture_stdout,
462 capture_stderr: self.capture_stderr,
463 wait: self.wait,
464 pgroup: self.pgroup,
465 max_output: self.max_output,
466 timeout_ms: self.timeout_ms,
467 kill_grace_ms: self.kill_grace_ms,
468 cancel: self.cancel,
469 backend: self.backend,
470 fd_policy: self.fd_policy,
471 early_exit: self.early_exit,
472 })
473 }
474}
475
476#[derive(Debug)]
478pub struct Output {
479 pub pid: pid_t,
481 pub status: Option<ExitStatus>,
483 pub stdout: Vec<u8>,
485 pub stderr: Vec<u8>,
487 pub timed_out: bool,
489 pub stdout_early_exited: bool,
491}
492
493fn validate_backend(opts: &SpawnOptions) -> Result<(), CoreError> {
494 validate_fd_policy(&opts.fd_policy)?;
495 match opts.backend {
496 SpawnBackend::PosixSpawn => {
497 if opts.ctx.cwd.is_some() {
498 return Err(CoreError::sys(libc::EINVAL, "posix_spawn cwd unsupported"));
499 }
500 if opts.pgroup.isolated {
501 return Err(CoreError::sys(
502 libc::EINVAL,
503 "posix_spawn setsid unsupported",
504 ));
505 }
506 if opts.fd_policy != SpawnFdPolicy::CloexecOnly {
507 return Err(CoreError::sys(
508 libc::EINVAL,
509 "posix_spawn fd policy unsupported",
510 ));
511 }
512 Ok(())
513 }
514 SpawnBackend::Fork => Ok(()),
515 }
516}
517
518fn validate_fd_policy(policy: &SpawnFdPolicy) -> Result<(), CoreError> {
519 if let SpawnFdPolicy::Allowlist(fds) = policy {
520 let mut seen = Vec::with_capacity(fds.len());
521 for &fd in fds {
522 if fd < 0 {
523 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist invalid"));
524 }
525 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
526 if flags < 0 {
527 return Err(CoreError::sys(errno(), "spawn fd allowlist fcntl(F_GETFD)"));
528 }
529 if seen.contains(&fd) {
530 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist duplicate"));
531 }
532 seen.push(fd);
533 }
534 }
535 Ok(())
536}
537
538pub type SpawnDrain = DrainState<fn(&[u8]) -> bool>;
540
541pub struct RunningProcess {
548 pub process: Process,
550 drain: SpawnDrain,
551}
552
553pub struct ManagedProcess {
561 running: Option<RunningProcess>,
562 timeout_at: Option<Instant>,
563 kill_grace: Duration,
564 cancel: CancelPolicy,
565 pgroup: ProcessGroup,
566 cancel_at: Option<Instant>,
567 kill_state: KillState,
568 status: Option<ExitStatus>,
569 timed_out: bool,
570}
571
572impl RunningProcess {
573 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
579 self.drain.register_with_reactor(reactor)
580 }
581
582 pub fn handle_reactor_event(
588 &mut self,
589 reactor: &mut Reactor,
590 event: &crate::fd::Event,
591 ) -> Result<(), CoreError> {
592 if self.drain.stdout_matches(event.token) {
593 if event.readable || event.hangup {
594 self.drain.handle_stdout_ready(reactor)?;
595 } else if event.error {
596 self.drain.drop_stdout(reactor)?;
597 }
598 } else if self.drain.stderr_matches(event.token) {
599 if event.readable || event.hangup {
600 self.drain.handle_stderr_ready(reactor)?;
601 } else if event.error {
602 self.drain.drop_stderr(reactor)?;
603 }
604 } else if self.drain.stdin_matches(event.token) {
605 if event.writable {
606 self.drain.handle_stdin_writable(reactor)?;
607 } else if event.error || event.hangup {
608 self.drain.drop_stdin(reactor)?;
609 }
610 }
611 Ok(())
612 }
613
614 pub fn io_done(&self) -> bool {
616 self.drain.is_done()
617 }
618
619 pub fn into_output_parts(self) -> (Vec<u8>, Vec<u8>) {
621 self.drain.into_parts()
622 }
623}
624
625impl ManagedProcess {
626 pub fn pid(&self) -> pid_t {
628 self.running
629 .as_ref()
630 .expect("managed process already completed")
631 .process
632 .pid()
633 }
634
635 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
637 self.running
638 .as_mut()
639 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
640 .register_with_reactor(reactor)
641 }
642
643 pub fn handle_reactor_event(
645 &mut self,
646 reactor: &mut Reactor,
647 event: &crate::fd::Event,
648 ) -> Result<(), CoreError> {
649 self.running
650 .as_mut()
651 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
652 .handle_reactor_event(reactor, event)
653 }
654
655 pub fn request_cancel(&mut self) {
658 self.cancel_at.get_or_insert_with(Instant::now);
659 }
660
661 pub fn next_deadline(&self) -> Option<Instant> {
667 self.running.as_ref()?;
668 let now = Instant::now();
669 let mut next = now + Duration::from_millis(100);
670 if !self.timed_out
671 && let Some(timeout_at) = self.timeout_at
672 && timeout_at < next
673 {
674 next = timeout_at;
675 }
676 if self.kill_state == KillState::TermSent
677 && let Some(cancel_at) = self.cancel_at
678 {
679 let kill_at = cancel_at + self.kill_grace;
680 if kill_at < next {
681 next = kill_at;
682 }
683 }
684 Some(next)
685 }
686
687 pub fn poll_completion(&mut self, reactor: &mut Reactor) -> Result<Option<Output>, CoreError> {
693 let now = Instant::now();
694 if !self.timed_out
695 && let Some(timeout_at) = self.timeout_at
696 && now >= timeout_at
697 {
698 self.timed_out = true;
699 self.cancel_at.get_or_insert(timeout_at);
700 }
701
702 self.advance_cancel(now)?;
703
704 let running = self
705 .running
706 .as_ref()
707 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
708 if self.status.is_none() {
709 self.status = running.process.wait_step()?;
710 }
711
712 let io_done = running.io_done();
713 if self.status.is_some() && (io_done || self.cancel_at.is_some()) {
714 return self.finish(reactor, !io_done).map(Some);
715 }
716 Ok(None)
717 }
718
719 fn advance_cancel(&mut self, now: Instant) -> Result<(), CoreError> {
720 let Some(cancel_at) = self.cancel_at else {
721 return Ok(());
722 };
723 let running = self
724 .running
725 .as_ref()
726 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
727 let process = &running.process;
728 let pid = process.pid();
729 let pgid = effective_pgid(pid, self.pgroup);
730 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
731 match self.kill_state {
732 KillState::None => match self.cancel {
733 CancelPolicy::None => {}
734 CancelPolicy::Graceful => {
735 let result = signal_process(process, target_is_group, pgid, libc::SIGTERM);
736 self.kill_state = if result.is_ok() {
737 KillState::TermSent
738 } else {
739 KillState::KillSent
740 };
741 }
742 CancelPolicy::Kill => {
743 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
744 self.kill_state = KillState::KillSent;
745 }
746 },
747 KillState::TermSent if now >= cancel_at + self.kill_grace => {
748 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
749 self.kill_state = KillState::KillSent;
750 }
751 _ => {}
752 }
753 Ok(())
754 }
755
756 fn finish(&mut self, reactor: &mut Reactor, force_close: bool) -> Result<Output, CoreError> {
757 let mut running = self
758 .running
759 .take()
760 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
761 for slot in running.drain.take_all_slots() {
762 if force_close {
763 let _ = reactor.del(&slot.fd);
764 } else {
765 reactor.del(&slot.fd)?;
766 }
767 }
768 let pid = running.process.pid();
769 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
770 running.drain.into_parts_with_state();
771 if output_limit_exceeded {
772 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
773 }
774 Ok(Output {
775 pid,
776 status: self.status.take(),
777 stdout,
778 stderr,
779 timed_out: self.timed_out,
780 stdout_early_exited,
781 })
782 }
783}
784
785impl Drop for ManagedProcess {
786 fn drop(&mut self) {
787 let Some(running) = self.running.take() else {
788 return;
789 };
790 let process = &running.process;
791 let pid = process.pid();
792 let pgid = effective_pgid(pid, self.pgroup);
793 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
794 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
795 let _ = process.wait_blocking();
796 }
797}
798
799fn effective_pgid(pid: pid_t, pgroup: ProcessGroup) -> pid_t {
800 match pgroup.leader {
801 Some(0) | None => pid,
802 Some(leader) => leader,
803 }
804}
805
806fn signal_process(
807 process: &Process,
808 target_is_group: bool,
809 pgid: pid_t,
810 signal: i32,
811) -> Result<(), CoreError> {
812 if target_is_group {
813 process.kill_group(pgid, signal)
814 } else {
815 process.kill(signal)
816 }
817}
818
819pub fn spawn_start(opts: SpawnOptions) -> Result<RunningProcess, CoreError> {
834 if !opts.wait && (opts.stdin.is_some() || opts.capture_stdout || opts.capture_stderr) {
835 return Err(CoreError::sys(
836 libc::EINVAL,
837 "background I/O capture not supported (wait must be true)",
838 ));
839 }
840
841 validate_backend(&opts)?;
842
843 let (pid, drain) = match opts.backend {
844 SpawnBackend::PosixSpawn => spawn_posix_internal(opts)?,
845 SpawnBackend::Fork => spawn_fork_internal(opts)?,
846 };
847
848 Ok(RunningProcess {
849 process: Process::new(pid),
850 drain,
851 })
852}
853
854pub fn spawn_managed(opts: SpawnOptions) -> Result<ManagedProcess, CoreError> {
857 if !opts.wait {
858 return Err(CoreError::sys(
859 libc::EINVAL,
860 "managed process requires wait=true",
861 ));
862 }
863 let timeout_at = opts
864 .timeout_ms
865 .map(|ms| Instant::now() + Duration::from_millis(ms as u64));
866 let kill_grace = Duration::from_millis(opts.kill_grace_ms as u64);
867 let cancel = opts.cancel;
868 let pgroup = opts.pgroup;
869 let running = spawn_start(opts)?;
870 Ok(ManagedProcess {
871 running: Some(running),
872 timeout_at,
873 kill_grace,
874 cancel,
875 pgroup,
876 cancel_at: None,
877 kill_state: KillState::None,
878 status: None,
879 timed_out: false,
880 })
881}
882
883pub fn spawn(opts: SpawnOptions) -> Result<Output, CoreError> {
892 let wait = opts.wait;
893 let timeout_ms = opts.timeout_ms;
894 let kill_grace_ms = opts.kill_grace_ms;
895 let cancel = opts.cancel;
896 let pgroup = opts.pgroup;
897
898 let mut reactor = Reactor::new()?;
899 let running = spawn_start(opts)?;
900
901 let pid = running.process.pid();
902 let mut drain = running.drain;
903
904 drain.register_with_reactor(&mut reactor)?;
905
906 if !wait {
907 let (stdout, stderr) = drain.into_parts();
908 return Ok(Output {
909 pid,
910 status: None,
911 stdout,
912 stderr,
913 timed_out: false,
914 stdout_early_exited: false,
915 });
916 }
917
918 wait_loop(
919 pid,
920 drain,
921 reactor,
922 timeout_ms,
923 kill_grace_ms,
924 cancel,
925 pgroup,
926 )
927}
928
929#[derive(Debug, Clone, Copy, PartialEq, Eq)]
930enum KillState {
931 None,
932 TermSent,
933 KillSent,
934}
935
936fn wait_loop(
937 pid: pid_t,
938 mut drain: crate::io::DrainState<fn(&[u8]) -> bool>,
939 mut reactor: Reactor,
940 timeout_ms: Option<u32>,
941 kill_grace_ms: u32,
942 cancel: CancelPolicy,
943 pgroup: ProcessGroup,
944) -> Result<Output, CoreError> {
945 let process = Process::new(pid);
946 let pgid = effective_pgid(pid, pgroup);
951 let mut status_raw = process.wait_step()?;
952 let mut state = KillState::None;
953 let mut timed_out = false;
954
955 let start_time = std::time::Instant::now();
956 let deadline = timeout_ms.map(|t| std::time::Duration::from_millis(t as u64));
957
958 loop {
959 let mut poll_timeout = -1;
960
961 if let Some(dl) = deadline {
962 let elapsed = start_time.elapsed();
963 if elapsed >= dl {
964 timed_out = true;
965 let elapsed_over = (elapsed - dl).as_millis();
966
967 let target_is_group = pgroup.isolated || pgroup.leader.is_some();
968
969 match state {
970 KillState::None => {
971 if cancel == CancelPolicy::Graceful {
972 let r = if target_is_group {
973 process.kill_group(pgid, libc::SIGTERM)
974 } else {
975 process.kill(libc::SIGTERM)
976 };
977 if r.is_err() {
978 state = KillState::KillSent; } else {
980 state = KillState::TermSent;
981 }
982 } else if cancel == CancelPolicy::Kill {
983 let _ = if target_is_group {
984 process.kill_group(pgid, libc::SIGKILL)
985 } else {
986 process.kill(libc::SIGKILL)
987 };
988 state = KillState::KillSent;
989 } else {
990 }
992 }
993 KillState::TermSent if elapsed_over > kill_grace_ms as u128 => {
994 let _ = if target_is_group {
995 process.kill_group(pgid, libc::SIGKILL)
996 } else {
997 process.kill(libc::SIGKILL)
998 };
999 state = KillState::KillSent;
1000 }
1001 _ => {}
1002 }
1003 poll_timeout = 100; } else {
1005 let remaining = dl - elapsed;
1006 poll_timeout = remaining.as_millis().min(i32::MAX as u128) as i32;
1007 }
1008 }
1009
1010 if status_raw.is_none()
1011 && let Some(s) = process.wait_step()?
1012 {
1013 status_raw = Some(s);
1014 }
1015
1016 if drain.is_done() {
1017 let s = if status_raw.is_some() {
1018 status_raw.take()
1019 } else if deadline.is_none() {
1020 Some(process.wait_blocking()?)
1023 } else {
1024 None
1029 };
1030
1031 if let Some(s) = s {
1032 for slot in drain.take_all_slots() {
1033 reactor.del(&slot.fd)?;
1034 }
1035 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
1036 drain.into_parts_with_state();
1037 if output_limit_exceeded {
1038 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
1039 }
1040 return Ok(Output {
1041 pid,
1042 status: Some(s),
1043 stdout,
1044 stderr,
1045 timed_out,
1046 stdout_early_exited,
1047 });
1048 }
1049 }
1050
1051 if timed_out && status_raw.is_some() {
1056 for slot in drain.take_all_slots() {
1057 let _ = reactor.del(&slot.fd);
1058 }
1059 let (stdout, stderr, _output_limit_exceeded, stdout_early_exited) =
1060 drain.into_parts_with_state();
1061 return Ok(Output {
1062 pid,
1063 status: status_raw,
1064 stdout,
1065 stderr,
1066 timed_out: true,
1067 stdout_early_exited,
1068 });
1069 }
1070
1071 let timeout = poll_timeout;
1072
1073 let mut events = Vec::new();
1074 let nevents = reactor.wait(&mut events, 64, timeout)?;
1075
1076 for ev in events.iter().take(nevents) {
1077 if drain.stdout_matches(ev.token) {
1078 if ev.readable || ev.hangup {
1079 drain.handle_stdout_ready(&mut reactor)?;
1080 } else if ev.error {
1081 drain.drop_stdout(&mut reactor)?;
1082 }
1083 } else if drain.stderr_matches(ev.token) {
1084 if ev.readable || ev.hangup {
1085 drain.handle_stderr_ready(&mut reactor)?;
1086 } else if ev.error {
1087 drain.drop_stderr(&mut reactor)?;
1088 }
1089 } else if drain.stdin_matches(ev.token) {
1090 if ev.writable {
1091 drain.handle_stdin_writable(&mut reactor)?;
1092 } else if ev.error || ev.hangup {
1093 drain.drop_stdin(&mut reactor)?;
1094 }
1095 }
1096 }
1097 }
1098}