#[cfg(unix)]
use std::{env, ffi::OsString};
use std::{
ffi::OsStr,
io::{self, ErrorKind, Read, Write},
process::{Child, ChildStdin, ChildStdout, Command, Output, Stdio},
thread,
};
pub use execute_command_macro::{command, command_args};
use execute_command_tokens::command_tokens;
const DEFAULT_READER_BUFFER_SIZE: usize = 256;
#[inline]
fn take_child_stdin(child: &mut Child) -> Result<ChildStdin, io::Error> {
child.stdin.take().ok_or_else(|| io::Error::other("child stdin was not piped"))
}
#[inline]
fn take_child_stdout(child: &mut Child) -> Result<ChildStdout, io::Error> {
child.stdout.take().ok_or_else(|| io::Error::other("child stdout was not piped"))
}
#[inline]
fn write_stdin<D: ?Sized + AsRef<[u8]>>(mut stdin: ChildStdin, data: &D) -> Result<(), io::Error> {
stdin.write_all(data.as_ref())
}
fn copy_reader_to_stdin<const N: usize>(
mut stdin: ChildStdin,
reader: &mut dyn Read,
) -> Result<(), io::Error> {
const { assert!(N > 0, "reader buffer size must be greater than zero") };
let mut buffer = [0u8; N];
loop {
match reader.read(&mut buffer) {
Ok(0) => break,
Ok(c) => stdin.write_all(&buffer[0..c])?,
Err(ref err) if err.kind() == ErrorKind::Interrupted => (),
Err(err) => return Err(err),
}
}
Ok(())
}
fn wait_with_stdin_writer<T, W, F>(wait: W, write_stdin: F) -> Result<T, io::Error>
where
T: Send,
W: FnOnce() -> Result<T, io::Error> + Send,
F: FnOnce() -> Result<(), io::Error>, {
thread::scope(|scope| {
let wait_handle = scope.spawn(wait);
let write_result = write_stdin();
let wait_result = match wait_handle.join() {
Ok(result) => result,
Err(_) => Err(io::Error::other("child wait thread panicked")),
};
match write_result {
Ok(()) => wait_result,
Err(err) => Err(err),
}
})
}
fn kill_and_wait_children(mut children: Vec<Child>) {
for child in &mut children {
let _ = child.kill();
}
for mut child in children {
let _ = child.wait();
}
}
fn wait_upstream_children(children: Vec<Child>) -> Result<(), io::Error> {
let mut first_error = None;
for mut child in children {
if let Err(err) = child.wait() {
if first_error.is_none() {
first_error = Some(err);
}
}
}
match first_error {
Some(err) => Err(err),
None => Ok(()),
}
}
fn finish_pipeline_result<T>(
result: Result<T, io::Error>,
upstream_children: Vec<Child>,
) -> Result<T, io::Error> {
let upstream_result = wait_upstream_children(upstream_children);
match result {
Ok(value) => {
upstream_result?;
Ok(value)
},
Err(err) => Err(err),
}
}
fn spawn_pipeline(
first: &mut Command,
others: &mut [&mut Command],
) -> Result<(Vec<Child>, Child), io::Error> {
let mut upstream_children = Vec::with_capacity(others.len());
let mut previous_child = first.spawn()?;
let last_index = others.len() - 1;
for other in others.iter_mut().take(last_index) {
let stdout = match take_child_stdout(&mut previous_child) {
Ok(stdout) => stdout,
Err(err) => {
upstream_children.push(previous_child);
kill_and_wait_children(upstream_children);
return Err(err);
},
};
other.stdin(stdout);
other.stdout(Stdio::piped());
other.stderr(Stdio::null());
upstream_children.push(previous_child);
previous_child = match other.spawn() {
Ok(child) => child,
Err(err) => {
kill_and_wait_children(upstream_children);
return Err(err);
},
};
}
let stdout = match take_child_stdout(&mut previous_child) {
Ok(stdout) => stdout,
Err(err) => {
upstream_children.push(previous_child);
kill_and_wait_children(upstream_children);
return Err(err);
},
};
let last_other = &mut others[last_index];
last_other.stdin(stdout);
upstream_children.push(previous_child);
match last_other.spawn() {
Ok(last_child) => Ok((upstream_children, last_child)),
Err(err) => {
kill_and_wait_children(upstream_children);
Err(err)
},
}
}
pub trait Execute {
fn execute(&mut self) -> Result<Option<i32>, io::Error>;
fn execute_output(&mut self) -> Result<Output, io::Error>;
#[inline]
fn execute_check_exit_status_code(
&mut self,
expected_exit_status_code: i32,
) -> Result<(), io::Error> {
match self.execute()? {
Some(exit_status_code) if exit_status_code == expected_exit_status_code => Ok(()),
_ => Err(io::Error::other("unexpected exit status")),
}
}
fn execute_input<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
) -> Result<Option<i32>, io::Error>;
fn execute_input_output<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
) -> Result<Output, io::Error>;
#[inline]
fn execute_input_reader(&mut self, reader: &mut dyn Read) -> Result<Option<i32>, io::Error> {
self.execute_input_reader2::<DEFAULT_READER_BUFFER_SIZE>(reader)
}
fn execute_input_reader2<const N: usize>(
&mut self,
reader: &mut dyn Read,
) -> Result<Option<i32>, io::Error>;
#[inline]
fn execute_input_reader_output(&mut self, reader: &mut dyn Read) -> Result<Output, io::Error> {
self.execute_input_reader_output2::<DEFAULT_READER_BUFFER_SIZE>(reader)
}
fn execute_input_reader_output2<const N: usize>(
&mut self,
reader: &mut dyn Read,
) -> Result<Output, io::Error>;
fn execute_multiple(&mut self, others: &mut [&mut Command]) -> Result<Option<i32>, io::Error>;
fn execute_multiple_output(&mut self, others: &mut [&mut Command])
-> Result<Output, io::Error>;
fn execute_multiple_input<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
others: &mut [&mut Command],
) -> Result<Option<i32>, io::Error>;
fn execute_multiple_input_output<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
others: &mut [&mut Command],
) -> Result<Output, io::Error>;
#[inline]
fn execute_multiple_input_reader(
&mut self,
reader: &mut dyn Read,
others: &mut [&mut Command],
) -> Result<Option<i32>, io::Error> {
self.execute_multiple_input_reader2::<DEFAULT_READER_BUFFER_SIZE>(reader, others)
}
fn execute_multiple_input_reader2<const N: usize>(
&mut self,
reader: &mut dyn Read,
others: &mut [&mut Command],
) -> Result<Option<i32>, io::Error>;
#[inline]
fn execute_multiple_input_reader_output(
&mut self,
reader: &mut dyn Read,
others: &mut [&mut Command],
) -> Result<Output, io::Error> {
self.execute_multiple_input_reader_output2::<DEFAULT_READER_BUFFER_SIZE>(reader, others)
}
fn execute_multiple_input_reader_output2<const N: usize>(
&mut self,
reader: &mut dyn Read,
others: &mut [&mut Command],
) -> Result<Output, io::Error>;
}
impl Execute for Command {
#[inline]
fn execute(&mut self) -> Result<Option<i32>, io::Error> {
self.stdout(Stdio::null());
self.stderr(Stdio::null());
Ok(self.status()?.code())
}
#[inline]
fn execute_output(&mut self) -> Result<Output, io::Error> {
self.spawn()?.wait_with_output()
}
#[inline]
fn execute_input<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
) -> Result<Option<i32>, io::Error> {
self.stdin(Stdio::piped());
self.stdout(Stdio::null());
self.stderr(Stdio::null());
let mut child = self.spawn()?;
let stdin = take_child_stdin(&mut child)?;
wait_with_stdin_writer(
move || child.wait().map(|status| status.code()),
|| write_stdin(stdin, data),
)
}
#[inline]
fn execute_input_output<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
) -> Result<Output, io::Error> {
self.stdin(Stdio::piped());
let mut child = self.spawn()?;
let stdin = take_child_stdin(&mut child)?;
wait_with_stdin_writer(move || child.wait_with_output(), || write_stdin(stdin, data))
}
#[inline]
fn execute_input_reader2<const N: usize>(
&mut self,
reader: &mut dyn Read,
) -> Result<Option<i32>, io::Error> {
self.stdin(Stdio::piped());
self.stdout(Stdio::null());
self.stderr(Stdio::null());
let mut child = self.spawn()?;
let stdin = take_child_stdin(&mut child)?;
wait_with_stdin_writer(
move || child.wait().map(|status| status.code()),
|| copy_reader_to_stdin::<N>(stdin, reader),
)
}
#[inline]
fn execute_input_reader_output2<const N: usize>(
&mut self,
reader: &mut dyn Read,
) -> Result<Output, io::Error> {
self.stdin(Stdio::piped());
let mut child = self.spawn()?;
let stdin = take_child_stdin(&mut child)?;
wait_with_stdin_writer(
move || child.wait_with_output(),
|| copy_reader_to_stdin::<N>(stdin, reader),
)
}
fn execute_multiple(&mut self, others: &mut [&mut Command]) -> Result<Option<i32>, io::Error> {
if others.is_empty() {
return self.execute();
}
self.stdout(Stdio::piped());
self.stderr(Stdio::null());
let others_length_dec = others.len() - 1;
let last_other = &mut others[others_length_dec];
last_other.stdout(Stdio::null());
last_other.stderr(Stdio::null());
let (upstream_children, mut last_child) = spawn_pipeline(self, others)?;
let status_result = last_child.wait().map(|status| status.code());
finish_pipeline_result(status_result, upstream_children)
}
fn execute_multiple_output(
&mut self,
others: &mut [&mut Command],
) -> Result<Output, io::Error> {
if others.is_empty() {
return self.execute_output();
}
self.stdout(Stdio::piped());
self.stderr(Stdio::null());
let (upstream_children, last_child) = spawn_pipeline(self, others)?;
let output_result = last_child.wait_with_output();
finish_pipeline_result(output_result, upstream_children)
}
fn execute_multiple_input<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
others: &mut [&mut Command],
) -> Result<Option<i32>, io::Error> {
if others.is_empty() {
return self.execute_input(data);
}
self.stdin(Stdio::piped());
self.stdout(Stdio::piped());
self.stderr(Stdio::null());
let others_length_dec = others.len() - 1;
let last_other = &mut others[others_length_dec];
last_other.stdout(Stdio::null());
last_other.stderr(Stdio::null());
let (mut upstream_children, mut last_child) = spawn_pipeline(self, others)?;
let stdin = take_child_stdin(&mut upstream_children[0])?;
let status_result = wait_with_stdin_writer(
move || last_child.wait().map(|status| status.code()),
|| write_stdin(stdin, data),
);
finish_pipeline_result(status_result, upstream_children)
}
fn execute_multiple_input_output<D: ?Sized + AsRef<[u8]>>(
&mut self,
data: &D,
others: &mut [&mut Command],
) -> Result<Output, io::Error> {
if others.is_empty() {
return self.execute_input_output(data);
}
self.stdin(Stdio::piped());
self.stdout(Stdio::piped());
self.stderr(Stdio::null());
let (mut upstream_children, last_child) = spawn_pipeline(self, others)?;
let stdin = take_child_stdin(&mut upstream_children[0])?;
let output_result = wait_with_stdin_writer(
move || last_child.wait_with_output(),
|| write_stdin(stdin, data),
);
finish_pipeline_result(output_result, upstream_children)
}
fn execute_multiple_input_reader2<const N: usize>(
&mut self,
reader: &mut dyn Read,
others: &mut [&mut Command],
) -> Result<Option<i32>, io::Error> {
if others.is_empty() {
return self.execute_input_reader2::<N>(reader);
}
self.stdin(Stdio::piped());
self.stdout(Stdio::piped());
self.stderr(Stdio::null());
let others_length_dec = others.len() - 1;
let last_other = &mut others[others_length_dec];
last_other.stdout(Stdio::null());
last_other.stderr(Stdio::null());
let (mut upstream_children, mut last_child) = spawn_pipeline(self, others)?;
let stdin = take_child_stdin(&mut upstream_children[0])?;
let status_result = wait_with_stdin_writer(
move || last_child.wait().map(|status| status.code()),
|| copy_reader_to_stdin::<N>(stdin, reader),
);
finish_pipeline_result(status_result, upstream_children)
}
fn execute_multiple_input_reader_output2<const N: usize>(
&mut self,
reader: &mut dyn Read,
others: &mut [&mut Command],
) -> Result<Output, io::Error> {
if others.is_empty() {
return self.execute_input_reader_output2::<N>(reader);
}
self.stdin(Stdio::piped());
self.stdout(Stdio::piped());
self.stderr(Stdio::null());
let (mut upstream_children, last_child) = spawn_pipeline(self, others)?;
let stdin = take_child_stdin(&mut upstream_children[0])?;
let output_result = wait_with_stdin_writer(
move || last_child.wait_with_output(),
|| copy_reader_to_stdin::<N>(stdin, reader),
);
finish_pipeline_result(output_result, upstream_children)
}
}
#[cfg(unix)]
#[inline]
pub fn shell<S: AsRef<OsStr>>(cmd: S) -> Command {
use std::sync::LazyLock;
static SHELL: LazyLock<OsString> = LazyLock::new(|| {
env::var_os("SHELL").unwrap_or_else(|| OsString::from(String::from("sh")))
});
let mut command = Command::new(&*SHELL);
command.arg("-c");
command.arg(cmd);
command
}
#[cfg(windows)]
#[inline]
pub fn shell<S: AsRef<OsStr>>(cmd: S) -> Command {
let mut command = Command::new("cmd.exe");
command.arg("/c");
command.arg(cmd);
command
}
#[inline]
pub fn command<S: AsRef<str>>(cmd: S) -> Command {
let tokens = command_tokens(cmd);
if tokens.is_empty() {
Command::new("")
} else {
let mut command = Command::new(&tokens[0]);
command.args(&tokens[1..]);
command
}
}