use std::ffi::CString;
use nix::unistd::{execvp, fork, ForkResult};
use crate::builtin::{classify_builtin, exec_regular_builtin, BuiltinKind};
use crate::builtin::special::exec_special_builtin;
use crate::env::jobs;
use crate::expand::expand_words;
use crate::parser::ast::{Assignment, SimpleCommand};
use crate::signal;
use super::command::wait_child;
use super::redirect::RedirectState;
use super::Executor;
impl Executor {
pub(crate) fn exec_simple_command(&mut self, cmd: &SimpleCommand) -> i32 {
let expanded = match expand_words(&mut self.env, &cmd.words) {
Ok(words) => words,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
};
if self.env.exec.flow_control.is_some() {
self.env.exec.last_exit_status = 1;
return 1;
}
if expanded.is_empty() {
let mut last_cmd_sub_status = 0i32;
for assignment in &cmd.assignments {
self.env.exec.last_exit_status = 0;
let value = match assignment.value.as_ref() {
Some(w) => match crate::expand::expand_word_to_string(&mut self.env, w) {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
},
None => String::new(),
};
last_cmd_sub_status = self.env.exec.last_exit_status;
if let Err(e) = self.env.vars.set_with_options(&assignment.name, value, self.env.mode.options.allexport) {
eprintln!("yosh: {}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
}
self.env.exec.last_exit_status = last_cmd_sub_status;
return last_cmd_sub_status;
}
if self.env.mode.options.xtrace && !expanded.is_empty() {
eprintln!("+ {}", expanded.join(" "));
}
let mut expanded_iter = expanded.into_iter();
let command_name = expanded_iter.next().unwrap();
let args: Vec<String> = expanded_iter.collect();
let cmd_str_for_hooks = std::iter::once(command_name.as_str())
.chain(args.iter().map(|s| s.as_str()))
.collect::<Vec<_>>()
.join(" ");
self.plugins.call_pre_exec(&mut self.env, &cmd_str_for_hooks);
if let Some(func_def) = self.env.functions.get(&command_name).cloned() {
let saved = match self.apply_temp_assignments(&cmd.assignments) {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
};
let mut redirect_state = RedirectState::new();
if let Err(e) = redirect_state.apply(&cmd.redirects, &mut self.env, true) {
eprintln!("yosh: {}", e);
self.restore_assignments(saved);
self.env.exec.last_exit_status = 1;
return 1;
}
let status = self.exec_function_call(&func_def, &args);
redirect_state.restore();
self.restore_assignments(saved);
self.plugins.call_post_exec(&mut self.env, &cmd_str_for_hooks, status);
self.env.exec.last_exit_status = status;
return status;
}
if command_name == "wait" {
let saved = match self.apply_temp_assignments(&cmd.assignments) {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
};
let mut redirect_state = RedirectState::new();
if let Err(e) = redirect_state.apply(&cmd.redirects, &mut self.env, true) {
eprintln!("yosh: {}", e);
self.restore_assignments(saved);
self.env.exec.last_exit_status = 1;
return 1;
}
let status = self.builtin_wait(&args);
redirect_state.restore();
self.restore_assignments(saved);
self.plugins.call_post_exec(&mut self.env, &cmd_str_for_hooks, status);
self.env.exec.last_exit_status = status;
return status;
}
if command_name == "fg" || command_name == "bg" || command_name == "jobs" {
let saved = match self.apply_temp_assignments(&cmd.assignments) {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
};
let mut redirect_state = RedirectState::new();
if let Err(e) = redirect_state.apply(&cmd.redirects, &mut self.env, true) {
eprintln!("yosh: {}", e);
self.restore_assignments(saved);
self.env.exec.last_exit_status = 1;
return 1;
}
let status = match command_name.as_str() {
"fg" => self.builtin_fg(&args),
"bg" => self.builtin_bg(&args),
"jobs" => self.builtin_jobs(&args),
_ => unreachable!(),
};
redirect_state.restore();
self.restore_assignments(saved);
self.plugins.call_post_exec(&mut self.env, &cmd_str_for_hooks, status);
self.env.exec.last_exit_status = status;
return status;
}
match classify_builtin(&command_name) {
BuiltinKind::Special => {
for assignment in &cmd.assignments {
let value = match assignment.value.as_ref() {
Some(w) => match crate::expand::expand_word_to_string(&mut self.env, w) {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
},
None => String::new(),
};
if let Err(e) = self.env.vars.set_with_options(&assignment.name, value, self.env.mode.options.allexport) {
eprintln!("yosh: {}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
}
if command_name == "exec" && args.is_empty() {
let mut redirect_state = RedirectState::new();
if let Err(e) = redirect_state.apply(&cmd.redirects, &mut self.env, false) {
eprintln!("yosh: {}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
self.env.exec.last_exit_status = 0;
return 0;
}
let mut redirect_state = RedirectState::new();
if let Err(e) = redirect_state.apply(&cmd.redirects, &mut self.env, true) {
eprintln!("yosh: {}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
let status = exec_special_builtin(&command_name, &args, self);
redirect_state.restore();
self.plugins.call_post_exec(&mut self.env, &cmd_str_for_hooks, status);
self.env.exec.last_exit_status = status;
status
}
BuiltinKind::Regular => {
let saved = match self.apply_temp_assignments(&cmd.assignments) {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
};
let mut redirect_state = RedirectState::new();
if let Err(e) = redirect_state.apply(&cmd.redirects, &mut self.env, true) {
eprintln!("yosh: {}", e);
self.restore_assignments(saved);
self.env.exec.last_exit_status = 1;
return 1;
}
let old_pwd = if command_name == "cd" {
self.env.vars.get("PWD").map(|s| s.to_string())
} else {
None
};
let status = exec_regular_builtin(&command_name, &args, &mut self.env);
redirect_state.restore();
self.restore_assignments(saved);
self.plugins.call_post_exec(&mut self.env, &cmd_str_for_hooks, status);
if command_name == "cd" && status == 0 {
let old = old_pwd.unwrap_or_default();
let new_dir = self.env.vars.get("PWD").unwrap_or("").to_string();
self.plugins.call_on_cd(&mut self.env, &old, &new_dir);
}
self.env.exec.last_exit_status = status;
status
}
BuiltinKind::NotBuiltin => {
if let Some(status) = self.plugins.exec_command(&mut self.env, &command_name, &args) {
self.plugins.call_post_exec(&mut self.env, &cmd_str_for_hooks, status);
self.env.exec.last_exit_status = status;
return status;
}
let env_vars = match self.build_env_vars(&cmd.assignments) {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
self.env.exec.last_exit_status = 1;
return 1;
}
};
let status = self.exec_external_with_redirects(
&command_name, &args, &env_vars, &cmd.redirects,
);
self.plugins.call_post_exec(&mut self.env, &cmd_str_for_hooks, status);
self.env.exec.last_exit_status = status;
status
}
}
}
pub(crate) fn build_env_vars(&mut self, assignments: &[Assignment]) -> crate::error::Result<Vec<(String, String)>> {
let mut vars = self.env.vars.environ().to_vec();
for assign in assignments {
let value = match assign.value.as_ref() {
Some(w) => crate::expand::expand_word_to_string(&mut self.env, w)?,
None => String::new(),
};
if let Some(entry) = vars.iter_mut().find(|(k, _)| k == &assign.name) {
entry.1 = value;
} else {
vars.push((assign.name.clone(), value));
}
}
Ok(vars)
}
pub(crate) fn exec_external_with_redirects(
&mut self,
cmd: &str,
args: &[String],
env_vars: &[(String, String)],
redirects: &[crate::parser::ast::Redirect],
) -> i32 {
let c_cmd = match CString::new(cmd) {
Ok(s) => s,
Err(_) => {
eprintln!("yosh: {}: invalid command name", cmd);
return 127;
}
};
let mut c_args: Vec<CString> = Vec::with_capacity(args.len() + 1);
c_args.push(c_cmd.clone());
for a in args {
match CString::new(a.as_str()) {
Ok(s) => c_args.push(s),
Err(_) => {
eprintln!("yosh: {}: invalid argument", a);
return 1;
}
}
}
let monitor = self.env.mode.options.monitor;
let shell_pgid = self.env.process.shell_pgid;
let ignored = self.env.traps.ignored_signals();
match unsafe { fork() } {
Err(e) => {
eprintln!("yosh: fork: {}", e);
1
}
Ok(ForkResult::Child) => {
if monitor {
let my_pid = nix::unistd::getpid();
nix::unistd::setpgid(my_pid, my_pid).ok();
signal::setup_foreground_child_signals(&ignored);
} else {
signal::reset_child_signals(&ignored);
}
let mut redir_state = RedirectState::new();
if let Err(e) = redir_state.apply(redirects, &mut self.env, false) {
eprintln!("yosh: {}", e);
std::process::exit(1);
}
for (k, v) in env_vars {
if let (Ok(c_key), Ok(c_val)) = (
CString::new(k.as_str()),
CString::new(v.as_str()),
) {
unsafe { libc::setenv(c_key.as_ptr(), c_val.as_ptr(), 1) };
}
}
let err = execvp(&c_cmd, &c_args).unwrap_err();
use nix::errno::Errno;
let exit_code = match err {
Errno::ENOENT => {
eprintln!("yosh: {}: command not found", cmd);
127
}
Errno::EACCES => {
eprintln!("yosh: {}: permission denied", cmd);
126
}
_ => {
eprintln!("yosh: {}: {}", cmd, err);
127
}
};
std::process::exit(exit_code);
}
Ok(ForkResult::Parent { child }) => {
if monitor {
nix::unistd::setpgid(child, child).ok();
let full_cmd = std::iter::once(cmd)
.chain(args.iter().map(|s| s.as_str()))
.collect::<Vec<_>>()
.join(" ");
let job_id = self.env.process.jobs.add_job(
child,
vec![child],
full_cmd,
true,
);
jobs::give_terminal(child).ok();
let result = self.wait_for_foreground_job(job_id);
jobs::take_terminal(shell_pgid).ok();
result.last_status
} else {
wait_child(child)
}
}
}
}
pub(crate) fn apply_temp_assignments(
&mut self,
assignments: &[Assignment],
) -> crate::error::Result<Vec<(String, Option<String>)>> {
let mut saved = Vec::new();
for assignment in assignments {
let old_val = self.env.vars.get(&assignment.name).map(|s| s.to_string());
saved.push((assignment.name.clone(), old_val));
let value = match assignment.value.as_ref() {
Some(w) => crate::expand::expand_word_to_string(&mut self.env, w)?,
None => String::new(),
};
let _ = self.env.vars.set(&assignment.name, value);
}
Ok(saved)
}
pub(crate) fn restore_assignments(&mut self, saved: Vec<(String, Option<String>)>) {
for (name, old_val) in saved {
match old_val {
Some(val) => {
let _ = self.env.vars.set(&name, val);
}
None => {
let _ = self.env.vars.unset(&name);
}
}
}
}
}