nu_system/
foreground.rs

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
18/// A simple wrapper for [`std::process::Child`]
19///
20/// It can only be created by [`ForegroundChild::spawn`].
21///
22/// # Spawn behavior
23/// ## Unix
24///
25/// For interactive shells, the spawned child process will get its own process group id,
26/// and it will be put in the foreground (by making stdin belong to the child's process group).
27/// On drop, the calling process's group will become the foreground process group once again.
28///
29/// For non-interactive mode, processes are spawned normally without any foreground process handling.
30///
31/// ## Other systems
32///
33/// It does nothing special on non-unix systems, so `spawn` is the same as [`std::process::Command::spawn`].
34pub struct ForegroundChild {
35    inner: Child,
36    #[cfg(unix)]
37    pipeline_state: Option<Arc<(AtomicU32, AtomicU32)>>,
38
39    // this is unix-only since we don't have to deal with process groups in windows
40    #[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    // the child may be stopped multiple times, we loop until it exits
119    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                // keep waiting
139            }
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        // bring child's process group back into foreground and continue it
168
169        // we only keep the guard for its drop impl
170        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            && pcnt.fetch_sub(1, Ordering::SeqCst) == 1
205        {
206            pgrp.store(0, Ordering::SeqCst);
207
208            if self.interactive {
209                child_pgroup::reset()
210            }
211        }
212    }
213}
214
215/// Keeps a specific already existing process in the foreground as long as the [`ForegroundGuard`].
216/// If the process needs to be spawned in the foreground, use [`ForegroundChild`] instead. This is
217/// used to temporarily bring frozen and plugin processes into the foreground.
218///
219/// # OS-specific behavior
220/// ## Unix
221///
222/// If there is already a foreground external process running, spawned with [`ForegroundChild`],
223/// this expects the process ID to remain in the process group created by the [`ForegroundChild`]
224/// for the lifetime of the guard, and keeps the terminal controlling process group set to that.
225/// If there is no foreground external process running, this sets the foreground process group to
226/// the provided process ID. The process group that is expected can be retrieved with
227/// [`.pgrp()`](Self::pgrp) if different from the provided process ID.
228///
229/// ## Other systems
230///
231/// It does nothing special on non-unix systems.
232#[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    /// Move the given process to the foreground.
242    #[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        // Might have to retry due to race conditions on the atomics
253        loop {
254            // Try to give control to the child, if there isn't currently a foreground group
255            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                // We don't need the child to change process group. Make the guard now so that if there
262                // is an error, it will be cleaned up
263                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                // Set the terminal controlling process group to the child process
271                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                    // Avoid a race condition: only increment if count is > 0
277                    if count > 0 { Some(count + 1) } else { None }
278                })
279                .is_ok()
280            {
281                // We successfully added another count to the foreground process group, which means
282                // we only need to tell the child process to join this one
283                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                // The state has changed, we'll have to retry
294                continue;
295            }
296        }
297    }
298
299    /// Move the given process to the foreground.
300    #[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    /// If the child process is expected to join a different process group to be in the foreground,
310    /// this returns `Some(pgrp)`. This only ever returns `Some` on Unix.
311    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    /// This should only be called once by `Drop`
323    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                // Clean up if we are the last one around
331                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// It's a simpler version of fish shell's external process handling.
345#[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    /// Alternative to having to call `std::io::stdin()` just to get the file descriptor of stdin
360    ///
361    /// # Safety
362    /// I/O safety of reading from `STDIN_FILENO` unclear.
363    ///
364    /// Currently only intended to access `tcsetpgrp` and `tcgetpgrp` with the I/O safe `nix`
365    /// interface.
366    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            // Safety:
373            // POSIX only allows async-signal-safe functions to be called.
374            // `sigaction` and `getpid` are async-signal-safe according to:
375            // https://manpages.ubuntu.com/manpages/bionic/man7/signal-safety.7.html
376            // Also, `set_foreground_pid` is async-signal-safe.
377            external_command.pre_exec(move || {
378                // When this callback is run, std::process has already:
379                // - reset SIGPIPE to SIG_DFL
380
381                // According to glibc's job control manual:
382                // https://www.gnu.org/software/libc/manual/html_node/Launching-Jobs.html
383                // This has to be done *both* in the parent and here in the child due to race conditions.
384                set_foreground_pid(Pid::this(), existing_pgrp, background);
385
386                // `terminal.rs` makes the shell process ignore some signals,
387                //  so we set them to their default behavior for our child
388                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        // Safety: needs to be async-signal-safe.
409        // `setpgid` and `tcsetpgrp` are async-signal-safe.
410
411        // `existing_pgrp` is 0 when we don't have an existing foreground process in the pipeline.
412        // A pgrp of 0 means the calling process's pid for `setpgid`. But not for `tcsetpgrp`.
413        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    /// Reset the foreground process group to the shell
426    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}