#![cfg_attr(feature = "strict", deny(warnings))]
#![cfg_attr(feature = "strict", deny(missing_docs))]
extern crate libc;
#[cfg(feature="noprofile")]
extern crate cpuprofiler;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(not(target_os = "linux"))]
mod generic;
#[cfg(target_os = "linux")]
use linux as imp;
#[cfg(not(target_os = "linux"))]
use generic as imp;
pub use imp::{Stdio};
use std::ffi::{OsString, OsStr};
use std::path::PathBuf;
pub struct Command {
envs_set: std::collections::HashMap<OsString,OsString>,
envs_removed: std::collections::HashSet<OsString>,
envs_cleared: bool,
am_blind: bool,
inner: imp::Command,
}
impl Command {
pub fn new<S: AsRef<OsStr>>(program: S) -> Command {
Command {
envs_set: std::collections::HashMap::new(),
envs_removed: std::collections::HashSet::new(),
envs_cleared: false,
am_blind: false,
inner: imp::Command::new(program),
}
}
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
self.inner.arg(arg);
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Command
where I: IntoIterator<Item=S>, S: AsRef<OsStr>
{
for arg in args {
self.inner.arg(arg.as_ref());
}
self
}
pub fn current_dir<P: AsRef<std::path::Path>>(&mut self, dir: P) -> &mut Command {
self.inner.current_dir(dir);
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command
where K: AsRef<OsStr>, V: AsRef<OsStr>
{
self.envs_removed.remove(key.as_ref());
self.envs_set.insert(key.as_ref().to_os_string(), val.as_ref().to_os_string());
self
}
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Command
where I: IntoIterator<Item=(K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>
{
for (k,v) in vars {
self.env(k,v);
}
self
}
pub fn env_remove<K>(&mut self, key: K) -> &mut Command
where K: AsRef<OsStr>
{
self.envs_set.remove(key.as_ref());
self.envs_removed.insert(key.as_ref().to_os_string());
self
}
pub fn env_clear(&mut self) -> &mut Command
{
self.envs_cleared = true;
self.envs_set.clear();
self.envs_removed.clear();
self
}
pub fn stdin(&mut self, cfg: Stdio) -> &mut Command {
self.inner.stdin(cfg);
self
}
pub fn stdout(&mut self, cfg: Stdio) -> &mut Command {
self.inner.stdout(cfg);
self
}
pub fn stderr(&mut self, cfg: Stdio) -> &mut Command {
self.inner.stderr(cfg);
self
}
pub fn save_stdouterr(&mut self) -> &mut Command {
self.inner.save_stdouterr();
self
}
pub fn log_stdouterr(&mut self, path: &std::path::Path) -> &mut Command {
self.inner.log_stdouterr(path);
self
}
pub fn status(&mut self) -> std::io::Result<Status> {
if self.am_blind {
self.inner.blind(self.envs_cleared, &self.envs_removed, &self.envs_set)
.map(|s| Status { inner: s })
} else {
self.inner.status(self.envs_cleared, &self.envs_removed, &self.envs_set)
.map(|s| Status { inner: s })
}
}
pub fn blind(&mut self, am_blind: bool) -> &mut Command {
self.am_blind = am_blind;
self
}
pub fn spawn(self) -> std::io::Result<Child> {
if self.am_blind {
unimplemented!()
} else {
self.inner.spawn(self.envs_cleared, self.envs_removed, self.envs_set)
.map(|s| Child { inner: s })
}
}
pub fn spawn_and_hook<F>(self, status_hook: F) -> std::io::Result<Killer>
where F: FnOnce(std::io::Result<Status>) + Send + 'static
{
let (tx,rx) = std::sync::mpsc::channel();
if self.am_blind {
self.inner.spawn_to_chans_blind(self.envs_cleared, self.envs_removed,
self.envs_set, tx, status_hook)?;
} else {
self.inner.spawn_to_chans(self.envs_cleared, self.envs_removed, self.envs_set,
tx, status_hook)?;
}
match rx.recv() {
Ok(Some(k)) => Ok(k),
Ok(None) => unreachable!(),
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other,e)),
}
}
}
#[derive(Debug)]
pub struct Child {
inner: imp::Child,
}
impl Child {
pub fn kill(&mut self) -> std::io::Result<()> {
self.inner.kill()
}
pub fn terminate(&mut self) -> std::io::Result<()> {
self.inner.terminate()
}
pub fn wait(&mut self) -> std::io::Result<Status> {
self.inner.wait().map(|s| Status { inner: s })
}
pub fn try_wait(&mut self) -> std::io::Result<Option<Status>> {
self.inner.try_wait().map(|s| s.map(|s| Status { inner: s}))
}
}
#[derive(Debug, Copy, Clone)]
pub struct Killer {
inner: imp::Killer,
}
impl Killer {
pub fn kill(&mut self) -> std::io::Result<()> {
self.inner.kill()
}
pub fn terminate(&mut self) -> std::io::Result<()> {
self.inner.terminate()
}
}
#[derive(Debug)]
pub struct Status {
inner: imp::Status,
}
impl Status {
pub fn status(&self) -> std::process::ExitStatus {
self.inner.status()
}
pub fn read_from_directories(&self) -> std::collections::HashSet<PathBuf> {
self.inner.read_from_directories()
}
pub fn read_from_files(&self) -> std::collections::HashSet<PathBuf> {
self.inner.read_from_files()
}
pub fn written_to_files(&self) -> std::collections::HashSet<PathBuf> {
self.inner.written_to_files()
}
pub fn mkdir_directories(&self) -> std::collections::HashSet<PathBuf> {
self.inner.mkdir_directories()
}
pub fn stdout(&mut self) -> std::io::Result<Option<Box<std::io::Read>>> {
self.inner.stdout()
}
}
#[cfg(target_os = "linux")]
#[cfg(test)]
fn count_file_descriptors() -> usize {
let mut count = 0;
for _ in std::fs::read_dir("/proc/self/fd").unwrap() {
count += 1;
}
println!("open file descriptors: {}", count);
count
}
#[cfg(target_os = "linux")]
#[test]
fn test_have_closed_fds() {
let fds = count_file_descriptors();
{
let status = Command::new("echo")
.arg("-n")
.arg("hello")
.save_stdouterr()
.status()
.expect("failed to execute echo");
assert!(count_file_descriptors() > fds,
"save_stdouterr should open a file descriptor?");
println!("status: {:?}", status);
}
assert_eq!(count_file_descriptors(), fds);
{
Command::new("ls")
.status()
.expect("failed to execute ls");
assert_eq!(count_file_descriptors(), fds);
}
assert_eq!(count_file_descriptors(), fds);
{
Command::new("ls")
.stdin(Stdio::null())
.status()
.expect("failed to execute ls");
assert_eq!(count_file_descriptors(), fds);
}
assert_eq!(count_file_descriptors(), fds);
{
let status = Command::new("echo")
.arg("-n")
.arg("hello")
.stdin(Stdio::null())
.save_stdouterr()
.status()
.expect("failed to execute echo");
assert!(count_file_descriptors() > fds);
println!("status: {:?}", status);
}
assert_eq!(count_file_descriptors(), fds);
{
let status = Command::new("echo")
.arg("-n")
.arg("hello")
.stdin(Stdio::null())
.log_stdouterr(&std::path::Path::new("/tmp/test-file"))
.status()
.expect("failed to execute echo");
assert!(count_file_descriptors() > fds);
println!("status: {:?}", status);
}
assert_eq!(count_file_descriptors(), fds);
}