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}