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::{O_CLOEXEC, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG, pid_t, pipe2, waitpid};
21
22mod exec;
23mod fork;
24mod posix;
25
26use exec::ExecContext;
27use fork::spawn_fork_internal;
28use posix::spawn_posix_internal;
29
30unsafe extern "C" {
31 pub(crate) static mut environ: *mut *mut libc::c_char;
32}
33
34const D_STATE_REAP_BOUND: Duration = Duration::from_millis(500);
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub enum CancelPolicy {
44 #[default]
46 None,
47 Graceful,
49 Kill,
51}
52
53#[derive(Debug, Clone, Copy, Default)]
55pub struct ProcessGroup {
56 pub leader: Option<pid_t>,
58 pub isolated: bool,
60}
61
62impl ProcessGroup {
63 pub fn new(leader: Option<pid_t>, isolated: bool) -> Self {
65 Self { leader, isolated }
66 }
67}
68
69#[inline(always)]
70fn errno() -> i32 {
71 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
72}
73
74fn relocate_above_stdio(fd: RawFd, op: &'static str) -> Result<RawFd, CoreError> {
80 if fd >= 3 {
81 return Ok(fd);
82 }
83 let new = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 3) };
84 syscall_ret(new, op)?;
85 unsafe {
86 libc::close(fd);
87 }
88 Ok(new)
89}
90
91#[inline(always)]
97fn make_pipe() -> Result<(Fd, Fd), CoreError> {
98 let mut fds = [0; 2];
99 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC) };
100 syscall_ret(r, "pipe2")?;
101 let r0 = match relocate_above_stdio(fds[0], "pipe2:relocate") {
102 Ok(fd) => fd,
103 Err(e) => {
104 unsafe {
107 libc::close(fds[0]);
108 }
109 return Err(e);
110 }
111 };
112 let r1 = match relocate_above_stdio(fds[1], "pipe2:relocate") {
113 Ok(fd) => fd,
114 Err(e) => {
115 unsafe {
118 libc::close(r0);
119 libc::close(fds[1]);
120 }
121 return Err(e);
122 }
123 };
124 Ok((Fd::new(r0, "pipe2")?, Fd::new(r1, "pipe2")?))
125}
126
127fn make_cloexec_pipe() -> Result<(RawFd, RawFd), CoreError> {
128 let mut fds = [0; 2];
129 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC) };
130 syscall_ret(r, "pipe2")?;
131 let r0 = match relocate_above_stdio(fds[0], "pipe2:relocate") {
132 Ok(fd) => fd,
133 Err(e) => {
134 unsafe {
135 libc::close(fds[0]);
136 }
137 return Err(e);
138 }
139 };
140 let r1 = match relocate_above_stdio(fds[1], "pipe2:relocate") {
141 Ok(fd) => fd,
142 Err(e) => {
143 unsafe {
144 libc::close(r0);
145 libc::close(fds[1]);
146 }
147 return Err(e);
148 }
149 };
150 Ok((r0, r1))
151}
152
153struct Pipes {
154 stdin_r: Option<Fd>,
155 stdin_w: Option<Fd>,
156 stdout_r: Option<Fd>,
157 stdout_w: Option<Fd>,
158 stderr_r: Option<Fd>,
159 stderr_w: Option<Fd>,
160}
161
162impl Pipes {
163 fn new(in_buf: Option<&[u8]>, out: bool, err: bool) -> Result<Self, CoreError> {
164 let (stdin_r, stdin_w) = if in_buf.is_some() {
165 let (r, w) = make_pipe()?;
166 (Some(r), Some(w))
167 } else {
168 (None, None)
169 };
170
171 let (stdout_r, stdout_w) = if out {
172 let (r, w) = make_pipe()?;
173 (Some(r), Some(w))
174 } else {
175 (None, None)
176 };
177
178 let (stderr_r, stderr_w) = if err {
179 let (r, w) = make_pipe()?;
180 (Some(r), Some(w))
181 } else {
182 (None, None)
183 };
184
185 Ok(Self {
186 stdin_r,
187 stdin_w,
188 stdout_r,
189 stdout_w,
190 stderr_r,
191 stderr_w,
192 })
193 }
194
195 #[inline(always)]
196 fn close_all(&mut self) {
197 self.stdin_r.take();
198 self.stdin_w.take();
199 self.stdout_r.take();
200 self.stdout_w.take();
201 self.stderr_r.take();
202 self.stderr_w.take();
203 }
204}
205
206#[derive(Debug, PartialEq, Eq)]
208pub enum ExitStatus {
209 Exited(i32),
211 Signaled(i32),
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum SpawnBackend {
218 PosixSpawn,
220 Fork,
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Default)]
229pub enum SpawnFdPolicy {
230 #[default]
232 CloexecOnly,
233 CloseFrom3,
236 Allowlist(Vec<RawFd>),
243}
244
245#[inline(always)]
246fn decode_status(status: i32) -> ExitStatus {
247 if WIFEXITED(status) {
248 ExitStatus::Exited(WEXITSTATUS(status))
249 } else if WIFSIGNALED(status) {
250 ExitStatus::Signaled(WTERMSIG(status))
251 } else {
252 ExitStatus::Exited(-1)
253 }
254}
255
256pub struct Process {
264 pid: pid_t,
265}
266
267impl Process {
268 pub fn new(pid: pid_t) -> Self {
270 Self { pid }
271 }
272
273 pub fn pid(&self) -> pid_t {
275 self.pid
276 }
277
278 pub fn wait_step(&self) -> Result<Option<ExitStatus>, CoreError> {
284 loop {
285 let mut status = 0;
286 let r = unsafe { waitpid(self.pid, &mut status, libc::WNOHANG) };
287 if r == 0 {
288 return Ok(None);
289 }
290 if r < 0 {
291 let e = errno();
292 if e == libc::EINTR {
293 continue;
294 }
295 return Err(CoreError::sys(e, "waitpid_step"));
296 }
297 return Ok(Some(decode_status(status)));
298 }
299 }
300
301 pub fn wait_blocking(&self) -> Result<ExitStatus, CoreError> {
306 loop {
307 let mut status = 0;
308 let r = unsafe { waitpid(self.pid, &mut status, 0) };
309 if r < 0 {
310 let e = errno();
311 if e == libc::EINTR {
312 continue;
313 }
314 return Err(CoreError::sys(e, "waitpid_blocking"));
315 }
316 return Ok(decode_status(status));
317 }
318 }
319
320 pub fn kill(&self, sig: i32) -> Result<(), CoreError> {
328 if self.pid <= 0 {
329 return Err(CoreError::sys(libc::EINVAL, "kill: invalid pid"));
330 }
331 let r = unsafe { libc::kill(self.pid, sig) };
332 if r < 0 {
333 let e = errno();
334 if e == libc::ESRCH {
335 return Ok(());
336 }
337 syscall_ret(-1, "kill")?;
338 }
339 Ok(())
340 }
341
342 pub fn kill_pgroup(&self, sig: i32) -> Result<(), CoreError> {
349 self.kill_group(self.pid, sig)
350 }
351
352 pub fn kill_group(&self, pgid: pid_t, sig: i32) -> Result<(), CoreError> {
362 if pgid <= 0 {
363 return Err(CoreError::sys(libc::EINVAL, "kill_group: invalid pgid"));
364 }
365 let r = unsafe { libc::kill(-pgid, sig) };
366 if r < 0 {
367 let e = errno();
368 if e == libc::ESRCH {
369 return Ok(());
370 }
371 syscall_ret(-1, "kill_group")?;
372 }
373 Ok(())
374 }
375}
376
377#[derive(Clone)]
379pub struct SpawnOptions {
380 ctx: ExecContext,
381 stdin: Option<Box<[u8]>>,
382 capture_stdout: bool,
383 capture_stderr: bool,
384 wait: bool,
385 pgroup: ProcessGroup,
386 max_output: usize,
387 timeout_ms: Option<u32>,
388 kill_grace_ms: u32,
389 cancel: CancelPolicy,
390 backend: SpawnBackend,
391 fd_policy: SpawnFdPolicy,
392 early_exit: Option<fn(&[u8]) -> bool>,
393}
394
395impl SpawnOptions {
396 pub fn builder(argv: Vec<String>, backend: SpawnBackend) -> SpawnOptionsBuilder {
398 SpawnOptionsBuilder::new(argv, backend)
399 }
400
401 pub fn run(self) -> Result<Output, CoreError> {
403 spawn(self)
404 }
405}
406
407#[derive(Clone)]
409pub struct SpawnOptionsBuilder {
410 argv: Vec<String>,
411 env: Option<Vec<String>>,
412 cwd: Option<String>,
413 stdin: Option<Box<[u8]>>,
414 capture_stdout: bool,
415 capture_stderr: bool,
416 wait: bool,
417 pgroup: ProcessGroup,
418 max_output: usize,
419 timeout_ms: Option<u32>,
420 kill_grace_ms: u32,
421 cancel: CancelPolicy,
422 backend: SpawnBackend,
423 fd_policy: SpawnFdPolicy,
424 early_exit: Option<fn(&[u8]) -> bool>,
425}
426
427impl SpawnOptionsBuilder {
428 pub fn new(argv: Vec<String>, backend: SpawnBackend) -> Self {
430 Self {
431 argv,
432 env: None,
433 cwd: None,
434 stdin: None,
435 capture_stdout: false,
436 capture_stderr: false,
437 wait: true,
438 pgroup: ProcessGroup::default(),
439 max_output: 1024 * 1024,
440 timeout_ms: None,
441 kill_grace_ms: 2000,
442 cancel: CancelPolicy::Kill,
443 backend,
444 fd_policy: SpawnFdPolicy::default(),
445 early_exit: None,
446 }
447 }
448
449 pub fn env(mut self, env: Vec<String>) -> Self {
451 self.env = Some(env);
452 self
453 }
454
455 pub fn cwd(mut self, cwd: String) -> Self {
457 self.cwd = Some(cwd);
458 self
459 }
460
461 pub fn stdin(mut self, data: impl Into<Box<[u8]>>) -> Self {
463 self.stdin = Some(data.into());
464 self
465 }
466
467 pub fn capture_stdout(mut self) -> Self {
469 self.capture_stdout = true;
470 self
471 }
472
473 pub fn capture_stderr(mut self) -> Self {
475 self.capture_stderr = true;
476 self
477 }
478
479 pub fn wait(mut self, wait: bool) -> Self {
481 self.wait = wait;
482 self
483 }
484
485 pub fn pgroup(mut self, pgroup: ProcessGroup) -> Self {
487 self.pgroup = pgroup;
488 self
489 }
490
491 pub fn max_output(mut self, max: usize) -> Self {
496 self.max_output = max;
497 self
498 }
499
500 pub fn timeout_ms(mut self, ms: u32) -> Self {
502 self.timeout_ms = Some(ms);
503 self
504 }
505
506 pub fn kill_grace_ms(mut self, ms: u32) -> Self {
508 self.kill_grace_ms = ms;
509 self
510 }
511
512 pub fn cancel(mut self, policy: CancelPolicy) -> Self {
514 self.cancel = policy;
515 self
516 }
517
518 pub fn fd_policy(mut self, policy: SpawnFdPolicy) -> Self {
520 self.fd_policy = policy;
521 self
522 }
523
524 pub fn early_exit(mut self, callback: fn(&[u8]) -> bool) -> Self {
526 self.early_exit = Some(callback);
527 self
528 }
529
530 pub fn build(self) -> Result<SpawnOptions, CoreError> {
532 let ctx = ExecContext::new(self.argv, self.env, self.cwd)?;
533 Ok(SpawnOptions {
534 ctx,
535 stdin: self.stdin,
536 capture_stdout: self.capture_stdout,
537 capture_stderr: self.capture_stderr,
538 wait: self.wait,
539 pgroup: self.pgroup,
540 max_output: self.max_output,
541 timeout_ms: self.timeout_ms,
542 kill_grace_ms: self.kill_grace_ms,
543 cancel: self.cancel,
544 backend: self.backend,
545 fd_policy: self.fd_policy,
546 early_exit: self.early_exit,
547 })
548 }
549}
550
551#[derive(Debug)]
553pub struct Output {
554 pub pid: pid_t,
556 pub status: Option<ExitStatus>,
558 pub stdout: Vec<u8>,
560 pub stderr: Vec<u8>,
562 pub timed_out: bool,
564 pub stdout_early_exited: bool,
566}
567
568fn validate_backend(opts: &SpawnOptions) -> Result<(), CoreError> {
569 validate_fd_policy(&opts.fd_policy)?;
570 match opts.backend {
571 SpawnBackend::PosixSpawn => {
572 if opts.ctx.cwd.is_some() {
573 return Err(CoreError::sys(libc::EINVAL, "posix_spawn cwd unsupported"));
574 }
575 if opts.pgroup.isolated {
576 return Err(CoreError::sys(
577 libc::EINVAL,
578 "posix_spawn setsid unsupported",
579 ));
580 }
581 if opts.fd_policy != SpawnFdPolicy::CloexecOnly {
582 return Err(CoreError::sys(
583 libc::EINVAL,
584 "posix_spawn fd policy unsupported",
585 ));
586 }
587 Ok(())
588 }
589 SpawnBackend::Fork => {
590 if opts.pgroup.isolated && opts.pgroup.leader.is_some_and(|l| l != 0) {
595 return Err(CoreError::sys(
596 libc::EINVAL,
597 "fork isolated + custom setpgid leader unsupported",
598 ));
599 }
600 Ok(())
601 }
602 }
603}
604
605fn validate_fd_policy(policy: &SpawnFdPolicy) -> Result<(), CoreError> {
606 if let SpawnFdPolicy::Allowlist(fds) = policy {
607 let mut seen = Vec::with_capacity(fds.len());
608 for &fd in fds {
609 if fd < 0 {
610 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist invalid"));
611 }
612 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
613 if flags < 0 {
614 return Err(CoreError::sys(errno(), "spawn fd allowlist fcntl(F_GETFD)"));
615 }
616 if seen.contains(&fd) {
617 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist duplicate"));
618 }
619 seen.push(fd);
620 }
621 }
622 Ok(())
623}
624
625pub type SpawnDrain = DrainState<fn(&[u8]) -> bool>;
627
628pub struct RunningProcess {
635 pub process: Process,
637 drain: SpawnDrain,
638}
639
640pub struct ManagedProcess {
648 running: Option<RunningProcess>,
649 pid: pid_t,
650 timeout_at: Option<Instant>,
651 kill_grace: Duration,
652 cancel: CancelPolicy,
653 pgroup: ProcessGroup,
654 cancel_at: Option<Instant>,
655 kill_state: KillState,
656 status: Option<ExitStatus>,
657 timed_out: bool,
658 kill_sent_at: Option<Instant>,
659}
660
661impl RunningProcess {
662 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
668 self.drain.register_with_reactor(reactor)
669 }
670
671 pub fn handle_reactor_event(
677 &mut self,
678 reactor: &mut Reactor,
679 event: &crate::fd::Event,
680 ) -> Result<(), CoreError> {
681 if self.drain.stdout_matches(event.token) {
682 if event.readable || event.hangup {
683 self.drain.handle_stdout_ready(reactor)?;
684 } else if event.error {
685 self.drain.drop_stdout(reactor)?;
686 }
687 } else if self.drain.stderr_matches(event.token) {
688 if event.readable || event.hangup {
689 self.drain.handle_stderr_ready(reactor)?;
690 } else if event.error {
691 self.drain.drop_stderr(reactor)?;
692 }
693 } else if self.drain.stdin_matches(event.token) {
694 if event.writable {
695 self.drain.handle_stdin_writable(reactor)?;
696 } else if event.error || event.hangup {
697 self.drain.drop_stdin(reactor)?;
698 }
699 }
700 Ok(())
701 }
702
703 pub fn io_done(&self) -> bool {
705 self.drain.is_done()
706 }
707
708 pub fn into_output_parts(self) -> (Vec<u8>, Vec<u8>) {
710 self.drain.into_parts()
711 }
712}
713
714impl ManagedProcess {
715 pub fn pid(&self) -> pid_t {
720 self.pid
721 }
722
723 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
725 self.running
726 .as_mut()
727 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
728 .register_with_reactor(reactor)
729 }
730
731 pub fn handle_reactor_event(
733 &mut self,
734 reactor: &mut Reactor,
735 event: &crate::fd::Event,
736 ) -> Result<(), CoreError> {
737 self.running
738 .as_mut()
739 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
740 .handle_reactor_event(reactor, event)
741 }
742
743 pub fn request_cancel(&mut self) {
746 self.cancel_at.get_or_insert_with(Instant::now);
747 }
748
749 pub fn next_deadline(&self) -> Option<Instant> {
755 self.running.as_ref()?;
756 let now = Instant::now();
757 let mut next = now + Duration::from_millis(100);
758 if !self.timed_out
759 && let Some(timeout_at) = self.timeout_at
760 && timeout_at < next
761 {
762 next = timeout_at;
763 }
764 if self.kill_state == KillState::TermSent
765 && let Some(cancel_at) = self.cancel_at
766 {
767 let kill_at = cancel_at + self.kill_grace;
768 if kill_at < next {
769 next = kill_at;
770 }
771 }
772 if let Some(sent_at) = self.kill_sent_at {
775 let bail_at = sent_at + D_STATE_REAP_BOUND;
776 if bail_at < next {
777 next = bail_at;
778 }
779 }
780 Some(next)
781 }
782
783 pub fn poll_completion(&mut self, reactor: &mut Reactor) -> Result<Option<Output>, CoreError> {
792 let now = Instant::now();
793 if !self.timed_out
794 && let Some(timeout_at) = self.timeout_at
795 && now >= timeout_at
796 {
797 self.timed_out = true;
798 self.cancel_at.get_or_insert(timeout_at);
799 }
800
801 self.advance_cancel(now)?;
802
803 let running = self
804 .running
805 .as_ref()
806 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
807 if self.status.is_none() {
808 self.status = running.process.wait_step()?;
809 }
810
811 let io_done = running.io_done();
812 if self.status.is_some() && (io_done || self.cancel_at.is_some()) {
813 return self.finish(reactor, !io_done).map(Some);
814 }
815 if self.status.is_none()
819 && self
820 .kill_sent_at
821 .is_some_and(|t| now.duration_since(t) >= D_STATE_REAP_BOUND)
822 {
823 return self.finish(reactor, true).map(Some);
824 }
825 Ok(None)
826 }
827
828 fn advance_cancel(&mut self, now: Instant) -> Result<(), CoreError> {
829 let Some(cancel_at) = self.cancel_at else {
830 return Ok(());
831 };
832 if self.status.is_some() {
834 return Ok(());
835 }
836 let running = self
837 .running
838 .as_ref()
839 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
840 let process = &running.process;
841 let pid = process.pid();
842 let pgid = effective_pgid(pid, self.pgroup);
843 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
844 match self.kill_state {
845 KillState::None => match self.cancel {
846 CancelPolicy::None => {}
847 CancelPolicy::Graceful => {
848 let result = signal_process(process, target_is_group, pgid, libc::SIGTERM);
849 self.kill_state = if result.is_ok() {
850 KillState::TermSent
851 } else {
852 KillState::KillSent
853 };
854 if self.kill_state == KillState::KillSent {
855 self.kill_sent_at = Some(now);
856 }
857 }
858 CancelPolicy::Kill => {
859 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
860 self.kill_state = KillState::KillSent;
861 self.kill_sent_at = Some(now);
862 }
863 },
864 KillState::TermSent if now >= cancel_at + self.kill_grace => {
865 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
866 self.kill_state = KillState::KillSent;
867 self.kill_sent_at = Some(now);
868 }
869 _ => {}
870 }
871 Ok(())
872 }
873
874 fn finish(&mut self, reactor: &mut Reactor, force_close: bool) -> Result<Output, CoreError> {
875 let mut running = self
876 .running
877 .take()
878 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
879 for slot in running.drain.take_all_slots() {
880 if force_close {
881 let _ = reactor.del(&slot.fd);
882 } else {
883 reactor.del(&slot.fd)?;
884 }
885 }
886 let pid = running.process.pid();
887 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
888 running.drain.into_parts_with_state();
889 if output_limit_exceeded && !force_close {
894 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
895 }
896 Ok(Output {
897 pid,
898 status: self.status.take(),
899 stdout,
900 stderr,
901 timed_out: self.timed_out,
902 stdout_early_exited,
903 })
904 }
905}
906
907impl Drop for ManagedProcess {
908 fn drop(&mut self) {
909 let Some(running) = self.running.take() else {
910 return;
911 };
912 if self.status.is_some() {
916 return;
917 }
918 let process = &running.process;
919 let pid = process.pid();
920 let pgid = effective_pgid(pid, self.pgroup);
921 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
922 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
923 let deadline = Instant::now() + Duration::from_millis(100);
928 while Instant::now() < deadline {
929 match process.wait_step() {
930 Ok(Some(_)) => return,
931 Ok(None) => std::thread::sleep(Duration::from_millis(5)),
932 Err(_) => return,
933 }
934 }
935 }
936}
937
938fn effective_pgid(pid: pid_t, pgroup: ProcessGroup) -> pid_t {
939 match pgroup.leader {
940 Some(0) | None => pid,
941 Some(leader) => leader,
942 }
943}
944
945fn signal_process(
946 process: &Process,
947 target_is_group: bool,
948 pgid: pid_t,
949 signal: i32,
950) -> Result<(), CoreError> {
951 if target_is_group {
952 process.kill_group(pgid, signal)
953 } else {
954 process.kill(signal)
955 }
956}
957
958pub fn spawn_start(opts: SpawnOptions) -> Result<RunningProcess, CoreError> {
973 if !opts.wait && (opts.stdin.is_some() || opts.capture_stdout || opts.capture_stderr) {
974 return Err(CoreError::sys(
975 libc::EINVAL,
976 "background I/O capture not supported (wait must be true)",
977 ));
978 }
979
980 validate_backend(&opts)?;
981
982 let (pid, drain) = match opts.backend {
983 SpawnBackend::PosixSpawn => spawn_posix_internal(opts)?,
984 SpawnBackend::Fork => spawn_fork_internal(opts)?,
985 };
986
987 Ok(RunningProcess {
988 process: Process::new(pid),
989 drain,
990 })
991}
992
993pub fn spawn_managed(opts: SpawnOptions) -> Result<ManagedProcess, CoreError> {
996 if !opts.wait {
997 return Err(CoreError::sys(
998 libc::EINVAL,
999 "managed process requires wait=true",
1000 ));
1001 }
1002 let timeout_at = opts
1003 .timeout_ms
1004 .map(|ms| Instant::now() + Duration::from_millis(ms as u64));
1005 let kill_grace = Duration::from_millis(opts.kill_grace_ms as u64);
1006 let cancel = opts.cancel;
1007 let pgroup = opts.pgroup;
1008 let running = spawn_start(opts)?;
1009 let pid = running.process.pid();
1010 Ok(ManagedProcess {
1011 running: Some(running),
1012 pid,
1013 timeout_at,
1014 kill_grace,
1015 cancel,
1016 pgroup,
1017 cancel_at: None,
1018 kill_state: KillState::None,
1019 status: None,
1020 timed_out: false,
1021 kill_sent_at: None,
1022 })
1023}
1024
1025pub fn spawn(opts: SpawnOptions) -> Result<Output, CoreError> {
1034 let wait = opts.wait;
1035 let timeout_ms = opts.timeout_ms;
1036 let kill_grace_ms = opts.kill_grace_ms;
1037 let cancel = opts.cancel;
1038 let pgroup = opts.pgroup;
1039
1040 let mut reactor = Reactor::new()?;
1041 let running = spawn_start(opts)?;
1042
1043 let pid = running.process.pid();
1044 let mut drain = running.drain;
1045
1046 drain.register_with_reactor(&mut reactor)?;
1047
1048 if !wait {
1049 let (stdout, stderr) = drain.into_parts();
1050 return Ok(Output {
1051 pid,
1052 status: None,
1053 stdout,
1054 stderr,
1055 timed_out: false,
1056 stdout_early_exited: false,
1057 });
1058 }
1059
1060 wait_loop(
1061 pid,
1062 drain,
1063 reactor,
1064 timeout_ms,
1065 kill_grace_ms,
1066 cancel,
1067 pgroup,
1068 )
1069}
1070
1071#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1072enum KillState {
1073 None,
1074 TermSent,
1075 KillSent,
1076}
1077
1078fn wait_loop(
1079 pid: pid_t,
1080 mut drain: crate::io::DrainState<fn(&[u8]) -> bool>,
1081 mut reactor: Reactor,
1082 timeout_ms: Option<u32>,
1083 kill_grace_ms: u32,
1084 cancel: CancelPolicy,
1085 pgroup: ProcessGroup,
1086) -> Result<Output, CoreError> {
1087 let process = Process::new(pid);
1088 let pgid = effective_pgid(pid, pgroup);
1093 let mut status_raw = process.wait_step()?;
1094 let mut state = KillState::None;
1095 let mut timed_out = false;
1096 let mut kill_sent_at: Option<Instant> = None;
1100
1101 let start_time = std::time::Instant::now();
1102 let deadline = timeout_ms.map(|t| std::time::Duration::from_millis(t as u64));
1103
1104 loop {
1105 let mut poll_timeout = -1;
1106
1107 if let Some(dl) = deadline {
1108 let elapsed = start_time.elapsed();
1109 if elapsed >= dl {
1110 timed_out = true;
1111 let elapsed_over = (elapsed - dl).as_millis();
1112
1113 let target_is_group = pgroup.isolated || pgroup.leader.is_some();
1114
1115 if status_raw.is_none() {
1120 match state {
1121 KillState::None => {
1122 if cancel == CancelPolicy::Graceful {
1123 let r = if target_is_group {
1124 process.kill_group(pgid, libc::SIGTERM)
1125 } else {
1126 process.kill(libc::SIGTERM)
1127 };
1128 if r.is_err() {
1129 state = KillState::KillSent; kill_sent_at = Some(Instant::now());
1131 } else {
1132 state = KillState::TermSent;
1133 }
1134 } else if cancel == CancelPolicy::Kill {
1135 let _ = if target_is_group {
1136 process.kill_group(pgid, libc::SIGKILL)
1137 } else {
1138 process.kill(libc::SIGKILL)
1139 };
1140 state = KillState::KillSent;
1141 kill_sent_at = Some(Instant::now());
1142 } else {
1143 }
1145 }
1146 KillState::TermSent if elapsed_over > kill_grace_ms as u128 => {
1147 let _ = if target_is_group {
1148 process.kill_group(pgid, libc::SIGKILL)
1149 } else {
1150 process.kill(libc::SIGKILL)
1151 };
1152 state = KillState::KillSent;
1153 kill_sent_at = Some(Instant::now());
1154 }
1155 _ => {}
1156 }
1157 }
1158 poll_timeout = 100; } else {
1160 let remaining = dl - elapsed;
1161 poll_timeout = remaining.as_millis().min(i32::MAX as u128) as i32;
1162 }
1163 }
1164
1165 if status_raw.is_none()
1166 && let Some(s) = process.wait_step()?
1167 {
1168 status_raw = Some(s);
1169 }
1170
1171 if drain.is_done() {
1172 let s = if status_raw.is_some() {
1173 status_raw.take()
1174 } else if deadline.is_none() {
1175 Some(process.wait_blocking()?)
1178 } else {
1179 None
1184 };
1185
1186 if let Some(s) = s {
1187 for slot in drain.take_all_slots() {
1188 reactor.del(&slot.fd)?;
1189 }
1190 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
1191 drain.into_parts_with_state();
1192 if output_limit_exceeded {
1193 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
1194 }
1195 return Ok(Output {
1196 pid,
1197 status: Some(s),
1198 stdout,
1199 stderr,
1200 timed_out,
1201 stdout_early_exited,
1202 });
1203 }
1204 }
1205
1206 if timed_out && status_raw.is_some() {
1211 for slot in drain.take_all_slots() {
1212 let _ = reactor.del(&slot.fd);
1213 }
1214 let (stdout, stderr, _output_limit_exceeded, stdout_early_exited) =
1215 drain.into_parts_with_state();
1216 return Ok(Output {
1217 pid,
1218 status: status_raw,
1219 stdout,
1220 stderr,
1221 timed_out: true,
1222 stdout_early_exited,
1223 });
1224 }
1225
1226 if let Some(sent_at) = kill_sent_at
1232 && sent_at.elapsed() >= D_STATE_REAP_BOUND
1233 && status_raw.is_none()
1234 {
1235 for slot in drain.take_all_slots() {
1236 let _ = reactor.del(&slot.fd);
1237 }
1238 let (stdout, stderr, _output_limit_exceeded, stdout_early_exited) =
1239 drain.into_parts_with_state();
1240 return Ok(Output {
1241 pid,
1242 status: None,
1243 stdout,
1244 stderr,
1245 timed_out: true,
1246 stdout_early_exited,
1247 });
1248 }
1249
1250 let timeout = poll_timeout;
1251
1252 let mut events = Vec::new();
1253 let nevents = reactor.wait(&mut events, 64, timeout)?;
1254
1255 for ev in events.iter().take(nevents) {
1256 if drain.stdout_matches(ev.token) {
1257 if ev.readable || ev.hangup {
1258 drain.handle_stdout_ready(&mut reactor)?;
1259 } else if ev.error {
1260 drain.drop_stdout(&mut reactor)?;
1261 }
1262 } else if drain.stderr_matches(ev.token) {
1263 if ev.readable || ev.hangup {
1264 drain.handle_stderr_ready(&mut reactor)?;
1265 } else if ev.error {
1266 drain.drop_stderr(&mut reactor)?;
1267 }
1268 } else if drain.stdin_matches(ev.token) {
1269 if ev.writable {
1270 drain.handle_stdin_writable(&mut reactor)?;
1271 } else if ev.error || ev.hangup {
1272 drain.drop_stdin(&mut reactor)?;
1273 }
1274 }
1275 }
1276 }
1277}