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 fork;
26mod posix;
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> {
261 if self.pid <= 0 {
262 return Err(CoreError::sys(libc::EINVAL, "kill: invalid pid"));
263 }
264 let r = unsafe { libc::kill(self.pid, sig) };
265 if r < 0 {
266 let e = errno();
267 if e == libc::ESRCH {
268 return Ok(());
269 }
270 syscall_ret(-1, "kill")?;
271 }
272 Ok(())
273 }
274
275 pub fn kill_pgroup(&self, sig: i32) -> Result<(), CoreError> {
282 self.kill_group(self.pid, sig)
283 }
284
285 pub fn kill_group(&self, pgid: pid_t, sig: i32) -> Result<(), CoreError> {
295 if pgid <= 0 {
296 return Err(CoreError::sys(libc::EINVAL, "kill_group: invalid pgid"));
297 }
298 let r = unsafe { libc::kill(-pgid, sig) };
299 if r < 0 {
300 let e = errno();
301 if e == libc::ESRCH {
302 return Ok(());
303 }
304 syscall_ret(-1, "kill_group")?;
305 }
306 Ok(())
307 }
308}
309
310#[derive(Clone)]
312pub struct SpawnOptions {
313 ctx: ExecContext,
314 stdin: Option<Box<[u8]>>,
315 capture_stdout: bool,
316 capture_stderr: bool,
317 wait: bool,
318 pgroup: ProcessGroup,
319 max_output: usize,
320 timeout_ms: Option<u32>,
321 kill_grace_ms: u32,
322 cancel: CancelPolicy,
323 backend: SpawnBackend,
324 fd_policy: SpawnFdPolicy,
325 early_exit: Option<fn(&[u8]) -> bool>,
326}
327
328impl SpawnOptions {
329 pub fn builder(argv: Vec<String>, backend: SpawnBackend) -> SpawnOptionsBuilder {
331 SpawnOptionsBuilder::new(argv, backend)
332 }
333
334 pub fn run(self) -> Result<Output, CoreError> {
336 spawn(self)
337 }
338}
339
340#[derive(Clone)]
342pub struct SpawnOptionsBuilder {
343 argv: Vec<String>,
344 env: Option<Vec<String>>,
345 cwd: Option<String>,
346 stdin: Option<Box<[u8]>>,
347 capture_stdout: bool,
348 capture_stderr: bool,
349 wait: bool,
350 pgroup: ProcessGroup,
351 max_output: usize,
352 timeout_ms: Option<u32>,
353 kill_grace_ms: u32,
354 cancel: CancelPolicy,
355 backend: SpawnBackend,
356 fd_policy: SpawnFdPolicy,
357 early_exit: Option<fn(&[u8]) -> bool>,
358}
359
360impl SpawnOptionsBuilder {
361 pub fn new(argv: Vec<String>, backend: SpawnBackend) -> Self {
363 Self {
364 argv,
365 env: None,
366 cwd: None,
367 stdin: None,
368 capture_stdout: false,
369 capture_stderr: false,
370 wait: true,
371 pgroup: ProcessGroup::default(),
372 max_output: 1024 * 1024,
373 timeout_ms: None,
374 kill_grace_ms: 2000,
375 cancel: CancelPolicy::Kill,
376 backend,
377 fd_policy: SpawnFdPolicy::default(),
378 early_exit: None,
379 }
380 }
381
382 pub fn env(mut self, env: Vec<String>) -> Self {
384 self.env = Some(env);
385 self
386 }
387
388 pub fn cwd(mut self, cwd: String) -> Self {
390 self.cwd = Some(cwd);
391 self
392 }
393
394 pub fn stdin(mut self, data: impl Into<Box<[u8]>>) -> Self {
396 self.stdin = Some(data.into());
397 self
398 }
399
400 pub fn capture_stdout(mut self) -> Self {
402 self.capture_stdout = true;
403 self
404 }
405
406 pub fn capture_stderr(mut self) -> Self {
408 self.capture_stderr = true;
409 self
410 }
411
412 pub fn wait(mut self, wait: bool) -> Self {
414 self.wait = wait;
415 self
416 }
417
418 pub fn pgroup(mut self, pgroup: ProcessGroup) -> Self {
420 self.pgroup = pgroup;
421 self
422 }
423
424 pub fn max_output(mut self, max: usize) -> Self {
429 self.max_output = max;
430 self
431 }
432
433 pub fn timeout_ms(mut self, ms: u32) -> Self {
435 self.timeout_ms = Some(ms);
436 self
437 }
438
439 pub fn kill_grace_ms(mut self, ms: u32) -> Self {
441 self.kill_grace_ms = ms;
442 self
443 }
444
445 pub fn cancel(mut self, policy: CancelPolicy) -> Self {
447 self.cancel = policy;
448 self
449 }
450
451 pub fn fd_policy(mut self, policy: SpawnFdPolicy) -> Self {
453 self.fd_policy = policy;
454 self
455 }
456
457 pub fn early_exit(mut self, callback: fn(&[u8]) -> bool) -> Self {
459 self.early_exit = Some(callback);
460 self
461 }
462
463 pub fn build(self) -> Result<SpawnOptions, CoreError> {
465 let ctx = ExecContext::new(self.argv, self.env, self.cwd)?;
466 Ok(SpawnOptions {
467 ctx,
468 stdin: self.stdin,
469 capture_stdout: self.capture_stdout,
470 capture_stderr: self.capture_stderr,
471 wait: self.wait,
472 pgroup: self.pgroup,
473 max_output: self.max_output,
474 timeout_ms: self.timeout_ms,
475 kill_grace_ms: self.kill_grace_ms,
476 cancel: self.cancel,
477 backend: self.backend,
478 fd_policy: self.fd_policy,
479 early_exit: self.early_exit,
480 })
481 }
482}
483
484#[derive(Debug)]
486pub struct Output {
487 pub pid: pid_t,
489 pub status: Option<ExitStatus>,
491 pub stdout: Vec<u8>,
493 pub stderr: Vec<u8>,
495 pub timed_out: bool,
497 pub stdout_early_exited: bool,
499}
500
501fn validate_backend(opts: &SpawnOptions) -> Result<(), CoreError> {
502 validate_fd_policy(&opts.fd_policy)?;
503 match opts.backend {
504 SpawnBackend::PosixSpawn => {
505 if opts.ctx.cwd.is_some() {
506 return Err(CoreError::sys(libc::EINVAL, "posix_spawn cwd unsupported"));
507 }
508 if opts.pgroup.isolated {
509 return Err(CoreError::sys(
510 libc::EINVAL,
511 "posix_spawn setsid unsupported",
512 ));
513 }
514 if opts.fd_policy != SpawnFdPolicy::CloexecOnly {
515 return Err(CoreError::sys(
516 libc::EINVAL,
517 "posix_spawn fd policy unsupported",
518 ));
519 }
520 Ok(())
521 }
522 SpawnBackend::Fork => {
523 if opts.pgroup.isolated && opts.pgroup.leader.is_some_and(|l| l != 0) {
528 return Err(CoreError::sys(
529 libc::EINVAL,
530 "fork isolated + custom setpgid leader unsupported",
531 ));
532 }
533 Ok(())
534 }
535 }
536}
537
538fn validate_fd_policy(policy: &SpawnFdPolicy) -> Result<(), CoreError> {
539 if let SpawnFdPolicy::Allowlist(fds) = policy {
540 let mut seen = Vec::with_capacity(fds.len());
541 for &fd in fds {
542 if fd < 0 {
543 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist invalid"));
544 }
545 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
546 if flags < 0 {
547 return Err(CoreError::sys(errno(), "spawn fd allowlist fcntl(F_GETFD)"));
548 }
549 if seen.contains(&fd) {
550 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist duplicate"));
551 }
552 seen.push(fd);
553 }
554 }
555 Ok(())
556}
557
558pub type SpawnDrain = DrainState<fn(&[u8]) -> bool>;
560
561pub struct RunningProcess {
568 pub process: Process,
570 drain: SpawnDrain,
571}
572
573pub struct ManagedProcess {
581 running: Option<RunningProcess>,
582 pid: pid_t,
583 timeout_at: Option<Instant>,
584 kill_grace: Duration,
585 cancel: CancelPolicy,
586 pgroup: ProcessGroup,
587 cancel_at: Option<Instant>,
588 kill_state: KillState,
589 status: Option<ExitStatus>,
590 timed_out: bool,
591}
592
593impl RunningProcess {
594 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
600 self.drain.register_with_reactor(reactor)
601 }
602
603 pub fn handle_reactor_event(
609 &mut self,
610 reactor: &mut Reactor,
611 event: &crate::fd::Event,
612 ) -> Result<(), CoreError> {
613 if self.drain.stdout_matches(event.token) {
614 if event.readable || event.hangup {
615 self.drain.handle_stdout_ready(reactor)?;
616 } else if event.error {
617 self.drain.drop_stdout(reactor)?;
618 }
619 } else if self.drain.stderr_matches(event.token) {
620 if event.readable || event.hangup {
621 self.drain.handle_stderr_ready(reactor)?;
622 } else if event.error {
623 self.drain.drop_stderr(reactor)?;
624 }
625 } else if self.drain.stdin_matches(event.token) {
626 if event.writable {
627 self.drain.handle_stdin_writable(reactor)?;
628 } else if event.error || event.hangup {
629 self.drain.drop_stdin(reactor)?;
630 }
631 }
632 Ok(())
633 }
634
635 pub fn io_done(&self) -> bool {
637 self.drain.is_done()
638 }
639
640 pub fn into_output_parts(self) -> (Vec<u8>, Vec<u8>) {
642 self.drain.into_parts()
643 }
644}
645
646impl ManagedProcess {
647 pub fn pid(&self) -> pid_t {
652 self.pid
653 }
654
655 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
657 self.running
658 .as_mut()
659 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
660 .register_with_reactor(reactor)
661 }
662
663 pub fn handle_reactor_event(
665 &mut self,
666 reactor: &mut Reactor,
667 event: &crate::fd::Event,
668 ) -> Result<(), CoreError> {
669 self.running
670 .as_mut()
671 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
672 .handle_reactor_event(reactor, event)
673 }
674
675 pub fn request_cancel(&mut self) {
678 self.cancel_at.get_or_insert_with(Instant::now);
679 }
680
681 pub fn next_deadline(&self) -> Option<Instant> {
687 self.running.as_ref()?;
688 let now = Instant::now();
689 let mut next = now + Duration::from_millis(100);
690 if !self.timed_out
691 && let Some(timeout_at) = self.timeout_at
692 && timeout_at < next
693 {
694 next = timeout_at;
695 }
696 if self.kill_state == KillState::TermSent
697 && let Some(cancel_at) = self.cancel_at
698 {
699 let kill_at = cancel_at + self.kill_grace;
700 if kill_at < next {
701 next = kill_at;
702 }
703 }
704 Some(next)
705 }
706
707 pub fn poll_completion(&mut self, reactor: &mut Reactor) -> Result<Option<Output>, CoreError> {
716 let now = Instant::now();
717 if !self.timed_out
718 && let Some(timeout_at) = self.timeout_at
719 && now >= timeout_at
720 {
721 self.timed_out = true;
722 self.cancel_at.get_or_insert(timeout_at);
723 }
724
725 self.advance_cancel(now)?;
726
727 let running = self
728 .running
729 .as_ref()
730 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
731 if self.status.is_none() {
732 self.status = running.process.wait_step()?;
733 }
734
735 let io_done = running.io_done();
736 if self.status.is_some() && (io_done || self.cancel_at.is_some()) {
737 return self.finish(reactor, !io_done).map(Some);
738 }
739 Ok(None)
740 }
741
742 fn advance_cancel(&mut self, now: Instant) -> Result<(), CoreError> {
743 let Some(cancel_at) = self.cancel_at else {
744 return Ok(());
745 };
746 let running = self
747 .running
748 .as_ref()
749 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
750 let process = &running.process;
751 let pid = process.pid();
752 let pgid = effective_pgid(pid, self.pgroup);
753 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
754 match self.kill_state {
755 KillState::None => match self.cancel {
756 CancelPolicy::None => {}
757 CancelPolicy::Graceful => {
758 let result = signal_process(process, target_is_group, pgid, libc::SIGTERM);
759 self.kill_state = if result.is_ok() {
760 KillState::TermSent
761 } else {
762 KillState::KillSent
763 };
764 }
765 CancelPolicy::Kill => {
766 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
767 self.kill_state = KillState::KillSent;
768 }
769 },
770 KillState::TermSent if now >= cancel_at + self.kill_grace => {
771 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
772 self.kill_state = KillState::KillSent;
773 }
774 _ => {}
775 }
776 Ok(())
777 }
778
779 fn finish(&mut self, reactor: &mut Reactor, force_close: bool) -> Result<Output, CoreError> {
780 let mut running = self
781 .running
782 .take()
783 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
784 for slot in running.drain.take_all_slots() {
785 if force_close {
786 let _ = reactor.del(&slot.fd);
787 } else {
788 reactor.del(&slot.fd)?;
789 }
790 }
791 let pid = running.process.pid();
792 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
793 running.drain.into_parts_with_state();
794 if output_limit_exceeded && !force_close {
799 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
800 }
801 Ok(Output {
802 pid,
803 status: self.status.take(),
804 stdout,
805 stderr,
806 timed_out: self.timed_out,
807 stdout_early_exited,
808 })
809 }
810}
811
812impl Drop for ManagedProcess {
813 fn drop(&mut self) {
814 let Some(running) = self.running.take() else {
815 return;
816 };
817 if self.status.is_some() {
821 return;
822 }
823 let process = &running.process;
824 let pid = process.pid();
825 let pgid = effective_pgid(pid, self.pgroup);
826 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
827 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
828 let deadline = Instant::now() + Duration::from_millis(100);
833 while Instant::now() < deadline {
834 match process.wait_step() {
835 Ok(Some(_)) => return,
836 Ok(None) => std::thread::sleep(Duration::from_millis(5)),
837 Err(_) => return,
838 }
839 }
840 }
841}
842
843fn effective_pgid(pid: pid_t, pgroup: ProcessGroup) -> pid_t {
844 match pgroup.leader {
845 Some(0) | None => pid,
846 Some(leader) => leader,
847 }
848}
849
850fn signal_process(
851 process: &Process,
852 target_is_group: bool,
853 pgid: pid_t,
854 signal: i32,
855) -> Result<(), CoreError> {
856 if target_is_group {
857 process.kill_group(pgid, signal)
858 } else {
859 process.kill(signal)
860 }
861}
862
863pub fn spawn_start(opts: SpawnOptions) -> Result<RunningProcess, CoreError> {
878 if !opts.wait && (opts.stdin.is_some() || opts.capture_stdout || opts.capture_stderr) {
879 return Err(CoreError::sys(
880 libc::EINVAL,
881 "background I/O capture not supported (wait must be true)",
882 ));
883 }
884
885 validate_backend(&opts)?;
886
887 let (pid, drain) = match opts.backend {
888 SpawnBackend::PosixSpawn => spawn_posix_internal(opts)?,
889 SpawnBackend::Fork => spawn_fork_internal(opts)?,
890 };
891
892 Ok(RunningProcess {
893 process: Process::new(pid),
894 drain,
895 })
896}
897
898pub fn spawn_managed(opts: SpawnOptions) -> Result<ManagedProcess, CoreError> {
901 if !opts.wait {
902 return Err(CoreError::sys(
903 libc::EINVAL,
904 "managed process requires wait=true",
905 ));
906 }
907 let timeout_at = opts
908 .timeout_ms
909 .map(|ms| Instant::now() + Duration::from_millis(ms as u64));
910 let kill_grace = Duration::from_millis(opts.kill_grace_ms as u64);
911 let cancel = opts.cancel;
912 let pgroup = opts.pgroup;
913 let running = spawn_start(opts)?;
914 let pid = running.process.pid();
915 Ok(ManagedProcess {
916 running: Some(running),
917 pid,
918 timeout_at,
919 kill_grace,
920 cancel,
921 pgroup,
922 cancel_at: None,
923 kill_state: KillState::None,
924 status: None,
925 timed_out: false,
926 })
927}
928
929pub fn spawn(opts: SpawnOptions) -> Result<Output, CoreError> {
938 let wait = opts.wait;
939 let timeout_ms = opts.timeout_ms;
940 let kill_grace_ms = opts.kill_grace_ms;
941 let cancel = opts.cancel;
942 let pgroup = opts.pgroup;
943
944 let mut reactor = Reactor::new()?;
945 let running = spawn_start(opts)?;
946
947 let pid = running.process.pid();
948 let mut drain = running.drain;
949
950 drain.register_with_reactor(&mut reactor)?;
951
952 if !wait {
953 let (stdout, stderr) = drain.into_parts();
954 return Ok(Output {
955 pid,
956 status: None,
957 stdout,
958 stderr,
959 timed_out: false,
960 stdout_early_exited: false,
961 });
962 }
963
964 wait_loop(
965 pid,
966 drain,
967 reactor,
968 timeout_ms,
969 kill_grace_ms,
970 cancel,
971 pgroup,
972 )
973}
974
975#[derive(Debug, Clone, Copy, PartialEq, Eq)]
976enum KillState {
977 None,
978 TermSent,
979 KillSent,
980}
981
982fn wait_loop(
983 pid: pid_t,
984 mut drain: crate::io::DrainState<fn(&[u8]) -> bool>,
985 mut reactor: Reactor,
986 timeout_ms: Option<u32>,
987 kill_grace_ms: u32,
988 cancel: CancelPolicy,
989 pgroup: ProcessGroup,
990) -> Result<Output, CoreError> {
991 let process = Process::new(pid);
992 let pgid = effective_pgid(pid, pgroup);
997 let mut status_raw = process.wait_step()?;
998 let mut state = KillState::None;
999 let mut timed_out = false;
1000
1001 let start_time = std::time::Instant::now();
1002 let deadline = timeout_ms.map(|t| std::time::Duration::from_millis(t as u64));
1003
1004 loop {
1005 let mut poll_timeout = -1;
1006
1007 if let Some(dl) = deadline {
1008 let elapsed = start_time.elapsed();
1009 if elapsed >= dl {
1010 timed_out = true;
1011 let elapsed_over = (elapsed - dl).as_millis();
1012
1013 let target_is_group = pgroup.isolated || pgroup.leader.is_some();
1014
1015 match state {
1016 KillState::None => {
1017 if cancel == CancelPolicy::Graceful {
1018 let r = if target_is_group {
1019 process.kill_group(pgid, libc::SIGTERM)
1020 } else {
1021 process.kill(libc::SIGTERM)
1022 };
1023 if r.is_err() {
1024 state = KillState::KillSent; } else {
1026 state = KillState::TermSent;
1027 }
1028 } else if cancel == CancelPolicy::Kill {
1029 let _ = if target_is_group {
1030 process.kill_group(pgid, libc::SIGKILL)
1031 } else {
1032 process.kill(libc::SIGKILL)
1033 };
1034 state = KillState::KillSent;
1035 } else {
1036 }
1038 }
1039 KillState::TermSent if elapsed_over > kill_grace_ms as u128 => {
1040 let _ = if target_is_group {
1041 process.kill_group(pgid, libc::SIGKILL)
1042 } else {
1043 process.kill(libc::SIGKILL)
1044 };
1045 state = KillState::KillSent;
1046 }
1047 _ => {}
1048 }
1049 poll_timeout = 100; } else {
1051 let remaining = dl - elapsed;
1052 poll_timeout = remaining.as_millis().min(i32::MAX as u128) as i32;
1053 }
1054 }
1055
1056 if status_raw.is_none()
1057 && let Some(s) = process.wait_step()?
1058 {
1059 status_raw = Some(s);
1060 }
1061
1062 if drain.is_done() {
1063 let s = if status_raw.is_some() {
1064 status_raw.take()
1065 } else if deadline.is_none() {
1066 Some(process.wait_blocking()?)
1069 } else {
1070 None
1075 };
1076
1077 if let Some(s) = s {
1078 for slot in drain.take_all_slots() {
1079 reactor.del(&slot.fd)?;
1080 }
1081 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
1082 drain.into_parts_with_state();
1083 if output_limit_exceeded {
1084 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
1085 }
1086 return Ok(Output {
1087 pid,
1088 status: Some(s),
1089 stdout,
1090 stderr,
1091 timed_out,
1092 stdout_early_exited,
1093 });
1094 }
1095 }
1096
1097 if timed_out && status_raw.is_some() {
1102 for slot in drain.take_all_slots() {
1103 let _ = reactor.del(&slot.fd);
1104 }
1105 let (stdout, stderr, _output_limit_exceeded, stdout_early_exited) =
1106 drain.into_parts_with_state();
1107 return Ok(Output {
1108 pid,
1109 status: status_raw,
1110 stdout,
1111 stderr,
1112 timed_out: true,
1113 stdout_early_exited,
1114 });
1115 }
1116
1117 let timeout = poll_timeout;
1118
1119 let mut events = Vec::new();
1120 let nevents = reactor.wait(&mut events, 64, timeout)?;
1121
1122 for ev in events.iter().take(nevents) {
1123 if drain.stdout_matches(ev.token) {
1124 if ev.readable || ev.hangup {
1125 drain.handle_stdout_ready(&mut reactor)?;
1126 } else if ev.error {
1127 drain.drop_stdout(&mut reactor)?;
1128 }
1129 } else if drain.stderr_matches(ev.token) {
1130 if ev.readable || ev.hangup {
1131 drain.handle_stderr_ready(&mut reactor)?;
1132 } else if ev.error {
1133 drain.drop_stderr(&mut reactor)?;
1134 }
1135 } else if drain.stdin_matches(ev.token) {
1136 if ev.writable {
1137 drain.handle_stdin_writable(&mut reactor)?;
1138 } else if ev.error || ev.hangup {
1139 drain.drop_stdin(&mut reactor)?;
1140 }
1141 }
1142 }
1143 }
1144}