use env_name_str::EnvNameString;
use shared_child::SharedChild;
use shared_thread::SharedThread;
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::mem;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Output, Stdio};
use std::sync::{Arc, MutexGuard, OnceLock, RwLock};
#[cfg(not(windows))]
use std::os::unix::prelude::*;
#[cfg(windows)]
use std::os::windows::prelude::*;
#[cfg(not(windows))]
use std::os::fd::IntoRawFd as IntoRawFdOrHandle;
#[cfg(windows)]
use std::os::windows::io::IntoRawHandle as IntoRawFdOrHandle;
mod env_name_str;
#[cfg(unix)]
pub mod unix;
use ExpressionInner::*;
use IoExpressionInner::*;
pub fn cmd<T, U>(program: T, args: U) -> Expression
where
T: IntoExecutablePath,
U: IntoIterator,
U::Item: Into<OsString>,
{
let mut argv_vec = Vec::new();
argv_vec.push(program.to_executable());
argv_vec.extend(args.into_iter().map(Into::<OsString>::into));
Expression::new(Cmd(argv_vec))
}
#[macro_export]
macro_rules! cmd {
( $program:expr $(, $arg:expr )* $(,)? ) => {
{
use std::ffi::OsString;
let args: std::vec::Vec<OsString> = std::vec![$( Into::<OsString>::into($arg) ),*];
$crate::cmd($program, args)
}
};
}
#[derive(Clone)]
#[must_use]
pub struct Expression(Arc<ExpressionInner>);
impl Expression {
pub fn run(&self) -> io::Result<Output> {
self.start()?.into_output()
}
pub fn read(&self) -> io::Result<String> {
let mut reader = self.reader()?;
let mut output = String::new();
reader.read_to_string(&mut output)?;
while output.ends_with('\n') || output.ends_with('\r') {
output.truncate(output.len() - 1);
}
Ok(output)
}
pub fn start(&self) -> io::Result<Handle> {
let stdout_capture = OutputCaptureContext::new();
let stderr_capture = OutputCaptureContext::new();
let context = IoContext::new(&stdout_capture, &stderr_capture);
Ok(Handle {
inner: self.0.start(context)?,
result: OnceLock::new(),
readers: RwLock::new((
stdout_capture.maybe_read_thread(),
stderr_capture.maybe_read_thread(),
)),
})
}
pub fn reader(&self) -> io::Result<ReaderHandle> {
let stdout_capture = OutputCaptureContext::new();
let stderr_capture = OutputCaptureContext::new();
let context = IoContext::new(&stdout_capture, &stderr_capture);
let handle = Handle {
inner: self.stdout_capture().0.start(context)?,
result: OnceLock::new(),
readers: RwLock::new((None, stderr_capture.maybe_read_thread())),
};
Ok(ReaderHandle {
handle,
reader: stdout_capture.pair.into_inner().expect("pipe opened").0,
})
}
pub fn pipe<T: Into<Expression>>(&self, right: T) -> Expression {
Self::new(Pipe(self.clone(), right.into()))
}
pub fn stdin_bytes<T: Into<Vec<u8>>>(&self, bytes: T) -> Expression {
Self::new(Io(StdinBytes(Arc::new(bytes.into())), self.clone()))
}
pub fn stdin_path<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(StdinPath(path.into()), self.clone()))
}
pub fn stdin_file<T: IntoRawFdOrHandle>(&self, file: T) -> Expression {
Self::new(Io(StdinFile(owned_from_raw(file)), self.clone()))
}
pub fn stdin_null(&self) -> Expression {
Self::new(Io(StdinNull, self.clone()))
}
pub fn stdout_path<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(StdoutPath(path.into()), self.clone()))
}
pub fn stdout_file<T: IntoRawFdOrHandle>(&self, file: T) -> Expression {
Self::new(Io(StdoutFile(owned_from_raw(file)), self.clone()))
}
pub fn stdout_null(&self) -> Expression {
Self::new(Io(StdoutNull, self.clone()))
}
pub fn stdout_capture(&self) -> Expression {
Self::new(Io(StdoutCapture, self.clone()))
}
pub fn stdout_to_stderr(&self) -> Expression {
Self::new(Io(StdoutToStderr, self.clone()))
}
pub fn stderr_path<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(StderrPath(path.into()), self.clone()))
}
pub fn stderr_file<T: IntoRawFdOrHandle>(&self, file: T) -> Expression {
Self::new(Io(StderrFile(owned_from_raw(file)), self.clone()))
}
pub fn stderr_null(&self) -> Expression {
Self::new(Io(StderrNull, self.clone()))
}
pub fn stderr_capture(&self) -> Expression {
Self::new(Io(StderrCapture, self.clone()))
}
pub fn stderr_to_stdout(&self) -> Expression {
Self::new(Io(StderrToStdout, self.clone()))
}
pub fn stdout_stderr_swap(&self) -> Expression {
Self::new(Io(StdoutStderrSwap, self.clone()))
}
pub fn dir<T: Into<PathBuf>>(&self, path: T) -> Expression {
Self::new(Io(Dir(path.into()), self.clone()))
}
pub fn env<T, U>(&self, name: T, val: U) -> Expression
where
T: Into<OsString>,
U: Into<OsString>,
{
Self::new(Io(Env(EnvNameString::from(name), val.into()), self.clone()))
}
pub fn env_remove<T>(&self, name: T) -> Expression
where
T: Into<OsString>,
{
Self::new(Io(EnvRemove(EnvNameString::from(name)), self.clone()))
}
pub fn full_env<T, U, V>(&self, name_vals: T) -> Expression
where
T: IntoIterator<Item = (U, V)>,
U: Into<OsString>,
V: Into<OsString>,
{
let env_map = name_vals
.into_iter()
.map(|(k, v)| (EnvNameString::from(k), v.into()))
.collect();
Self::new(Io(FullEnv(env_map), self.clone()))
}
pub fn unchecked(&self) -> Expression {
Self::new(Io(Unchecked, self.clone()))
}
pub fn before_spawn<F>(&self, hook: F) -> Expression
where
F: Fn(&mut Command) -> io::Result<()> + Send + Sync + 'static,
{
Self::new(Io(BeforeSpawn(BeforeSpawnHook::new(hook)), self.clone()))
}
fn new(inner: ExpressionInner) -> Expression {
Expression(Arc::new(inner))
}
}
impl fmt::Debug for Expression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> From<&'a Expression> for Expression {
fn from(expr: &Expression) -> Expression {
expr.clone()
}
}
#[derive(Debug)]
pub struct Handle {
inner: HandleInner,
result: OnceLock<(ExpressionStatus, Output)>,
readers: RwLock<(Option<ReaderThread>, Option<ReaderThread>)>,
}
impl Handle {
pub fn wait(&self) -> io::Result<&Output> {
wait_on_handle_and_output(self, WaitMode::Blocking)?;
self.try_wait().transpose().expect("already exited")
}
#[cfg(feature = "timeout")]
pub fn wait_timeout(&self, timeout: std::time::Duration) -> io::Result<Option<&Output>> {
let deadline = std::time::Instant::now() + timeout;
self.wait_deadline(deadline)
}
#[cfg(feature = "timeout")]
pub fn wait_deadline(&self, deadline: std::time::Instant) -> io::Result<Option<&Output>> {
wait_on_handle_and_output(self, WaitMode::Deadline(deadline))?;
self.try_wait()
}
pub fn try_wait(&self) -> io::Result<Option<&Output>> {
let Some((expression_status, output)) =
wait_on_handle_and_output(self, WaitMode::NonBlocking)?
else {
return Ok(None);
};
if expression_status.is_checked_error() {
return Err(io::Error::new(
io::ErrorKind::Other,
expression_status.message(),
));
}
Ok(Some(output))
}
pub fn into_output(self) -> io::Result<Output> {
self.wait()?;
let (_, output) = self.result.into_inner().expect("result missing");
Ok(output)
}
pub fn kill(&self) -> io::Result<()> {
self.inner.kill()
}
pub fn pids(&self) -> Vec<u32> {
self.inner.pids()
}
}
fn wait_on_handle_and_output(
handle: &Handle,
mode: WaitMode,
) -> io::Result<Option<&(ExpressionStatus, Output)>> {
let Some(status) = handle.inner.wait(mode)? else {
return Ok(None);
};
let shared_lock = handle.readers.read().unwrap();
let (maybe_stdout_reader, maybe_stderr_reader) = &*shared_lock;
if let Some(stdout_reader) = maybe_stdout_reader {
if mode.maybe_join_io_thread(stdout_reader)?.is_none() {
return Ok(None);
}
}
if let Some(stderr_reader) = maybe_stderr_reader {
if mode.maybe_join_io_thread(stderr_reader)?.is_none() {
return Ok(None);
}
}
drop(shared_lock);
let mut unique_lock = handle.readers.write().unwrap();
let (maybe_stdout_reader, maybe_stderr_reader) = &mut *unique_lock;
let stdout: Vec<u8> = maybe_stdout_reader
.take()
.map(SharedThread::into_output)
.transpose()
.expect("IO errors already short-circuited")
.unwrap_or_default();
let stderr: Vec<u8> = maybe_stderr_reader
.take()
.map(SharedThread::into_output)
.transpose()
.expect("IO errors already short-circuited")
.unwrap_or_default();
let output = Output {
status: status.status,
stdout,
stderr,
};
let _ = handle.result.set((status, output)); Ok(handle.result.get())
}
#[derive(Debug)]
enum ExpressionInner {
Cmd(Vec<OsString>),
Pipe(Expression, Expression),
Io(IoExpressionInner, Expression),
}
impl ExpressionInner {
fn start(&self, context: IoContext) -> io::Result<HandleInner> {
Ok(match self {
Cmd(argv) => HandleInner::Child(ChildHandle::start(argv, context)?),
Pipe(left, right) => {
HandleInner::Pipe(Box::new(PipeHandle::start(left, right, context)?))
}
Io(io_inner, expr) => start_io(io_inner, expr, context)?,
})
}
}
#[derive(Debug)]
enum HandleInner {
Child(ChildHandle),
Pipe(Box<PipeHandle>),
StdinBytes(Box<StdinBytesHandle>),
Unchecked(Box<HandleInner>),
}
impl HandleInner {
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
match self {
HandleInner::Child(child_handle) => child_handle.wait(mode),
HandleInner::Pipe(pipe_handle) => pipe_handle.wait(mode),
HandleInner::StdinBytes(stdin_bytes_handle) => stdin_bytes_handle.wait(mode),
HandleInner::Unchecked(inner_handle) => {
Ok(inner_handle.wait(mode)?.map(|mut status| {
status.checked = false;
status
}))
}
}
}
fn kill(&self) -> io::Result<()> {
match self {
HandleInner::Child(child_handle) => child_handle.kill(),
HandleInner::Pipe(pipe_handle) => pipe_handle.kill(),
HandleInner::StdinBytes(stdin_bytes_handle) => stdin_bytes_handle.kill(),
HandleInner::Unchecked(inner_handle) => inner_handle.kill(),
}
}
fn pids(&self) -> Vec<u32> {
match self {
HandleInner::Child(child_handle) => vec![child_handle.child().id()],
HandleInner::Pipe(pipe_handle) => pipe_handle.pids(),
HandleInner::StdinBytes(stdin_bytes_handle) => stdin_bytes_handle.inner_handle.pids(),
HandleInner::Unchecked(inner_handle) => inner_handle.pids(),
}
}
}
#[cfg(not(windows))]
static LEAKED_CHILDREN: RwLock<Vec<std::process::Child>> = RwLock::new(Vec::new());
#[cfg(not(windows))]
fn cleanup_leaked_children() {
if !LEAKED_CHILDREN.read().unwrap().is_empty() {
LEAKED_CHILDREN.write().unwrap().retain_mut(|child| {
match child.try_wait() {
Ok(Some(_)) => false, Ok(None) => true, Err(e) => {
if cfg!(test) {
panic!("cleanup_leaked_children failed: {e}");
}
false }
}
});
}
}
#[derive(Debug)]
struct ChildHandle {
child: Option<shared_child::SharedChild>, command_string: String,
}
impl ChildHandle {
fn start(argv: &[OsString], context: IoContext) -> io::Result<ChildHandle> {
#[cfg(not(windows))]
cleanup_leaked_children();
let exe = canonicalize_exe_path_for_dir(&argv[0], &context)?;
let mut command = Command::new(exe);
command.args(&argv[1..]);
if !matches!(context.stdin, IoValue::ParentStdin) {
command.stdin(context.stdin.into_stdio()?);
}
if !matches!(context.stdout, IoValue::ParentStdout) {
command.stdout(context.stdout.into_stdio()?);
}
if !matches!(context.stderr, IoValue::ParentStderr) {
command.stderr(context.stderr.into_stdio()?);
}
if let Some(dir) = context.dir {
command.current_dir(dir);
}
command.env_clear();
for (name, val) in context.env {
command.env(name, val);
}
for hook in context.before_spawn_hooks.iter() {
hook.call(&mut command)?;
}
let spawn_guard = pipe_and_spawn_lock_guard();
let shared_child = SharedChild::spawn(&mut command)?;
drop(spawn_guard);
let command_string = format!("{:?}", argv);
Ok(ChildHandle {
child: Some(shared_child),
command_string,
})
}
fn child(&self) -> &SharedChild {
self.child
.as_ref()
.expect("ChildHandle should not yet have been dropped")
}
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let maybe_status = mode.maybe_wait_on_child(self.child())?;
if let Some(status) = maybe_status {
Ok(Some(ExpressionStatus {
status,
checked: true,
command: self.command_string.clone(),
}))
} else {
Ok(None)
}
}
fn kill(&self) -> io::Result<()> {
self.child().kill()
}
}
#[cfg(not(windows))]
impl Drop for ChildHandle {
fn drop(&mut self) {
let child = self.child.take().expect("only drop should take the child");
match child.try_wait() {
Ok(Some(_)) => (),
Err(e) => {
if cfg!(test) {
panic!("ChildHandle cleanup failed: {e}");
}
}
Ok(None) => LEAKED_CHILDREN.write().unwrap().push(child.into_inner()),
}
}
}
#[derive(Debug)]
struct PipeHandle {
left_handle: HandleInner,
right_handle: HandleInner,
}
impl PipeHandle {
fn start(left: &Expression, right: &Expression, context: IoContext) -> io::Result<PipeHandle> {
let (reader, writer) = open_pipe_protected()?;
let mut left_context = context.try_clone()?;
left_context.stdout = IoValue::Handle(writer.into());
let mut right_context = context;
right_context.stdin = IoValue::Handle(reader.into());
let left_handle = left.0.start(left_context)?;
let right_handle = right.0.start(right_context)?;
Ok(PipeHandle {
left_handle,
right_handle,
})
}
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let left_wait_result = self.left_handle.wait(mode);
let right_wait_result = self.right_handle.wait(mode);
let left_status = left_wait_result?;
let right_status = right_wait_result?;
Ok(pipe_status_precedence(left_status, right_status))
}
fn kill(&self) -> io::Result<()> {
let left_kill_result = self.left_handle.kill();
let right_kill_result = self.right_handle.kill();
left_kill_result.and(right_kill_result)
}
fn pids(&self) -> Vec<u32> {
let mut pids = self.left_handle.pids();
pids.extend_from_slice(&self.right_handle.pids());
pids
}
}
fn pipe_status_precedence(
left_maybe_status: Option<ExpressionStatus>,
right_maybe_status: Option<ExpressionStatus>,
) -> Option<ExpressionStatus> {
let (left_status, right_status) = match (left_maybe_status, right_maybe_status) {
(Some(left), Some(right)) => (left, right),
_ => return None,
};
Some(if right_status.is_checked_error() {
right_status
} else if left_status.is_checked_error() {
left_status
} else if !right_status.status.success() {
right_status
} else {
left_status
})
}
fn start_io(
io_inner: &IoExpressionInner,
expr_inner: &Expression,
mut context: IoContext,
) -> io::Result<HandleInner> {
match io_inner {
StdinBytes(v) => {
return Ok(HandleInner::StdinBytes(Box::new(StdinBytesHandle::start(
expr_inner,
context,
Arc::clone(v),
)?)));
}
StdinPath(p) => {
context.stdin = IoValue::Handle(File::open(p)?.into());
}
StdinFile(f) => {
context.stdin = IoValue::Handle(f.try_clone()?);
}
StdinNull => {
context.stdin = IoValue::Null;
}
StdoutPath(p) => {
context.stdout = IoValue::Handle(File::create(p)?.into());
}
StdoutFile(f) => {
context.stdout = IoValue::Handle(f.try_clone()?);
}
StdoutNull => {
context.stdout = IoValue::Null;
}
StdoutCapture => {
context.stdout = IoValue::Handle(context.stdout_capture.write_pipe()?.into());
}
StdoutToStderr => {
context.stdout = context.stderr.try_clone()?;
}
StderrPath(p) => {
context.stderr = IoValue::Handle(File::create(p)?.into());
}
StderrFile(f) => {
context.stderr = IoValue::Handle(f.try_clone()?);
}
StderrNull => {
context.stderr = IoValue::Null;
}
StderrCapture => {
context.stderr = IoValue::Handle(context.stderr_capture.write_pipe()?.into());
}
StderrToStdout => {
context.stderr = context.stdout.try_clone()?;
}
StdoutStderrSwap => {
mem::swap(&mut context.stdout, &mut context.stderr);
}
Dir(p) => {
context.dir = Some(p.clone());
}
Env(name, val) => {
context.env.insert(name.clone(), val.clone());
}
EnvRemove(name) => {
context.env.remove(name);
}
FullEnv(map) => {
context.env = map.clone();
}
Unchecked => {
let inner_handle = expr_inner.0.start(context)?;
return Ok(HandleInner::Unchecked(Box::new(inner_handle)));
}
BeforeSpawn(hook) => {
context.before_spawn_hooks.push(hook.clone());
}
}
expr_inner.0.start(context)
}
#[derive(Debug)]
struct StdinBytesHandle {
inner_handle: HandleInner,
writer_thread: SharedThread<io::Result<()>>,
}
impl StdinBytesHandle {
fn start(
expression: &Expression,
mut context: IoContext,
input: Arc<Vec<u8>>,
) -> io::Result<StdinBytesHandle> {
let (reader, mut writer) = open_pipe_protected()?;
context.stdin = IoValue::Handle(reader.into());
let inner_handle = expression.0.start(context)?;
let writer_thread = SharedThread::spawn(move || {
match writer.write_all(&input) {
Err(e) if e.kind() != io::ErrorKind::BrokenPipe => Err(e),
_ => Ok(()),
}
});
Ok(StdinBytesHandle {
inner_handle,
writer_thread,
})
}
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let maybe_status = self.inner_handle.wait(mode)?;
let io_finished = mode.maybe_join_io_thread(&self.writer_thread)?.is_some();
if !io_finished {
return Ok(None);
}
Ok(maybe_status)
}
fn kill(&self) -> io::Result<()> {
self.inner_handle.kill()
}
}
#[derive(Debug)]
enum IoExpressionInner {
StdinBytes(Arc<Vec<u8>>),
StdinPath(PathBuf),
StdinFile(FdOrHandle),
StdinNull,
StdoutPath(PathBuf),
StdoutFile(FdOrHandle),
StdoutNull,
StdoutCapture,
StdoutToStderr,
StderrPath(PathBuf),
StderrFile(FdOrHandle),
StderrNull,
StderrCapture,
StderrToStdout,
StdoutStderrSwap,
Dir(PathBuf),
Env(EnvNameString, OsString),
EnvRemove(EnvNameString),
FullEnv(HashMap<EnvNameString, OsString>),
Unchecked,
BeforeSpawn(BeforeSpawnHook),
}
type HookFn = Arc<dyn Fn(&mut Command) -> io::Result<()> + Send + Sync>;
#[derive(Clone)]
struct BeforeSpawnHook {
inner: HookFn,
}
impl BeforeSpawnHook {
fn new<F>(hook: F) -> Self
where
F: Fn(&mut Command) -> io::Result<()> + Send + Sync + 'static,
{
Self {
inner: Arc::new(hook),
}
}
fn call(&self, command: &mut Command) -> io::Result<()> {
(self.inner)(command)
}
}
impl fmt::Debug for BeforeSpawnHook {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<closure>")
}
}
#[derive(Debug)]
struct IoContext<'a> {
stdin: IoValue,
stdout: IoValue,
stderr: IoValue,
stdout_capture: &'a OutputCaptureContext,
stderr_capture: &'a OutputCaptureContext,
dir: Option<PathBuf>,
env: HashMap<EnvNameString, OsString>,
before_spawn_hooks: Vec<BeforeSpawnHook>,
}
impl<'a> IoContext<'a> {
fn new(
stdout_capture: &'a OutputCaptureContext,
stderr_capture: &'a OutputCaptureContext,
) -> Self {
Self {
stdin: IoValue::ParentStdin,
stdout: IoValue::ParentStdout,
stderr: IoValue::ParentStderr,
stdout_capture,
stderr_capture,
dir: None,
env: std::env::vars_os().map(|(k, v)| (k.into(), v)).collect(),
before_spawn_hooks: Vec::new(),
}
}
fn try_clone(&self) -> io::Result<IoContext<'a>> {
Ok(IoContext {
stdin: self.stdin.try_clone()?,
stdout: self.stdout.try_clone()?,
stderr: self.stderr.try_clone()?,
stdout_capture: self.stdout_capture,
stderr_capture: self.stderr_capture,
dir: self.dir.clone(),
env: self.env.clone(),
before_spawn_hooks: self.before_spawn_hooks.clone(),
})
}
}
#[derive(Debug)]
enum IoValue {
ParentStdin,
ParentStdout,
ParentStderr,
Null,
Handle(FdOrHandle),
}
impl IoValue {
fn try_clone(&self) -> io::Result<IoValue> {
Ok(match self {
IoValue::ParentStdin => IoValue::ParentStdin,
IoValue::ParentStdout => IoValue::ParentStdout,
IoValue::ParentStderr => IoValue::ParentStderr,
IoValue::Null => IoValue::Null,
IoValue::Handle(f) => IoValue::Handle(f.try_clone()?),
})
}
fn into_stdio(self) -> io::Result<Stdio> {
Ok(match self {
IoValue::ParentStdin => os_pipe::dup_stdin()?.into(),
IoValue::ParentStdout => os_pipe::dup_stdout()?.into(),
IoValue::ParentStderr => os_pipe::dup_stderr()?.into(),
IoValue::Null => Stdio::null(),
IoValue::Handle(f) => f.into(),
})
}
}
#[derive(Clone, Debug)]
struct ExpressionStatus {
status: ExitStatus,
checked: bool,
command: String,
}
impl ExpressionStatus {
fn is_checked_error(&self) -> bool {
self.checked && !self.status.success()
}
fn message(&self) -> String {
format!(
"command {} exited with code {}",
self.command,
self.exit_code_string()
)
}
#[cfg(not(windows))]
fn exit_code_string(&self) -> String {
if self.status.code().is_none() {
return format!("<signal {}>", self.status.signal().unwrap());
}
self.status.code().unwrap().to_string()
}
#[cfg(windows)]
fn exit_code_string(&self) -> String {
self.status.code().unwrap().to_string()
}
}
fn canonicalize_exe_path_for_dir(exe_name: &OsStr, context: &IoContext) -> io::Result<OsString> {
let has_separator = exe_name
.to_string_lossy()
.chars()
.any(std::path::is_separator);
let is_relative = Path::new(exe_name).is_relative();
if context.dir.is_some() && has_separator && is_relative {
Path::new(exe_name).canonicalize().map(Into::into)
} else {
Ok(exe_name.to_owned())
}
}
fn dotify_relative_exe_path(path: &Path) -> PathBuf {
Path::new(".").join(path)
}
pub trait IntoExecutablePath {
fn to_executable(self) -> OsString;
}
impl<'a> IntoExecutablePath for &'a Path {
fn to_executable(self) -> OsString {
dotify_relative_exe_path(self).into()
}
}
impl IntoExecutablePath for PathBuf {
fn to_executable(self) -> OsString {
dotify_relative_exe_path(&self).into()
}
}
impl<'a> IntoExecutablePath for &'a PathBuf {
fn to_executable(self) -> OsString {
dotify_relative_exe_path(self).into()
}
}
impl<'a> IntoExecutablePath for &'a str {
fn to_executable(self) -> OsString {
self.into()
}
}
impl IntoExecutablePath for String {
fn to_executable(self) -> OsString {
self.into()
}
}
impl<'a> IntoExecutablePath for &'a String {
fn to_executable(self) -> OsString {
self.into()
}
}
impl<'a> IntoExecutablePath for &'a OsStr {
fn to_executable(self) -> OsString {
self.into()
}
}
impl IntoExecutablePath for OsString {
fn to_executable(self) -> OsString {
self
}
}
impl<'a> IntoExecutablePath for &'a OsString {
fn to_executable(self) -> OsString {
self.into()
}
}
fn clone_io_error(error: &io::Error) -> io::Error {
if let Some(code) = error.raw_os_error() {
io::Error::from_raw_os_error(code)
} else {
io::Error::new(error.kind(), error.to_string())
}
}
#[derive(Clone, Copy, Debug)]
enum WaitMode {
Blocking,
NonBlocking,
#[cfg(feature = "timeout")]
Deadline(std::time::Instant),
}
impl WaitMode {
fn maybe_wait_on_child(self, child: &SharedChild) -> io::Result<Option<ExitStatus>> {
match self {
WaitMode::Blocking => child.wait().map(Some),
WaitMode::NonBlocking => child.try_wait(),
#[cfg(feature = "timeout")]
WaitMode::Deadline(deadline) => child.wait_deadline(deadline),
}
}
fn maybe_join_io_thread<T>(
self,
io_thread: &SharedThread<io::Result<T>>,
) -> io::Result<Option<&T>> {
match self {
WaitMode::Blocking => match io_thread.join() {
Ok(val) => Ok(Some(val)),
Err(e) => Err(clone_io_error(e)),
},
WaitMode::NonBlocking => match io_thread.try_join() {
Some(Ok(val)) => Ok(Some(val)),
Some(Err(e)) => Err(clone_io_error(e)),
None => Ok(None),
},
#[cfg(feature = "timeout")]
WaitMode::Deadline(deadline) => match io_thread.join_deadline(deadline) {
Some(Ok(val)) => Ok(Some(val)),
Some(Err(e)) => Err(clone_io_error(e)),
None => Ok(None),
},
}
}
}
type ReaderThread = SharedThread<io::Result<Vec<u8>>>;
#[derive(Debug)]
struct OutputCaptureContext {
pair: OnceLock<(os_pipe::PipeReader, os_pipe::PipeWriter)>,
}
impl OutputCaptureContext {
fn new() -> Self {
Self {
pair: OnceLock::new(),
}
}
fn write_pipe(&self) -> io::Result<os_pipe::PipeWriter> {
match self.pair.get() {
Some((_, writer)) => writer.try_clone(),
None => {
let (reader, writer) = open_pipe_protected()?;
let clone = writer.try_clone();
self.pair.set((reader, writer)).unwrap();
clone
}
}
}
fn maybe_read_thread(self) -> Option<ReaderThread> {
if let Some((mut reader, _)) = self.pair.into_inner() {
Some(SharedThread::spawn(move || {
let mut output = Vec::new();
reader.read_to_end(&mut output)?;
Ok(output)
}))
} else {
None
}
}
}
#[derive(Debug)]
pub struct ReaderHandle {
handle: Handle,
reader: os_pipe::PipeReader,
}
impl ReaderHandle {
pub fn try_wait(&self) -> io::Result<Option<&Output>> {
self.handle.try_wait()
}
pub fn kill(&self) -> io::Result<()> {
self.handle.kill()
}
pub fn pids(&self) -> Vec<u32> {
self.handle.pids()
}
}
impl<'a> Read for &'a ReaderHandle {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = (&self.reader).read(buf)?;
if n == 0 && !buf.is_empty() {
self.handle.wait()?;
}
Ok(n)
}
}
impl Read for ReaderHandle {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(&*self).read(buf)
}
}
#[cfg(not(windows))]
type FdOrHandle = OwnedFd;
#[cfg(windows)]
type FdOrHandle = OwnedHandle;
#[cfg(not(windows))]
fn owned_from_raw(raw: impl IntoRawFd) -> OwnedFd {
unsafe { OwnedFd::from_raw_fd(raw.into_raw_fd()) }
}
#[cfg(windows)]
fn owned_from_raw(raw: impl IntoRawHandle) -> OwnedHandle {
unsafe { OwnedHandle::from_raw_handle(raw.into_raw_handle()) }
}
fn open_pipe_protected() -> io::Result<(os_pipe::PipeReader, os_pipe::PipeWriter)> {
let _guard = pipe_and_spawn_lock_guard(); os_pipe::pipe()
}
#[allow(unreachable_code)]
fn pipe_and_spawn_lock_guard() -> Option<MutexGuard<'static, ()>> {
#[cfg(any(target_os = "aix", target_vendor = "apple", target_os = "haiku"))]
{
use std::sync::Mutex;
static PIPE_OPENING_LOCK: Mutex<()> = Mutex::new(());
return Some(PIPE_OPENING_LOCK.lock().unwrap());
}
None
}
#[cfg(test)]
mod test;