use {ExitStatus, Fd, IFS_DEFAULT, STDERR_FILENO};
use error::{CommandError, RuntimeError};
use io::{FileDesc, Permissions};
use spawn::SpawnBoxed;
use std::borrow::{Borrow, Cow};
use std::convert::From;
use std::hash::Hash;
use std::error::Error;
use std::fmt;
use std::io::Result as IoResult;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
use std::rc::Rc;
use tokio_core::reactor::Remote;
use env::atomic;
use env::atomic::FnEnv as AtomicFnEnv;
use env::{ArgsEnv, ArgumentsEnvironment, AsyncIoEnvironment, ChangeWorkingDirectoryEnvironment,
ExecEnv, ExecutableData, ExecutableEnvironment, ExportedVariableEnvironment,
FileDescEnv, FileDescEnvironment, FnEnv, FunctionEnvironment,
IsInteractiveEnvironment, LastStatusEnv, LastStatusEnvironment,
PlatformSpecificAsyncIoEnv, ReportErrorEnvironment, ShiftArgumentsEnvironment,
SetArgumentsEnvironment, StringWrapper, SubEnvironment, UnsetFunctionEnvironment,
UnsetVariableEnvironment, VarEnv, VariableEnvironment, VirtualWorkingDirEnv,
WorkingDirectoryEnvironment};
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct EnvConfig<A, IO, FD, L, V, EX, WD, N, ERR> {
pub interactive: bool,
pub args_env: A,
pub async_io_env: IO,
pub file_desc_env: FD,
pub last_status_env: L,
pub var_env: V,
pub exec_env: EX,
pub working_dir_env: WD,
pub fn_name: PhantomData<N>,
pub fn_error: PhantomData<ERR>,
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> EnvConfig<A, IO, FD, L, V, EX, WD, N, ERR> {
pub fn change_args_env<T>(self, args_env: T) -> EnvConfig<T, IO, FD, L, V, EX, WD, N, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: args_env,
async_io_env: self.async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: self.last_status_env,
var_env: self.var_env,
exec_env: self.exec_env,
working_dir_env: self.working_dir_env,
fn_name: self.fn_name,
fn_error: self.fn_error,
}
}
pub fn change_async_io_env<T>(self, async_io_env: T) -> EnvConfig<A, T, FD, L, V, EX, WD, N, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: self.last_status_env,
var_env: self.var_env,
exec_env: self.exec_env,
working_dir_env: self.working_dir_env,
fn_name: self.fn_name,
fn_error: self.fn_error,
}
}
pub fn change_file_desc_env<T>(self, file_desc_env: T) -> EnvConfig<A, IO, T, L, V, EX, WD, N, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: self.async_io_env,
file_desc_env: file_desc_env,
last_status_env: self.last_status_env,
var_env: self.var_env,
exec_env: self.exec_env,
working_dir_env: self.working_dir_env,
fn_name: self.fn_name,
fn_error: self.fn_error,
}
}
pub fn change_last_status_env<T>(self, last_status_env: T) -> EnvConfig<A, IO, FD, T, V, EX, WD, N, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: self.async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: last_status_env,
var_env: self.var_env,
exec_env: self.exec_env,
working_dir_env: self.working_dir_env,
fn_name: self.fn_name,
fn_error: self.fn_error,
}
}
pub fn change_var_env<T>(self, var_env: T) -> EnvConfig<A, IO, FD, L, T, EX, WD, N, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: self.async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: self.last_status_env,
var_env: var_env,
exec_env: self.exec_env,
working_dir_env: self.working_dir_env,
fn_name: self.fn_name,
fn_error: self.fn_error,
}
}
pub fn change_exec_env<T>(self, exec_env: T) -> EnvConfig<A, IO, FD, L, V, T, WD, N, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: self.async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: self.last_status_env,
var_env: self.var_env,
exec_env: exec_env,
working_dir_env: self.working_dir_env,
fn_name: self.fn_name,
fn_error: self.fn_error,
}
}
pub fn change_working_dir_env<T>(self, working_dir_env: T) -> EnvConfig<A, IO, FD, L, V, EX, T, N, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: self.async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: self.last_status_env,
var_env: self.var_env,
exec_env: self.exec_env,
working_dir_env: working_dir_env,
fn_name: self.fn_name,
fn_error: self.fn_error,
}
}
pub fn change_fn_name<T>(self) -> EnvConfig<A, IO, FD, L, V, EX, WD, T, ERR> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: self.async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: self.last_status_env,
var_env: self.var_env,
exec_env: self.exec_env,
working_dir_env: self.working_dir_env,
fn_name: PhantomData,
fn_error: self.fn_error,
}
}
pub fn change_fn_error<T>(self) -> EnvConfig<A, IO, FD, L, V, EX, WD, N, T> {
EnvConfig {
interactive: self.interactive,
args_env: self.args_env,
async_io_env: self.async_io_env,
file_desc_env: self.file_desc_env,
last_status_env: self.last_status_env,
var_env: self.var_env,
exec_env: self.exec_env,
working_dir_env: self.working_dir_env,
fn_name: self.fn_name,
fn_error: PhantomData,
}
}
}
pub type DefaultEnvConfig<T> =
EnvConfig<
ArgsEnv<T>,
PlatformSpecificAsyncIoEnv,
FileDescEnv<Rc<FileDesc>>,
LastStatusEnv,
VarEnv<T, T>,
ExecEnv,
VirtualWorkingDirEnv,
T,
RuntimeError,
>;
pub type DefaultEnvConfigRc = DefaultEnvConfig<Rc<String>>;
pub type DefaultAtomicEnvConfig<T> =
EnvConfig<
atomic::ArgsEnv<T>,
PlatformSpecificAsyncIoEnv,
atomic::FileDescEnv<Arc<FileDesc>>,
LastStatusEnv,
atomic::VarEnv<T, T>,
ExecEnv,
atomic::VirtualWorkingDirEnv,
T,
RuntimeError,
>;
pub type DefaultAtomicEnvConfigArc = DefaultAtomicEnvConfig<Arc<String>>;
impl<T> DefaultEnvConfig<T> where T: Eq + Hash + From<String> {
pub fn new(remote: Remote, fallback_num_threads: Option<usize>) -> IoResult<Self> {
Ok(DefaultEnvConfig {
interactive: false,
args_env: ArgsEnv::new(),
async_io_env: PlatformSpecificAsyncIoEnv::new(remote.clone(), fallback_num_threads),
file_desc_env: try!(FileDescEnv::with_process_stdio()),
last_status_env: LastStatusEnv::new(),
var_env: VarEnv::with_process_env_vars(),
exec_env: ExecEnv::new(remote),
working_dir_env: try!(VirtualWorkingDirEnv::with_process_working_dir()),
fn_name: PhantomData,
fn_error: PhantomData,
})
}
}
impl<T> DefaultAtomicEnvConfig<T> where T: Eq + Hash + From<String> {
pub fn new_atomic(remote: Remote, fallback_num_threads: Option<usize>) -> IoResult<Self> {
Ok(DefaultAtomicEnvConfig {
interactive: false,
args_env: atomic::ArgsEnv::new(),
async_io_env: PlatformSpecificAsyncIoEnv::new(remote.clone(), fallback_num_threads),
file_desc_env: try!(atomic::FileDescEnv::with_process_stdio()),
last_status_env: LastStatusEnv::new(),
var_env: atomic::VarEnv::with_process_env_vars(),
exec_env: ExecEnv::new(remote),
working_dir_env: try!(atomic::VirtualWorkingDirEnv::with_process_working_dir()),
fn_name: PhantomData,
fn_error: PhantomData,
})
}
}
macro_rules! impl_env {
($(#[$attr:meta])* pub struct $Env:ident, $FnEnv:ident, $Rc:ident, $($extra:tt)*) => {
$(#[$attr])*
pub struct $Env<A, IO, FD, L, V, EX, WD, N: Eq + Hash, ERR> {
interactive: bool,
args_env: A,
async_io_env: IO,
file_desc_env: FD,
fn_env: $FnEnv<N, $Rc<SpawnBoxed<$Env<A, IO, FD, L, V, EX, WD, N, ERR>, Error = ERR> $($extra)*>>,
last_status_env: L,
var_env: V,
exec_env: EX,
working_dir_env: WD,
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where N: Hash + Eq,
{
pub fn with_config(cfg: EnvConfig<A, IO, FD, L, V, EX, WD, N, ERR>) -> Self
where V: ExportedVariableEnvironment,
V::VarName: From<String>,
V::Var: Borrow<String> + From<String>,
{
let mut env = $Env {
interactive: cfg.interactive,
args_env: cfg.args_env,
async_io_env: cfg.async_io_env,
fn_env: $FnEnv::new(),
file_desc_env: cfg.file_desc_env,
last_status_env: cfg.last_status_env,
var_env: cfg.var_env,
exec_env: cfg.exec_env,
working_dir_env: cfg.working_dir_env,
};
let sh_lvl = "SHLVL".to_owned().into();
let level = env.var(&sh_lvl)
.and_then(|lvl| lvl.borrow().parse::<isize>().ok().map(|l| l+1))
.unwrap_or(1);
env.set_exported_var(sh_lvl.into(), level.to_string().into(), true);
env.set_var("IFS".to_owned().into(), IFS_DEFAULT.to_owned().into());
env
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> Clone for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where A: Clone,
FD: Clone,
L: Clone,
V: Clone,
N: Hash + Eq,
IO: Clone,
EX: Clone,
WD: Clone,
{
fn clone(&self) -> Self {
$Env {
interactive: self.interactive,
args_env: self.args_env.clone(),
async_io_env: self.async_io_env.clone(),
file_desc_env: self.file_desc_env.clone(),
fn_env: self.fn_env.clone(),
last_status_env: self.last_status_env.clone(),
var_env: self.var_env.clone(),
exec_env: self.exec_env.clone(),
working_dir_env: self.working_dir_env.clone(),
}
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> fmt::Debug for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where A: fmt::Debug,
FD: fmt::Debug,
L: fmt::Debug,
V: fmt::Debug,
N: Hash + Eq + Ord + fmt::Debug,
IO: fmt::Debug,
EX: fmt::Debug,
WD: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use std::collections::BTreeSet;
let fn_names: BTreeSet<_> = self.fn_env.fn_names().collect();
fmt.debug_struct(stringify!($Env))
.field("interactive", &self.interactive)
.field("args_env", &self.args_env)
.field("async_io_env", &self.async_io_env)
.field("file_desc_env", &self.file_desc_env)
.field("functions", &fn_names)
.field("last_status_env", &self.last_status_env)
.field("var_env", &self.var_env)
.field("exec_env", &self.exec_env)
.field("working_dir_env", &self.working_dir_env)
.finish()
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> From<EnvConfig<A, IO, FD, L, V, EX, WD, N, ERR>>
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where N: Hash + Eq,
V: ExportedVariableEnvironment,
V::VarName: From<String>,
V::Var: Borrow<String> + From<String>,
{
fn from(cfg: EnvConfig<A, IO, FD, L, V, EX, WD, N, ERR>) -> Self {
Self::with_config(cfg)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> IsInteractiveEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where N: Hash + Eq,
{
fn is_interactive(&self) -> bool {
self.interactive
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> SubEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where A: SubEnvironment,
FD: SubEnvironment,
L: SubEnvironment,
V: SubEnvironment,
N: Hash + Eq,
IO: SubEnvironment,
EX: SubEnvironment,
WD: SubEnvironment,
{
fn sub_env(&self) -> Self {
$Env {
interactive: self.is_interactive(),
args_env: self.args_env.sub_env(),
async_io_env: self.async_io_env.sub_env(),
file_desc_env: self.file_desc_env.sub_env(),
fn_env: self.fn_env.sub_env(),
last_status_env: self.last_status_env.sub_env(),
var_env: self.var_env.sub_env(),
exec_env: self.exec_env.sub_env(),
working_dir_env: self.working_dir_env.sub_env(),
}
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> ArgumentsEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where A: ArgumentsEnvironment,
A::Arg: Clone,
N: Hash + Eq,
{
type Arg = A::Arg;
fn name(&self) -> &Self::Arg {
self.args_env.name()
}
fn arg(&self, idx: usize) -> Option<&Self::Arg> {
self.args_env.arg(idx)
}
fn args_len(&self) -> usize {
self.args_env.args_len()
}
fn args(&self) -> Cow<[Self::Arg]> {
self.args_env.args()
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> SetArgumentsEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where A: SetArgumentsEnvironment,
N: Hash + Eq,
{
type Args = A::Args;
fn set_args(&mut self, new_args: Self::Args) -> Self::Args {
self.args_env.set_args(new_args)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> ShiftArgumentsEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where A: ShiftArgumentsEnvironment,
N: Hash + Eq,
{
fn shift_args(&mut self, amt: usize) {
self.args_env.shift_args(amt)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> AsyncIoEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where IO: AsyncIoEnvironment,
N: Hash + Eq,
{
type Read = IO::Read;
type WriteAll = IO::WriteAll;
fn read_async(&mut self, fd: FileDesc) -> Self::Read {
self.async_io_env.read_async(fd)
}
fn write_all(&mut self, fd: FileDesc, data: Vec<u8>) -> Self::WriteAll {
self.async_io_env.write_all(fd, data)
}
fn write_all_best_effort(&mut self, fd: FileDesc, data: Vec<u8>) {
self.async_io_env.write_all_best_effort(fd, data);
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> FileDescEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where FD: FileDescEnvironment,
N: Hash + Eq,
{
type FileHandle = FD::FileHandle;
fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)> {
self.file_desc_env.file_desc(fd)
}
fn set_file_desc(&mut self, fd: Fd, fdes: Self::FileHandle, perms: Permissions) {
self.file_desc_env.set_file_desc(fd, fdes, perms)
}
fn close_file_desc(&mut self, fd: Fd) {
self.file_desc_env.close_file_desc(fd)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> ReportErrorEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where A: ArgumentsEnvironment,
A::Arg: fmt::Display,
FD: FileDescEnvironment,
FD::FileHandle: Borrow<FileDesc>,
N: Hash + Eq,
{
fn report_error(&self, err: &Error) {
use std::io::Write;
if let Some((fd, _)) = self.file_desc(STDERR_FILENO) {
let _ = writeln!(fd.borrow(), "{}: {}", self.name(), err);
}
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> FunctionEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where N: Hash + Eq + Clone,
{
type FnName = N;
type Fn = $Rc<SpawnBoxed<Self, Error = ERR> $($extra)*>;
fn function(&self, name: &Self::FnName) -> Option<&Self::Fn> {
self.fn_env.function(name)
}
fn set_function(&mut self, name: Self::FnName, func: Self::Fn) {
self.fn_env.set_function(name, func);
}
fn has_function(&self, name: &Self::FnName) -> bool {
self.fn_env.has_function(name)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> UnsetFunctionEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where N: Hash + Eq + Clone,
{
fn unset_function(&mut self, name: &Self::FnName) {
self.fn_env.unset_function(name);
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> LastStatusEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where L: LastStatusEnvironment,
N: Hash + Eq,
{
fn last_status(&self) -> ExitStatus {
self.last_status_env.last_status()
}
fn set_last_status(&mut self, status: ExitStatus) {
self.last_status_env.set_last_status(status);
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> VariableEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where V: VariableEnvironment,
N: Hash + Eq,
{
type VarName = V::VarName;
type Var = V::Var;
fn var<Q: ?Sized>(&self, name: &Q) -> Option<&Self::Var>
where Self::VarName: Borrow<Q>, Q: Hash + Eq,
{
self.var_env.var(name)
}
fn set_var(&mut self, name: Self::VarName, val: Self::Var) {
self.var_env.set_var(name, val);
}
fn env_vars(&self) -> Cow<[(&Self::VarName, &Self::Var)]> {
self.var_env.env_vars()
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> ExportedVariableEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where V: ExportedVariableEnvironment,
N: Hash + Eq,
{
fn exported_var(&self, name: &Self::VarName) -> Option<(&Self::Var, bool)> {
self.var_env.exported_var(name)
}
fn set_exported_var(&mut self, name: Self::VarName, val: Self::Var, exported: bool) {
self.var_env.set_exported_var(name, val, exported)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> UnsetVariableEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where V: UnsetVariableEnvironment,
N: Hash + Eq,
{
fn unset_var<Q: ?Sized>(&mut self, name: &Q)
where Self::VarName: Borrow<Q>, Q: Hash + Eq
{
self.var_env.unset_var(name)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> ExecutableEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where V: UnsetVariableEnvironment,
N: Hash + Eq,
EX: ExecutableEnvironment,
{
type Future = EX::Future;
fn spawn_executable(&mut self, data: ExecutableData)
-> Result<Self::Future, CommandError>
{
self.exec_env.spawn_executable(data)
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> WorkingDirectoryEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where N: Hash + Eq,
WD: WorkingDirectoryEnvironment,
{
fn path_relative_to_working_dir<'a>(&self, path: Cow<'a, Path>) -> Cow<'a, Path> {
self.working_dir_env.path_relative_to_working_dir(path)
}
fn current_working_dir(&self) -> &Path {
self.working_dir_env.current_working_dir()
}
}
impl<A, IO, FD, L, V, EX, WD, N, ERR> ChangeWorkingDirectoryEnvironment
for $Env<A, IO, FD, L, V, EX, WD, N, ERR>
where N: Hash + Eq,
WD: ChangeWorkingDirectoryEnvironment,
{
fn change_working_dir<'a>(&mut self, path: Cow<'a, Path>) -> IoResult<()> {
self.working_dir_env.change_working_dir(path)
}
}
}
}
impl_env!(
pub struct Env,
FnEnv,
Rc,
);
impl_env!(
pub struct AtomicEnv,
AtomicFnEnv,
Arc,
+ Send + Sync
);
pub type DefaultEnv<T> =
Env<
ArgsEnv<T>,
PlatformSpecificAsyncIoEnv,
FileDescEnv<Rc<FileDesc>>,
LastStatusEnv,
VarEnv<T, T>,
ExecEnv,
VirtualWorkingDirEnv,
T,
RuntimeError,
>;
pub type DefaultEnvRc = DefaultEnv<Rc<String>>;
pub type DefaultAtomicEnv<T> =
AtomicEnv<
atomic::ArgsEnv<T>,
PlatformSpecificAsyncIoEnv,
atomic::FileDescEnv<Arc<FileDesc>>,
LastStatusEnv,
atomic::VarEnv<T, T>,
ExecEnv,
atomic::VirtualWorkingDirEnv,
T,
RuntimeError,
>;
pub type DefaultAtomicEnvArc = DefaultAtomicEnv<Arc<String>>;
impl<T> DefaultEnv<T> where T: StringWrapper {
pub fn new(remote: Remote, fallback_num_threads: Option<usize>) -> IoResult<Self> {
DefaultEnvConfig::new(remote, fallback_num_threads).map(Self::with_config)
}
}
impl<T> DefaultAtomicEnv<T> where T: StringWrapper {
pub fn new_atomic(remote: Remote, fallback_num_threads: Option<usize>) -> IoResult<Self> {
DefaultAtomicEnvConfig::new_atomic(remote, fallback_num_threads).map(Self::with_config)
}
}
#[cfg(test)]
mod tests {
extern crate tokio_core;
use env::{DefaultEnvConfigRc, DefaultEnvRc, IsInteractiveEnvironment};
#[test]
fn test_env_is_interactive() {
let lp = tokio_core::reactor::Core::new().unwrap();
for &interactive in &[true, false] {
let env = DefaultEnvRc::with_config(DefaultEnvConfigRc {
interactive: interactive,
..DefaultEnvConfigRc::new(lp.remote(), Some(1)).unwrap()
});
assert_eq!(env.is_interactive(), interactive);
}
}
}