pty_process/
command.rs

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