1use std::{
4 ffi::OsStr,
5 io::{self, Write},
6 process::{Command, ExitStatus, Stdio},
7 thread,
8};
9
10#[derive(Debug)]
12#[non_exhaustive]
13#[allow(missing_docs)]
14pub enum ChildCommandError {
15 #[non_exhaustive]
16 SpawnChild { source: io::Error },
17
18 #[non_exhaustive]
19 WriteToStdin { source: io::Error },
20
21 #[non_exhaustive]
22 ReadOutput { source: io::Error },
23
24 #[non_exhaustive]
25 UnsuccessfulStatus { status: ExitStatus, stderr: String },
26}
27impl core::fmt::Display for ChildCommandError {
28 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29 match &self {
30 Self::SpawnChild { .. } => write!(f, "could not spawn child process"),
31 Self::WriteToStdin { .. } => write!(f, "writing to child's stdin failed"),
32 Self::ReadOutput { .. } => write!(f, "reading child's output failed"),
33 Self::UnsuccessfulStatus { status, stderr, .. } => write!(
34 f,
35 "child process reported exit code {status:?}, with stderr: {stderr}"
36 ),
37 }
38 }
39}
40impl core::error::Error for ChildCommandError {
41 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
42 match &self {
43 Self::ReadOutput { source, .. }
44 | Self::WriteToStdin { source, .. }
45 | Self::SpawnChild { source, .. } => Some(source),
46 _ => None,
47 }
48 }
49}
50
51pub fn process_using_child<C: AsRef<OsStr>, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
57 command: C,
58 args: I,
59 data: &[u8],
60) -> Result<Vec<u8>, ChildCommandError> {
61 let mut child = Command::new(command)
62 .args(args)
63 .stdout(Stdio::piped())
64 .stderr(Stdio::piped())
65 .stdin(Stdio::piped())
66 .spawn()
67 .map_err(|source| ChildCommandError::SpawnChild { source })?;
68
69 let mut stdin = child.stdin.take().expect("stdin handle to be present");
70 let output = thread::scope(|s| {
71 let writer = s.spawn(move || stdin.write_all(data));
72
73 let output = child
74 .wait_with_output()
75 .map_err(|source| ChildCommandError::ReadOutput { source });
76
77 let write_result = writer
78 .join()
79 .expect("writer thread to not panic")
80 .map_err(|source| ChildCommandError::WriteToStdin { source });
81
82 if let Some(error) = write_result.err() {
83 return Err(error);
84 }
85
86 output
87 })?;
88
89 if !output.status.success() {
90 return Err(ChildCommandError::UnsuccessfulStatus {
91 status: output.status,
92 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
93 });
94 }
95
96 Ok(output.stdout)
97}