1use std::ffi::CString;
13use std::mem::MaybeUninit;
14use std::os::unix::io::RawFd;
15use std::ptr;
16use std::time::{Duration, Instant};
17
18use crate::CoreError;
19use crate::error::{posix_ret, syscall_ret};
20use crate::fd::Fd;
21use crate::signal::SignalRuntime;
22use libc::{
23 O_CLOEXEC, O_NONBLOCK, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG, c_char, pid_t, pipe2,
24 waitpid,
25};
26
27unsafe extern "C" {
28 pub(crate) static mut environ: *mut *mut libc::c_char;
29}
30
31pub(crate) const POSIX_SPAWN_SETPGROUP: i32 = 2;
32pub(crate) const POSIX_SPAWN_SETSIGDEF: i32 = 4;
33pub(crate) const POSIX_SPAWN_SETSIGMASK: i32 = 8;
34
35unsafe extern "C" {
36 pub(crate) fn posix_spawn(
37 pid: *mut libc::pid_t,
38 path: *const libc::c_char,
39 file_actions: *const libc::posix_spawn_file_actions_t,
40 attrp: *const libc::posix_spawnattr_t,
41 argv: *const *mut libc::c_char,
42 envp: *const *mut libc::c_char,
43 ) -> libc::c_int;
44
45 pub(crate) fn posix_spawn_file_actions_addclose(
46 file_actions: *mut libc::posix_spawn_file_actions_t,
47 fd: libc::c_int,
48 ) -> libc::c_int;
49
50 pub(crate) fn posix_spawn_file_actions_adddup2(
51 file_actions: *mut libc::posix_spawn_file_actions_t,
52 fd: libc::c_int,
53 newfd: libc::c_int,
54 ) -> libc::c_int;
55
56 pub(crate) fn posix_spawn_file_actions_destroy(
57 file_actions: *mut libc::posix_spawn_file_actions_t,
58 ) -> libc::c_int;
59
60 pub(crate) fn posix_spawn_file_actions_init(
61 file_actions: *mut libc::posix_spawn_file_actions_t,
62 ) -> libc::c_int;
63
64 pub(crate) fn posix_spawnattr_destroy(attr: *mut libc::posix_spawnattr_t) -> libc::c_int;
65
66 pub(crate) fn posix_spawnattr_init(attr: *mut libc::posix_spawnattr_t) -> libc::c_int;
67
68 pub(crate) fn posix_spawnattr_setflags(
69 attr: *mut libc::posix_spawnattr_t,
70 flags: libc::c_short,
71 ) -> libc::c_int;
72
73 pub(crate) fn posix_spawnattr_setpgroup(
74 attr: *mut libc::posix_spawnattr_t,
75 pgroup: libc::pid_t,
76 ) -> libc::c_int;
77
78 pub(crate) fn posix_spawnattr_setsigdefault(
79 attr: *mut libc::posix_spawnattr_t,
80 sigdefault: *const libc::sigset_t,
81 ) -> libc::c_int;
82
83 pub(crate) fn posix_spawnattr_setsigmask(
84 attr: *mut libc::posix_spawnattr_t,
85 sigmask: *const libc::sigset_t,
86 ) -> libc::c_int;
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
91pub enum CancelPolicy {
92 #[default]
94 None,
95 Graceful,
97 Kill,
99}
100
101#[derive(Debug, Clone, Copy, Default)]
103pub struct ProcessGroup {
104 pub leader: Option<pid_t>,
106 pub isolated: bool,
108}
109
110impl ProcessGroup {
111 pub fn new(leader: Option<pid_t>, isolated: bool) -> Self {
113 Self { leader, isolated }
114 }
115}
116
117#[inline(always)]
118fn errno() -> i32 {
119 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
120}
121
122#[inline(always)]
125fn make_pipe() -> Result<(Fd, Fd), CoreError> {
126 let mut fds = [0; 2];
127 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC | O_NONBLOCK) };
128 syscall_ret(r, "pipe2")?;
129 Ok((Fd::new(fds[0], "pipe2")?, Fd::new(fds[1], "pipe2")?))
130}
131
132fn make_cloexec_pipe() -> Result<(RawFd, RawFd), CoreError> {
133 let mut fds = [0; 2];
134 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC) };
135 syscall_ret(r, "pipe2")?;
136 Ok((fds[0], fds[1]))
137}
138
139#[repr(u8)]
140#[derive(Clone, Copy)]
141enum ChildSetupOp {
142 DupStdin = 1,
143 DupStdout = 2,
144 DupStderr = 3,
145 Setsid = 4,
146 Chdir = 5,
147 Setpgid = 6,
148 SignalMask = 7,
149 Execve = 8,
150}
151
152impl ChildSetupOp {
153 fn as_str(self) -> &'static str {
154 match self {
155 Self::DupStdin => "fork child dup2 stdin",
156 Self::DupStdout => "fork child dup2 stdout",
157 Self::DupStderr => "fork child dup2 stderr",
158 Self::Setsid => "fork child setsid",
159 Self::Chdir => "fork child chdir",
160 Self::Setpgid => "fork child setpgid",
161 Self::SignalMask => "fork child signal setup",
162 Self::Execve => "fork child execve",
163 }
164 }
165
166 fn from_u8(value: u8) -> Self {
167 match value {
168 1 => Self::DupStdin,
169 2 => Self::DupStdout,
170 3 => Self::DupStderr,
171 4 => Self::Setsid,
172 5 => Self::Chdir,
173 6 => Self::Setpgid,
174 7 => Self::SignalMask,
175 _ => Self::Execve,
176 }
177 }
178}
179
180unsafe fn report_child_setup_error(fd: RawFd, op: ChildSetupOp, code: i32) -> ! {
181 let mut msg = [0u8; 5];
182 msg[..4].copy_from_slice(&code.to_ne_bytes());
183 msg[4] = op as u8;
184 let mut written = 0;
185 while written < msg.len() {
186 let n = unsafe {
187 libc::write(
188 fd,
189 msg[written..].as_ptr().cast::<libc::c_void>(),
190 msg.len() - written,
191 )
192 };
193 if n <= 0 {
194 break;
195 }
196 written += n as usize;
197 }
198 unsafe {
199 libc::_exit(127);
200 }
201}
202
203fn read_child_setup_error(fd: RawFd) -> Result<Option<CoreError>, CoreError> {
204 let mut msg = [0u8; 5];
205 let mut read_len = 0;
206 loop {
207 let n = unsafe {
208 libc::read(
209 fd,
210 msg[read_len..].as_mut_ptr().cast::<libc::c_void>(),
211 msg.len() - read_len,
212 )
213 };
214 if n == 0 {
215 return Ok(None);
216 }
217 if n < 0 {
218 let code = errno();
219 if code == libc::EINTR {
220 continue;
221 }
222 return Err(CoreError::sys(code, "read fork child setup error"));
223 }
224 read_len += n as usize;
225 if read_len == msg.len() {
226 let code = i32::from_ne_bytes([msg[0], msg[1], msg[2], msg[3]]);
227 return Ok(Some(CoreError::sys(
228 code,
229 ChildSetupOp::from_u8(msg[4]).as_str(),
230 )));
231 }
232 }
233}
234
235struct Pipes {
236 stdin_r: Option<Fd>,
237 stdin_w: Option<Fd>,
238 stdout_r: Option<Fd>,
239 stdout_w: Option<Fd>,
240 stderr_r: Option<Fd>,
241 stderr_w: Option<Fd>,
242}
243
244impl Pipes {
245 fn new(in_buf: Option<&[u8]>, out: bool, err: bool) -> Result<Self, CoreError> {
246 let (stdin_r, stdin_w) = if in_buf.is_some() {
247 let (r, w) = make_pipe()?;
248 (Some(r), Some(w))
249 } else {
250 (None, None)
251 };
252
253 let (stdout_r, stdout_w) = if out {
254 let (r, w) = make_pipe()?;
255 (Some(r), Some(w))
256 } else {
257 (None, None)
258 };
259
260 let (stderr_r, stderr_w) = if err {
261 let (r, w) = make_pipe()?;
262 (Some(r), Some(w))
263 } else {
264 (None, None)
265 };
266
267 Ok(Self {
268 stdin_r,
269 stdin_w,
270 stdout_r,
271 stdout_w,
272 stderr_r,
273 stderr_w,
274 })
275 }
276
277 #[inline(always)]
278 fn close_all(&mut self) {
279 self.stdin_r.take();
280 self.stdin_w.take();
281 self.stdout_r.take();
282 self.stdout_w.take();
283 self.stderr_r.take();
284 self.stderr_w.take();
285 }
286}
287
288#[derive(Debug, PartialEq, Eq)]
290pub enum ExitStatus {
291 Exited(i32),
293 Signaled(i32),
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub enum SpawnBackend {
300 PosixSpawn,
302 Fork,
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, Default)]
311pub enum SpawnFdPolicy {
312 #[default]
314 CloexecOnly,
315 CloseFrom3,
318 Allowlist(Vec<RawFd>),
325}
326
327#[derive(Clone)]
329enum ExecArgv {
330 Dynamic(Vec<CString>),
332}
333
334#[derive(Clone)]
336struct ExecContext {
337 argv: ExecArgv,
338 envp: Option<Vec<CString>>,
339 cwd: Option<CString>,
340}
341
342impl ExecContext {
343 fn new(
345 argv: Vec<String>,
346 env: Option<Vec<String>>,
347 cwd: Option<String>,
348 ) -> Result<Self, CoreError> {
349 if argv.is_empty() {
350 return Err(CoreError::sys(libc::EINVAL, "exec argv empty"));
351 }
352
353 let c_argv: Vec<CString> = argv
354 .into_iter()
355 .map(|s| {
356 CString::new(s).map_err(|_| CoreError::sys(libc::EINVAL, "exec argv contains nul"))
357 })
358 .collect::<Result<_, _>>()?;
359
360 let c_envp = match env {
361 Some(vars) => Some(
362 vars.into_iter()
363 .map(|s| {
364 CString::new(s)
365 .map_err(|_| CoreError::sys(libc::EINVAL, "exec env contains nul"))
366 })
367 .collect::<Result<Vec<_>, _>>()?,
368 ),
369 None => None,
370 };
371
372 let c_cwd = match cwd {
373 Some(c) => Some(
374 CString::new(c)
375 .map_err(|_| CoreError::sys(libc::EINVAL, "exec cwd contains nul"))?,
376 ),
377 None => None,
378 };
379
380 Ok(Self {
381 argv: ExecArgv::Dynamic(c_argv),
382 envp: c_envp,
383 cwd: c_cwd,
384 })
385 }
386
387 fn get_argv_ptrs(&self) -> Vec<*mut libc::c_char> {
389 let mut ptrs = Vec::new();
390 match &self.argv {
391 ExecArgv::Dynamic(v) => {
392 for s in v {
393 ptrs.push(s.as_ptr() as *mut libc::c_char);
394 }
395 }
396 }
397 ptrs.push(ptr::null_mut());
398 ptrs
399 }
400
401 fn get_envp_ptrs(&self) -> Option<Vec<*mut libc::c_char>> {
403 self.envp.as_ref().map(|envp| {
404 let mut ptrs = Vec::new();
405 for s in envp {
406 ptrs.push(s.as_ptr() as *mut libc::c_char);
407 }
408 ptrs.push(ptr::null_mut());
409 ptrs
410 })
411 }
412}
413
414#[inline(always)]
415fn decode_status(status: i32) -> ExitStatus {
416 if WIFEXITED(status) {
417 ExitStatus::Exited(WEXITSTATUS(status))
418 } else if WIFSIGNALED(status) {
419 ExitStatus::Signaled(WTERMSIG(status))
420 } else {
421 ExitStatus::Exited(-1)
422 }
423}
424
425pub struct Process {
433 pid: pid_t,
434}
435
436impl Process {
437 pub fn new(pid: pid_t) -> Self {
439 Self { pid }
440 }
441
442 pub fn pid(&self) -> pid_t {
444 self.pid
445 }
446
447 pub fn wait_step(&self) -> Result<Option<ExitStatus>, CoreError> {
453 loop {
454 let mut status = 0;
455 let r = unsafe { waitpid(self.pid, &mut status, libc::WNOHANG) };
456 if r == 0 {
457 return Ok(None);
458 }
459 if r < 0 {
460 let e = errno();
461 if e == libc::EINTR {
462 continue;
463 }
464 return Err(CoreError::sys(e, "waitpid_step"));
465 }
466 return Ok(Some(decode_status(status)));
467 }
468 }
469
470 pub fn wait_blocking(&self) -> Result<ExitStatus, CoreError> {
475 loop {
476 let mut status = 0;
477 let r = unsafe { waitpid(self.pid, &mut status, 0) };
478 if r < 0 {
479 let e = errno();
480 if e == libc::EINTR {
481 continue;
482 }
483 return Err(CoreError::sys(e, "waitpid_blocking"));
484 }
485 return Ok(decode_status(status));
486 }
487 }
488
489 pub fn kill(&self, sig: i32) -> Result<(), CoreError> {
496 let r = unsafe { libc::kill(self.pid, sig) };
497 if r < 0 {
498 let e = errno();
499 if e == libc::ESRCH {
500 return Ok(());
501 }
502 syscall_ret(-1, "kill")?;
503 }
504 Ok(())
505 }
506
507 pub fn kill_pgroup(&self, sig: i32) -> Result<(), CoreError> {
514 self.kill_group(self.pid, sig)
515 }
516
517 pub fn kill_group(&self, pgid: pid_t, sig: i32) -> Result<(), CoreError> {
526 let r = unsafe { libc::kill(-pgid, sig) };
527 if r < 0 {
528 let e = errno();
529 if e == libc::ESRCH {
530 return Ok(());
531 }
532 syscall_ret(-1, "kill_group")?;
533 }
534 Ok(())
535 }
536}
537
538#[derive(Clone)]
540pub struct SpawnOptions {
541 ctx: ExecContext,
542 stdin: Option<Box<[u8]>>,
543 capture_stdout: bool,
544 capture_stderr: bool,
545 wait: bool,
546 pgroup: ProcessGroup,
547 max_output: usize,
548 timeout_ms: Option<u32>,
549 kill_grace_ms: u32,
550 cancel: CancelPolicy,
551 backend: SpawnBackend,
552 fd_policy: SpawnFdPolicy,
553 early_exit: Option<fn(&[u8]) -> bool>,
554}
555
556impl SpawnOptions {
557 pub fn builder(argv: Vec<String>, backend: SpawnBackend) -> SpawnOptionsBuilder {
559 SpawnOptionsBuilder::new(argv, backend)
560 }
561
562 pub fn run(self) -> Result<Output, CoreError> {
564 spawn(self)
565 }
566}
567
568#[derive(Clone)]
570pub struct SpawnOptionsBuilder {
571 argv: Vec<String>,
572 env: Option<Vec<String>>,
573 cwd: Option<String>,
574 stdin: Option<Box<[u8]>>,
575 capture_stdout: bool,
576 capture_stderr: bool,
577 wait: bool,
578 pgroup: ProcessGroup,
579 max_output: usize,
580 timeout_ms: Option<u32>,
581 kill_grace_ms: u32,
582 cancel: CancelPolicy,
583 backend: SpawnBackend,
584 fd_policy: SpawnFdPolicy,
585 early_exit: Option<fn(&[u8]) -> bool>,
586}
587
588impl SpawnOptionsBuilder {
589 pub fn new(argv: Vec<String>, backend: SpawnBackend) -> Self {
591 Self {
592 argv,
593 env: None,
594 cwd: None,
595 stdin: None,
596 capture_stdout: false,
597 capture_stderr: false,
598 wait: true,
599 pgroup: ProcessGroup::default(),
600 max_output: 1024 * 1024,
601 timeout_ms: None,
602 kill_grace_ms: 2000,
603 cancel: CancelPolicy::Kill,
604 backend,
605 fd_policy: SpawnFdPolicy::default(),
606 early_exit: None,
607 }
608 }
609
610 pub fn env(mut self, env: Vec<String>) -> Self {
612 self.env = Some(env);
613 self
614 }
615
616 pub fn cwd(mut self, cwd: String) -> Self {
618 self.cwd = Some(cwd);
619 self
620 }
621
622 pub fn stdin(mut self, data: impl Into<Box<[u8]>>) -> Self {
624 self.stdin = Some(data.into());
625 self
626 }
627
628 pub fn capture_stdout(mut self) -> Self {
630 self.capture_stdout = true;
631 self
632 }
633
634 pub fn capture_stderr(mut self) -> Self {
636 self.capture_stderr = true;
637 self
638 }
639
640 pub fn wait(mut self, wait: bool) -> Self {
642 self.wait = wait;
643 self
644 }
645
646 pub fn pgroup(mut self, pgroup: ProcessGroup) -> Self {
648 self.pgroup = pgroup;
649 self
650 }
651
652 pub fn max_output(mut self, max: usize) -> Self {
657 self.max_output = max;
658 self
659 }
660
661 pub fn timeout_ms(mut self, ms: u32) -> Self {
663 self.timeout_ms = Some(ms);
664 self
665 }
666
667 pub fn kill_grace_ms(mut self, ms: u32) -> Self {
669 self.kill_grace_ms = ms;
670 self
671 }
672
673 pub fn cancel(mut self, policy: CancelPolicy) -> Self {
675 self.cancel = policy;
676 self
677 }
678
679 pub fn fd_policy(mut self, policy: SpawnFdPolicy) -> Self {
681 self.fd_policy = policy;
682 self
683 }
684
685 pub fn early_exit(mut self, callback: fn(&[u8]) -> bool) -> Self {
687 self.early_exit = Some(callback);
688 self
689 }
690
691 pub fn build(self) -> Result<SpawnOptions, CoreError> {
693 let ctx = ExecContext::new(self.argv, self.env, self.cwd)?;
694 Ok(SpawnOptions {
695 ctx,
696 stdin: self.stdin,
697 capture_stdout: self.capture_stdout,
698 capture_stderr: self.capture_stderr,
699 wait: self.wait,
700 pgroup: self.pgroup,
701 max_output: self.max_output,
702 timeout_ms: self.timeout_ms,
703 kill_grace_ms: self.kill_grace_ms,
704 cancel: self.cancel,
705 backend: self.backend,
706 fd_policy: self.fd_policy,
707 early_exit: self.early_exit,
708 })
709 }
710}
711
712#[derive(Debug)]
714pub struct Output {
715 pub pid: pid_t,
717 pub status: Option<ExitStatus>,
719 pub stdout: Vec<u8>,
721 pub stderr: Vec<u8>,
723 pub timed_out: bool,
725 pub stdout_early_exited: bool,
727}
728
729fn validate_backend(opts: &SpawnOptions) -> Result<(), CoreError> {
730 validate_fd_policy(&opts.fd_policy)?;
731 match opts.backend {
732 SpawnBackend::PosixSpawn => {
733 if opts.ctx.cwd.is_some() {
734 return Err(CoreError::sys(libc::EINVAL, "posix_spawn cwd unsupported"));
735 }
736 if opts.pgroup.isolated {
737 return Err(CoreError::sys(
738 libc::EINVAL,
739 "posix_spawn setsid unsupported",
740 ));
741 }
742 if opts.fd_policy != SpawnFdPolicy::CloexecOnly {
743 return Err(CoreError::sys(
744 libc::EINVAL,
745 "posix_spawn fd policy unsupported",
746 ));
747 }
748 Ok(())
749 }
750 SpawnBackend::Fork => Ok(()),
751 }
752}
753
754fn validate_fd_policy(policy: &SpawnFdPolicy) -> Result<(), CoreError> {
755 if let SpawnFdPolicy::Allowlist(fds) = policy {
756 let mut seen = Vec::with_capacity(fds.len());
757 for &fd in fds {
758 if fd < 0 {
759 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist invalid"));
760 }
761 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
762 if flags < 0 {
763 return Err(CoreError::sys(errno(), "spawn fd allowlist fcntl(F_GETFD)"));
764 }
765 if seen.contains(&fd) {
766 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist duplicate"));
767 }
768 seen.push(fd);
769 }
770 }
771 Ok(())
772}
773
774use crate::io::DrainState;
775
776pub type SpawnDrain = DrainState<fn(&[u8]) -> bool>;
778
779pub struct RunningProcess {
786 pub process: Process,
788 drain: SpawnDrain,
789}
790
791pub struct ManagedProcess {
799 running: Option<RunningProcess>,
800 timeout_at: Option<Instant>,
801 kill_grace: Duration,
802 cancel: CancelPolicy,
803 pgroup: ProcessGroup,
804 cancel_at: Option<Instant>,
805 kill_state: KillState,
806 status: Option<ExitStatus>,
807 timed_out: bool,
808}
809
810impl RunningProcess {
811 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
817 self.drain.register_with_reactor(reactor)
818 }
819
820 pub fn handle_reactor_event(
826 &mut self,
827 reactor: &mut Reactor,
828 event: &crate::fd::Event,
829 ) -> Result<(), CoreError> {
830 if self.drain.stdout_matches(event.token) {
831 if event.readable || event.hangup {
832 self.drain.handle_stdout_ready(reactor)?;
833 } else if event.error {
834 self.drain.drop_stdout(reactor)?;
835 }
836 } else if self.drain.stderr_matches(event.token) {
837 if event.readable || event.hangup {
838 self.drain.handle_stderr_ready(reactor)?;
839 } else if event.error {
840 self.drain.drop_stderr(reactor)?;
841 }
842 } else if self.drain.stdin_matches(event.token) {
843 if event.writable {
844 self.drain.handle_stdin_writable(reactor)?;
845 } else if event.error || event.hangup {
846 self.drain.drop_stdin(reactor)?;
847 }
848 }
849 Ok(())
850 }
851
852 pub fn io_done(&self) -> bool {
854 self.drain.is_done()
855 }
856
857 pub fn into_output_parts(self) -> (Vec<u8>, Vec<u8>) {
859 self.drain.into_parts()
860 }
861}
862
863impl ManagedProcess {
864 pub fn pid(&self) -> pid_t {
866 self.running
867 .as_ref()
868 .expect("managed process already completed")
869 .process
870 .pid()
871 }
872
873 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
875 self.running
876 .as_mut()
877 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
878 .register_with_reactor(reactor)
879 }
880
881 pub fn handle_reactor_event(
883 &mut self,
884 reactor: &mut Reactor,
885 event: &crate::fd::Event,
886 ) -> Result<(), CoreError> {
887 self.running
888 .as_mut()
889 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?
890 .handle_reactor_event(reactor, event)
891 }
892
893 pub fn request_cancel(&mut self) {
896 self.cancel_at.get_or_insert_with(Instant::now);
897 }
898
899 pub fn next_deadline(&self) -> Option<Instant> {
905 self.running.as_ref()?;
906 let now = Instant::now();
907 let mut next = now + Duration::from_millis(100);
908 if !self.timed_out
909 && let Some(timeout_at) = self.timeout_at
910 && timeout_at < next
911 {
912 next = timeout_at;
913 }
914 if self.kill_state == KillState::TermSent
915 && let Some(cancel_at) = self.cancel_at
916 {
917 let kill_at = cancel_at + self.kill_grace;
918 if kill_at < next {
919 next = kill_at;
920 }
921 }
922 Some(next)
923 }
924
925 pub fn poll_completion(&mut self, reactor: &mut Reactor) -> Result<Option<Output>, CoreError> {
931 let now = Instant::now();
932 if !self.timed_out
933 && let Some(timeout_at) = self.timeout_at
934 && now >= timeout_at
935 {
936 self.timed_out = true;
937 self.cancel_at.get_or_insert(timeout_at);
938 }
939
940 self.advance_cancel(now)?;
941
942 let running = self
943 .running
944 .as_ref()
945 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
946 if self.status.is_none() {
947 self.status = running.process.wait_step()?;
948 }
949
950 let io_done = running.io_done();
951 if self.status.is_some() && (io_done || self.cancel_at.is_some()) {
952 return self.finish(reactor, !io_done).map(Some);
953 }
954 Ok(None)
955 }
956
957 fn advance_cancel(&mut self, now: Instant) -> Result<(), CoreError> {
958 let Some(cancel_at) = self.cancel_at else {
959 return Ok(());
960 };
961 let running = self
962 .running
963 .as_ref()
964 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
965 let process = &running.process;
966 let pid = process.pid();
967 let pgid = effective_pgid(pid, self.pgroup);
968 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
969 match self.kill_state {
970 KillState::None => match self.cancel {
971 CancelPolicy::None => {}
972 CancelPolicy::Graceful => {
973 let result = signal_process(process, target_is_group, pgid, libc::SIGTERM);
974 self.kill_state = if result.is_ok() {
975 KillState::TermSent
976 } else {
977 KillState::KillSent
978 };
979 }
980 CancelPolicy::Kill => {
981 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
982 self.kill_state = KillState::KillSent;
983 }
984 },
985 KillState::TermSent if now >= cancel_at + self.kill_grace => {
986 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
987 self.kill_state = KillState::KillSent;
988 }
989 _ => {}
990 }
991 Ok(())
992 }
993
994 fn finish(&mut self, reactor: &mut Reactor, force_close: bool) -> Result<Output, CoreError> {
995 let mut running = self
996 .running
997 .take()
998 .ok_or_else(|| CoreError::sys(libc::EINVAL, "managed process completed"))?;
999 for slot in running.drain.take_all_slots() {
1000 if force_close {
1001 let _ = reactor.del(&slot.fd);
1002 } else {
1003 reactor.del(&slot.fd)?;
1004 }
1005 }
1006 let pid = running.process.pid();
1007 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
1008 running.drain.into_parts_with_state();
1009 if output_limit_exceeded {
1010 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
1011 }
1012 Ok(Output {
1013 pid,
1014 status: self.status.take(),
1015 stdout,
1016 stderr,
1017 timed_out: self.timed_out,
1018 stdout_early_exited,
1019 })
1020 }
1021}
1022
1023impl Drop for ManagedProcess {
1024 fn drop(&mut self) {
1025 let Some(running) = self.running.take() else {
1026 return;
1027 };
1028 let process = &running.process;
1029 let pid = process.pid();
1030 let pgid = effective_pgid(pid, self.pgroup);
1031 let target_is_group = self.pgroup.isolated || self.pgroup.leader.is_some();
1032 let _ = signal_process(process, target_is_group, pgid, libc::SIGKILL);
1033 let _ = process.wait_blocking();
1034 }
1035}
1036
1037fn effective_pgid(pid: pid_t, pgroup: ProcessGroup) -> pid_t {
1038 match pgroup.leader {
1039 Some(0) | None => pid,
1040 Some(leader) => leader,
1041 }
1042}
1043
1044fn signal_process(
1045 process: &Process,
1046 target_is_group: bool,
1047 pgid: pid_t,
1048 signal: i32,
1049) -> Result<(), CoreError> {
1050 if target_is_group {
1051 process.kill_group(pgid, signal)
1052 } else {
1053 process.kill(signal)
1054 }
1055}
1056
1057use crate::reactor::Reactor;
1058
1059pub fn spawn_start(opts: SpawnOptions) -> Result<RunningProcess, CoreError> {
1074 if !opts.wait && (opts.stdin.is_some() || opts.capture_stdout || opts.capture_stderr) {
1075 return Err(CoreError::sys(
1076 libc::EINVAL,
1077 "background I/O capture not supported (wait must be true)",
1078 ));
1079 }
1080
1081 validate_backend(&opts)?;
1082
1083 let (pid, drain) = match opts.backend {
1084 SpawnBackend::PosixSpawn => spawn_posix_internal(opts)?,
1085 SpawnBackend::Fork => spawn_fork_internal(opts)?,
1086 };
1087
1088 Ok(RunningProcess {
1089 process: Process::new(pid),
1090 drain,
1091 })
1092}
1093
1094pub fn spawn_managed(opts: SpawnOptions) -> Result<ManagedProcess, CoreError> {
1097 if !opts.wait {
1098 return Err(CoreError::sys(
1099 libc::EINVAL,
1100 "managed process requires wait=true",
1101 ));
1102 }
1103 let timeout_at = opts
1104 .timeout_ms
1105 .map(|ms| Instant::now() + Duration::from_millis(ms as u64));
1106 let kill_grace = Duration::from_millis(opts.kill_grace_ms as u64);
1107 let cancel = opts.cancel;
1108 let pgroup = opts.pgroup;
1109 let running = spawn_start(opts)?;
1110 Ok(ManagedProcess {
1111 running: Some(running),
1112 timeout_at,
1113 kill_grace,
1114 cancel,
1115 pgroup,
1116 cancel_at: None,
1117 kill_state: KillState::None,
1118 status: None,
1119 timed_out: false,
1120 })
1121}
1122
1123pub fn spawn(opts: SpawnOptions) -> Result<Output, CoreError> {
1132 let wait = opts.wait;
1133 let timeout_ms = opts.timeout_ms;
1134 let kill_grace_ms = opts.kill_grace_ms;
1135 let cancel = opts.cancel;
1136 let pgroup = opts.pgroup;
1137
1138 let mut reactor = Reactor::new()?;
1139 let running = spawn_start(opts)?;
1140
1141 let pid = running.process.pid();
1142 let mut drain = running.drain;
1143
1144 drain.register_with_reactor(&mut reactor)?;
1145
1146 if !wait {
1147 let (stdout, stderr) = drain.into_parts();
1148 return Ok(Output {
1149 pid,
1150 status: None,
1151 stdout,
1152 stderr,
1153 timed_out: false,
1154 stdout_early_exited: false,
1155 });
1156 }
1157
1158 wait_loop(
1159 pid,
1160 drain,
1161 reactor,
1162 timeout_ms,
1163 kill_grace_ms,
1164 cancel,
1165 pgroup,
1166 )
1167}
1168
1169fn spawn_posix_internal(opts: SpawnOptions) -> Result<(pid_t, SpawnDrain), CoreError> {
1170 let mut pipes = Pipes::new(
1171 opts.stdin.as_deref(),
1172 opts.capture_stdout,
1173 opts.capture_stderr,
1174 )?;
1175
1176 let exe_ptr = match &opts.ctx.argv {
1177 ExecArgv::Dynamic(v) => v[0].as_ptr(),
1178 };
1179
1180 let argv = opts.ctx.get_argv_ptrs();
1181 let envp = opts.ctx.get_envp_ptrs();
1182
1183 let actions = MaybeUninit::zeroed();
1184 let mut actions = unsafe { actions.assume_init() };
1185 if let Err(e) = posix_ret(
1186 unsafe { posix_spawn_file_actions_init(&mut actions) },
1187 "file_actions_init",
1188 ) {
1189 pipes.close_all();
1190 return Err(e);
1191 }
1192
1193 struct Actions(*mut libc::posix_spawn_file_actions_t);
1194 impl Drop for Actions {
1195 fn drop(&mut self) {
1196 unsafe {
1197 posix_spawn_file_actions_destroy(self.0);
1198 }
1199 }
1200 }
1201 let _guard = Actions(&mut actions);
1202
1203 if let (Some(r), Some(w)) = (&pipes.stdin_r, &pipes.stdin_w) {
1204 if let Err(e) = posix_ret(
1205 unsafe { posix_spawn_file_actions_adddup2(&mut actions, r.raw(), 0) },
1206 "dup2 stdin",
1207 ) {
1208 pipes.close_all();
1209 return Err(e);
1210 }
1211 if let Err(e) = posix_ret(
1212 unsafe { posix_spawn_file_actions_addclose(&mut actions, r.raw()) },
1213 "close stdin pipe",
1214 ) {
1215 pipes.close_all();
1216 return Err(e);
1217 }
1218 if let Err(e) = posix_ret(
1219 unsafe { posix_spawn_file_actions_addclose(&mut actions, w.raw()) },
1220 "close stdin write pipe",
1221 ) {
1222 pipes.close_all();
1223 return Err(e);
1224 }
1225 }
1226
1227 if let (Some(r), Some(w)) = (&pipes.stdout_r, &pipes.stdout_w) {
1228 if let Err(e) = posix_ret(
1229 unsafe { posix_spawn_file_actions_adddup2(&mut actions, w.raw(), 1) },
1230 "dup2 stdout",
1231 ) {
1232 pipes.close_all();
1233 return Err(e);
1234 }
1235 if let Err(e) = posix_ret(
1236 unsafe { posix_spawn_file_actions_addclose(&mut actions, w.raw()) },
1237 "close stdout pipe",
1238 ) {
1239 pipes.close_all();
1240 return Err(e);
1241 }
1242 if let Err(e) = posix_ret(
1243 unsafe { posix_spawn_file_actions_addclose(&mut actions, r.raw()) },
1244 "close stdout read pipe",
1245 ) {
1246 pipes.close_all();
1247 return Err(e);
1248 }
1249 }
1250
1251 if let (Some(r), Some(w)) = (&pipes.stderr_r, &pipes.stderr_w) {
1252 if let Err(e) = posix_ret(
1253 unsafe { posix_spawn_file_actions_adddup2(&mut actions, w.raw(), 2) },
1254 "dup2 stderr",
1255 ) {
1256 pipes.close_all();
1257 return Err(e);
1258 }
1259 if let Err(e) = posix_ret(
1260 unsafe { posix_spawn_file_actions_addclose(&mut actions, w.raw()) },
1261 "close stderr pipe",
1262 ) {
1263 pipes.close_all();
1264 return Err(e);
1265 }
1266 if let Err(e) = posix_ret(
1267 unsafe { posix_spawn_file_actions_addclose(&mut actions, r.raw()) },
1268 "close stderr read pipe",
1269 ) {
1270 pipes.close_all();
1271 return Err(e);
1272 }
1273 }
1274
1275 let attr = MaybeUninit::zeroed();
1276 let mut attr = unsafe { attr.assume_init() };
1277 if let Err(e) = posix_ret(unsafe { posix_spawnattr_init(&mut attr) }, "attr_init") {
1278 pipes.close_all();
1279 return Err(e);
1280 }
1281
1282 struct Attr(*mut libc::posix_spawnattr_t);
1283 impl Drop for Attr {
1284 fn drop(&mut self) {
1285 unsafe {
1286 posix_spawnattr_destroy(self.0);
1287 }
1288 }
1289 }
1290 let _attr = Attr(&mut attr);
1291
1292 let mut flags = 0;
1293
1294 if let Some(pg) = opts.pgroup.leader {
1295 flags |= POSIX_SPAWN_SETPGROUP;
1296 if let Err(e) = posix_ret(
1297 unsafe { posix_spawnattr_setpgroup(&mut attr, pg) },
1298 "setpgroup",
1299 ) {
1300 pipes.close_all();
1301 return Err(e);
1302 }
1303 }
1304
1305 flags |= POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
1306
1307 if let Err(e) = posix_ret(
1308 unsafe { posix_spawnattr_setflags(&mut attr, flags as _) },
1309 "setflags",
1310 ) {
1311 pipes.close_all();
1312 return Err(e);
1313 }
1314
1315 let empty_mask = SignalRuntime::empty_set();
1316 let def = SignalRuntime::set_with(&[libc::SIGPIPE])?;
1317
1318 if let Err(e) = posix_ret(
1319 unsafe { posix_spawnattr_setsigmask(&mut attr, &empty_mask) },
1320 "setsigmask",
1321 ) {
1322 pipes.close_all();
1323 return Err(e);
1324 }
1325 if let Err(e) = posix_ret(
1326 unsafe { posix_spawnattr_setsigdefault(&mut attr, &def) },
1327 "setsigdefault",
1328 ) {
1329 pipes.close_all();
1330 return Err(e);
1331 }
1332
1333 let mut pid: pid_t = 0;
1334
1335 let envp_ptr = envp.as_ref().map_or_else(
1336 || unsafe { environ as *const *mut c_char },
1337 |e: &Vec<*mut c_char>| e.as_ptr(),
1338 );
1339
1340 if let Err(e) = posix_ret(
1341 unsafe { posix_spawn(&mut pid, exe_ptr, &actions, &attr, argv.as_ptr(), envp_ptr) },
1342 "posix_spawn",
1343 ) {
1344 pipes.close_all();
1345 return Err(e);
1346 }
1347
1348 drop(pipes.stdin_r.take());
1349 drop(pipes.stdout_w.take());
1350 drop(pipes.stderr_w.take());
1351
1352 let drain = crate::io::DrainState::new(
1353 pipes.stdin_w.take().filter(|_| opts.stdin.is_some()),
1354 opts.stdin,
1355 pipes.stdout_r.take(),
1356 pipes.stderr_r.take(),
1357 opts.max_output,
1358 opts.early_exit,
1359 )?;
1360
1361 Ok((pid, drain))
1362}
1363
1364fn collect_required_pipe_fds(pipes: &Pipes) -> Vec<RawFd> {
1365 let mut fds = Vec::new();
1366 if let Some(fd) = &pipes.stdin_r {
1367 fds.push(fd.raw());
1368 }
1369 if let Some(fd) = &pipes.stdin_w {
1370 fds.push(fd.raw());
1371 }
1372 if let Some(fd) = &pipes.stdout_r {
1373 fds.push(fd.raw());
1374 }
1375 if let Some(fd) = &pipes.stdout_w {
1376 fds.push(fd.raw());
1377 }
1378 if let Some(fd) = &pipes.stderr_r {
1379 fds.push(fd.raw());
1380 }
1381 if let Some(fd) = &pipes.stderr_w {
1382 fds.push(fd.raw());
1383 }
1384 fds
1385}
1386
1387fn collect_open_fds_for_child_policy(policy: &SpawnFdPolicy) -> Result<Vec<RawFd>, CoreError> {
1388 match policy {
1389 SpawnFdPolicy::CloexecOnly => Ok(Vec::new()),
1390 SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
1391 let dir_fd = unsafe {
1392 libc::open(
1393 c"/proc/self/fd".as_ptr(),
1394 libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
1395 )
1396 };
1397 if dir_fd < 0 {
1398 return Err(CoreError::sys(errno(), "open /proc/self/fd"));
1399 }
1400
1401 let dir = unsafe { libc::fdopendir(dir_fd) };
1402 if dir.is_null() {
1403 let code = errno();
1404 unsafe {
1405 libc::close(dir_fd);
1406 }
1407 return Err(CoreError::sys(code, "fdopendir /proc/self/fd"));
1408 }
1409
1410 let mut open_fds = Vec::new();
1411 loop {
1412 let entry = unsafe { libc::readdir(dir) };
1413 if entry.is_null() {
1414 break;
1415 }
1416 let name = unsafe { std::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) };
1417 if let Ok(s) = name.to_str()
1418 && let Ok(fd) = s.parse::<RawFd>()
1419 && fd != dir_fd
1420 {
1421 open_fds.push(fd);
1422 }
1423 }
1424 unsafe {
1425 libc::closedir(dir);
1426 }
1427 Ok(open_fds)
1428 }
1429 }
1430}
1431
1432fn close_child_fds_for_policy(policy: &SpawnFdPolicy, required_fds: &[RawFd], open_fds: &[RawFd]) {
1433 match policy {
1434 SpawnFdPolicy::CloexecOnly => {}
1435 SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
1436 for &fd in open_fds {
1437 if fd > 2
1438 && !required_fds.contains(&fd)
1439 && !matches!(policy, SpawnFdPolicy::Allowlist(allowlist) if allowlist.contains(&fd))
1440 {
1441 unsafe {
1442 libc::close(fd);
1443 }
1444 }
1445 }
1446 }
1447 }
1448}
1449
1450fn spawn_fork_internal(opts: SpawnOptions) -> Result<(pid_t, SpawnDrain), CoreError> {
1451 let mut pipes = Pipes::new(
1452 opts.stdin.as_deref(),
1453 opts.capture_stdout,
1454 opts.capture_stderr,
1455 )?;
1456
1457 let exe_ptr = match &opts.ctx.argv {
1458 ExecArgv::Dynamic(v) => v[0].as_ptr(),
1459 };
1460
1461 let argv = opts.ctx.get_argv_ptrs();
1462 let envp = opts.ctx.get_envp_ptrs();
1463 let cwd_cstr = &opts.ctx.cwd;
1464 let (child_error_r, child_error_w) = make_cloexec_pipe()?;
1465 let mut required_fds = collect_required_pipe_fds(&pipes);
1466 required_fds.push(child_error_w);
1467 let open_fds = collect_open_fds_for_child_policy(&opts.fd_policy)?;
1468
1469 let pid = unsafe { libc::fork() };
1470
1471 if pid < 0 {
1472 unsafe {
1473 libc::close(child_error_r);
1474 libc::close(child_error_w);
1475 }
1476 pipes.close_all();
1477 syscall_ret(-1, "fork")?;
1478 }
1479
1480 if pid == 0 {
1481 unsafe {
1483 libc::close(child_error_r);
1484 }
1485
1486 if let (Some(r), Some(_)) = (&pipes.stdin_r, &pipes.stdin_w) {
1488 unsafe {
1489 if libc::dup2(r.raw(), 0) < 0 {
1490 report_child_setup_error(child_error_w, ChildSetupOp::DupStdin, errno());
1491 }
1492 }
1493 }
1494
1495 if let (Some(_), Some(w)) = (&pipes.stdout_r, &pipes.stdout_w) {
1497 unsafe {
1498 if libc::dup2(w.raw(), 1) < 0 {
1499 report_child_setup_error(child_error_w, ChildSetupOp::DupStdout, errno());
1500 }
1501 }
1502 }
1503
1504 if let (Some(_), Some(w)) = (&pipes.stderr_r, &pipes.stderr_w) {
1506 unsafe {
1507 if libc::dup2(w.raw(), 2) < 0 {
1508 report_child_setup_error(child_error_w, ChildSetupOp::DupStderr, errno());
1509 }
1510 }
1511 }
1512
1513 pipes.close_all();
1515
1516 close_child_fds_for_policy(&opts.fd_policy, &required_fds, &open_fds);
1517
1518 if opts.pgroup.isolated {
1520 unsafe {
1522 if libc::setsid() < 0 {
1523 report_child_setup_error(child_error_w, ChildSetupOp::Setsid, errno());
1524 }
1525 }
1526 }
1527
1528 if let Some(cwd) = cwd_cstr {
1530 unsafe {
1532 if libc::chdir(cwd.as_ptr()) != 0 {
1533 report_child_setup_error(child_error_w, ChildSetupOp::Chdir, errno());
1534 }
1535 }
1536 }
1537
1538 if let Some(pg) = opts.pgroup.leader {
1540 unsafe {
1542 if libc::setpgid(0, pg) < 0 {
1543 report_child_setup_error(child_error_w, ChildSetupOp::Setpgid, errno());
1544 }
1545 }
1546 }
1547
1548 let envp_ptr = envp.as_ref().map_or_else(
1549 || unsafe { environ as *const *mut c_char },
1550 |e: &Vec<*mut c_char>| e.as_ptr(),
1551 );
1552
1553 if let Err(err) = SignalRuntime::unblock_all() {
1556 unsafe {
1557 report_child_setup_error(
1558 child_error_w,
1559 ChildSetupOp::SignalMask,
1560 err.raw_os_error().unwrap_or(libc::EIO),
1561 );
1562 }
1563 }
1564 if let Err(err) = SignalRuntime::reset_default(libc::SIGPIPE) {
1565 unsafe {
1566 report_child_setup_error(
1567 child_error_w,
1568 ChildSetupOp::SignalMask,
1569 err.raw_os_error().unwrap_or(libc::EIO),
1570 );
1571 }
1572 }
1573
1574 unsafe {
1577 libc::execve(
1578 exe_ptr,
1579 argv.as_ptr() as *const *const _,
1580 envp_ptr as *const *const _,
1581 );
1582 report_child_setup_error(child_error_w, ChildSetupOp::Execve, errno());
1583 }
1584 }
1585
1586 unsafe {
1588 libc::close(child_error_w);
1589 }
1590 match read_child_setup_error(child_error_r) {
1591 Ok(Some(err)) => {
1592 unsafe {
1593 libc::close(child_error_r);
1594 let mut status = 0;
1595 let _ = libc::waitpid(pid, &mut status, 0);
1596 }
1597 pipes.close_all();
1598 return Err(err);
1599 }
1600 Ok(None) => {}
1601 Err(err) => {
1602 unsafe {
1603 libc::close(child_error_r);
1604 }
1605 pipes.close_all();
1606 return Err(err);
1607 }
1608 }
1609 unsafe {
1610 libc::close(child_error_r);
1611 }
1612 drop(pipes.stdin_r.take());
1613 drop(pipes.stdout_w.take());
1614 drop(pipes.stderr_w.take());
1615
1616 let drain = crate::io::DrainState::new(
1617 pipes.stdin_w.take().filter(|_| opts.stdin.is_some()),
1618 opts.stdin,
1619 pipes.stdout_r.take(),
1620 pipes.stderr_r.take(),
1621 opts.max_output,
1622 opts.early_exit,
1623 )?;
1624
1625 Ok((pid, drain))
1626}
1627
1628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1629enum KillState {
1630 None,
1631 TermSent,
1632 KillSent,
1633}
1634
1635fn wait_loop(
1636 pid: pid_t,
1637 mut drain: crate::io::DrainState<fn(&[u8]) -> bool>,
1638 mut reactor: Reactor,
1639 timeout_ms: Option<u32>,
1640 kill_grace_ms: u32,
1641 cancel: CancelPolicy,
1642 pgroup: ProcessGroup,
1643) -> Result<Output, CoreError> {
1644 let process = Process::new(pid);
1645 let pgid = effective_pgid(pid, pgroup);
1650 let mut status_raw = process.wait_step()?;
1651 let mut state = KillState::None;
1652 let mut timed_out = false;
1653
1654 let start_time = std::time::Instant::now();
1655 let deadline = timeout_ms.map(|t| std::time::Duration::from_millis(t as u64));
1656
1657 loop {
1658 let mut poll_timeout = -1;
1659
1660 if let Some(dl) = deadline {
1661 let elapsed = start_time.elapsed();
1662 if elapsed >= dl {
1663 timed_out = true;
1664 let elapsed_over = (elapsed - dl).as_millis();
1665
1666 let target_is_group = pgroup.isolated || pgroup.leader.is_some();
1667
1668 match state {
1669 KillState::None => {
1670 if cancel == CancelPolicy::Graceful {
1671 let r = if target_is_group {
1672 process.kill_group(pgid, libc::SIGTERM)
1673 } else {
1674 process.kill(libc::SIGTERM)
1675 };
1676 if r.is_err() {
1677 state = KillState::KillSent; } else {
1679 state = KillState::TermSent;
1680 }
1681 } else if cancel == CancelPolicy::Kill {
1682 let _ = if target_is_group {
1683 process.kill_group(pgid, libc::SIGKILL)
1684 } else {
1685 process.kill(libc::SIGKILL)
1686 };
1687 state = KillState::KillSent;
1688 } else {
1689 }
1691 }
1692 KillState::TermSent if elapsed_over > kill_grace_ms as u128 => {
1693 let _ = if target_is_group {
1694 process.kill_group(pgid, libc::SIGKILL)
1695 } else {
1696 process.kill(libc::SIGKILL)
1697 };
1698 state = KillState::KillSent;
1699 }
1700 _ => {}
1701 }
1702 poll_timeout = 100; } else {
1704 let remaining = dl - elapsed;
1705 poll_timeout = remaining.as_millis().min(i32::MAX as u128) as i32;
1706 }
1707 }
1708
1709 if status_raw.is_none()
1710 && let Some(s) = process.wait_step()?
1711 {
1712 status_raw = Some(s);
1713 }
1714
1715 if drain.is_done() {
1716 let s = if status_raw.is_some() {
1717 status_raw.take()
1718 } else if deadline.is_none() {
1719 Some(process.wait_blocking()?)
1722 } else {
1723 None
1728 };
1729
1730 if let Some(s) = s {
1731 for slot in drain.take_all_slots() {
1732 reactor.del(&slot.fd)?;
1733 }
1734 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
1735 drain.into_parts_with_state();
1736 if output_limit_exceeded {
1737 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
1738 }
1739 return Ok(Output {
1740 pid,
1741 status: Some(s),
1742 stdout,
1743 stderr,
1744 timed_out,
1745 stdout_early_exited,
1746 });
1747 }
1748 }
1749
1750 if timed_out && status_raw.is_some() {
1755 for slot in drain.take_all_slots() {
1756 let _ = reactor.del(&slot.fd);
1757 }
1758 let (stdout, stderr, _output_limit_exceeded, stdout_early_exited) =
1759 drain.into_parts_with_state();
1760 return Ok(Output {
1761 pid,
1762 status: status_raw,
1763 stdout,
1764 stderr,
1765 timed_out: true,
1766 stdout_early_exited,
1767 });
1768 }
1769
1770 let timeout = poll_timeout;
1771
1772 let mut events = Vec::new();
1773 let nevents = reactor.wait(&mut events, 64, timeout)?;
1774
1775 for ev in events.iter().take(nevents) {
1776 if drain.stdout_matches(ev.token) {
1777 if ev.readable || ev.hangup {
1778 drain.handle_stdout_ready(&mut reactor)?;
1779 } else if ev.error {
1780 drain.drop_stdout(&mut reactor)?;
1781 }
1782 } else if drain.stderr_matches(ev.token) {
1783 if ev.readable || ev.hangup {
1784 drain.handle_stderr_ready(&mut reactor)?;
1785 } else if ev.error {
1786 drain.drop_stderr(&mut reactor)?;
1787 }
1788 } else if drain.stdin_matches(ev.token) {
1789 if ev.writable {
1790 drain.handle_stdin_writable(&mut reactor)?;
1791 } else if ev.error || ev.hangup {
1792 drain.drop_stdin(&mut reactor)?;
1793 }
1794 }
1795 }
1796 }
1797}