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