use once_cell::sync::OnceCell;
use shared_child::SharedChild;
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, Mutex};
use std::thread::JoinHandle;
#[cfg(not(windows))]
use std::os::unix::prelude::*;
#[cfg(windows)]
use std::os::windows::prelude::*;
#[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: &[OsString] = &[$( 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: OnceCell::new(),
readers: Mutex::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: OnceCell::new(),
readers: Mutex::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()))
}
#[cfg(not(windows))]
pub fn stdin_file<T: IntoRawFd>(&self, file: T) -> Expression {
Self::new(Io(StdinFile(into_file(file)), self.clone()))
}
#[cfg(windows)]
pub fn stdin_file<T: IntoRawHandle>(&self, file: T) -> Expression {
Self::new(Io(StdinFile(into_file(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()))
}
#[cfg(not(windows))]
pub fn stdout_file<T: IntoRawFd>(&self, file: T) -> Expression {
Self::new(Io(StdoutFile(into_file(file)), self.clone()))
}
#[cfg(windows)]
pub fn stdout_file<T: IntoRawHandle>(&self, file: T) -> Expression {
Self::new(Io(StdoutFile(into_file(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()))
}
#[cfg(not(windows))]
pub fn stderr_file<T: IntoRawFd>(&self, file: T) -> Expression {
Self::new(Io(StderrFile(into_file(file)), self.clone()))
}
#[cfg(windows)]
pub fn stderr_file<T: IntoRawHandle>(&self, file: T) -> Expression {
Self::new(Io(StderrFile(into_file(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(canonicalize_env_var_name(name.into()), val.into()),
self.clone(),
))
}
pub fn env_remove<T>(&self, name: T) -> Expression
where
T: Into<OsString>,
{
Self::new(Io(
EnvRemove(canonicalize_env_var_name(name.into())),
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)| (canonicalize_env_var_name(k.into()), 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: OnceCell<(ExpressionStatus, Output)>,
readers: Mutex<(Option<ReaderThread>, Option<ReaderThread>)>,
}
impl Handle {
pub fn wait(&self) -> io::Result<&Output> {
let (expression_status, output) = wait_on_handle_and_ouput(self)?;
if expression_status.is_checked_error() {
return Err(io::Error::new(
io::ErrorKind::Other,
expression_status.message(),
));
}
Ok(output)
}
pub fn try_wait(&self) -> io::Result<Option<&Output>> {
if self.inner.wait(WaitMode::Nonblocking)?.is_none() {
Ok(None)
} else {
self.wait().map(Some)
}
}
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()?;
self.inner.wait(WaitMode::Blocking)?;
Ok(())
}
pub fn pids(&self) -> Vec<u32> {
self.inner.pids()
}
}
fn wait_on_handle_and_ouput(handle: &Handle) -> io::Result<&(ExpressionStatus, Output)> {
let mut readers_lock = handle.readers.lock().expect("readers lock poisoned");
if let Some(result) = handle.result.get() {
Ok(result)
} else {
let status = handle
.inner
.wait(WaitMode::Blocking)?
.expect("blocking wait can't return None");
let (stdout_reader, stderr_reader) = &mut *readers_lock;
let stdout = stdout_reader
.take()
.map(|t| t.join().expect("stdout reader error"))
.unwrap_or(Ok(Vec::new()))?;
let stderr = stderr_reader
.take()
.map(|t| t.join().expect("stderr reader error"))
.unwrap_or(Ok(Vec::new()))?;
let output = Output {
status: status.status,
stdout,
stderr,
};
Ok(handle.result.get_or_init(|| (status, output)))
}
}
#[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(start_argv(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(),
}
}
}
fn start_argv(argv: &[OsString], context: IoContext) -> io::Result<ChildHandle> {
let exe = canonicalize_exe_path_for_dir(&argv[0], &context)?;
let mut command = Command::new(exe);
command.args(&argv[1..]);
command.stdin(context.stdin.into_stdio()?);
command.stdout(context.stdout.into_stdio()?);
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 shared_child = SharedChild::spawn(&mut command)?;
let command_string = format!("{:?}", argv);
Ok(ChildHandle {
child: shared_child,
command_string: command_string,
})
}
#[derive(Debug)]
struct ChildHandle {
child: shared_child::SharedChild,
command_string: String,
}
impl ChildHandle {
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let maybe_status = match mode {
WaitMode::Blocking => Some(self.child.wait()?),
WaitMode::Nonblocking => self.child.try_wait()?,
};
if let Some(status) = maybe_status {
Ok(Some(ExpressionStatus {
status: status,
checked: true,
command: self.command_string.clone(),
}))
} else {
Ok(None)
}
}
fn kill(&self) -> io::Result<()> {
self.child.kill()
}
}
#[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) = os_pipe::pipe()?;
let mut left_context = context.try_clone()?;
left_context.stdout = IoValue::Handle(into_file(writer));
let mut right_context = context;
right_context.stdin = IoValue::Handle(into_file(reader));
let left_handle = left.0.start(left_context)?;
let right_result = right.0.start(right_context);
match right_result {
Ok(right_handle) => Ok(PipeHandle {
left_handle: left_handle,
right_handle: right_handle,
}),
Err(e) => {
left_handle.kill()?;
left_handle.wait(WaitMode::Blocking)?;
Err(e)
}
}
}
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)?);
}
StdinFile(f) => {
context.stdin = IoValue::Handle(f.try_clone()?);
}
StdinNull => {
context.stdin = IoValue::Null;
}
StdoutPath(p) => {
context.stdout = IoValue::Handle(File::create(p)?);
}
StdoutFile(f) => {
context.stdout = IoValue::Handle(f.try_clone()?);
}
StdoutNull => {
context.stdout = IoValue::Null;
}
StdoutCapture => {
context.stdout = IoValue::Handle(into_file(context.stdout_capture.write_pipe()?));
}
StdoutToStderr => {
context.stdout = context.stderr.try_clone()?;
}
StderrPath(p) => {
context.stderr = IoValue::Handle(File::create(p)?);
}
StderrFile(f) => {
context.stderr = IoValue::Handle(f.try_clone()?);
}
StderrNull => {
context.stderr = IoValue::Null;
}
StderrCapture => {
context.stderr = IoValue::Handle(into_file(context.stderr_capture.write_pipe()?));
}
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) = os_pipe::pipe()?;
context.stdin = IoValue::Handle(into_file(reader));
let inner = expression.0.start(context)?;
let thread = std::thread::spawn(move || writer.write_all(&input));
Ok(StdinBytesHandle {
inner_handle: inner,
writer_thread: SharedThread::new(thread),
})
}
fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
let wait_res = self.inner_handle.wait(mode);
if mode.should_join_background_thread(&wait_res) {
match self.writer_thread.join() {
Err(err) if err.kind() != io::ErrorKind::BrokenPipe => {
return Err(clone_io_error(err));
}
_ => {}
}
}
wait_res
}
fn kill(&self) -> io::Result<()> {
self.inner_handle.kill()
}
}
#[derive(Debug)]
enum IoExpressionInner {
StdinBytes(Arc<Vec<u8>>),
StdinPath(PathBuf),
StdinFile(File),
StdinNull,
StdoutPath(PathBuf),
StdoutFile(File),
StdoutNull,
StdoutCapture,
StdoutToStderr,
StderrPath(PathBuf),
StderrFile(File),
StderrNull,
StderrCapture,
StderrToStdout,
StdoutStderrSwap,
Dir(PathBuf),
Env(OsString, OsString),
EnvRemove(OsString),
FullEnv(HashMap<OsString, OsString>),
Unchecked,
BeforeSpawn(BeforeSpawnHook),
}
#[derive(Clone)]
struct BeforeSpawnHook {
inner: Arc<dyn Fn(&mut Command) -> io::Result<()> + Send + Sync>,
}
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<OsString, 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().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(File),
}
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(),
})
}
}
#[cfg(not(windows))]
fn into_file<T: IntoRawFd>(handle: T) -> File {
unsafe { File::from_raw_fd(handle.into_raw_fd()) }
}
#[cfg(windows)]
fn into_file<T: IntoRawHandle>(handle: T) -> File {
unsafe { File::from_raw_handle(handle.into_raw_handle()) }
}
#[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(Debug)]
struct SharedThread<T> {
result: OnceCell<T>,
handle: Mutex<Option<JoinHandle<T>>>,
}
impl<T> SharedThread<T> {
fn new(handle: JoinHandle<T>) -> Self {
SharedThread {
result: OnceCell::new(),
handle: Mutex::new(Some(handle)),
}
}
fn join(&self) -> &T {
let mut handle_lock = self.handle.lock().expect("shared thread handle poisoned");
if let Some(handle) = handle_lock.take() {
let ret = handle.join().expect("panic on shared thread");
self.result
.set(ret)
.map_err(|_| "result cell unexpectedly full")
.unwrap();
}
self.result.get().expect("result cell unexpectedly empty")
}
}
#[derive(Clone, Copy, Debug)]
enum WaitMode {
Blocking,
Nonblocking,
}
impl WaitMode {
fn should_join_background_thread(
&self,
expression_result: &io::Result<Option<ExpressionStatus>>,
) -> bool {
if let WaitMode::Blocking = self {
true
} else if let Ok(Some(_)) = expression_result {
true
} else {
false
}
}
}
#[cfg(windows)]
fn canonicalize_env_var_name(name: OsString) -> OsString {
match name.into_string() {
Ok(name) => name.to_uppercase().into(),
Err(name) => name,
}
}
#[cfg(not(windows))]
fn canonicalize_env_var_name(name: OsString) -> OsString {
name
}
type ReaderThread = JoinHandle<io::Result<Vec<u8>>>;
#[derive(Debug)]
struct OutputCaptureContext {
pair: OnceCell<(os_pipe::PipeReader, os_pipe::PipeWriter)>,
}
impl OutputCaptureContext {
fn new() -> Self {
Self {
pair: OnceCell::new(),
}
}
fn write_pipe(&self) -> io::Result<os_pipe::PipeWriter> {
let (_, writer) = self.pair.get_or_try_init(|| os_pipe::pipe())?;
writer.try_clone()
}
fn maybe_read_thread(self) -> Option<ReaderThread> {
if let Some((mut reader, _)) = self.pair.into_inner() {
Some(std::thread::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.len() > 0 {
self.handle.wait()?;
}
Ok(n)
}
}
impl Read for ReaderHandle {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(&*self).read(buf)
}
}
impl Drop for ReaderHandle {
fn drop(&mut self) {
let _ = self.handle.kill();
}
}
#[cfg(test)]
mod test;