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}