#[cfg(unix)]
mod os {
pub const SHELL: [&str; 2] = ["sh", "-c"];
}
#[cfg(windows)]
mod os {
pub const SHELL: [&str; 2] = ["cmd.exe", "/c"];
}
use crate::communicate::Communicator;
use crate::process::ExitStatus;
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::fs::File;
use std::io::{self, Read, Write};
use std::ops::BitOr;
use std::path::Path;
use std::sync::Arc;
use crate::job::Job;
pub(crate) use crate::job::{ReadAdapter, ReadErrAdapter, WriteAdapter};
use crate::pipeline::Pipeline;
use crate::spawn::{Arg, OsOptions, SpawnResult, display_escape, spawn};
use os::*;
#[derive(Debug)]
pub enum Redirection {
None,
Pipe,
Merge,
File(File),
Null,
}
#[must_use]
pub struct Exec {
command: OsString,
args: Vec<Arg>,
check_success: bool,
stdin_data: Option<InputData>,
pub(crate) stdin_redirect: Arc<Redirection>,
pub(crate) stdout_redirect: Arc<Redirection>,
pub(crate) stderr_redirect: Arc<Redirection>,
detached: bool,
executable: Option<OsString>,
env: Option<Vec<(OsString, OsString)>>,
cwd: Option<OsString>,
os_options: OsOptions,
}
impl Exec {
pub fn cmd(command: impl Into<OsString>) -> Exec {
Exec {
command: command.into(),
args: vec![],
check_success: false,
stdin_data: None,
stdin_redirect: Arc::new(Redirection::None),
stdout_redirect: Arc::new(Redirection::None),
stderr_redirect: Arc::new(Redirection::None),
detached: false,
executable: None,
env: None,
cwd: None,
os_options: Default::default(),
}
}
pub fn shell(cmdstr: impl Into<OsString>) -> Exec {
let cmd = Exec::cmd(SHELL[0]).args(&SHELL[1..]);
#[cfg(not(windows))]
{
cmd.arg(cmdstr)
}
#[cfg(windows)]
{
use crate::ExecExt;
cmd.raw_arg(cmdstr)
}
}
pub fn arg(mut self, arg: impl Into<OsString>) -> Exec {
self.args.push(Arg::Regular(arg.into()));
self
}
pub fn args(mut self, args: impl IntoIterator<Item = impl Into<OsString>>) -> Exec {
self.args
.extend(args.into_iter().map(|x| Arg::Regular(x.into())));
self
}
pub fn detached(mut self) -> Exec {
self.detached = true;
self
}
pub fn checked(mut self) -> Exec {
self.check_success = true;
self
}
fn ensure_env(&mut self) -> &mut Vec<(OsString, OsString)> {
self.env.get_or_insert_with(|| env::vars_os().collect())
}
pub fn env_clear(mut self) -> Exec {
self.env = Some(vec![]);
self
}
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Exec {
self.ensure_env().push((key.into(), value.into()));
self
}
pub fn env_extend(
mut self,
vars: impl IntoIterator<Item = (impl Into<OsString>, impl Into<OsString>)>,
) -> Exec {
self.ensure_env()
.extend(vars.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
pub fn env_remove(mut self, key: impl Into<OsString>) -> Exec {
let key = key.into();
self.ensure_env().retain(|(k, _v)| *k != key);
self
}
pub fn cwd(mut self, dir: impl AsRef<Path>) -> Exec {
self.cwd = Some(dir.as_ref().as_os_str().to_owned());
self
}
pub fn stdin<T>(mut self, stdin: T) -> Exec
where
InputRedirection: FromSource<T>,
{
match InputRedirection::from_source(stdin) {
InputRedirection::Redirection(new) => {
self.stdin_redirect = Arc::new(new);
self.stdin_data = None;
}
InputRedirection::Data(data) => {
self.stdin_redirect = Arc::new(Redirection::Pipe);
self.stdin_data = Some(data);
}
}
self
}
pub fn stdout<T>(mut self, stdout: T) -> Exec
where
Redirection: FromSink<T>,
{
self.stdout_redirect = Arc::new(Redirection::from_sink(stdout));
self
}
pub fn stderr<T>(mut self, stderr: T) -> Exec
where
Redirection: FromSink<T>,
{
self.stderr_redirect = Arc::new(Redirection::from_sink(stderr));
self
}
fn check_no_stdin_data(&self, meth: &str) {
if self.stdin_data.is_some() {
panic!("{} called with input data specified", meth);
}
}
pub(crate) fn spawn(self) -> io::Result<SpawnResult> {
let mut argv = self.args;
argv.insert(0, Arg::Regular(self.command));
spawn(
argv,
self.stdin_redirect,
self.stdout_redirect,
self.stderr_redirect,
self.detached,
self.executable.as_deref(),
self.env.as_deref(),
self.cwd.as_deref(),
self.os_options,
)
}
pub fn start(mut self) -> io::Result<Job> {
let stdin_data = self.stdin_data.take().unwrap_or_default();
let check_success = self.check_success;
let result = self.spawn()?;
Ok(Job {
stdin: result.stdin,
stdout: result.stdout,
stderr: result.stderr,
stdin_data,
check_success,
processes: vec![result.process],
})
}
pub fn join(self) -> io::Result<ExitStatus> {
self.start()?.join()
}
pub fn stream_stdout(self) -> io::Result<impl Read> {
self.check_no_stdin_data("stream_stdout");
Ok(ReadAdapter(self.stdout(Redirection::Pipe).start()?))
}
pub fn stream_stderr(self) -> io::Result<impl Read> {
self.check_no_stdin_data("stream_stderr");
Ok(ReadErrAdapter(self.stderr(Redirection::Pipe).start()?))
}
pub fn stream_stdin(self) -> io::Result<impl Write> {
self.check_no_stdin_data("stream_stdin");
Ok(WriteAdapter(self.stdin(Redirection::Pipe).start()?))
}
pub fn communicate(mut self) -> io::Result<Communicator> {
self = self.detached();
if matches!(*self.stdout_redirect, Redirection::None) {
self = self.stdout(Redirection::Pipe);
}
if matches!(*self.stderr_redirect, Redirection::None) {
self = self.stderr(Redirection::Pipe);
}
self.start()?.communicate()
}
pub fn capture(mut self) -> io::Result<Capture> {
if matches!(*self.stdout_redirect, Redirection::None) {
self = self.stdout(Redirection::Pipe);
}
if matches!(*self.stderr_redirect, Redirection::None) {
self = self.stderr(Redirection::Pipe);
}
self.start()?.capture()
}
pub fn to_cmdline_lossy(&self) -> String {
let mut out = String::new();
if let Some(cmd_env) = &self.env {
let current: Vec<_> = env::vars_os().collect();
let current_map: HashMap<_, _> = current.iter().map(|(x, y)| (x, y)).collect();
for (k, v) in cmd_env {
if current_map.get(k) == Some(&v) {
continue;
}
out.push_str(&display_escape(&k.to_string_lossy()));
out.push('=');
out.push_str(&display_escape(&v.to_string_lossy()));
out.push(' ');
}
let cmd_env: HashMap<_, _> = cmd_env.iter().map(|(k, v)| (k, v)).collect();
for (k, _) in current {
if !cmd_env.contains_key(&k) {
out.push_str(&display_escape(&k.to_string_lossy()));
out.push('=');
out.push(' ');
}
}
}
out.push_str(&display_escape(&self.command.to_string_lossy()));
for arg in &self.args {
out.push(' ');
out.push_str(&arg.display_escaped());
}
out
}
pub(crate) fn stdin_is_set(&self) -> bool {
!matches!(*self.stdin_redirect, Redirection::None)
}
pub(crate) fn stdout_is_set(&self) -> bool {
!matches!(*self.stdout_redirect, Redirection::None)
}
#[cfg(unix)]
pub(crate) fn setpgid_is_set(&self) -> bool {
self.os_options.setpgid_is_set()
}
#[cfg(unix)]
pub(crate) fn set_pgid_value(&mut self, pgid: u32) {
self.os_options.set_pgid_value(pgid);
}
}
impl BitOr for Exec {
type Output = Pipeline;
fn bitor(self, rhs: Exec) -> Pipeline {
Pipeline::new().pipe(self).pipe(rhs)
}
}
impl fmt::Debug for Exec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Exec {{ {} }}", self.to_cmdline_lossy())
}
}
#[derive(Debug)]
pub struct Capture {
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub exit_status: ExitStatus,
}
impl Capture {
pub fn stdout_str(&self) -> String {
String::from_utf8_lossy(&self.stdout).into_owned()
}
pub fn stderr_str(&self) -> String {
String::from_utf8_lossy(&self.stderr).into_owned()
}
pub fn success(&self) -> bool {
self.exit_status.success()
}
pub fn check(self) -> io::Result<Self> {
if self.success() {
Ok(self)
} else {
Err(io::Error::other(format!(
"command failed: {}",
self.exit_status
)))
}
}
}
pub struct InputData(Box<dyn Read + Send + Sync>);
impl InputData {
pub fn from_bytes(data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self {
InputData(Box::new(io::Cursor::new(data)))
}
pub fn from_reader(reader: impl Read + Send + Sync + 'static) -> Self {
InputData(Box::new(reader))
}
}
impl Read for InputData {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl Default for InputData {
fn default() -> Self {
InputData(Box::new(io::empty()))
}
}
impl fmt::Debug for InputData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("InputData(..)")
}
}
#[derive(Debug)]
pub enum InputRedirection {
Redirection(Redirection),
Data(InputData),
}
pub trait FromSource<T> {
fn from_source(source: T) -> Self;
}
pub trait FromSink<T> {
fn from_sink(sink: T) -> Self;
}
impl FromSource<Redirection> for InputRedirection {
fn from_source(source: Redirection) -> Self {
if let Redirection::Merge = source {
panic!("Redirection::Merge is only allowed for output streams");
}
InputRedirection::Redirection(source)
}
}
impl FromSource<File> for InputRedirection {
fn from_source(source: File) -> Self {
InputRedirection::Redirection(Redirection::File(source))
}
}
impl FromSource<InputData> for InputRedirection {
fn from_source(source: InputData) -> Self {
InputRedirection::Data(source)
}
}
impl FromSource<Vec<u8>> for InputRedirection {
fn from_source(source: Vec<u8>) -> Self {
InputRedirection::Data(InputData::from_bytes(source))
}
}
impl FromSource<&'static str> for InputRedirection {
fn from_source(source: &'static str) -> Self {
InputRedirection::Data(InputData::from_bytes(source))
}
}
impl FromSource<&'static [u8]> for InputRedirection {
fn from_source(source: &'static [u8]) -> Self {
InputRedirection::Data(InputData::from_bytes(source))
}
}
impl<const N: usize> FromSource<&'static [u8; N]> for InputRedirection {
fn from_source(source: &'static [u8; N]) -> Self {
InputRedirection::Data(InputData::from_bytes(source))
}
}
impl<const N: usize> FromSource<[u8; N]> for InputRedirection {
fn from_source(source: [u8; N]) -> Self {
InputRedirection::Data(InputData::from_bytes(source))
}
}
impl FromSource<Box<[u8]>> for InputRedirection {
fn from_source(source: Box<[u8]>) -> Self {
InputRedirection::Data(InputData::from_bytes(source))
}
}
impl FromSink<Redirection> for Redirection {
fn from_sink(sink: Redirection) -> Self {
sink
}
}
impl FromSink<File> for Redirection {
fn from_sink(sink: File) -> Self {
Redirection::File(sink)
}
}
#[cfg(unix)]
pub mod unix {
use super::Exec;
use crate::job::Job;
use crate::pipeline::Pipeline;
use crate::unix::ProcessExt;
use std::io;
pub trait JobExt {
fn send_signal(&self, signal: i32) -> io::Result<()>;
fn send_signal_group(&self, signal: i32) -> io::Result<()>;
}
impl JobExt for Job {
fn send_signal(&self, signal: i32) -> io::Result<()> {
for p in &self.processes {
p.send_signal(signal)?;
}
Ok(())
}
fn send_signal_group(&self, signal: i32) -> io::Result<()> {
if let Some(p) = self.processes.first() {
p.send_signal_group(signal)?;
}
Ok(())
}
}
pub trait ExecExt {
fn setuid(self, uid: u32) -> Self;
fn setgid(self, gid: u32) -> Self;
fn setpgid(self) -> Self;
}
impl ExecExt for Exec {
fn setuid(mut self, uid: u32) -> Exec {
self.os_options.setuid = Some(uid);
self
}
fn setgid(mut self, gid: u32) -> Exec {
self.os_options.setgid = Some(gid);
self
}
fn setpgid(mut self) -> Exec {
self.os_options.setpgid = Some(0);
self
}
}
pub trait PipelineExt {
fn setpgid(self) -> Self;
}
impl PipelineExt for Pipeline {
fn setpgid(mut self) -> Pipeline {
self.set_setpgid(true);
self
}
}
}
#[cfg(any(windows, docsrs))]
pub mod windows {
use std::ffi::OsString;
use super::Exec;
#[cfg(windows)]
use crate::spawn::Arg;
pub const CREATE_NO_WINDOW: u32 = 0x08000000;
pub const CREATE_NEW_CONSOLE: u32 = 0x00000010;
pub const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
pub const DETACHED_PROCESS: u32 = 0x00000008;
pub trait ExecExt {
fn creation_flags(self, flags: u32) -> Self;
fn raw_arg(self, arg: impl Into<OsString>) -> Self;
}
impl ExecExt for Exec {
fn creation_flags(mut self, flags: u32) -> Exec {
#[cfg(windows)]
{
self.os_options.creation_flags = flags;
self
}
#[cfg(not(windows))]
{
let _ = flags;
unimplemented!()
}
}
fn raw_arg(mut self, arg: impl Into<OsString>) -> Exec {
#[cfg(windows)]
{
self.args.push(Arg::Raw(arg.into()));
self
}
#[cfg(not(windows))]
{
let _ = arg;
unimplemented!()
}
}
}
}