use std::ffi::CString;
use std::ops::ControlFlow::Break;
use yash_env::builtin::Result;
use yash_env::io::print_error;
use yash_env::semantics::Field;
use yash_env::Env;
use yash_semantics::command::simple_command::{replace_current_process, to_c_strings};
use yash_semantics::command_search::search_path;
use yash_semantics::Divert::Abort;
use yash_semantics::ExitStatus;
pub async fn main(env: &mut Env, args: Vec<Field>) -> Result {
let mut result = Result::default();
result.retain_redirs();
if let Some(name) = args.first() {
result.set_divert(Break(Abort(None)));
let path = if name.value.contains('/') {
CString::new(name.value.clone()).ok()
} else {
search_path(env, name.value.as_str())
};
if let Some(path) = path {
let location = name.origin.clone();
let args = to_c_strings(args);
replace_current_process(env, path, args, location).await;
result.set_exit_status(env.exit_status);
} else {
print_error(
env,
format!("cannot execute external utility {:?}", name.value).into(),
"utility not found".into(),
&name.origin,
)
.await;
result.set_exit_status(ExitStatus::NOT_FOUND);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::FutureExt;
use std::cell::RefCell;
use std::rc::Rc;
use yash_env::system::r#virtual::{FileBody, Inode};
use yash_env::system::Mode;
use yash_env::variable::{Scope, PATH};
use yash_env::VirtualSystem;
#[test]
fn retains_redirs_without_args() {
let mut env = Env::new_virtual();
let result = main(&mut env, vec![]).now_or_never().unwrap();
assert_eq!(result.exit_status(), ExitStatus::SUCCESS);
assert!(result.should_retain_redirs());
}
#[test]
fn executes_external_utility_when_given_operand() {
let system = VirtualSystem::new();
let mut env = Env::with_system(Box::new(system.clone()));
let mut content = Inode::default();
content.body = FileBody::Regular {
content: Vec::new(),
is_native_executable: true,
};
content.permissions.set(Mode::USER_EXEC, true);
let content = Rc::new(RefCell::new(content));
system
.state
.borrow_mut()
.file_system
.save("/bin/echo", content)
.unwrap();
let path = &mut env.variables.get_or_new(PATH, Scope::Global);
path.assign("/bin", None).unwrap();
path.export(true);
let args = Field::dummies(["echo"]);
_ = main(&mut env, args).now_or_never().unwrap();
let process = &system.current_process();
let arguments = process.last_exec().as_ref().unwrap();
assert_eq!(arguments.0, c"/bin/echo".to_owned());
assert_eq!(arguments.1, [c"echo".to_owned()]);
assert_eq!(arguments.2, [c"PATH=/bin".to_owned()]);
}
#[test]
fn passing_argument_to_external_utility() {
let system = VirtualSystem::new();
let mut env = Env::with_system(Box::new(system.clone()));
let mut content = Inode::default();
content.body = FileBody::Regular {
content: Vec::new(),
is_native_executable: true,
};
content.permissions.set(Mode::USER_EXEC, true);
let content = Rc::new(RefCell::new(content));
system
.state
.borrow_mut()
.file_system
.save("/usr/bin/ls", content)
.unwrap();
let path = &mut env.variables.get_or_new(PATH, Scope::Global);
path.assign("/usr/bin", None).unwrap();
path.export(true);
let args = Field::dummies(["ls", "-l"]);
_ = main(&mut env, args).now_or_never().unwrap();
let process = &system.current_process();
let arguments = process.last_exec().as_ref().unwrap();
assert_eq!(arguments.0, c"/usr/bin/ls".to_owned());
assert_eq!(arguments.1, [c"ls".to_owned(), c"-l".to_owned()]);
assert_eq!(arguments.2, [c"PATH=/usr/bin".to_owned()]);
}
#[test]
fn utility_name_with_slash() {
let system = VirtualSystem::new();
let mut env = Env::with_system(Box::new(system.clone()));
let mut content = Inode::default();
content.body = FileBody::Regular {
content: Vec::new(),
is_native_executable: true,
};
content.permissions.set(Mode::USER_EXEC, true);
let content = Rc::new(RefCell::new(content));
system
.state
.borrow_mut()
.file_system
.save("/bin/echo", content)
.unwrap();
let args = Field::dummies(["/bin/echo"]);
_ = main(&mut env, args).now_or_never().unwrap();
let process = &system.current_process();
let arguments = process.last_exec().as_ref().unwrap();
assert_eq!(arguments.0, c"/bin/echo".to_owned());
assert_eq!(arguments.1, [c"/bin/echo".to_owned()]);
assert_eq!(arguments.2, []);
}
#[test]
fn utility_not_found() {
let system = VirtualSystem::new();
let mut env = Env::with_system(Box::new(system.clone()));
let mut content = Inode::default();
content.body = FileBody::Regular {
content: Vec::new(),
is_native_executable: true,
};
content.permissions.set(Mode::USER_EXEC, true);
let content = Rc::new(RefCell::new(content));
system
.state
.borrow_mut()
.file_system
.save("/bin/echo", content)
.unwrap();
let args = Field::dummies(["echo"]);
let result = main(&mut env, args).now_or_never().unwrap();
assert_eq!(result.exit_status(), ExitStatus::NOT_FOUND);
assert_eq!(result.divert(), Break(Abort(None)));
let process = &system.current_process();
assert_eq!(process.last_exec(), &None);
}
#[test]
fn utility_not_executable() {
let system = VirtualSystem::new();
let mut env = Env::with_system(Box::new(system.clone()));
let mut content = Inode::default();
content.body = FileBody::Regular {
content: Vec::new(),
is_native_executable: true,
};
let content = Rc::new(RefCell::new(content));
system
.state
.borrow_mut()
.file_system
.save("/bin/echo", content)
.unwrap();
let args = Field::dummies(["/bin/echo"]);
let result = main(&mut env, args).now_or_never().unwrap();
assert_eq!(result.exit_status(), ExitStatus::NOEXEC);
assert_eq!(result.divert(), Break(Abort(None)));
}
}