use crate::error::{
Error,
ErrorKind::{ChildError, ExecError},
};
use std::{
convert::TryFrom,
io::{self, ErrorKind, Read, Write},
process::{Child, Command, ExitStatus, Stdio},
};
#[derive(Debug)]
pub struct CapturingExecutor {
child: Child,
exit_status: Option<ExitStatus>,
}
impl CapturingExecutor {
pub(crate) fn new(command: &mut Command) -> Result<Self, Error> {
command.stdin(Stdio::piped());
command.stdout(Stdio::piped());
command.stderr(Stdio::piped());
let child = command.spawn().map_err(|e| error!(ExecError, with: e, "failed to execute child"))?;
Ok(Self { child, exit_status: None })
}
pub fn close_stdin(&mut self) {
self.child.stdin = None
}
pub fn close_stdout(&mut self) {
self.child.stdout = None
}
pub fn close_stderr(&mut self) {
self.child.stderr = None
}
pub fn is_running(&mut self) -> Result<bool, Error> {
if self.exit_status.is_none() {
self.exit_status =
self.child.try_wait().map_err(|e| error!(ExecError, with: e, "failed to get child exit status"))?;
}
Ok(self.exit_status.is_none())
}
pub fn wait(mut self) -> Result<(), Error> {
self.child.stdin = None;
let exit_status = self
.exit_status
.take()
.map_or_else(|| self.child.wait(), Ok)
.map_err(|e| error!(ExecError, with: e, "failed to get child exit status"))?;
if !exit_status.success() {
let mut stderr_bytes = Vec::new();
if let Some(mut stderr) = self.child.stderr {
stderr
.read_to_end(&mut stderr_bytes)
.map_err(|e| error!(ExecError, with: e, "failed to read child stderr"))?;
}
let stderr_string = String::from_utf8_lossy(&stderr_bytes);
return Err(error!(ExecError, "child process failed ({:?}): {}", exit_status.code(), stderr_string));
}
Ok(())
}
}
impl TryFrom<CapturingExecutor> for Vec<u8> {
type Error = Error;
fn try_from(mut executor: CapturingExecutor) -> Result<Self, Error> {
executor.close_stdin();
let mut stdout = Vec::new();
executor
.read_to_end(&mut stdout)
.map_err(|e| error!(ChildError, with: e, "failed to read stdout from child"))?;
executor.wait()?;
Ok(stdout)
}
}
impl TryFrom<CapturingExecutor> for String {
type Error = Error;
fn try_from(executor: CapturingExecutor) -> Result<Self, Error> {
let stdout = Vec::try_from(executor)?;
String::from_utf8(stdout).map_err(|e| error!(ChildError, with: e, "cannot read child's stdout as string"))
}
}
impl Read for CapturingExecutor {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let stdout = self.child.stdout.as_mut().ok_or(ErrorKind::BrokenPipe)?;
stdout.read(buf)
}
}
impl Write for CapturingExecutor {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let stdin = self.child.stdin.as_mut().ok_or(ErrorKind::BrokenPipe)?;
stdin.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
let stdin = self.child.stdin.as_mut().ok_or(ErrorKind::BrokenPipe)?;
stdin.flush()
}
}