use super::compound::{
complete_command_list_step, run_case_clause_with, run_for_clause_with, run_if_clause_with,
run_loop_clause_iteration, run_loop_core,
};
use super::simple_command::{run_planned_simple_command, run_simple_command};
use super::*;
pub(super) fn duplicate_shell_fd(
fd: sys::FileDescriptor,
) -> Result<sys::FileDescriptor, io::Error> {
if fd.is_valid() {
fd.dup()
} else {
Ok(sys::FileDescriptor::INVALID)
}
}
fn run_with_owned_stdio<R: Runtime>(
state: &mut ShellState,
run: impl FnOnce(&mut ShellState, &mut R) -> CommandOutcome,
runtime: &mut R,
stdin_fd: sys::FileDescriptor,
stdout_fd: sys::FileDescriptor,
stderr_fd: sys::FileDescriptor,
) -> CommandOutcome {
let old_stdin = state.stdin_fd;
let old_stdout = state.stdout_fd;
let old_stderr = state.stderr_fd;
let owned_stdin = match duplicate_shell_fd(stdin_fd) {
Ok(fd) => fd,
Err(err) => {
report_stdio_dup_error(state, "stdin", &err);
return CommandOutcome::from_state(1, state);
}
};
let owned_stdout = match duplicate_shell_fd(stdout_fd) {
Ok(fd) => fd,
Err(err) => {
owned_stdin.close();
report_stdio_dup_error(state, "stdout", &err);
return CommandOutcome::from_state(1, state);
}
};
let owned_stderr = match duplicate_shell_fd(stderr_fd) {
Ok(fd) => fd,
Err(err) => {
owned_stdin.close();
owned_stdout.close();
report_stdio_dup_error(state, "stderr", &err);
return CommandOutcome::from_state(1, state);
}
};
state.stdin_fd = owned_stdin;
state.stdout_fd = owned_stdout;
state.stderr_fd = owned_stderr;
let outcome = run(state, runtime);
let status = if state.exit_code >= 0 {
state.exit_code
} else {
outcome.status
};
for fd in [state.stdin_fd, state.stdout_fd, state.stderr_fd] {
fd.close();
}
state.stdin_fd = old_stdin;
state.stdout_fd = old_stdout;
state.stderr_fd = old_stderr;
CommandOutcome {
status,
control: outcome.control,
}
}
pub(super) fn run_command_with_stdio<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
cmd: &AstCommand,
stdin_fd: sys::FileDescriptor,
stdout_fd: sys::FileDescriptor,
stderr_fd: sys::FileDescriptor,
) -> i32 {
run_with_owned_stdio(
state,
|state, runtime| run_command(state, runtime, cmd),
runtime,
stdin_fd,
stdout_fd,
stderr_fd,
)
.status
}
pub(super) fn run_planned_simple_command_with_stdio<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
plan: &PlannedSimpleCommand<'_>,
stdin_fd: sys::FileDescriptor,
stdout_fd: sys::FileDescriptor,
stderr_fd: sys::FileDescriptor,
) -> i32 {
run_with_owned_stdio(
state,
|state, runtime| run_planned_simple_command(state, runtime, plan),
runtime,
stdin_fd,
stdout_fd,
stderr_fd,
)
.status
}
fn duplicate_subshell_stdio(
state: &ShellState,
) -> Result<
(
sys::FileDescriptor,
sys::FileDescriptor,
sys::FileDescriptor,
),
io::Error,
> {
Ok((
duplicate_shell_fd(state.stdin_fd)?,
duplicate_shell_fd(state.stdout_fd)?,
duplicate_shell_fd(state.stderr_fd)?,
))
}
fn close_subshell_stdio(state: &ShellState) {
for fd in [state.stdin_fd, state.stdout_fd, state.stderr_fd] {
if fd.is_valid() {
fd.close();
}
}
}
fn report_stdio_dup_error(state: &ShellState, stream: &str, err: &io::Error) {
shell_errln(
state,
&state.prefixed_message(format!("failed to duplicate {stream}: {err}")),
);
}
fn run_redirected_body<R: Runtime, T: ?Sized>(
state: &mut ShellState,
runtime: &mut R,
io_redirects: &[IoRedirect],
body: &T,
run_body: impl FnOnce(&mut ShellState, &mut R, &T) -> CommandOutcome,
) -> CommandOutcome {
let Ok(redirects) = RedirectGuard::apply(state, runtime, io_redirects) else {
return CommandOutcome::from_state(1, state);
};
let outcome = run_body(state, runtime, body);
redirects.restore(state);
outcome
}
fn run_redirected_subshell<R: Runtime, T: ?Sized>(
state: &mut ShellState,
runtime: &mut R,
io_redirects: &[IoRedirect],
body: &T,
process_global_guard: bool,
run_body: impl FnOnce(&mut ShellState, &mut R, &T) -> CommandOutcome,
) -> CommandOutcome {
let Ok(redirects) = RedirectGuard::apply(state, runtime, io_redirects) else {
return CommandOutcome::from_state(1, state);
};
let mut child_runtime = match runtime.fork() {
Ok(runtime) => runtime,
Err(err) => {
redirects.restore(state);
shell_errln(
state,
&state.prefixed_message(format!("failed to fork subshell runtime: {err}")),
);
return CommandOutcome::from_state(1, state);
}
};
let mut child = state.fork_for(ExecutionContextKind::Subshell);
match duplicate_subshell_stdio(state) {
Ok(stdio) => {
(child.stdin_fd, child.stdout_fd, child.stderr_fd) = stdio;
}
Err(err) => {
redirects.restore(state);
report_stdio_dup_error(state, "subshell stdio", &err);
return CommandOutcome::from_state(1, state);
}
}
let _process_globals = process_global_guard.then(|| ProcessGlobalGuard::capture_for(&child));
let body_outcome = run_body(&mut child, &mut child_runtime, body);
shell_traps::run_exit_trap(&mut child, &mut child_runtime);
let status = if child.exit_code >= 0 {
child.exit_code
} else {
body_outcome.status
};
close_subshell_stdio(&child);
redirects.restore(state);
CommandOutcome::status(status)
}
pub(super) fn run_command<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
cmd: &AstCommand,
) -> CommandOutcome {
if state.has_option(OPT_NOEXEC) && !state.interactive {
return CommandOutcome::status(0);
}
match cmd {
AstCommand::Simple(sc) => run_simple_command(state, runtime, sc),
AstCommand::BraceGroup(group) => run_redirected_body(
state,
runtime,
group.io_redirects(),
group.body(),
|state, runtime, body| {
let status = run_built_command_list_array_from_ast(state, runtime, body);
CommandOutcome::from_state(status, state)
},
),
AstCommand::Subshell(group) => run_redirected_subshell(
state,
runtime,
group.io_redirects(),
group.body(),
super::exec::command_lists_may_mutate_current_process_globals(state, group.body()),
|state, runtime, body| {
let status = run_built_command_list_array_from_ast(state, runtime, body);
CommandOutcome::from_state(status, state)
},
),
AstCommand::If(if_clause) => run_redirected_body(
state,
runtime,
if_clause.io_redirects(),
if_clause,
|state, runtime, if_clause| {
let status = run_if_clause_with(
state,
runtime,
if_clause,
run_built_command_list_array_from_ast,
);
CommandOutcome::from_state(status, state)
},
),
AstCommand::For(clause) => run_redirected_body(
state,
runtime,
clause.io_redirects(),
clause,
|state, runtime, clause| {
let status = run_for_clause_with(
state,
runtime,
clause,
run_built_command_list_array_from_ast,
);
CommandOutcome::from_state(status, state)
},
),
AstCommand::Loop(clause) => run_redirected_body(
state,
runtime,
clause.io_redirects(),
clause,
|state, runtime, clause| {
let status = run_loop_core(state, runtime, |state, runtime| {
run_loop_clause_iteration(
state,
runtime,
clause,
run_built_command_list_array_from_ast,
)
});
CommandOutcome::from_state(status, state)
},
),
AstCommand::Case(clause) => run_redirected_body(
state,
runtime,
clause.io_redirects(),
clause,
|state, runtime, clause| {
let status = run_case_clause_with(
state,
runtime,
clause,
run_built_command_list_array_from_ast,
);
CommandOutcome::from_state(status, state)
},
),
AstCommand::FunctionDef(function_definition) => {
CommandOutcome::status(run_function_def(state, function_definition))
}
}
}
fn execute_background<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
raw_and_or_list: &AndOrList,
) -> i32 {
if !state.definition.security_policy.allow_background_jobs() {
shell_errln(
state,
"background jobs are disabled by shell security policy",
);
return 1;
}
match shell_jobs::run_background(state, runtime, raw_and_or_list) {
Ok(pid) => {
state.set_last_background_pid(pid);
0
}
Err(err) => {
state.set_last_background_pid(None);
err
}
}
}
fn execute_built_command_list<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
cl: &CommandList,
) -> CommandOutcome {
if cl.ampersand {
return CommandOutcome::status(execute_background(state, runtime, &cl.and_or_list));
}
let planned = exec::build_runtime_and_or_list(state, runtime, &cl.and_or_list);
let status = exec::run_exec_and_or_list(state, runtime, &planned);
CommandOutcome::from_state(status, state)
}
fn execute_planned_command_list<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
cl: &ExecCommandList<'_>,
) -> CommandOutcome {
if cl.ampersand() {
return CommandOutcome::status(execute_background(state, runtime, cl.raw_and_or_list()));
}
let planned = exec::realize_runtime_and_or_list(state, runtime, cl.and_or_list());
let status = exec::run_exec_and_or_list(state, runtime, &planned);
CommandOutcome::from_state(status, state)
}
fn run_command_list_array_with<R: Runtime, T>(
state: &mut ShellState,
runtime: &mut R,
lists: &[T],
mut execute_list: impl FnMut(&mut ShellState, &mut R, &T) -> CommandOutcome,
) -> i32 {
let mut status = 0;
for cl in lists {
if state.exit_code >= 0 || state.control_flow != ControlFlow::None {
break;
}
if state.has_option(OPT_NOEXEC) && !state.interactive {
status = 0;
break;
}
let outcome = execute_list(state, runtime, cl);
status = outcome.status;
complete_command_list_step(state, runtime, status);
if outcome.control != ControlFlow::None {
break;
}
}
status
}
fn run_built_command_list_array_from_ast<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
lists: &[CommandList],
) -> i32 {
run_command_list_array_with(state, runtime, lists, execute_built_command_list)
}
pub(super) fn run_exec_command_list_array<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
lists: &[ExecCommandList<'_>],
) -> i32 {
run_command_list_array_with(state, runtime, lists, execute_planned_command_list)
}
fn run_function_def(state: &mut ShellState, fd: &FunctionDefinition) -> i32 {
state.set_function_definition(
fd.name.clone(),
*fd.body.clone(),
fd.io_redirects().to_vec(),
);
0
}