pty_process/blocking/
command.rs

1use std::os::unix::process::CommandExt as _;
2
3/// Wrapper around [`std::process::Command`]
4pub struct Command {
5    inner: std::process::Command,
6    stdin: bool,
7    stdout: bool,
8    stderr: bool,
9    pre_exec_set: bool,
10    pre_exec: Option<
11        Box<dyn FnMut() -> std::io::Result<()> + Send + Sync + 'static>,
12    >,
13}
14
15impl Command {
16    /// See [`std::process::Command::new`]
17    pub fn new<S: AsRef<std::ffi::OsStr>>(program: S) -> Self {
18        Self {
19            inner: std::process::Command::new(program),
20            stdin: false,
21            stdout: false,
22            stderr: false,
23            pre_exec_set: false,
24            pre_exec: None,
25        }
26    }
27
28    /// See [`std::process::Command::arg`]
29    #[must_use]
30    pub fn arg<S: AsRef<std::ffi::OsStr>>(mut self, arg: S) -> Self {
31        self.inner.arg(arg);
32        self
33    }
34
35    /// See [`std::process::Command::args`]
36    #[must_use]
37    pub fn args<I, S>(mut self, args: I) -> Self
38    where
39        I: IntoIterator<Item = S>,
40        S: AsRef<std::ffi::OsStr>,
41    {
42        self.inner.args(args);
43        self
44    }
45
46    /// See [`std::process::Command::env`]
47    #[must_use]
48    pub fn env<K, V>(mut self, key: K, val: V) -> Self
49    where
50        K: AsRef<std::ffi::OsStr>,
51        V: AsRef<std::ffi::OsStr>,
52    {
53        self.inner.env(key, val);
54        self
55    }
56
57    /// See [`std::process::Command::envs`]
58    #[must_use]
59    pub fn envs<I, K, V>(mut self, vars: I) -> Self
60    where
61        I: IntoIterator<Item = (K, V)>,
62        K: AsRef<std::ffi::OsStr>,
63        V: AsRef<std::ffi::OsStr>,
64    {
65        self.inner.envs(vars);
66        self
67    }
68
69    /// See [`std::process::Command::env_remove`]
70    #[must_use]
71    pub fn env_remove<K: AsRef<std::ffi::OsStr>>(mut self, key: K) -> Self {
72        self.inner.env_remove(key);
73        self
74    }
75
76    /// See [`std::process::Command::env_clear`]
77    #[must_use]
78    pub fn env_clear(mut self) -> Self {
79        self.inner.env_clear();
80        self
81    }
82
83    /// See [`std::process::Command::current_dir`]
84    #[must_use]
85    pub fn current_dir<P: AsRef<std::path::Path>>(mut self, dir: P) -> Self {
86        self.inner.current_dir(dir);
87        self
88    }
89
90    /// See [`std::process::Command::stdin`]
91    #[must_use]
92    pub fn stdin<T: Into<std::process::Stdio>>(mut self, cfg: T) -> Self {
93        self.stdin = true;
94        self.inner.stdin(cfg);
95        self
96    }
97
98    /// See [`std::process::Command::stdout`]
99    #[must_use]
100    pub fn stdout<T: Into<std::process::Stdio>>(mut self, cfg: T) -> Self {
101        self.stdout = true;
102        self.inner.stdout(cfg);
103        self
104    }
105
106    /// See [`std::process::Command::stderr`]
107    #[must_use]
108    pub fn stderr<T: Into<std::process::Stdio>>(mut self, cfg: T) -> Self {
109        self.stderr = true;
110        self.inner.stderr(cfg);
111        self
112    }
113
114    /// Executes the command as a child process via
115    /// [`std::process::Command::spawn`] on the given pty. The pty will be
116    /// attached to all of `stdin`, `stdout`, and `stderr` of the child,
117    /// unless those file descriptors were previously overridden through calls
118    /// to [`stdin`](Self::stdin), [`stdout`](Self::stdout), or
119    /// [`stderr`](Self::stderr). The newly created child process will also be
120    /// made the session leader of a new session, and will have the given
121    /// pty set as its controlling terminal.
122    ///
123    /// # Errors
124    /// Returns an error if we fail to allocate new file descriptors for
125    /// attaching the pty to the child process, or if we fail to spawn the
126    /// child process (see the documentation for
127    /// [`std::process::Command::spawn`]), or if we fail to make the child a
128    /// session leader or set its controlling terminal.
129    #[allow(clippy::needless_pass_by_value)]
130    pub fn spawn(
131        mut self,
132        pts: crate::blocking::Pts,
133    ) -> crate::Result<std::process::Child> {
134        self.spawn_impl(&pts)
135    }
136
137    /// Executes the command as a child process via
138    /// [`std::process::Command::spawn`] on the given pty. The pty will be
139    /// attached to all of `stdin`, `stdout`, and `stderr` of the child,
140    /// unless those file descriptors were previously overridden through calls
141    /// to [`stdin`](Self::stdin), [`stdout`](Self::stdout), or
142    /// [`stderr`](Self::stderr). The newly created child process will also be
143    /// made the session leader of a new session, and will have the given
144    /// pty set as its controlling terminal.
145    ///
146    /// Differs from `spawn` in that it borrows the pty rather than consuming
147    /// it, allowing for multiple commands to be spawned onto the same pty in
148    /// sequence, but this functionality is not available on macos.
149    ///
150    /// # Errors
151    /// Returns an error if we fail to allocate new file descriptors for
152    /// attaching the pty to the child process, or if we fail to spawn the
153    /// child process (see the documentation for
154    /// [`std::process::Command::spawn`]), or if we fail to make the child a
155    /// session leader or set its controlling terminal.
156    #[cfg(not(target_os = "macos"))]
157    pub fn spawn_borrowed(
158        &mut self,
159        pts: &crate::blocking::Pts,
160    ) -> crate::Result<std::process::Child> {
161        self.spawn_impl(pts)
162    }
163
164    fn spawn_impl(
165        &mut self,
166        pts: &crate::blocking::Pts,
167    ) -> crate::Result<std::process::Child> {
168        let (stdin, stdout, stderr) = pts.0.setup_subprocess()?;
169
170        if !self.stdin {
171            self.inner.stdin(stdin);
172        }
173        if !self.stdout {
174            self.inner.stdout(stdout);
175        }
176        if !self.stderr {
177            self.inner.stderr(stderr);
178        }
179
180        let mut session_leader = pts.0.session_leader();
181        // Safety: setsid() is an async-signal-safe function and ioctl() is a
182        // raw syscall (which is inherently async-signal-safe).
183        if let Some(mut custom) = self.pre_exec.take() {
184            unsafe {
185                self.inner.pre_exec(move || {
186                    session_leader()?;
187                    custom()?;
188                    Ok(())
189                })
190            };
191        } else if !self.pre_exec_set {
192            unsafe { self.inner.pre_exec(session_leader) };
193        }
194        self.pre_exec_set = true;
195
196        Ok(self.inner.spawn()?)
197    }
198
199    /// See [`std::os::unix::process::CommandExt::uid`]
200    #[must_use]
201    pub fn uid(mut self, id: u32) -> Self {
202        self.inner.uid(id);
203        self
204    }
205
206    /// See [`std::os::unix::process::CommandExt::gid`]
207    #[must_use]
208    pub fn gid(mut self, id: u32) -> Self {
209        self.inner.gid(id);
210        self
211    }
212
213    /// See [`std::os::unix::process::CommandExt::pre_exec`]
214    #[allow(clippy::missing_safety_doc)]
215    #[must_use]
216    pub unsafe fn pre_exec<F>(mut self, f: F) -> Self
217    where
218        F: FnMut() -> std::io::Result<()> + Send + Sync + 'static,
219    {
220        self.pre_exec = Some(Box::new(f));
221        self
222    }
223
224    /// See [`std::os::unix::process::CommandExt::arg0`]
225    #[must_use]
226    pub fn arg0<S>(mut self, arg: S) -> Self
227    where
228        S: AsRef<std::ffi::OsStr>,
229    {
230        self.inner.arg0(arg);
231        self
232    }
233}