use super::*;
use crate::plan::{CommandResolutionError, PlannedSimpleCommand, PlannedSimpleCommandKind};
struct PreparedSimpleCommand {
redirects: RedirectGuard,
old_vars: Vec<(String, Option<Variable>)>,
restore_assignments: bool,
}
pub(crate) fn plan_simple_command<'ast, R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
sc: &'ast SimpleCommand,
) -> Result<PlannedSimpleCommand<'ast>, i32> {
shell_resolve::resolve_simple_command(state, runtime, sc)
}
fn prepare_simple_command<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
plan: &PlannedSimpleCommand<'_>,
) -> Result<PreparedSimpleCommand, i32> {
if !matches!(
plan.kind(),
PlannedSimpleCommandKind::AssignmentsOnly { .. }
) {
let argv = plan.argv();
if state.has_option(OPT_XTRACE) && !argv.is_empty() {
let ps4 = state.env_get("PS4").unwrap_or("+ ").to_string();
let trace = format!("{ps4}{}", argv.join(" "));
shell_errln(state, &trace);
}
}
match plan.kind() {
PlannedSimpleCommandKind::AssignmentsOnly { .. } => {
set_assignment_values(
state,
runtime,
plan.assignments(),
plan.assignment_attributes().bits(),
false,
"",
)?;
let redirects = RedirectGuard::apply(state, runtime, plan.redirects())?;
Ok(PreparedSimpleCommand {
redirects,
old_vars: Vec::new(),
restore_assignments: false,
})
}
_ => {
let redirects = RedirectGuard::apply(state, runtime, plan.redirects())?;
let context = plan.command_name().unwrap_or("");
let old_vars = match set_assignment_values(
state,
runtime,
plan.assignments(),
plan.assignment_attributes().bits(),
plan.restore_assignments(),
context,
) {
Ok(old_vars) => old_vars,
Err(status) => {
redirects.restore(state);
return Err(status);
}
};
Ok(PreparedSimpleCommand {
redirects,
old_vars,
restore_assignments: plan.restore_assignments(),
})
}
}
}
fn commit_simple_command(state: &mut ShellState, prepared: PreparedSimpleCommand) {
if prepared.restore_assignments {
restore_assignment_values(state, prepared.old_vars);
}
prepared.redirects.restore(state);
}
pub(super) fn run_planned_simple_command<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
plan: &PlannedSimpleCommand<'_>,
) -> i32 {
let prepared = match prepare_simple_command(state, runtime, plan) {
Ok(prepared) => prepared,
Err(status) => return status,
};
let status = match plan.kind() {
PlannedSimpleCommandKind::AssignmentsOnly {
has_command_substitution,
} => {
if *has_command_substitution {
state.last_status
} else {
0
}
}
PlannedSimpleCommandKind::Function { command_name, argv } => {
let Some(func_body) = state.functions.get(command_name).cloned() else {
commit_simple_command(state, prepared);
return 0;
};
let old_frame = std::mem::replace(&mut state.frame, argv.clone());
state.function_depth += 1;
let status = super::exec::LazyNode::owned_command(
func_body,
DeferredReason::NeedsCurrentShellState,
)
.execute_command(state, runtime);
state.function_depth -= 1;
state.frame = old_frame;
if state.branch == BranchControl::Return {
state.branch = BranchControl::None;
}
status
}
PlannedSimpleCommandKind::Builtin { argv } => {
shell_builtins::run_builtin(state, runtime, argv).unwrap_or(1)
}
PlannedSimpleCommandKind::ShellOverride { argv } => {
shell_resolve::run_shell_override(state, runtime, argv).unwrap_or(1)
}
PlannedSimpleCommandKind::CommandNotFoundHandler { argv } => {
shell_resolve::run_command_not_found_handler(state, runtime, argv).unwrap_or(127)
}
PlannedSimpleCommandKind::External { program, argv } => match runtime
.spawn_external_command(
&sys::ExternalCommand {
program: program.clone(),
argv: argv.clone(),
env: state.exported_env(),
cwd: state.cwd.clone(),
create_process_group: false,
passed_fds: state.inherited_fds(),
},
sys::SpawnStdio {
stdin_fd: state.stdin_fd,
stdout_fd: state.stdout_fd,
stderr_fd: state.stderr_fd,
},
&[],
sys::SpawnMode::Foreground,
) {
Ok(child) => runtime.wait_child(child.handle),
Err(err) => sys::spawn_error_exit_status(&err),
},
PlannedSimpleCommandKind::UnspecifiedUtility { command_name } => {
if state.interactive {
shell_errln(
state,
&format!("{command_name}: The behavior of this command is undefined."),
);
} else {
shell_errln(
state,
&format!(
"{command_name}: The behavior of this command is undefined. This is an error in your script. Aborting."
),
);
state.exit_code = 1;
}
1
}
PlannedSimpleCommandKind::ResolutionFailure {
command_name,
resolution,
} => {
let resolution = match resolution {
CommandResolutionError::NotExecutable(path) => {
shell_resolve::CommandResolution::NotExecutable(path.clone())
}
CommandResolutionError::NotFound => shell_resolve::CommandResolution::NotFound,
};
shell_resolve::report_command_resolution_error(
state,
command_name,
&resolution,
plan.source_line(),
);
shell_resolve::command_failure_status(&resolution)
}
};
commit_simple_command(state, prepared);
status
}
pub(super) fn run_simple_command<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
sc: &SimpleCommand,
) -> i32 {
let plan = match plan_simple_command(state, runtime, sc) {
Ok(plan) => plan,
Err(status) => return status,
};
run_planned_simple_command(state, runtime, &plan)
}