use std::cell::RefCell;
use std::env;
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::fs::File;
use std::io;
use std::rc::Rc;
use std::result;
use std::time::Duration;
use crate::communicate;
use crate::os_common::{ExitStatus, StandardStream};
use self::ChildState::*;
pub use self::os::ext as os_ext;
pub use self::os::make_pipe;
pub use communicate::Communicator;
#[derive(Debug)]
pub struct Popen {
pub stdin: Option<File>,
pub stdout: Option<File>,
pub stderr: Option<File>,
child_state: ChildState,
detached: bool,
}
#[derive(Debug)]
enum ChildState {
Preparing, Running {
pid: u32,
#[allow(dead_code)]
ext: os::ExtChildState,
},
Finished(ExitStatus),
}
#[derive(Debug)]
pub struct PopenConfig {
pub stdin: Redirection,
pub stdout: Redirection,
pub stderr: Redirection,
pub detached: bool,
pub executable: Option<OsString>,
pub env: Option<Vec<(OsString, OsString)>>,
pub cwd: Option<OsString>,
#[cfg(unix)]
pub setuid: Option<u32>,
#[cfg(unix)]
pub setgid: Option<u32>,
#[cfg(unix)]
pub setpgid: bool,
#[doc(hidden)]
pub _use_default_to_construct: (),
}
impl PopenConfig {
pub fn try_clone(&self) -> io::Result<PopenConfig> {
Ok(PopenConfig {
stdin: self.stdin.try_clone()?,
stdout: self.stdout.try_clone()?,
stderr: self.stderr.try_clone()?,
detached: self.detached,
executable: self.executable.as_ref().cloned(),
env: self.env.clone(),
cwd: self.cwd.clone(),
#[cfg(unix)]
setuid: self.setuid,
#[cfg(unix)]
setgid: self.setgid,
#[cfg(unix)]
setpgid: self.setpgid,
_use_default_to_construct: (),
})
}
pub fn current_env() -> Vec<(OsString, OsString)> {
env::vars_os().collect()
}
}
impl Default for PopenConfig {
fn default() -> PopenConfig {
PopenConfig {
stdin: Redirection::None,
stdout: Redirection::None,
stderr: Redirection::None,
detached: false,
executable: None,
env: None,
cwd: None,
#[cfg(unix)]
setuid: None,
#[cfg(unix)]
setgid: None,
#[cfg(unix)]
setpgid: false,
_use_default_to_construct: (),
}
}
}
#[derive(Debug)]
pub enum Redirection {
None,
Pipe,
Merge,
File(File),
RcFile(Rc<File>),
}
impl Redirection {
pub fn try_clone(&self) -> io::Result<Redirection> {
Ok(match *self {
Redirection::None => Redirection::None,
Redirection::Pipe => Redirection::Pipe,
Redirection::Merge => Redirection::Merge,
Redirection::File(ref f) => Redirection::File(f.try_clone()?),
Redirection::RcFile(ref f) => Redirection::RcFile(Rc::clone(&f)),
})
}
}
impl Popen {
pub fn create(argv: &[impl AsRef<OsStr>], config: PopenConfig) -> Result<Popen> {
if argv.is_empty() {
return Err(PopenError::LogicError("argv must not be empty"));
}
let argv: Vec<OsString> = argv.iter().map(|p| p.as_ref().to_owned()).collect();
let mut inst = Popen {
stdin: None,
stdout: None,
stderr: None,
child_state: ChildState::Preparing,
detached: config.detached,
};
inst.os_start(argv, config)?;
Ok(inst)
}
fn setup_streams(
&mut self,
stdin: Redirection,
stdout: Redirection,
stderr: Redirection,
) -> Result<(Option<Rc<File>>, Option<Rc<File>>, Option<Rc<File>>)> {
fn prepare_pipe(
parent_writes: bool,
parent_ref: &mut Option<File>,
child_ref: &mut Option<Rc<File>>,
) -> Result<()> {
let (read, write) = os::make_pipe()?;
let (parent_end, child_end) = if parent_writes {
(write, read)
} else {
(read, write)
};
os::set_inheritable(&parent_end, false)?;
*parent_ref = Some(parent_end);
*child_ref = Some(Rc::new(child_end));
Ok(())
}
fn prepare_file(file: File, child_ref: &mut Option<Rc<File>>) -> io::Result<()> {
os::set_inheritable(&file, true)?;
*child_ref = Some(Rc::new(file));
Ok(())
}
fn prepare_rc_file(file: Rc<File>, child_ref: &mut Option<Rc<File>>) -> io::Result<()> {
os::set_inheritable(&file, true)?;
*child_ref = Some(file);
Ok(())
}
fn reuse_stream(
dest: &mut Option<Rc<File>>,
src: &mut Option<Rc<File>>,
src_id: StandardStream,
) -> io::Result<()> {
if src.is_none() {
*src = Some(get_standard_stream(src_id)?);
}
*dest = Some(Rc::clone(src.as_ref().unwrap()));
Ok(())
}
enum MergeKind {
ErrToOut, OutToErr, None,
}
let mut merge: MergeKind = MergeKind::None;
let (mut child_stdin, mut child_stdout, mut child_stderr) = (None, None, None);
match stdin {
Redirection::Pipe => prepare_pipe(true, &mut self.stdin, &mut child_stdin)?,
Redirection::File(file) => prepare_file(file, &mut child_stdin)?,
Redirection::RcFile(file) => prepare_rc_file(file, &mut child_stdin)?,
Redirection::Merge => {
return Err(PopenError::LogicError(
"Redirection::Merge not valid for stdin",
));
}
Redirection::None => (),
};
match stdout {
Redirection::Pipe => prepare_pipe(false, &mut self.stdout, &mut child_stdout)?,
Redirection::File(file) => prepare_file(file, &mut child_stdout)?,
Redirection::RcFile(file) => prepare_rc_file(file, &mut child_stdout)?,
Redirection::Merge => merge = MergeKind::OutToErr,
Redirection::None => (),
};
match stderr {
Redirection::Pipe => prepare_pipe(false, &mut self.stderr, &mut child_stderr)?,
Redirection::File(file) => prepare_file(file, &mut child_stderr)?,
Redirection::RcFile(file) => prepare_rc_file(file, &mut child_stderr)?,
Redirection::Merge => merge = MergeKind::ErrToOut,
Redirection::None => (),
};
match merge {
MergeKind::ErrToOut => {
reuse_stream(&mut child_stderr, &mut child_stdout, StandardStream::Output)?
}
MergeKind::OutToErr => {
reuse_stream(&mut child_stdout, &mut child_stderr, StandardStream::Error)?
}
MergeKind::None => (),
}
Ok((child_stdin, child_stdout, child_stderr))
}
pub fn detach(&mut self) {
self.detached = true;
}
pub fn pid(&self) -> Option<u32> {
match self.child_state {
Running { pid, .. } => Some(pid),
_ => None,
}
}
pub fn exit_status(&self) -> Option<ExitStatus> {
match self.child_state {
Finished(exit_status) => Some(exit_status),
_ => None,
}
}
pub fn communicate_start(&mut self, input_data: Option<Vec<u8>>) -> Communicator {
communicate::communicate(
self.stdin.take(),
self.stdout.take(),
self.stderr.take(),
input_data,
)
}
pub fn communicate_bytes(
&mut self,
input_data: Option<&[u8]>,
) -> io::Result<(Option<Vec<u8>>, Option<Vec<u8>>)> {
self.communicate_start(input_data.map(|i| i.to_vec()))
.read()
.map_err(|e| e.error)
}
pub fn communicate(
&mut self,
input_data: Option<&str>,
) -> io::Result<(Option<String>, Option<String>)> {
self.communicate_start(input_data.map(|s| s.as_bytes().to_vec()))
.read_string()
.map_err(|e| e.error)
}
pub fn poll(&mut self) -> Option<ExitStatus> {
self.wait_timeout(Duration::from_secs(0)).unwrap_or(None)
}
pub fn wait(&mut self) -> Result<ExitStatus> {
self.os_wait()
}
pub fn wait_timeout(&mut self, dur: Duration) -> Result<Option<ExitStatus>> {
self.os_wait_timeout(dur)
}
pub fn terminate(&mut self) -> io::Result<()> {
self.os_terminate()
}
pub fn kill(&mut self) -> io::Result<()> {
self.os_kill()
}
}
trait PopenOs {
fn os_start(&mut self, argv: Vec<OsString>, config: PopenConfig) -> Result<()>;
fn os_wait(&mut self) -> Result<ExitStatus>;
fn os_wait_timeout(&mut self, dur: Duration) -> Result<Option<ExitStatus>>;
fn os_terminate(&mut self) -> io::Result<()>;
fn os_kill(&mut self) -> io::Result<()>;
}
#[cfg(unix)]
mod os {
use super::*;
use crate::posix;
use std::collections::HashSet;
use std::ffi::OsString;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::io::AsRawFd;
use std::time::{Duration, Instant};
use crate::os_common::ExitStatus;
use crate::unix::PopenExt;
pub type ExtChildState = ();
impl super::PopenOs for Popen {
fn os_start(&mut self, argv: Vec<OsString>, config: PopenConfig) -> Result<()> {
let mut exec_fail_pipe = posix::pipe()?;
set_inheritable(&exec_fail_pipe.0, false)?;
set_inheritable(&exec_fail_pipe.1, false)?;
{
let child_ends = self.setup_streams(config.stdin, config.stdout, config.stderr)?;
let child_env = config.env.as_deref().map(format_env);
let cmd_to_exec = config.executable.as_ref().unwrap_or(&argv[0]);
let just_exec = posix::prep_exec(cmd_to_exec, &argv, child_env.as_deref())?;
unsafe {
match posix::fork()? {
Some(child_pid) => {
self.child_state = Running {
pid: child_pid,
ext: (),
};
}
None => {
drop(exec_fail_pipe.0);
let result = Popen::do_exec(
just_exec,
child_ends,
config.cwd.as_deref(),
config.setuid,
config.setgid,
config.setpgid,
);
let error_code = match result {
Ok(()) => unreachable!(),
Err(e) => e.raw_os_error().unwrap_or(-1),
} as u32;
exec_fail_pipe
.1
.write_all(&[
error_code as u8,
(error_code >> 8) as u8,
(error_code >> 16) as u8,
(error_code >> 24) as u8,
])
.ok();
posix::_exit(127);
}
}
}
}
drop(exec_fail_pipe.1);
let mut error_buf = [0u8; 4];
let read_cnt = exec_fail_pipe.0.read(&mut error_buf)?;
if read_cnt == 0 {
Ok(())
} else if read_cnt == 4 {
let error_code: u32 = error_buf[0] as u32
| (error_buf[1] as u32) << 8
| (error_buf[2] as u32) << 16
| (error_buf[3] as u32) << 24;
Err(PopenError::from(io::Error::from_raw_os_error(
error_code as i32,
)))
} else {
Err(PopenError::LogicError("invalid read_count from exec pipe"))
}
}
fn os_wait(&mut self) -> Result<ExitStatus> {
while let Running { .. } = self.child_state {
self.waitpid(true)?;
}
Ok(self.exit_status().unwrap())
}
fn os_wait_timeout(&mut self, dur: Duration) -> Result<Option<ExitStatus>> {
use std::cmp::min;
if let Finished(exit_status) = self.child_state {
return Ok(Some(exit_status));
}
let deadline = Instant::now() + dur;
let mut delay = Duration::from_millis(1);
loop {
self.waitpid(false)?;
if let Finished(exit_status) = self.child_state {
return Ok(Some(exit_status));
}
let now = Instant::now();
if now >= deadline {
return Ok(None);
}
let remaining = deadline.duration_since(now);
::std::thread::sleep(min(delay, remaining));
delay = min(delay * 2, Duration::from_millis(100));
}
}
fn os_terminate(&mut self) -> io::Result<()> {
self.send_signal(posix::SIGTERM)
}
fn os_kill(&mut self) -> io::Result<()> {
self.send_signal(posix::SIGKILL)
}
}
fn format_env(env: &[(OsString, OsString)]) -> Vec<OsString> {
let mut seen = HashSet::<&OsStr>::new();
let mut formatted: Vec<_> = env
.iter()
.rev()
.filter(|&(k, _)| seen.insert(k))
.map(|(k, v)| {
let mut fmt = k.clone();
fmt.push("=");
fmt.push(v);
fmt
})
.collect();
formatted.reverse();
formatted
}
trait PopenOsImpl: super::PopenOs {
fn do_exec(
just_exec: impl FnOnce() -> io::Result<()>,
child_ends: (Option<Rc<File>>, Option<Rc<File>>, Option<Rc<File>>),
cwd: Option<&OsStr>,
setuid: Option<u32>,
setgid: Option<u32>,
setpgid: bool,
) -> io::Result<()>;
fn waitpid(&mut self, block: bool) -> io::Result<()>;
}
impl PopenOsImpl for Popen {
fn do_exec(
just_exec: impl FnOnce() -> io::Result<()>,
child_ends: (Option<Rc<File>>, Option<Rc<File>>, Option<Rc<File>>),
cwd: Option<&OsStr>,
setuid: Option<u32>,
setgid: Option<u32>,
setpgid: bool,
) -> io::Result<()> {
if let Some(cwd) = cwd {
env::set_current_dir(cwd)?;
}
let (stdin, stdout, stderr) = child_ends;
if let Some(stdin) = stdin {
if stdin.as_raw_fd() != 0 {
posix::dup2(stdin.as_raw_fd(), 0)?;
}
}
if let Some(stdout) = stdout {
if stdout.as_raw_fd() != 1 {
posix::dup2(stdout.as_raw_fd(), 1)?;
}
}
if let Some(stderr) = stderr {
if stderr.as_raw_fd() != 2 {
posix::dup2(stderr.as_raw_fd(), 2)?;
}
}
posix::reset_sigpipe()?;
if let Some(uid) = setuid {
posix::setuid(uid)?;
}
if let Some(gid) = setgid {
posix::setgid(gid)?;
}
if setpgid {
posix::setpgid(0, 0)?;
}
just_exec()?;
unreachable!();
}
fn waitpid(&mut self, block: bool) -> io::Result<()> {
match self.child_state {
Preparing => panic!("child_state == Preparing"),
Running { pid, .. } => {
match posix::waitpid(pid, if block { 0 } else { posix::WNOHANG }) {
Err(e) => {
if let Some(errno) = e.raw_os_error() {
if errno == posix::ECHILD {
self.child_state = Finished(ExitStatus::Undetermined);
return Ok(());
}
}
return Err(e);
}
Ok((pid_out, exit_status)) => {
if pid_out == pid {
self.child_state = Finished(exit_status);
}
}
}
}
Finished(..) => (),
}
Ok(())
}
}
pub fn set_inheritable(f: &File, inheritable: bool) -> io::Result<()> {
if inheritable {
} else {
let fd = f.as_raw_fd();
let old = posix::fcntl(fd, posix::F_GETFD, None)?;
posix::fcntl(fd, posix::F_SETFD, Some(old | posix::FD_CLOEXEC))?;
}
Ok(())
}
pub fn make_pipe() -> io::Result<(File, File)> {
posix::pipe()
}
pub mod ext {
use crate::popen::ChildState::*;
use crate::popen::Popen;
use crate::posix;
use std::io;
pub trait PopenExt {
fn send_signal(&self, signal: i32) -> io::Result<()>;
}
impl PopenExt for Popen {
fn send_signal(&self, signal: i32) -> io::Result<()> {
match self.child_state {
Preparing => panic!("child_state == Preparing"),
Running { pid, .. } => posix::kill(pid, signal),
Finished(..) => Ok(()),
}
}
}
}
}
#[cfg(windows)]
mod os {
use super::*;
use std::collections::HashSet;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs::{self, File};
use std::io;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::io::{AsRawHandle, RawHandle};
use std::time::Duration;
use crate::os_common::{ExitStatus, StandardStream};
use crate::win32;
#[derive(Debug)]
pub struct ExtChildState(win32::Handle);
impl super::PopenOs for Popen {
fn os_start(&mut self, argv: Vec<OsString>, config: PopenConfig) -> Result<()> {
fn raw(opt: &Option<Rc<File>>) -> Option<RawHandle> {
opt.as_ref().map(|f| f.as_raw_handle())
}
let (mut child_stdin, mut child_stdout, mut child_stderr) =
self.setup_streams(config.stdin, config.stdout, config.stderr)?;
ensure_child_stream(&mut child_stdin, StandardStream::Input)?;
ensure_child_stream(&mut child_stdout, StandardStream::Output)?;
ensure_child_stream(&mut child_stderr, StandardStream::Error)?;
let cmdline = assemble_cmdline(argv)?;
let env_block = config.env.map(|env| format_env_block(&env));
let executable = config.executable.map(locate_in_path);
let (handle, pid) = win32::CreateProcess(
executable.as_ref().map(OsString::as_ref),
&cmdline,
&env_block,
&config.cwd.as_deref(),
true,
0,
raw(&child_stdin),
raw(&child_stdout),
raw(&child_stderr),
win32::STARTF_USESTDHANDLES,
)?;
self.child_state = Running {
pid: pid as u32,
ext: ExtChildState(handle),
};
Ok(())
}
fn os_wait(&mut self) -> Result<ExitStatus> {
self.wait_handle(None)?;
match self.child_state {
Preparing => panic!("child_state == Preparing"),
Finished(exit_status) => Ok(exit_status),
Running { .. } => Err(PopenError::LogicError("Failed to obtain exit status")),
}
}
fn os_wait_timeout(&mut self, dur: Duration) -> Result<Option<ExitStatus>> {
if let Finished(exit_status) = self.child_state {
return Ok(Some(exit_status));
}
self.wait_handle(Some(dur))?;
Ok(self.exit_status())
}
fn os_terminate(&mut self) -> io::Result<()> {
let mut new_child_state = None;
if let Running {
ext: ExtChildState(ref handle),
..
} = self.child_state
{
match win32::TerminateProcess(handle, 1) {
Err(err) => {
if err.raw_os_error() != Some(win32::ERROR_ACCESS_DENIED as i32) {
return Err(err);
}
let rc = win32::GetExitCodeProcess(handle)?;
if rc == win32::STILL_ACTIVE {
return Err(err);
}
new_child_state = Some(Finished(ExitStatus::Exited(rc)));
}
Ok(_) => (),
}
}
if let Some(new_child_state) = new_child_state {
self.child_state = new_child_state;
}
Ok(())
}
fn os_kill(&mut self) -> io::Result<()> {
self.terminate()
}
}
fn format_env_block(env: &[(OsString, OsString)]) -> Vec<u16> {
fn to_uppercase(s: &OsStr) -> OsString {
OsString::from_wide(
&s.encode_wide()
.map(|c| {
if c < 128 {
(c as u8 as char).to_ascii_uppercase() as u16
} else {
c
}
})
.collect::<Vec<_>>(),
)
}
let mut pruned: Vec<_> = {
let mut seen = HashSet::<OsString>::new();
env.iter()
.rev()
.filter(|&(k, _)| seen.insert(to_uppercase(k)))
.collect()
};
pruned.reverse();
let mut block = vec![];
for (k, v) in pruned {
block.extend(k.encode_wide());
block.push('=' as u16);
block.extend(v.encode_wide());
block.push(0);
}
block.push(0);
block
}
trait PopenOsImpl {
fn wait_handle(&mut self, timeout: Option<Duration>) -> io::Result<Option<ExitStatus>>;
}
impl PopenOsImpl for Popen {
fn wait_handle(&mut self, timeout: Option<Duration>) -> io::Result<Option<ExitStatus>> {
let mut new_child_state = None;
if let Running {
ext: ExtChildState(ref handle),
..
} = self.child_state
{
let event = win32::WaitForSingleObject(handle, timeout)?;
if let win32::WaitEvent::OBJECT_0 = event {
let exit_code = win32::GetExitCodeProcess(handle)?;
new_child_state = Some(Finished(ExitStatus::Exited(exit_code)));
}
}
if let Some(new_child_state) = new_child_state {
self.child_state = new_child_state;
}
Ok(self.exit_status())
}
}
fn ensure_child_stream(stream: &mut Option<Rc<File>>, which: StandardStream) -> io::Result<()> {
if stream.is_none() {
*stream = Some(get_standard_stream(which)?);
}
Ok(())
}
pub fn set_inheritable(f: &File, inheritable: bool) -> io::Result<()> {
win32::SetHandleInformation(
f,
win32::HANDLE_FLAG_INHERIT,
if inheritable { 1 } else { 0 },
)?;
Ok(())
}
pub fn make_pipe() -> io::Result<(File, File)> {
win32::CreatePipe(true)
}
fn locate_in_path(executable: OsString) -> OsString {
if let Some(path) = env::var_os("PATH") {
for path in env::split_paths(&path) {
let path = path
.join(&executable)
.with_extension(::std::env::consts::EXE_EXTENSION);
if fs::metadata(&path).is_ok() {
return path.into_os_string();
}
}
}
executable
}
fn assemble_cmdline(argv: Vec<OsString>) -> io::Result<OsString> {
let mut cmdline = vec![];
let mut is_first = true;
for arg in argv {
if !is_first {
cmdline.push(' ' as u16);
} else {
is_first = false;
}
if arg.encode_wide().any(|c| c == 0) {
return Err(io::Error::from_raw_os_error(
win32::ERROR_BAD_PATHNAME as i32,
));
}
append_quoted(&arg, &mut cmdline);
}
Ok(OsString::from_wide(&cmdline))
}
fn append_quoted(arg: &OsStr, cmdline: &mut Vec<u16>) {
if !arg.is_empty()
&& !arg.encode_wide().any(|c| {
c == ' ' as u16
|| c == '\t' as u16
|| c == '\n' as u16
|| c == '\x0b' as u16
|| c == '\"' as u16
})
{
cmdline.extend(arg.encode_wide());
return;
}
cmdline.push('"' as u16);
let arg: Vec<_> = arg.encode_wide().collect();
let mut i = 0;
while i < arg.len() {
let mut num_backslashes = 0;
while i < arg.len() && arg[i] == '\\' as u16 {
i += 1;
num_backslashes += 1;
}
if i == arg.len() {
for _ in 0..num_backslashes * 2 {
cmdline.push('\\' as u16);
}
break;
} else if arg[i] == b'"' as u16 {
for _ in 0..num_backslashes * 2 + 1 {
cmdline.push('\\' as u16);
}
cmdline.push(arg[i]);
} else {
for _ in 0..num_backslashes {
cmdline.push('\\' as u16);
}
cmdline.push(arg[i]);
}
i += 1;
}
cmdline.push('"' as u16);
}
pub mod ext {}
}
impl Drop for Popen {
fn drop(&mut self) {
if let (false, &Running { .. }) = (self.detached, &self.child_state) {
self.wait().ok();
}
}
}
thread_local! {
static STREAMS: RefCell<[Option<Rc<File>>; 3]> = RefCell::default();
}
#[cfg(unix)]
use crate::posix::make_standard_stream;
#[cfg(windows)]
use crate::win32::make_standard_stream;
fn get_standard_stream(which: StandardStream) -> io::Result<Rc<File>> {
STREAMS.with(|streams| {
if let Some(ref stream) = streams.borrow()[which as usize] {
return Ok(Rc::clone(&stream));
}
let stream = make_standard_stream(which)?;
streams.borrow_mut()[which as usize] = Some(Rc::clone(&stream));
Ok(stream)
})
}
#[derive(Debug)]
#[non_exhaustive]
pub enum PopenError {
IoError(io::Error),
LogicError(&'static str),
}
impl From<io::Error> for PopenError {
fn from(err: io::Error) -> PopenError {
PopenError::IoError(err)
}
}
impl From<communicate::CommunicateError> for PopenError {
fn from(err: communicate::CommunicateError) -> PopenError {
PopenError::IoError(err.error)
}
}
impl Error for PopenError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
PopenError::IoError(ref err) => Some(err),
PopenError::LogicError(_msg) => None,
}
}
}
impl fmt::Display for PopenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
PopenError::IoError(ref err) => fmt::Display::fmt(err, f),
PopenError::LogicError(desc) => f.write_str(desc),
}
}
}
pub type Result<T> = result::Result<T, PopenError>;