nuclei/process/mod.rs
1//! Child processes: a [`Command`](crate::process::Command) spawns one, and
2//! its [`Child`](crate::process::Child) waits for the exit without holding a
3//! thread and talks to it through async pipes.
4//!
5//! A child's exit is reported by a shard's driver, which reaps the child
6//! (see `exit`); a wait never blocks a thread. A child whose `Child` goes
7//! before it exits is still reaped (on unix), by the shard that watches it,
8//! so it leaves no zombie behind while a runtime runs.
9
10use core::future::poll_fn;
11use core::pin::Pin;
12use core::task::{Context, Poll};
13use std::ffi::OsStr;
14use std::io;
15use std::path::Path;
16use std::sync::Arc;
17
18use futures_io::AsyncRead;
19
20pub use std::process::{ExitStatus, Output, Stdio};
21
22pub(crate) mod exit;
23#[cfg(any(target_os = "linux", target_os = "android"))]
24mod fallback;
25mod stdio;
26#[cfg(test)]
27mod tests;
28
29pub use stdio::{ChildStderr, ChildStdin, ChildStdout};
30
31use exit::Exit;
32
33/// Reaps the children of the `SIGCHLD` reaper that exited: the signal
34/// module's dispatch, on a delivery of `SIGCHLD`.
35#[cfg(any(target_os = "linux", target_os = "android"))]
36pub(crate) fn reap_fallback() {
37 fallback::reap();
38}
39
40/// A process to spawn, as [`std::process::Command`] describes it.
41#[derive(Debug)]
42pub struct Command {
43 std: std::process::Command,
44 /// What `output` gives the child's stdin unless this says otherwise.
45 stdin_set: bool,
46 kill_on_drop: bool,
47}
48
49impl Command {
50 /// A command for `program`, with its environment and working directory
51 /// the process's, and its standard streams inherited.
52 pub fn new(program: impl AsRef<OsStr>) -> Self {
53 Self {
54 std: std::process::Command::new(program),
55 stdin_set: false,
56 kill_on_drop: false,
57 }
58 }
59
60 /// Adds an argument.
61 pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
62 self.std.arg(arg);
63 self
64 }
65
66 /// Adds arguments.
67 pub fn args<I, S>(&mut self, args: I) -> &mut Self
68 where
69 I: IntoIterator<Item = S>,
70 S: AsRef<OsStr>,
71 {
72 self.std.args(args);
73 self
74 }
75
76 /// Sets an environment variable of the child.
77 pub fn env(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
78 self.std.env(key, value);
79 self
80 }
81
82 /// Sets environment variables of the child.
83 pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
84 where
85 I: IntoIterator<Item = (K, V)>,
86 K: AsRef<OsStr>,
87 V: AsRef<OsStr>,
88 {
89 self.std.envs(vars);
90 self
91 }
92
93 /// Takes an environment variable out of the child's environment.
94 pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
95 self.std.env_remove(key);
96 self
97 }
98
99 /// Starts the child with no environment but what is set after.
100 pub fn env_clear(&mut self) -> &mut Self {
101 self.std.env_clear();
102 self
103 }
104
105 /// Sets the child's working directory.
106 pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
107 self.std.current_dir(dir);
108 self
109 }
110
111 /// Sets the child's stdin.
112 pub fn stdin(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
113 self.std.stdin(cfg);
114 self.stdin_set = true;
115 self
116 }
117
118 /// Sets the child's stdout.
119 pub fn stdout(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
120 self.std.stdout(cfg);
121 self
122 }
123
124 /// Sets the child's stderr.
125 pub fn stderr(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
126 self.std.stderr(cfg);
127 self
128 }
129
130 /// Whether the child is ended, as [`Child::start_kill`] ends it, when
131 /// its [`Child`] is dropped before it exits (default false: it runs on,
132 /// and is still reaped once it exits).
133 ///
134 /// ```no_run
135 /// # nuclei::block_on(async {
136 /// use nuclei::process::Command;
137 ///
138 /// let child = Command::new("sleep").arg("60").kill_on_drop(true).spawn()?;
139 /// drop(child); // the child is ended here
140 /// # std::io::Result::Ok(())
141 /// # }).unwrap();
142 /// ```
143 pub fn kill_on_drop(&mut self, kill: bool) -> &mut Self {
144 self.kill_on_drop = kill;
145 self
146 }
147
148 /// Whether [`kill_on_drop`](Self::kill_on_drop) is set.
149 pub fn get_kill_on_drop(&self) -> bool {
150 self.kill_on_drop
151 }
152
153 /// The std command underneath, for what this one does not mirror.
154 pub fn as_std(&self) -> &std::process::Command {
155 &self.std
156 }
157
158 /// The std command underneath, to change.
159 pub fn as_std_mut(&mut self) -> &mut std::process::Command {
160 &mut self.std
161 }
162
163 /// The std command underneath, taken.
164 pub fn into_std(self) -> std::process::Command {
165 self.std
166 }
167
168 /// Spawns the child. Its piped streams are async.
169 ///
170 /// # Errors
171 ///
172 /// When the child cannot start, or its pipes cannot be taken over.
173 pub fn spawn(&mut self) -> io::Result<Child> {
174 self.spawn_with(std::process::Command::spawn)
175 }
176
177 /// Spawns the child through `with`, which starts it from the std
178 /// command underneath: for a way to start a child this type does not
179 /// mirror, such as a platform's own spawn call with extra attributes.
180 /// The child `with` returns is then this one's, its piped streams async.
181 ///
182 /// ```no_run
183 /// # nuclei::block_on(async {
184 /// use nuclei::process::Command;
185 ///
186 /// let mut child = Command::new("true").spawn_with(|std| std.spawn())?;
187 /// assert!(child.wait().await?.success());
188 /// # std::io::Result::Ok(())
189 /// # }).unwrap();
190 /// ```
191 ///
192 /// # Errors
193 ///
194 /// What `with` returns, and when the child's pipes cannot be taken
195 /// over.
196 pub fn spawn_with(
197 &mut self,
198 with: impl FnOnce(&mut std::process::Command) -> io::Result<std::process::Child>,
199 ) -> io::Result<Child> {
200 let mut std = with(&mut self.std)?;
201 let exit = match Exit::new(&std) {
202 Ok(exit) => exit,
203 Err(e) => {
204 if self.kill_on_drop {
205 // The error that matters is the one returned.
206 let _ = std.kill();
207 }
208 return Err(e);
209 }
210 };
211 let (stdin, stdout, stderr) = (std.stdin.take(), std.stdout.take(), std.stderr.take());
212 // Made before the pipes are taken over: should one fail, its drop
213 // still reaps the child (and ends it, if asked).
214 let mut child = Child {
215 stdin: None,
216 stdout: None,
217 stderr: None,
218 exit,
219 kill_on_drop: self.kill_on_drop,
220 _std: std,
221 };
222 child.stdin = stdin.map(ChildStdin::new).transpose()?;
223 child.stdout = stdout.map(ChildStdout::new).transpose()?;
224 child.stderr = stderr.map(ChildStderr::new).transpose()?;
225 Ok(child)
226 }
227
228 /// Spawns the child and waits for its exit; its streams are inherited
229 /// unless set.
230 ///
231 /// # Errors
232 ///
233 /// As for [`spawn`](Self::spawn) and [`Child::wait`].
234 pub async fn status(&mut self) -> io::Result<ExitStatus> {
235 self.spawn()?.wait().await
236 }
237
238 /// Spawns the child with its stdout and stderr piped (and its stdin
239 /// empty, unless set), and collects both until its exit.
240 ///
241 /// # Errors
242 ///
243 /// As for [`spawn`](Self::spawn) and [`Child::wait_with_output`].
244 pub async fn output(&mut self) -> io::Result<Output> {
245 if !self.stdin_set {
246 self.std.stdin(Stdio::null());
247 }
248 self.std.stdout(Stdio::piped()).stderr(Stdio::piped());
249 self.spawn()?.wait_with_output().await
250 }
251}
252
253impl From<std::process::Command> for Command {
254 fn from(std: std::process::Command) -> Self {
255 Self {
256 std,
257 stdin_set: false,
258 kill_on_drop: false,
259 }
260 }
261}
262
263/// A spawned child process.
264#[derive(Debug)]
265pub struct Child {
266 /// The child's stdin, if piped.
267 pub stdin: Option<ChildStdin>,
268 /// The child's stdout, if piped.
269 pub stdout: Option<ChildStdout>,
270 /// The child's stderr, if piped.
271 pub stderr: Option<ChildStderr>,
272 exit: Arc<Exit>,
273 kill_on_drop: bool,
274 /// The std child, kept for what it owns (its process handle).
275 _std: std::process::Child,
276}
277
278impl core::fmt::Debug for Exit {
279 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280 f.debug_struct("Exit")
281 .field("pid", &self.pid())
282 .finish_non_exhaustive()
283 }
284}
285
286impl Child {
287 /// The child's process id.
288 pub fn id(&self) -> u32 {
289 self.exit.pid()
290 }
291
292 /// Waits for the child's exit. Its stdin, if piped, is closed first, so
293 /// a child that reads it to its end can exit.
294 ///
295 /// # Errors
296 ///
297 /// When the exit cannot be watched, or the child was reaped by
298 /// something else (`ECHILD`: `SIGCHLD` ignored, or a `waitpid` of the
299 /// program's own).
300 pub async fn wait(&mut self) -> io::Result<ExitStatus> {
301 drop(self.stdin.take());
302 poll_fn(|cx| self.exit.poll_wait(cx)).await
303 }
304
305 /// The child's status if it exited, else `None`, without waiting.
306 ///
307 /// # Errors
308 ///
309 /// When the child's state cannot be read.
310 pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
311 self.exit.try_wait()
312 }
313
314 /// Starts ending the child, without waiting for it to go: `SIGKILL` on
315 /// unix, `TerminateProcess` on Windows. A child that exited already is
316 /// left alone.
317 ///
318 /// # Errors
319 ///
320 /// When the signal or the call fails.
321 pub fn start_kill(&mut self) -> io::Result<()> {
322 self.exit.kill()
323 }
324
325 /// Ends the child, as [`start_kill`](Self::start_kill) does, and waits
326 /// for its exit.
327 ///
328 /// ```no_run
329 /// # nuclei::block_on(async {
330 /// use nuclei::process::Command;
331 ///
332 /// let mut child = Command::new("sleep").arg("60").spawn()?;
333 /// child.kill().await?;
334 /// assert!(child.try_wait()?.is_some());
335 /// # std::io::Result::Ok(())
336 /// # }).unwrap();
337 /// ```
338 ///
339 /// # Errors
340 ///
341 /// As for [`start_kill`](Self::start_kill) and [`wait`](Self::wait).
342 pub async fn kill(&mut self) -> io::Result<()> {
343 self.start_kill()?;
344 self.wait().await.map(drop)
345 }
346
347 /// Waits for the child's exit, collecting its stdout and stderr (those
348 /// piped) until then. Its stdin, if piped, is closed first.
349 ///
350 /// # Errors
351 ///
352 /// As for [`wait`](Self::wait), and when a pipe cannot be read.
353 pub async fn wait_with_output(mut self) -> io::Result<Output> {
354 drop(self.stdin.take());
355 let (mut stdout, mut stderr) = (self.stdout.take(), self.stderr.take());
356 let (mut out, mut err) = (Vec::new(), Vec::new());
357 poll_fn(|cx| {
358 let out_done = drain(&mut stdout, &mut out, cx)?.is_ready();
359 let err_done = drain(&mut stderr, &mut err, cx)?.is_ready();
360 if out_done && err_done {
361 Poll::Ready(Ok::<(), io::Error>(()))
362 } else {
363 Poll::Pending
364 }
365 })
366 .await?;
367 let status = self.wait().await?;
368 Ok(Output {
369 status,
370 stdout: out,
371 stderr: err,
372 })
373 }
374}
375
376impl Drop for Child {
377 fn drop(&mut self) {
378 if self.kill_on_drop {
379 // A drop cannot report a failure, and the kill of a child of
380 // ours fails only once it is gone.
381 let _ = self.exit.kill();
382 }
383 // A child that runs on is still reaped once it exits.
384 #[cfg(unix)]
385 self.exit.watch_orphan();
386 }
387}
388
389/// Reads `pipe` into `into` until its end (then lets it go) or until it
390/// would wait.
391fn drain<R: AsyncRead + Unpin>(
392 pipe: &mut Option<R>,
393 into: &mut Vec<u8>,
394 cx: &mut Context<'_>,
395) -> Poll<io::Result<()>> {
396 const STEP: usize = 16 * 1024;
397 while let Some(reader) = pipe.as_mut() {
398 let at = into.len();
399 into.resize(at + STEP, 0);
400 let read = Pin::new(reader).poll_read(cx, &mut into[at..]);
401 match read {
402 Poll::Ready(Ok(0)) => {
403 into.truncate(at);
404 *pipe = None;
405 }
406 Poll::Ready(Ok(n)) => into.truncate(at + n),
407 Poll::Ready(Err(err)) => {
408 into.truncate(at);
409 return Poll::Ready(Err(err));
410 }
411 Poll::Pending => {
412 into.truncate(at);
413 return Poll::Pending;
414 }
415 }
416 }
417 Poll::Ready(Ok(()))
418}