Skip to main content

shrub_core/sys/unix/
commands.rs

1//! Command execution utilities.
2
3pub use std::os::unix::process::{CommandExt, ExitStatusExt};
4
5use command_fds::{CommandFdExt, FdMapping};
6
7use crate::{ShellFd, error, openfiles};
8
9/// Extension trait for injecting file descriptors into commands.
10pub trait CommandFdInjectionExt {
11    /// Injects the given open files as file descriptors into the command.
12    ///
13    /// # Arguments
14    ///
15    /// * `open_files` - A mapping of child file descriptors to open files.
16    fn inject_fds(
17        &mut self,
18        open_files: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
19    ) -> Result<(), error::Error>;
20}
21
22impl CommandFdInjectionExt for std::process::Command {
23    fn inject_fds(
24        &mut self,
25        open_files: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
26    ) -> Result<(), error::Error> {
27        let fd_mappings: Vec<FdMapping> = open_files
28            .map(|(child_fd, open_file)| -> Result<FdMapping, error::Error> {
29                let parent_fd = open_file.try_clone_to_owned()?;
30                Ok(FdMapping {
31                    child_fd,
32                    parent_fd,
33                })
34            })
35            .collect::<Result<Vec<_>, _>>()?;
36
37        self.fd_mappings(fd_mappings)
38            .map_err(|_e| error::ErrorKind::ChildCreationFailure)?;
39
40        Ok(())
41    }
42}
43
44/// Extension trait for arranging for commands to take the foreground.
45pub trait CommandFgControlExt {
46    /// Arranges for the command to take the foreground when it is executed.
47    fn take_foreground(&mut self);
48    /// Arranges for the command to become a session leader when it is executed.
49    fn lead_session(&mut self);
50}
51
52impl CommandFgControlExt for std::process::Command {
53    fn take_foreground(&mut self) {
54        // SAFETY:
55        // This arranges for a provided function to run in the context of
56        // the forked process before it exec's the target command. In general,
57        // rust can't guarantee safety of code running in such a context.
58        unsafe {
59            self.pre_exec(pre_exec_take_foreground);
60        }
61    }
62
63    fn lead_session(&mut self) {
64        // SAFETY:
65        // This arranges for a provided function to run in the context of
66        // the forked process before it exec's the target command. In general,
67        // rust can't guarantee safety of code running in such a context.
68        unsafe {
69            self.pre_exec(pre_exec_lead_session);
70        }
71    }
72}
73
74fn pre_exec_take_foreground() -> Result<(), std::io::Error> {
75    use crate::sys;
76
77    sys::terminal::move_self_to_foreground()?;
78    Ok(())
79}
80
81fn pre_exec_lead_session() -> Result<(), std::io::Error> {
82    if let Err(e) = nix::unistd::setsid() {
83        return Err(std::io::Error::other(format!(
84            "failed to become session leader: {e}"
85        )));
86    }
87
88    #[cfg(not(target_os = "macos"))]
89    let control = libc::TIOCSCTTY;
90    #[cfg(target_os = "macos")]
91    let control: u64 = libc::TIOCSCTTY.into();
92
93    // SAFETY:
94    // This is calling a libc function to set the controlling terminal.
95    let result = unsafe { libc::ioctl(0, control, 0) };
96    if result != 0 {
97        return Err(std::io::Error::other("failed to set controlling terminal"));
98    }
99
100    Ok(())
101}