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::*;
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) -> i32,
runtime: &mut R,
stdin_fd: sys::FileDescriptor,
stdout_fd: sys::FileDescriptor,
stderr_fd: sys::FileDescriptor,
) -> i32 {
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 1;
}
};
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 1;
}
};
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 1;
}
};
state.stdin_fd = owned_stdin;
state.stdout_fd = owned_stdout;
state.stderr_fd = owned_stderr;
let status = run(state, runtime);
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;
status
}
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,
)
}
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,
)
}
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) -> i32,
) -> i32 {
let Ok(redirects) = RedirectGuard::apply(state, runtime, io_redirects) else {
return 1;
};
let status = run_body(state, runtime, body);
redirects.restore(state);
status
}
fn run_redirected_subshell<R: Runtime, T: ?Sized>(
state: &mut ShellState,
runtime: &mut R,
io_redirects: &[IoRedirect],
body: &T,
run_body: impl FnOnce(&mut ShellState, &mut R, &T) -> i32,
) -> i32 {
let Ok(redirects) = RedirectGuard::apply(state, runtime, io_redirects) else {
return 1;
};
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 1;
}
};
let mut child = state.fork_session();
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 1;
}
}
let status = run_body(&mut child, &mut child_runtime, body);
close_subshell_stdio(&child);
redirects.restore(state);
status
}
pub(super) fn run_command<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
cmd: &AstCommand,
) -> i32 {
if state.has_option(OPT_NOEXEC) {
return 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| run_built_command_list_array_from_ast(state, runtime, body),
),
AstCommand::Subshell(group) => run_redirected_subshell(
state,
runtime,
group.io_redirects(),
group.body(),
|state, runtime, body| run_built_command_list_array_from_ast(state, runtime, body),
),
AstCommand::If(if_clause) => run_if_clause_with(
state,
runtime,
if_clause,
run_built_command_list_array_from_ast,
),
AstCommand::For(clause) => run_for_clause_with(
state,
runtime,
clause,
run_built_command_list_array_from_ast,
),
AstCommand::Loop(clause) => run_loop_core(state, runtime, |state, runtime| {
run_loop_clause_iteration(
state,
runtime,
clause,
run_built_command_list_array_from_ast,
)
}),
AstCommand::Case(clause) => run_case_clause_with(
state,
runtime,
clause,
run_built_command_list_array_from_ast,
),
AstCommand::FunctionDef(function_definition) => {
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
}
}
}
struct CommandListOutcome {
status: i32,
}
fn execute_built_command_list<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
cl: &CommandList,
) -> CommandListOutcome {
if cl.ampersand {
return CommandListOutcome {
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);
CommandListOutcome { status }
}
fn execute_planned_command_list<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
cl: &ExecCommandList<'_>,
) -> CommandListOutcome {
if cl.ampersand() {
return CommandListOutcome {
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);
CommandListOutcome { status }
}
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) -> CommandListOutcome,
) -> i32 {
let mut status = 0;
for cl in lists {
if state.exit_code >= 0 || state.branch != BranchControl::None {
break;
}
let outcome = execute_list(state, runtime, cl);
status = outcome.status;
complete_command_list_step(state, runtime, status);
}
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.functions.insert(fd.name.clone(), *fd.body.clone());
0
}