1use std::sync::{Arc, atomic::AtomicU32};
2
3use std::io;
4
5use std::process::{Child, Command};
6
7use crate::ExitStatus;
8
9#[cfg(unix)]
10use std::{io::IsTerminal, sync::atomic::Ordering};
11
12#[cfg(unix)]
13pub use child_pgroup::stdin_fd;
14
15#[cfg(unix)]
16use nix::{sys::signal, sys::wait, unistd::Pid};
17
18pub struct ForegroundChild {
35 inner: Child,
36 #[cfg(unix)]
37 pipeline_state: Option<Arc<(AtomicU32, AtomicU32)>>,
38
39 #[cfg(unix)]
41 interactive: bool,
42}
43
44impl ForegroundChild {
45 #[cfg(not(unix))]
46 pub fn spawn(mut command: Command) -> io::Result<Self> {
47 command.spawn().map(|child| Self { inner: child })
48 }
49
50 #[cfg(unix)]
51 pub fn spawn(
52 mut command: Command,
53 interactive: bool,
54 background: bool,
55 pipeline_state: &Arc<(AtomicU32, AtomicU32)>,
56 ) -> io::Result<Self> {
57 let interactive = interactive && io::stdin().is_terminal();
58
59 let uses_dedicated_process_group = interactive || background;
60
61 if uses_dedicated_process_group {
62 let (pgrp, pcnt) = pipeline_state.as_ref();
63 let existing_pgrp = pgrp.load(Ordering::SeqCst);
64 child_pgroup::prepare_command(&mut command, existing_pgrp, background);
65 command
66 .spawn()
67 .map(|child| {
68 child_pgroup::set(&child, existing_pgrp, background);
69
70 let _ = pcnt.fetch_add(1, Ordering::SeqCst);
71 if existing_pgrp == 0 {
72 pgrp.store(child.id(), Ordering::SeqCst);
73 }
74 Self {
75 inner: child,
76 pipeline_state: Some(pipeline_state.clone()),
77 interactive,
78 }
79 })
80 .inspect_err(|_e| {
81 if interactive {
82 child_pgroup::reset();
83 }
84 })
85 } else {
86 command.spawn().map(|child| Self {
87 inner: child,
88 pipeline_state: None,
89 interactive,
90 })
91 }
92 }
93
94 pub fn wait(&mut self) -> io::Result<ForegroundWaitStatus> {
95 #[cfg(unix)]
96 {
97 let child_pid = Pid::from_raw(self.inner.id() as i32);
98
99 unix_wait(child_pid).inspect(|result| {
100 if let (true, ForegroundWaitStatus::Frozen(_)) = (self.interactive, result) {
101 child_pgroup::reset();
102 }
103 })
104 }
105 #[cfg(not(unix))]
106 self.as_mut().wait().map(Into::into)
107 }
108
109 pub fn pid(&self) -> u32 {
110 self.inner.id()
111 }
112}
113
114#[cfg(unix)]
115fn unix_wait(child_pid: Pid) -> std::io::Result<ForegroundWaitStatus> {
116 use ForegroundWaitStatus::*;
117
118 loop {
120 let status = wait::waitpid(child_pid, Some(wait::WaitPidFlag::WUNTRACED));
121 match status {
122 Err(e) => {
123 return Err(e.into());
124 }
125 Ok(wait::WaitStatus::Exited(_, status)) => {
126 return Ok(Finished(ExitStatus::Exited(status)));
127 }
128 Ok(wait::WaitStatus::Signaled(_, signal, core_dumped)) => {
129 return Ok(Finished(ExitStatus::Signaled {
130 signal: signal as i32,
131 core_dumped,
132 }));
133 }
134 Ok(wait::WaitStatus::Stopped(_, _)) => {
135 return Ok(Frozen(UnfreezeHandle { child_pid }));
136 }
137 Ok(_) => {
138 }
140 };
141 }
142}
143
144pub enum ForegroundWaitStatus {
145 Finished(ExitStatus),
146 Frozen(UnfreezeHandle),
147}
148
149impl From<std::process::ExitStatus> for ForegroundWaitStatus {
150 fn from(status: std::process::ExitStatus) -> Self {
151 ForegroundWaitStatus::Finished(status.into())
152 }
153}
154
155#[derive(Debug)]
156pub struct UnfreezeHandle {
157 #[cfg(unix)]
158 child_pid: Pid,
159}
160
161impl UnfreezeHandle {
162 #[cfg(unix)]
163 pub fn unfreeze(
164 self,
165 pipeline_state: Option<Arc<(AtomicU32, AtomicU32)>>,
166 ) -> io::Result<ForegroundWaitStatus> {
167 let _guard = pipeline_state.map(|pipeline_state| {
171 ForegroundGuard::new(self.child_pid.as_raw() as u32, &pipeline_state)
172 });
173
174 if let Err(err) = signal::killpg(self.child_pid, signal::SIGCONT) {
175 return Err(err.into());
176 }
177
178 let child_pid = self.child_pid;
179
180 unix_wait(child_pid)
181 }
182
183 pub fn pid(&self) -> u32 {
184 #[cfg(unix)]
185 {
186 self.child_pid.as_raw() as u32
187 }
188
189 #[cfg(not(unix))]
190 0
191 }
192}
193
194impl AsMut<Child> for ForegroundChild {
195 fn as_mut(&mut self) -> &mut Child {
196 &mut self.inner
197 }
198}
199
200#[cfg(unix)]
201impl Drop for ForegroundChild {
202 fn drop(&mut self) {
203 if let Some((pgrp, pcnt)) = self.pipeline_state.as_deref() {
204 if pcnt.fetch_sub(1, Ordering::SeqCst) == 1 {
205 pgrp.store(0, Ordering::SeqCst);
206
207 if self.interactive {
208 child_pgroup::reset()
209 }
210 }
211 }
212 }
213}
214
215#[derive(Debug)]
233pub struct ForegroundGuard {
234 #[cfg(unix)]
235 pgrp: Option<u32>,
236 #[cfg(unix)]
237 pipeline_state: Arc<(AtomicU32, AtomicU32)>,
238}
239
240impl ForegroundGuard {
241 #[cfg(unix)]
243 pub fn new(
244 pid: u32,
245 pipeline_state: &Arc<(AtomicU32, AtomicU32)>,
246 ) -> std::io::Result<ForegroundGuard> {
247 use nix::unistd::{self, Pid};
248
249 let pid_nix = Pid::from_raw(pid as i32);
250 let (pgrp, pcnt) = pipeline_state.as_ref();
251
252 loop {
254 if pgrp
256 .compare_exchange(0, pid, Ordering::SeqCst, Ordering::SeqCst)
257 .is_ok()
258 {
259 let _ = pcnt.fetch_add(1, Ordering::SeqCst);
260
261 let guard = ForegroundGuard {
264 pgrp: None,
265 pipeline_state: pipeline_state.clone(),
266 };
267
268 log::trace!("Giving control of the terminal to the process group, pid={pid}");
269
270 unistd::tcsetpgrp(unsafe { stdin_fd() }, pid_nix)?;
272
273 return Ok(guard);
274 } else if pcnt
275 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
276 if count > 0 { Some(count + 1) } else { None }
278 })
279 .is_ok()
280 {
281 let pgrp = pgrp.load(Ordering::SeqCst);
284 log::trace!(
285 "Will ask the process pid={pid} to join pgrp={pgrp} for control of the \
286 terminal"
287 );
288 return Ok(ForegroundGuard {
289 pgrp: Some(pgrp),
290 pipeline_state: pipeline_state.clone(),
291 });
292 } else {
293 continue;
295 }
296 }
297 }
298
299 #[cfg(not(unix))]
301 pub fn new(
302 pid: u32,
303 pipeline_state: &Arc<(AtomicU32, AtomicU32)>,
304 ) -> std::io::Result<ForegroundGuard> {
305 let _ = (pid, pipeline_state);
306 Ok(ForegroundGuard {})
307 }
308
309 pub fn pgrp(&self) -> Option<u32> {
312 #[cfg(unix)]
313 {
314 self.pgrp
315 }
316 #[cfg(not(unix))]
317 {
318 None
319 }
320 }
321
322 fn reset_internal(&mut self) {
324 #[cfg(unix)]
325 {
326 log::trace!("Leaving the foreground group");
327
328 let (pgrp, pcnt) = self.pipeline_state.as_ref();
329 if pcnt.fetch_sub(1, Ordering::SeqCst) == 1 {
330 pgrp.store(0, Ordering::SeqCst);
332 child_pgroup::reset()
333 }
334 }
335 }
336}
337
338impl Drop for ForegroundGuard {
339 fn drop(&mut self) {
340 self.reset_internal();
341 }
342}
343
344#[cfg(unix)]
346mod child_pgroup {
347 use nix::{
348 sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction},
349 unistd::{self, Pid},
350 };
351 use std::{
352 os::{
353 fd::{AsFd, BorrowedFd},
354 unix::prelude::CommandExt,
355 },
356 process::{Child, Command},
357 };
358
359 pub unsafe fn stdin_fd() -> impl AsFd {
367 unsafe { BorrowedFd::borrow_raw(nix::libc::STDIN_FILENO) }
368 }
369
370 pub fn prepare_command(external_command: &mut Command, existing_pgrp: u32, background: bool) {
371 unsafe {
372 external_command.pre_exec(move || {
378 set_foreground_pid(Pid::this(), existing_pgrp, background);
385
386 let default = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
389
390 let _ = sigaction(Signal::SIGQUIT, &default);
391 let _ = sigaction(Signal::SIGTSTP, &default);
392 let _ = sigaction(Signal::SIGTERM, &default);
393
394 Ok(())
395 });
396 }
397 }
398
399 pub fn set(process: &Child, existing_pgrp: u32, background: bool) {
400 set_foreground_pid(
401 Pid::from_raw(process.id() as i32),
402 existing_pgrp,
403 background,
404 );
405 }
406
407 fn set_foreground_pid(pid: Pid, existing_pgrp: u32, background: bool) {
408 let pgrp = if existing_pgrp == 0 {
414 pid
415 } else {
416 Pid::from_raw(existing_pgrp as i32)
417 };
418 let _ = unistd::setpgid(pid, pgrp);
419
420 if !background {
421 let _ = unistd::tcsetpgrp(unsafe { stdin_fd() }, pgrp);
422 }
423 }
424
425 pub fn reset() {
427 if let Err(e) = unistd::tcsetpgrp(unsafe { stdin_fd() }, unistd::getpgrp()) {
428 eprintln!("ERROR: reset foreground id failed, tcsetpgrp result: {e:?}");
429 }
430 }
431}