1use std::ffi::CString;
13use std::mem::MaybeUninit;
14use std::os::unix::io::RawFd;
15use std::ptr;
16
17use crate::CoreError;
18use crate::error::{posix_ret, syscall_ret};
19use crate::reactor::Fd;
20use crate::signal::SignalRuntime;
21use libc::{
22 O_CLOEXEC, O_NONBLOCK, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG, c_char, pid_t, pipe2,
23 waitpid,
24};
25
26unsafe extern "C" {
27 pub(crate) static mut environ: *mut *mut libc::c_char;
28}
29
30pub(crate) const POSIX_SPAWN_SETPGROUP: i32 = 2;
31pub(crate) const POSIX_SPAWN_SETSIGDEF: i32 = 4;
32pub(crate) const POSIX_SPAWN_SETSIGMASK: i32 = 8;
33
34unsafe extern "C" {
35 pub(crate) fn posix_spawn(
36 pid: *mut libc::pid_t,
37 path: *const libc::c_char,
38 file_actions: *const libc::posix_spawn_file_actions_t,
39 attrp: *const libc::posix_spawnattr_t,
40 argv: *const *mut libc::c_char,
41 envp: *const *mut libc::c_char,
42 ) -> libc::c_int;
43
44 pub(crate) fn posix_spawn_file_actions_addclose(
45 file_actions: *mut libc::posix_spawn_file_actions_t,
46 fd: libc::c_int,
47 ) -> libc::c_int;
48
49 pub(crate) fn posix_spawn_file_actions_adddup2(
50 file_actions: *mut libc::posix_spawn_file_actions_t,
51 fd: libc::c_int,
52 newfd: libc::c_int,
53 ) -> libc::c_int;
54
55 pub(crate) fn posix_spawn_file_actions_destroy(
56 file_actions: *mut libc::posix_spawn_file_actions_t,
57 ) -> libc::c_int;
58
59 pub(crate) fn posix_spawn_file_actions_init(
60 file_actions: *mut libc::posix_spawn_file_actions_t,
61 ) -> libc::c_int;
62
63 pub(crate) fn posix_spawnattr_destroy(attr: *mut libc::posix_spawnattr_t) -> libc::c_int;
64
65 pub(crate) fn posix_spawnattr_init(attr: *mut libc::posix_spawnattr_t) -> libc::c_int;
66
67 pub(crate) fn posix_spawnattr_setflags(
68 attr: *mut libc::posix_spawnattr_t,
69 flags: libc::c_short,
70 ) -> libc::c_int;
71
72 pub(crate) fn posix_spawnattr_setpgroup(
73 attr: *mut libc::posix_spawnattr_t,
74 pgroup: libc::pid_t,
75 ) -> libc::c_int;
76
77 pub(crate) fn posix_spawnattr_setsigdefault(
78 attr: *mut libc::posix_spawnattr_t,
79 sigdefault: *const libc::sigset_t,
80 ) -> libc::c_int;
81
82 pub(crate) fn posix_spawnattr_setsigmask(
83 attr: *mut libc::posix_spawnattr_t,
84 sigmask: *const libc::sigset_t,
85 ) -> libc::c_int;
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
90pub enum CancelPolicy {
91 #[default]
93 None,
94 Graceful,
96 Kill,
98}
99
100#[derive(Debug, Clone, Copy, Default)]
102pub struct ProcessGroup {
103 pub leader: Option<pid_t>,
105 pub isolated: bool,
107}
108
109impl ProcessGroup {
110 pub fn new(leader: Option<pid_t>, isolated: bool) -> Self {
112 Self { leader, isolated }
113 }
114}
115
116#[inline(always)]
117fn errno() -> i32 {
118 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
119}
120
121#[inline(always)]
124fn make_pipe() -> Result<(Fd, Fd), CoreError> {
125 let mut fds = [0; 2];
126 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC | O_NONBLOCK) };
127 syscall_ret(r, "pipe2")?;
128 Ok((Fd::new(fds[0], "pipe2")?, Fd::new(fds[1], "pipe2")?))
129}
130
131fn make_cloexec_pipe() -> Result<(RawFd, RawFd), CoreError> {
132 let mut fds = [0; 2];
133 let r = unsafe { pipe2(fds.as_mut_ptr(), O_CLOEXEC) };
134 syscall_ret(r, "pipe2")?;
135 Ok((fds[0], fds[1]))
136}
137
138#[repr(u8)]
139#[derive(Clone, Copy)]
140enum ChildSetupOp {
141 DupStdin = 1,
142 DupStdout = 2,
143 DupStderr = 3,
144 Setsid = 4,
145 Chdir = 5,
146 Setpgid = 6,
147 SignalMask = 7,
148 Execve = 8,
149}
150
151impl ChildSetupOp {
152 fn as_str(self) -> &'static str {
153 match self {
154 Self::DupStdin => "fork child dup2 stdin",
155 Self::DupStdout => "fork child dup2 stdout",
156 Self::DupStderr => "fork child dup2 stderr",
157 Self::Setsid => "fork child setsid",
158 Self::Chdir => "fork child chdir",
159 Self::Setpgid => "fork child setpgid",
160 Self::SignalMask => "fork child signal setup",
161 Self::Execve => "fork child execve",
162 }
163 }
164
165 fn from_u8(value: u8) -> Self {
166 match value {
167 1 => Self::DupStdin,
168 2 => Self::DupStdout,
169 3 => Self::DupStderr,
170 4 => Self::Setsid,
171 5 => Self::Chdir,
172 6 => Self::Setpgid,
173 7 => Self::SignalMask,
174 _ => Self::Execve,
175 }
176 }
177}
178
179unsafe fn report_child_setup_error(fd: RawFd, op: ChildSetupOp, code: i32) -> ! {
180 let mut msg = [0u8; 5];
181 msg[..4].copy_from_slice(&code.to_ne_bytes());
182 msg[4] = op as u8;
183 let mut written = 0;
184 while written < msg.len() {
185 let n = unsafe {
186 libc::write(
187 fd,
188 msg[written..].as_ptr().cast::<libc::c_void>(),
189 msg.len() - written,
190 )
191 };
192 if n <= 0 {
193 break;
194 }
195 written += n as usize;
196 }
197 unsafe {
198 libc::_exit(127);
199 }
200}
201
202fn read_child_setup_error(fd: RawFd) -> Result<Option<CoreError>, CoreError> {
203 let mut msg = [0u8; 5];
204 let mut read_len = 0;
205 loop {
206 let n = unsafe {
207 libc::read(
208 fd,
209 msg[read_len..].as_mut_ptr().cast::<libc::c_void>(),
210 msg.len() - read_len,
211 )
212 };
213 if n == 0 {
214 return Ok(None);
215 }
216 if n < 0 {
217 let code = errno();
218 if code == libc::EINTR {
219 continue;
220 }
221 return Err(CoreError::sys(code, "read fork child setup error"));
222 }
223 read_len += n as usize;
224 if read_len == msg.len() {
225 let code = i32::from_ne_bytes([msg[0], msg[1], msg[2], msg[3]]);
226 return Ok(Some(CoreError::sys(
227 code,
228 ChildSetupOp::from_u8(msg[4]).as_str(),
229 )));
230 }
231 }
232}
233
234struct Pipes {
235 stdin_r: Option<Fd>,
236 stdin_w: Option<Fd>,
237 stdout_r: Option<Fd>,
238 stdout_w: Option<Fd>,
239 stderr_r: Option<Fd>,
240 stderr_w: Option<Fd>,
241}
242
243impl Pipes {
244 fn new(in_buf: Option<&[u8]>, out: bool, err: bool) -> Result<Self, CoreError> {
245 let (stdin_r, stdin_w) = if in_buf.is_some() {
246 let (r, w) = make_pipe()?;
247 (Some(r), Some(w))
248 } else {
249 (None, None)
250 };
251
252 let (stdout_r, stdout_w) = if out {
253 let (r, w) = make_pipe()?;
254 (Some(r), Some(w))
255 } else {
256 (None, None)
257 };
258
259 let (stderr_r, stderr_w) = if err {
260 let (r, w) = make_pipe()?;
261 (Some(r), Some(w))
262 } else {
263 (None, None)
264 };
265
266 Ok(Self {
267 stdin_r,
268 stdin_w,
269 stdout_r,
270 stdout_w,
271 stderr_r,
272 stderr_w,
273 })
274 }
275
276 #[inline(always)]
277 fn close_all(&mut self) {
278 self.stdin_r.take();
279 self.stdin_w.take();
280 self.stdout_r.take();
281 self.stdout_w.take();
282 self.stderr_r.take();
283 self.stderr_w.take();
284 }
285}
286
287#[derive(Debug, PartialEq, Eq)]
289pub enum ExitStatus {
290 Exited(i32),
292 Signaled(i32),
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum SpawnBackend {
299 PosixSpawn,
301 Fork,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Default)]
310pub enum SpawnFdPolicy {
311 #[default]
313 CloexecOnly,
314 CloseFrom3,
317 Allowlist(Vec<RawFd>),
324}
325
326#[derive(Clone)]
328enum ExecArgv {
329 Dynamic(Vec<CString>),
331}
332
333#[derive(Clone)]
335struct ExecContext {
336 argv: ExecArgv,
337 envp: Option<Vec<CString>>,
338 cwd: Option<CString>,
339}
340
341impl ExecContext {
342 fn new(
344 argv: Vec<String>,
345 env: Option<Vec<String>>,
346 cwd: Option<String>,
347 ) -> Result<Self, CoreError> {
348 if argv.is_empty() {
349 return Err(CoreError::sys(libc::EINVAL, "exec argv empty"));
350 }
351
352 let c_argv: Vec<CString> = argv
353 .into_iter()
354 .map(|s| {
355 CString::new(s).map_err(|_| CoreError::sys(libc::EINVAL, "exec argv contains nul"))
356 })
357 .collect::<Result<_, _>>()?;
358
359 let c_envp = match env {
360 Some(vars) => Some(
361 vars.into_iter()
362 .map(|s| {
363 CString::new(s)
364 .map_err(|_| CoreError::sys(libc::EINVAL, "exec env contains nul"))
365 })
366 .collect::<Result<Vec<_>, _>>()?,
367 ),
368 None => None,
369 };
370
371 let c_cwd = match cwd {
372 Some(c) => Some(
373 CString::new(c)
374 .map_err(|_| CoreError::sys(libc::EINVAL, "exec cwd contains nul"))?,
375 ),
376 None => None,
377 };
378
379 Ok(Self {
380 argv: ExecArgv::Dynamic(c_argv),
381 envp: c_envp,
382 cwd: c_cwd,
383 })
384 }
385
386 fn get_argv_ptrs(&self) -> Vec<*mut libc::c_char> {
388 let mut ptrs = Vec::new();
389 match &self.argv {
390 ExecArgv::Dynamic(v) => {
391 for s in v {
392 ptrs.push(s.as_ptr() as *mut libc::c_char);
393 }
394 }
395 }
396 ptrs.push(ptr::null_mut());
397 ptrs
398 }
399
400 fn get_envp_ptrs(&self) -> Option<Vec<*mut libc::c_char>> {
402 self.envp.as_ref().map(|envp| {
403 let mut ptrs = Vec::new();
404 for s in envp {
405 ptrs.push(s.as_ptr() as *mut libc::c_char);
406 }
407 ptrs.push(ptr::null_mut());
408 ptrs
409 })
410 }
411}
412
413#[inline(always)]
414fn decode_status(status: i32) -> ExitStatus {
415 if WIFEXITED(status) {
416 ExitStatus::Exited(WEXITSTATUS(status))
417 } else if WIFSIGNALED(status) {
418 ExitStatus::Signaled(WTERMSIG(status))
419 } else {
420 ExitStatus::Exited(-1)
421 }
422}
423
424pub struct Process {
432 pid: pid_t,
433}
434
435impl Process {
436 pub fn new(pid: pid_t) -> Self {
438 Self { pid }
439 }
440
441 pub fn pid(&self) -> pid_t {
443 self.pid
444 }
445
446 pub fn wait_step(&self) -> Result<Option<ExitStatus>, CoreError> {
452 loop {
453 let mut status = 0;
454 let r = unsafe { waitpid(self.pid, &mut status, libc::WNOHANG) };
455 if r == 0 {
456 return Ok(None);
457 }
458 if r < 0 {
459 let e = errno();
460 if e == libc::EINTR {
461 continue;
462 }
463 return Err(CoreError::sys(e, "waitpid_step"));
464 }
465 return Ok(Some(decode_status(status)));
466 }
467 }
468
469 pub fn wait_blocking(&self) -> Result<ExitStatus, CoreError> {
474 loop {
475 let mut status = 0;
476 let r = unsafe { waitpid(self.pid, &mut status, 0) };
477 if r < 0 {
478 let e = errno();
479 if e == libc::EINTR {
480 continue;
481 }
482 return Err(CoreError::sys(e, "waitpid_blocking"));
483 }
484 return Ok(decode_status(status));
485 }
486 }
487
488 pub fn kill(&self, sig: i32) -> Result<(), CoreError> {
495 let r = unsafe { libc::kill(self.pid, sig) };
496 if r < 0 {
497 let e = errno();
498 if e == libc::ESRCH {
499 return Ok(());
500 }
501 syscall_ret(-1, "kill")?;
502 }
503 Ok(())
504 }
505
506 pub fn kill_pgroup(&self, sig: i32) -> Result<(), CoreError> {
513 self.kill_group(self.pid, sig)
514 }
515
516 pub fn kill_group(&self, pgid: pid_t, sig: i32) -> Result<(), CoreError> {
525 let r = unsafe { libc::kill(-pgid, sig) };
526 if r < 0 {
527 let e = errno();
528 if e == libc::ESRCH {
529 return Ok(());
530 }
531 syscall_ret(-1, "kill_group")?;
532 }
533 Ok(())
534 }
535}
536
537#[derive(Clone)]
539pub struct SpawnOptions {
540 ctx: ExecContext,
541 stdin: Option<Box<[u8]>>,
542 capture_stdout: bool,
543 capture_stderr: bool,
544 wait: bool,
545 pgroup: ProcessGroup,
546 max_output: usize,
547 timeout_ms: Option<u32>,
548 kill_grace_ms: u32,
549 cancel: CancelPolicy,
550 backend: SpawnBackend,
551 fd_policy: SpawnFdPolicy,
552 early_exit: Option<fn(&[u8]) -> bool>,
553}
554
555impl SpawnOptions {
556 pub fn builder(argv: Vec<String>, backend: SpawnBackend) -> SpawnOptionsBuilder {
558 SpawnOptionsBuilder::new(argv, backend)
559 }
560
561 pub fn run(self) -> Result<Output, CoreError> {
563 spawn(self)
564 }
565}
566
567#[derive(Clone)]
569pub struct SpawnOptionsBuilder {
570 argv: Vec<String>,
571 env: Option<Vec<String>>,
572 cwd: Option<String>,
573 stdin: Option<Box<[u8]>>,
574 capture_stdout: bool,
575 capture_stderr: bool,
576 wait: bool,
577 pgroup: ProcessGroup,
578 max_output: usize,
579 timeout_ms: Option<u32>,
580 kill_grace_ms: u32,
581 cancel: CancelPolicy,
582 backend: SpawnBackend,
583 fd_policy: SpawnFdPolicy,
584 early_exit: Option<fn(&[u8]) -> bool>,
585}
586
587impl SpawnOptionsBuilder {
588 pub fn new(argv: Vec<String>, backend: SpawnBackend) -> Self {
590 Self {
591 argv,
592 env: None,
593 cwd: None,
594 stdin: None,
595 capture_stdout: false,
596 capture_stderr: false,
597 wait: true,
598 pgroup: ProcessGroup::default(),
599 max_output: 1024 * 1024,
600 timeout_ms: None,
601 kill_grace_ms: 2000,
602 cancel: CancelPolicy::Kill,
603 backend,
604 fd_policy: SpawnFdPolicy::default(),
605 early_exit: None,
606 }
607 }
608
609 pub fn env(mut self, env: Vec<String>) -> Self {
611 self.env = Some(env);
612 self
613 }
614
615 pub fn cwd(mut self, cwd: String) -> Self {
617 self.cwd = Some(cwd);
618 self
619 }
620
621 pub fn stdin(mut self, data: impl Into<Box<[u8]>>) -> Self {
623 self.stdin = Some(data.into());
624 self
625 }
626
627 pub fn capture_stdout(mut self) -> Self {
629 self.capture_stdout = true;
630 self
631 }
632
633 pub fn capture_stderr(mut self) -> Self {
635 self.capture_stderr = true;
636 self
637 }
638
639 pub fn wait(mut self, wait: bool) -> Self {
641 self.wait = wait;
642 self
643 }
644
645 pub fn pgroup(mut self, pgroup: ProcessGroup) -> Self {
647 self.pgroup = pgroup;
648 self
649 }
650
651 pub fn max_output(mut self, max: usize) -> Self {
656 self.max_output = max;
657 self
658 }
659
660 pub fn timeout_ms(mut self, ms: u32) -> Self {
662 self.timeout_ms = Some(ms);
663 self
664 }
665
666 pub fn kill_grace_ms(mut self, ms: u32) -> Self {
668 self.kill_grace_ms = ms;
669 self
670 }
671
672 pub fn cancel(mut self, policy: CancelPolicy) -> Self {
674 self.cancel = policy;
675 self
676 }
677
678 pub fn fd_policy(mut self, policy: SpawnFdPolicy) -> Self {
680 self.fd_policy = policy;
681 self
682 }
683
684 pub fn early_exit(mut self, callback: fn(&[u8]) -> bool) -> Self {
686 self.early_exit = Some(callback);
687 self
688 }
689
690 pub fn build(self) -> Result<SpawnOptions, CoreError> {
692 let ctx = ExecContext::new(self.argv, self.env, self.cwd)?;
693 Ok(SpawnOptions {
694 ctx,
695 stdin: self.stdin,
696 capture_stdout: self.capture_stdout,
697 capture_stderr: self.capture_stderr,
698 wait: self.wait,
699 pgroup: self.pgroup,
700 max_output: self.max_output,
701 timeout_ms: self.timeout_ms,
702 kill_grace_ms: self.kill_grace_ms,
703 cancel: self.cancel,
704 backend: self.backend,
705 fd_policy: self.fd_policy,
706 early_exit: self.early_exit,
707 })
708 }
709}
710
711#[derive(Debug)]
713pub struct Output {
714 pub pid: pid_t,
716 pub status: Option<ExitStatus>,
718 pub stdout: Vec<u8>,
720 pub stderr: Vec<u8>,
722 pub timed_out: bool,
724 pub stdout_early_exited: bool,
726}
727
728fn validate_backend(opts: &SpawnOptions) -> Result<(), CoreError> {
729 validate_fd_policy(&opts.fd_policy)?;
730 match opts.backend {
731 SpawnBackend::PosixSpawn => {
732 if opts.ctx.cwd.is_some() {
733 return Err(CoreError::sys(libc::EINVAL, "posix_spawn cwd unsupported"));
734 }
735 if opts.pgroup.isolated {
736 return Err(CoreError::sys(
737 libc::EINVAL,
738 "posix_spawn setsid unsupported",
739 ));
740 }
741 if opts.fd_policy != SpawnFdPolicy::CloexecOnly {
742 return Err(CoreError::sys(
743 libc::EINVAL,
744 "posix_spawn fd policy unsupported",
745 ));
746 }
747 Ok(())
748 }
749 SpawnBackend::Fork => Ok(()),
750 }
751}
752
753fn validate_fd_policy(policy: &SpawnFdPolicy) -> Result<(), CoreError> {
754 if let SpawnFdPolicy::Allowlist(fds) = policy {
755 let mut seen = Vec::with_capacity(fds.len());
756 for &fd in fds {
757 if fd < 0 {
758 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist invalid"));
759 }
760 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
761 if flags < 0 {
762 return Err(CoreError::sys(errno(), "spawn fd allowlist fcntl(F_GETFD)"));
763 }
764 if seen.contains(&fd) {
765 return Err(CoreError::sys(libc::EINVAL, "spawn fd allowlist duplicate"));
766 }
767 seen.push(fd);
768 }
769 }
770 Ok(())
771}
772
773use crate::io::DrainState;
774
775pub type SpawnDrain = DrainState<fn(&[u8]) -> bool>;
777
778pub struct RunningProcess {
785 pub process: Process,
787 drain: SpawnDrain,
788}
789
790impl RunningProcess {
791 pub fn register_with_reactor(&mut self, reactor: &mut Reactor) -> Result<(), CoreError> {
797 self.drain.register_with_reactor(reactor)
798 }
799
800 pub fn handle_reactor_event(
806 &mut self,
807 reactor: &mut Reactor,
808 event: &crate::reactor::Event,
809 ) -> Result<(), CoreError> {
810 if self.drain.stdout_matches(event.token) {
811 if event.readable || event.hangup {
812 self.drain.handle_stdout_ready(reactor)?;
813 } else if event.error {
814 self.drain.drop_stdout(reactor)?;
815 }
816 } else if self.drain.stderr_matches(event.token) {
817 if event.readable || event.hangup {
818 self.drain.handle_stderr_ready(reactor)?;
819 } else if event.error {
820 self.drain.drop_stderr(reactor)?;
821 }
822 } else if self.drain.stdin_matches(event.token) {
823 if event.writable {
824 self.drain.handle_stdin_writable(reactor)?;
825 } else if event.error || event.hangup {
826 self.drain.drop_stdin(reactor)?;
827 }
828 }
829 Ok(())
830 }
831
832 pub fn io_done(&self) -> bool {
834 self.drain.is_done()
835 }
836
837 pub fn into_output_parts(self) -> (Vec<u8>, Vec<u8>) {
839 self.drain.into_parts()
840 }
841}
842
843use crate::reactor::Reactor;
844
845pub fn spawn_start(opts: SpawnOptions) -> Result<RunningProcess, CoreError> {
860 if !opts.wait && (opts.stdin.is_some() || opts.capture_stdout || opts.capture_stderr) {
861 return Err(CoreError::sys(
862 libc::EINVAL,
863 "background I/O capture not supported (wait must be true)",
864 ));
865 }
866
867 validate_backend(&opts)?;
868
869 let (pid, drain) = match opts.backend {
870 SpawnBackend::PosixSpawn => spawn_posix_internal(opts)?,
871 SpawnBackend::Fork => spawn_fork_internal(opts)?,
872 };
873
874 Ok(RunningProcess {
875 process: Process::new(pid),
876 drain,
877 })
878}
879
880pub fn spawn(opts: SpawnOptions) -> Result<Output, CoreError> {
889 let wait = opts.wait;
890 let timeout_ms = opts.timeout_ms;
891 let kill_grace_ms = opts.kill_grace_ms;
892 let cancel = opts.cancel;
893 let pgroup = opts.pgroup;
894
895 let mut reactor = Reactor::new()?;
896 let running = spawn_start(opts)?;
897
898 let pid = running.process.pid();
899 let mut drain = running.drain;
900
901 drain.register_with_reactor(&mut reactor)?;
902
903 if !wait {
904 let (stdout, stderr) = drain.into_parts();
905 return Ok(Output {
906 pid,
907 status: None,
908 stdout,
909 stderr,
910 timed_out: false,
911 stdout_early_exited: false,
912 });
913 }
914
915 wait_loop(
916 pid,
917 drain,
918 reactor,
919 timeout_ms,
920 kill_grace_ms,
921 cancel,
922 pgroup,
923 )
924}
925
926fn spawn_posix_internal(opts: SpawnOptions) -> Result<(pid_t, SpawnDrain), CoreError> {
927 let mut pipes = Pipes::new(
928 opts.stdin.as_deref(),
929 opts.capture_stdout,
930 opts.capture_stderr,
931 )?;
932
933 let exe_ptr = match &opts.ctx.argv {
934 ExecArgv::Dynamic(v) => v[0].as_ptr(),
935 };
936
937 let argv = opts.ctx.get_argv_ptrs();
938 let envp = opts.ctx.get_envp_ptrs();
939
940 let actions = MaybeUninit::zeroed();
941 let mut actions = unsafe { actions.assume_init() };
942 if let Err(e) = posix_ret(
943 unsafe { posix_spawn_file_actions_init(&mut actions) },
944 "file_actions_init",
945 ) {
946 pipes.close_all();
947 return Err(e);
948 }
949
950 struct Actions(*mut libc::posix_spawn_file_actions_t);
951 impl Drop for Actions {
952 fn drop(&mut self) {
953 unsafe {
954 posix_spawn_file_actions_destroy(self.0);
955 }
956 }
957 }
958 let _guard = Actions(&mut actions);
959
960 if let (Some(r), Some(w)) = (&pipes.stdin_r, &pipes.stdin_w) {
961 if let Err(e) = posix_ret(
962 unsafe { posix_spawn_file_actions_adddup2(&mut actions, r.raw(), 0) },
963 "dup2 stdin",
964 ) {
965 pipes.close_all();
966 return Err(e);
967 }
968 if let Err(e) = posix_ret(
969 unsafe { posix_spawn_file_actions_addclose(&mut actions, r.raw()) },
970 "close stdin pipe",
971 ) {
972 pipes.close_all();
973 return Err(e);
974 }
975 if let Err(e) = posix_ret(
976 unsafe { posix_spawn_file_actions_addclose(&mut actions, w.raw()) },
977 "close stdin write pipe",
978 ) {
979 pipes.close_all();
980 return Err(e);
981 }
982 }
983
984 if let (Some(r), Some(w)) = (&pipes.stdout_r, &pipes.stdout_w) {
985 if let Err(e) = posix_ret(
986 unsafe { posix_spawn_file_actions_adddup2(&mut actions, w.raw(), 1) },
987 "dup2 stdout",
988 ) {
989 pipes.close_all();
990 return Err(e);
991 }
992 if let Err(e) = posix_ret(
993 unsafe { posix_spawn_file_actions_addclose(&mut actions, w.raw()) },
994 "close stdout pipe",
995 ) {
996 pipes.close_all();
997 return Err(e);
998 }
999 if let Err(e) = posix_ret(
1000 unsafe { posix_spawn_file_actions_addclose(&mut actions, r.raw()) },
1001 "close stdout read pipe",
1002 ) {
1003 pipes.close_all();
1004 return Err(e);
1005 }
1006 }
1007
1008 if let (Some(r), Some(w)) = (&pipes.stderr_r, &pipes.stderr_w) {
1009 if let Err(e) = posix_ret(
1010 unsafe { posix_spawn_file_actions_adddup2(&mut actions, w.raw(), 2) },
1011 "dup2 stderr",
1012 ) {
1013 pipes.close_all();
1014 return Err(e);
1015 }
1016 if let Err(e) = posix_ret(
1017 unsafe { posix_spawn_file_actions_addclose(&mut actions, w.raw()) },
1018 "close stderr pipe",
1019 ) {
1020 pipes.close_all();
1021 return Err(e);
1022 }
1023 if let Err(e) = posix_ret(
1024 unsafe { posix_spawn_file_actions_addclose(&mut actions, r.raw()) },
1025 "close stderr read pipe",
1026 ) {
1027 pipes.close_all();
1028 return Err(e);
1029 }
1030 }
1031
1032 let attr = MaybeUninit::zeroed();
1033 let mut attr = unsafe { attr.assume_init() };
1034 if let Err(e) = posix_ret(unsafe { posix_spawnattr_init(&mut attr) }, "attr_init") {
1035 pipes.close_all();
1036 return Err(e);
1037 }
1038
1039 struct Attr(*mut libc::posix_spawnattr_t);
1040 impl Drop for Attr {
1041 fn drop(&mut self) {
1042 unsafe {
1043 posix_spawnattr_destroy(self.0);
1044 }
1045 }
1046 }
1047 let _attr = Attr(&mut attr);
1048
1049 let mut flags = 0;
1050
1051 if let Some(pg) = opts.pgroup.leader {
1052 flags |= POSIX_SPAWN_SETPGROUP;
1053 if let Err(e) = posix_ret(
1054 unsafe { posix_spawnattr_setpgroup(&mut attr, pg) },
1055 "setpgroup",
1056 ) {
1057 pipes.close_all();
1058 return Err(e);
1059 }
1060 }
1061
1062 flags |= POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
1063
1064 if let Err(e) = posix_ret(
1065 unsafe { posix_spawnattr_setflags(&mut attr, flags as _) },
1066 "setflags",
1067 ) {
1068 pipes.close_all();
1069 return Err(e);
1070 }
1071
1072 let empty_mask = SignalRuntime::empty_set();
1073 let def = SignalRuntime::set_with(&[libc::SIGPIPE])?;
1074
1075 if let Err(e) = posix_ret(
1076 unsafe { posix_spawnattr_setsigmask(&mut attr, &empty_mask) },
1077 "setsigmask",
1078 ) {
1079 pipes.close_all();
1080 return Err(e);
1081 }
1082 if let Err(e) = posix_ret(
1083 unsafe { posix_spawnattr_setsigdefault(&mut attr, &def) },
1084 "setsigdefault",
1085 ) {
1086 pipes.close_all();
1087 return Err(e);
1088 }
1089
1090 let mut pid: pid_t = 0;
1091
1092 let envp_ptr = envp.as_ref().map_or_else(
1093 || unsafe { environ as *const *mut c_char },
1094 |e: &Vec<*mut c_char>| e.as_ptr(),
1095 );
1096
1097 if let Err(e) = posix_ret(
1098 unsafe { posix_spawn(&mut pid, exe_ptr, &actions, &attr, argv.as_ptr(), envp_ptr) },
1099 "posix_spawn",
1100 ) {
1101 pipes.close_all();
1102 return Err(e);
1103 }
1104
1105 drop(pipes.stdin_r.take());
1106 drop(pipes.stdout_w.take());
1107 drop(pipes.stderr_w.take());
1108
1109 let drain = crate::io::DrainState::new(
1110 pipes.stdin_w.take().filter(|_| opts.stdin.is_some()),
1111 opts.stdin,
1112 pipes.stdout_r.take(),
1113 pipes.stderr_r.take(),
1114 opts.max_output,
1115 opts.early_exit,
1116 )?;
1117
1118 Ok((pid, drain))
1119}
1120
1121fn collect_required_pipe_fds(pipes: &Pipes) -> Vec<RawFd> {
1122 let mut fds = Vec::new();
1123 if let Some(fd) = &pipes.stdin_r {
1124 fds.push(fd.raw());
1125 }
1126 if let Some(fd) = &pipes.stdin_w {
1127 fds.push(fd.raw());
1128 }
1129 if let Some(fd) = &pipes.stdout_r {
1130 fds.push(fd.raw());
1131 }
1132 if let Some(fd) = &pipes.stdout_w {
1133 fds.push(fd.raw());
1134 }
1135 if let Some(fd) = &pipes.stderr_r {
1136 fds.push(fd.raw());
1137 }
1138 if let Some(fd) = &pipes.stderr_w {
1139 fds.push(fd.raw());
1140 }
1141 fds
1142}
1143
1144fn collect_open_fds_for_child_policy(policy: &SpawnFdPolicy) -> Result<Vec<RawFd>, CoreError> {
1145 match policy {
1146 SpawnFdPolicy::CloexecOnly => Ok(Vec::new()),
1147 SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
1148 let dir_fd = unsafe {
1149 libc::open(
1150 c"/proc/self/fd".as_ptr(),
1151 libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
1152 )
1153 };
1154 if dir_fd < 0 {
1155 return Err(CoreError::sys(errno(), "open /proc/self/fd"));
1156 }
1157
1158 let dir = unsafe { libc::fdopendir(dir_fd) };
1159 if dir.is_null() {
1160 let code = errno();
1161 unsafe {
1162 libc::close(dir_fd);
1163 }
1164 return Err(CoreError::sys(code, "fdopendir /proc/self/fd"));
1165 }
1166
1167 let mut open_fds = Vec::new();
1168 loop {
1169 let entry = unsafe { libc::readdir(dir) };
1170 if entry.is_null() {
1171 break;
1172 }
1173 let name = unsafe { std::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) };
1174 if let Ok(s) = name.to_str()
1175 && let Ok(fd) = s.parse::<RawFd>()
1176 && fd != dir_fd
1177 {
1178 open_fds.push(fd);
1179 }
1180 }
1181 unsafe {
1182 libc::closedir(dir);
1183 }
1184 Ok(open_fds)
1185 }
1186 }
1187}
1188
1189fn close_child_fds_for_policy(policy: &SpawnFdPolicy, required_fds: &[RawFd], open_fds: &[RawFd]) {
1190 match policy {
1191 SpawnFdPolicy::CloexecOnly => {}
1192 SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
1193 for &fd in open_fds {
1194 if fd > 2
1195 && !required_fds.contains(&fd)
1196 && !matches!(policy, SpawnFdPolicy::Allowlist(allowlist) if allowlist.contains(&fd))
1197 {
1198 unsafe {
1199 libc::close(fd);
1200 }
1201 }
1202 }
1203 }
1204 }
1205}
1206
1207fn spawn_fork_internal(opts: SpawnOptions) -> Result<(pid_t, SpawnDrain), CoreError> {
1208 let mut pipes = Pipes::new(
1209 opts.stdin.as_deref(),
1210 opts.capture_stdout,
1211 opts.capture_stderr,
1212 )?;
1213
1214 let exe_ptr = match &opts.ctx.argv {
1215 ExecArgv::Dynamic(v) => v[0].as_ptr(),
1216 };
1217
1218 let argv = opts.ctx.get_argv_ptrs();
1219 let envp = opts.ctx.get_envp_ptrs();
1220 let cwd_cstr = &opts.ctx.cwd;
1221 let (child_error_r, child_error_w) = make_cloexec_pipe()?;
1222 let mut required_fds = collect_required_pipe_fds(&pipes);
1223 required_fds.push(child_error_w);
1224 let open_fds = collect_open_fds_for_child_policy(&opts.fd_policy)?;
1225
1226 let pid = unsafe { libc::fork() };
1227
1228 if pid < 0 {
1229 unsafe {
1230 libc::close(child_error_r);
1231 libc::close(child_error_w);
1232 }
1233 pipes.close_all();
1234 syscall_ret(-1, "fork")?;
1235 }
1236
1237 if pid == 0 {
1238 unsafe {
1240 libc::close(child_error_r);
1241 }
1242
1243 if let (Some(r), Some(_)) = (&pipes.stdin_r, &pipes.stdin_w) {
1245 unsafe {
1246 if libc::dup2(r.raw(), 0) < 0 {
1247 report_child_setup_error(child_error_w, ChildSetupOp::DupStdin, errno());
1248 }
1249 }
1250 }
1251
1252 if let (Some(_), Some(w)) = (&pipes.stdout_r, &pipes.stdout_w) {
1254 unsafe {
1255 if libc::dup2(w.raw(), 1) < 0 {
1256 report_child_setup_error(child_error_w, ChildSetupOp::DupStdout, errno());
1257 }
1258 }
1259 }
1260
1261 if let (Some(_), Some(w)) = (&pipes.stderr_r, &pipes.stderr_w) {
1263 unsafe {
1264 if libc::dup2(w.raw(), 2) < 0 {
1265 report_child_setup_error(child_error_w, ChildSetupOp::DupStderr, errno());
1266 }
1267 }
1268 }
1269
1270 pipes.close_all();
1272
1273 close_child_fds_for_policy(&opts.fd_policy, &required_fds, &open_fds);
1274
1275 if opts.pgroup.isolated {
1277 unsafe {
1279 if libc::setsid() < 0 {
1280 report_child_setup_error(child_error_w, ChildSetupOp::Setsid, errno());
1281 }
1282 }
1283 }
1284
1285 if let Some(cwd) = cwd_cstr {
1287 unsafe {
1289 if libc::chdir(cwd.as_ptr()) != 0 {
1290 report_child_setup_error(child_error_w, ChildSetupOp::Chdir, errno());
1291 }
1292 }
1293 }
1294
1295 if let Some(pg) = opts.pgroup.leader {
1297 unsafe {
1299 if libc::setpgid(0, pg) < 0 {
1300 report_child_setup_error(child_error_w, ChildSetupOp::Setpgid, errno());
1301 }
1302 }
1303 }
1304
1305 let envp_ptr = envp.as_ref().map_or_else(
1306 || unsafe { environ as *const *mut c_char },
1307 |e: &Vec<*mut c_char>| e.as_ptr(),
1308 );
1309
1310 if let Err(err) = SignalRuntime::unblock_all() {
1313 unsafe {
1314 report_child_setup_error(
1315 child_error_w,
1316 ChildSetupOp::SignalMask,
1317 err.raw_os_error().unwrap_or(libc::EIO),
1318 );
1319 }
1320 }
1321 if let Err(err) = SignalRuntime::reset_default(libc::SIGPIPE) {
1322 unsafe {
1323 report_child_setup_error(
1324 child_error_w,
1325 ChildSetupOp::SignalMask,
1326 err.raw_os_error().unwrap_or(libc::EIO),
1327 );
1328 }
1329 }
1330
1331 unsafe {
1334 libc::execve(
1335 exe_ptr,
1336 argv.as_ptr() as *const *const _,
1337 envp_ptr as *const *const _,
1338 );
1339 report_child_setup_error(child_error_w, ChildSetupOp::Execve, errno());
1340 }
1341 }
1342
1343 unsafe {
1345 libc::close(child_error_w);
1346 }
1347 match read_child_setup_error(child_error_r) {
1348 Ok(Some(err)) => {
1349 unsafe {
1350 libc::close(child_error_r);
1351 let mut status = 0;
1352 let _ = libc::waitpid(pid, &mut status, 0);
1353 }
1354 pipes.close_all();
1355 return Err(err);
1356 }
1357 Ok(None) => {}
1358 Err(err) => {
1359 unsafe {
1360 libc::close(child_error_r);
1361 }
1362 pipes.close_all();
1363 return Err(err);
1364 }
1365 }
1366 unsafe {
1367 libc::close(child_error_r);
1368 }
1369 drop(pipes.stdin_r.take());
1370 drop(pipes.stdout_w.take());
1371 drop(pipes.stderr_w.take());
1372
1373 let drain = crate::io::DrainState::new(
1374 pipes.stdin_w.take().filter(|_| opts.stdin.is_some()),
1375 opts.stdin,
1376 pipes.stdout_r.take(),
1377 pipes.stderr_r.take(),
1378 opts.max_output,
1379 opts.early_exit,
1380 )?;
1381
1382 Ok((pid, drain))
1383}
1384
1385enum KillState {
1386 None,
1387 TermSent,
1388 KillSent,
1389}
1390
1391fn wait_loop(
1392 pid: pid_t,
1393 mut drain: crate::io::DrainState<fn(&[u8]) -> bool>,
1394 mut reactor: Reactor,
1395 timeout_ms: Option<u32>,
1396 kill_grace_ms: u32,
1397 cancel: CancelPolicy,
1398 pgroup: ProcessGroup,
1399) -> Result<Output, CoreError> {
1400 let process = Process::new(pid);
1401 let pgid = pgroup.leader.unwrap_or(pid);
1406 let mut status_raw = process.wait_step()?;
1407 let mut state = KillState::None;
1408 let mut timed_out = false;
1409
1410 let start_time = std::time::Instant::now();
1411 let deadline = timeout_ms.map(|t| std::time::Duration::from_millis(t as u64));
1412
1413 loop {
1414 let mut poll_timeout = -1;
1415
1416 if let Some(dl) = deadline {
1417 let elapsed = start_time.elapsed();
1418 if elapsed >= dl {
1419 timed_out = true;
1420 let elapsed_over = (elapsed - dl).as_millis();
1421
1422 let target_is_group = pgroup.isolated || pgroup.leader.is_some();
1423
1424 match state {
1425 KillState::None => {
1426 if cancel == CancelPolicy::Graceful {
1427 let r = if target_is_group {
1428 process.kill_group(pgid, libc::SIGTERM)
1429 } else {
1430 process.kill(libc::SIGTERM)
1431 };
1432 if r.is_err() {
1433 state = KillState::KillSent; } else {
1435 state = KillState::TermSent;
1436 }
1437 } else if cancel == CancelPolicy::Kill {
1438 let _ = if target_is_group {
1439 process.kill_group(pgid, libc::SIGKILL)
1440 } else {
1441 process.kill(libc::SIGKILL)
1442 };
1443 state = KillState::KillSent;
1444 } else {
1445 }
1447 }
1448 KillState::TermSent if elapsed_over > kill_grace_ms as u128 => {
1449 let _ = if target_is_group {
1450 process.kill_group(pgid, libc::SIGKILL)
1451 } else {
1452 process.kill(libc::SIGKILL)
1453 };
1454 state = KillState::KillSent;
1455 }
1456 _ => {}
1457 }
1458 poll_timeout = 100; } else {
1460 let remaining = dl - elapsed;
1461 poll_timeout = remaining.as_millis().min(i32::MAX as u128) as i32;
1462 }
1463 }
1464
1465 if status_raw.is_none()
1466 && let Some(s) = process.wait_step()?
1467 {
1468 status_raw = Some(s);
1469 }
1470
1471 if drain.is_done() {
1472 let s = if status_raw.is_some() {
1473 status_raw.take()
1474 } else if deadline.is_none() {
1475 Some(process.wait_blocking()?)
1478 } else {
1479 None
1484 };
1485
1486 if let Some(s) = s {
1487 for slot in drain.take_all_slots() {
1488 reactor.del(&slot.fd)?;
1489 }
1490 let (stdout, stderr, output_limit_exceeded, stdout_early_exited) =
1491 drain.into_parts_with_state();
1492 if output_limit_exceeded {
1493 return Err(CoreError::sys(libc::EOVERFLOW, "spawn output limit"));
1494 }
1495 return Ok(Output {
1496 pid,
1497 status: Some(s),
1498 stdout,
1499 stderr,
1500 timed_out,
1501 stdout_early_exited,
1502 });
1503 }
1504 }
1505
1506 if timed_out && status_raw.is_some() {
1511 for slot in drain.take_all_slots() {
1512 let _ = reactor.del(&slot.fd);
1513 }
1514 let (stdout, stderr, _output_limit_exceeded, stdout_early_exited) =
1515 drain.into_parts_with_state();
1516 return Ok(Output {
1517 pid,
1518 status: status_raw,
1519 stdout,
1520 stderr,
1521 timed_out: true,
1522 stdout_early_exited,
1523 });
1524 }
1525
1526 let timeout = poll_timeout;
1527
1528 let mut events = Vec::new();
1529 let nevents = reactor.wait(&mut events, 64, timeout)?;
1530
1531 for ev in events.iter().take(nevents) {
1532 if drain.stdout_matches(ev.token) {
1533 if ev.readable || ev.hangup {
1534 drain.handle_stdout_ready(&mut reactor)?;
1535 } else if ev.error {
1536 drain.drop_stdout(&mut reactor)?;
1537 }
1538 } else if drain.stderr_matches(ev.token) {
1539 if ev.readable || ev.hangup {
1540 drain.handle_stderr_ready(&mut reactor)?;
1541 } else if ev.error {
1542 drain.drop_stderr(&mut reactor)?;
1543 }
1544 } else if drain.stdin_matches(ev.token) {
1545 if ev.writable {
1546 drain.handle_stdin_writable(&mut reactor)?;
1547 } else if ev.error || ev.hangup {
1548 drain.drop_stdin(&mut reactor)?;
1549 }
1550 }
1551 }
1552 }
1553}