use nix::unistd::{ForkResult, fork};
use crate::env::FlowControl;
use crate::error::{RuntimeErrorKind, ShellError};
use crate::expand::expand_words;
use crate::parser::ast::{
CaseItem, CaseTerminator, CompleteCommand, CompoundCommand, Redirect, Word,
};
use crate::signal;
use super::Executor;
use super::command;
use super::redirect::RedirectState;
impl Executor {
pub(crate) fn exec_compound_command(
&mut self,
compound: &CompoundCommand,
redirects: &[Redirect],
) -> Result<i32, ShellError> {
let _ = self.env.vars.set("LINENO", compound.line.to_string());
let saved = self
.apply_temp_assignments(&compound.assignments)
.inspect_err(|_| {
self.env.exec.last_exit_status = 1;
})?;
let mut redirect_state = RedirectState::new();
if let Err(e) = redirect_state.apply(redirects, &mut self.env, true) {
self.restore_assignments(saved);
self.env.exec.last_exit_status = 1;
return Err(ShellError::runtime(RuntimeErrorKind::RedirectFailed, e));
}
let status = match &compound.kind {
crate::parser::ast::CompoundCommandKind::BraceGroup { body } => {
self.exec_brace_group(body)
}
crate::parser::ast::CompoundCommandKind::Subshell { body } => {
self.exec_subshell(body)?
}
crate::parser::ast::CompoundCommandKind::If {
condition,
then_part,
elif_parts,
else_part,
} => self.exec_if(condition, then_part, elif_parts, else_part),
crate::parser::ast::CompoundCommandKind::While { condition, body } => {
self.exec_loop(condition, body, false)
}
crate::parser::ast::CompoundCommandKind::Until { condition, body } => {
self.exec_loop(condition, body, true)
}
crate::parser::ast::CompoundCommandKind::For { var, words, body } => {
self.exec_for(var, words, body)?
}
crate::parser::ast::CompoundCommandKind::Case { word, items } => {
self.exec_case(word, items)?
}
};
redirect_state.restore();
self.restore_assignments(saved);
self.env.exec.last_exit_status = status;
Ok(status)
}
pub(crate) fn exec_body(&mut self, body: &[CompleteCommand]) -> i32 {
let mut status = 0;
for cmd in body {
status = self.exec_complete_command(cmd);
if self.env.exec.flow_control.is_some() {
break;
}
self.check_errexit(status);
if self.exit_requested.is_some() {
break;
}
self.process_pending_signals();
if self.exit_requested.is_some() {
break;
}
}
status
}
fn exec_brace_group(&mut self, body: &[CompleteCommand]) -> i32 {
self.exec_body(body)
}
fn exec_subshell(&mut self, body: &[CompleteCommand]) -> Result<i32, ShellError> {
match unsafe { fork() } {
Err(e) => Err(ShellError::runtime(
RuntimeErrorKind::IoError,
format!("fork: {}", e),
)),
Ok(ForkResult::Child) => {
let ignored = self.env.traps.ignored_signals();
self.env.traps.reset_for_subshell();
signal::reset_child_signals(&ignored);
let status = self.exec_body(body);
self.execute_exit_trap();
super::exit_child(status);
}
Ok(ForkResult::Parent { child }) => Ok(command::wait_child(child).unwrap_or(1)),
}
}
fn exec_if(
&mut self,
condition: &[CompleteCommand],
then_part: &[CompleteCommand],
elif_parts: &[(Vec<CompleteCommand>, Vec<CompleteCommand>)],
else_part: &Option<Vec<CompleteCommand>>,
) -> i32 {
let cond_status = self.with_errexit_suppressed(|e| e.exec_body(condition));
if self.env.exec.flow_control.is_some() {
return cond_status;
}
if cond_status == 0 {
return self.exec_body(then_part);
}
for (elif_cond, elif_body) in elif_parts {
let cond_status = self.with_errexit_suppressed(|e| e.exec_body(elif_cond));
if self.env.exec.flow_control.is_some() {
return cond_status;
}
if cond_status == 0 {
return self.exec_body(elif_body);
}
}
if let Some(else_body) = else_part {
return self.exec_body(else_body);
}
0
}
fn exec_loop(
&mut self,
condition: &[CompleteCommand],
body: &[CompleteCommand],
until: bool,
) -> i32 {
self.env.exec.loop_depth += 1;
let status = self.exec_loop_inner(condition, body, until);
self.env.exec.loop_depth -= 1;
status
}
fn exec_loop_inner(
&mut self,
condition: &[CompleteCommand],
body: &[CompleteCommand],
until: bool,
) -> i32 {
let mut status = 0;
loop {
let cond_status = self.with_errexit_suppressed(|e| e.exec_body(condition));
if self.env.exec.flow_control.is_some() {
return cond_status;
}
let should_run = if until {
cond_status != 0
} else {
cond_status == 0
};
if !should_run {
break;
}
status = self.exec_body(body);
match self.env.exec.flow_control.take() {
Some(FlowControl::Break(n)) => {
if n > 1 {
self.env.exec.flow_control = Some(FlowControl::Break(n - 1));
}
break;
}
Some(FlowControl::Continue(n)) => {
if n > 1 {
self.env.exec.flow_control = Some(FlowControl::Continue(n - 1));
break;
}
}
Some(other) => {
self.env.exec.flow_control = Some(other);
break;
}
None => {}
}
}
status
}
fn exec_for(
&mut self,
var: &str,
words: &Option<Vec<Word>>,
body: &[CompleteCommand],
) -> Result<i32, ShellError> {
self.env.exec.loop_depth += 1;
let result = self.exec_for_inner(var, words, body);
self.env.exec.loop_depth -= 1;
result
}
fn exec_for_inner(
&mut self,
var: &str,
words: &Option<Vec<Word>>,
body: &[CompleteCommand],
) -> Result<i32, ShellError> {
let items: Vec<String> = match words {
Some(word_list) => match expand_words(&mut self.env, word_list) {
Ok(words) => words,
Err(e) => {
self.env.exec.last_exit_status = 1;
return Err(e);
}
},
None => self.env.vars.positional_params().to_vec(),
};
let mut status = 0;
for item in &items {
if let Err(e) = self.env.vars.set(var, item.as_str()) {
return Err(ShellError::runtime(
RuntimeErrorKind::ReadonlyVariable,
e.to_string(),
));
}
status = self.exec_body(body);
match self.env.exec.flow_control.take() {
Some(FlowControl::Break(n)) => {
if n > 1 {
self.env.exec.flow_control = Some(FlowControl::Break(n - 1));
}
break;
}
Some(FlowControl::Continue(n)) => {
if n > 1 {
self.env.exec.flow_control = Some(FlowControl::Continue(n - 1));
break;
}
}
Some(other) => {
self.env.exec.flow_control = Some(other);
break;
}
None => {}
}
}
Ok(status)
}
fn exec_case(&mut self, word: &Word, items: &[CaseItem]) -> Result<i32, ShellError> {
let case_word = match crate::expand::expand_word_to_string(&mut self.env, word) {
Ok(w) => w,
Err(e) => {
self.env.exec.last_exit_status = 1;
return Err(e);
}
};
let mut status = 0;
let mut falling_through = false;
for item in items {
if !falling_through {
let mut matched = false;
for pattern in &item.patterns {
let pat = match crate::expand::expand_word_to_string(&mut self.env, pattern) {
Ok(p) => p,
Err(e) => {
self.env.exec.last_exit_status = 1;
return Err(e);
}
};
if crate::expand::pattern::matches(&pat, &case_word) {
matched = true;
break;
}
}
if !matched {
continue;
}
}
status = self.exec_body(&item.body);
if self.env.exec.flow_control.is_some() {
break;
}
match item.terminator {
CaseTerminator::Break => break,
CaseTerminator::FallThrough => {
falling_through = true;
}
}
}
Ok(status)
}
}
#[cfg(test)]
mod tests {
use crate::exec::Executor;
use crate::parser::Parser;
#[test]
fn compound_with_assignment_prefix_runs_inside_temp_scope() {
let source = "y=initial\nx=replaced if true; then echo $x; fi\necho post=$x";
let prog = Parser::new(source).parse_program().unwrap();
let mut exec = Executor::new("yosh", vec![]);
exec.exec_program(&prog);
assert_eq!(
exec.env.vars.get("x"),
None,
"x must not leak past compound"
);
assert_eq!(exec.env.vars.get("y"), Some("initial"));
}
}