use std::cell::RefCell;
use std::fs::File;
use std::io::{Write, pipe};
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd};
use std::rc::Rc;
use crate::parser::{Ast, Redirection};
use crate::state::ShellState;
use super::expansion::expand_variables_in_string;
use super::redirection::{apply_redirections, write_file_with_noclobber_public};
use super::execute;
const MAX_SUBSHELL_DEPTH: usize = 100;
pub(crate) fn execute_subshell(body: Ast, shell_state: &mut ShellState) -> i32 {
if shell_state.subshell_depth >= MAX_SUBSHELL_DEPTH {
if shell_state.colors_enabled {
eprintln!(
"{}Subshell nesting limit ({}) exceeded\x1b[0m",
shell_state.color_scheme.error, MAX_SUBSHELL_DEPTH
);
} else {
eprintln!("Subshell nesting limit ({}) exceeded", MAX_SUBSHELL_DEPTH);
}
shell_state.last_exit_code = 1;
return 1;
}
let original_dir = std::env::current_dir().ok();
let mut subshell_state = shell_state.clone();
match shell_state.fd_table.borrow().deep_clone() {
Ok(new_fd_table) => {
subshell_state.fd_table = Rc::new(RefCell::new(new_fd_table));
}
Err(e) => {
if shell_state.colors_enabled {
eprintln!(
"{}Failed to clone file descriptor table: {}\x1b[0m",
shell_state.color_scheme.error, e
);
} else {
eprintln!("Failed to clone file descriptor table: {}", e);
}
return 1;
}
}
subshell_state.subshell_depth = shell_state.subshell_depth + 1;
let parent_traps = shell_state.trap_handlers.lock().unwrap().clone();
subshell_state.trap_handlers = std::sync::Arc::new(std::sync::Mutex::new(parent_traps));
let exit_code = execute(body, &mut subshell_state);
let final_exit_code = if subshell_state.exit_requested {
subshell_state.exit_code
} else if subshell_state.is_returning() {
subshell_state.get_return_value().unwrap_or(exit_code)
} else {
exit_code
};
subshell_state.fd_table.borrow_mut().clear();
if let Some(dir) = original_dir {
let _ = std::env::set_current_dir(dir);
}
shell_state.last_exit_code = final_exit_code;
final_exit_code
}
pub(crate) fn execute_compound_with_redirections(
compound_ast: &Ast,
shell_state: &mut ShellState,
redirections: &[Redirection],
) -> i32 {
match compound_ast {
Ast::CommandGroup { body } => {
if let Err(e) = shell_state.fd_table.borrow_mut().save_all_fds() {
eprintln!("Error saving FDs: {}", e);
return 1;
}
if let Err(e) = apply_redirections(redirections, shell_state, None) {
if shell_state.colors_enabled {
eprintln!("{}{}\u{001b}[0m", shell_state.color_scheme.error, e);
} else {
eprintln!("{}", e);
}
shell_state.fd_table.borrow_mut().restore_all_fds().ok();
return 1;
}
let exit_code = execute(*body.clone(), shell_state);
if let Err(e) = shell_state.fd_table.borrow_mut().restore_all_fds() {
eprintln!("Error restoring FDs: {}", e);
}
exit_code
}
Ast::Subshell { body } => {
let has_output_redir = redirections.iter().any(|r| {
matches!(
r,
Redirection::Output(_)
| Redirection::Append(_)
| Redirection::FdOutput(_, _)
| Redirection::FdAppend(_, _)
)
});
if has_output_redir {
let mut subshell_state = shell_state.clone();
let capture_buffer = Rc::new(RefCell::new(Vec::new()));
subshell_state.capture_output = Some(capture_buffer.clone());
let exit_code = execute(*body.clone(), &mut subshell_state);
let output = capture_buffer.borrow().clone();
for redir in redirections {
match redir {
Redirection::Output(file) => {
let expanded_file = expand_variables_in_string(file, shell_state);
if let Err(e) = write_file_with_noclobber_public(
&expanded_file,
&output,
shell_state.options.noclobber,
false, shell_state,
) {
eprintln!("Redirection error: {}", e);
return 1;
}
}
Redirection::OutputClobber(file) => {
let expanded_file = expand_variables_in_string(file, shell_state);
if let Err(e) = write_file_with_noclobber_public(
&expanded_file,
&output,
false, true, shell_state,
) {
eprintln!("Redirection error: {}", e);
return 1;
}
}
Redirection::Append(file) => {
let expanded_file = expand_variables_in_string(file, shell_state);
use std::fs::OpenOptions;
let mut file_handle = match OpenOptions::new()
.append(true)
.create(true)
.open(&expanded_file)
{
Ok(f) => f,
Err(e) => {
if shell_state.colors_enabled {
eprintln!(
"{}Redirection error: {}\x1b[0m",
shell_state.color_scheme.error, e
);
} else {
eprintln!("Redirection error: {}", e);
}
return 1;
}
};
if let Err(e) = file_handle.write_all(&output) {
if shell_state.colors_enabled {
eprintln!(
"{}Redirection error: {}\x1b[0m",
shell_state.color_scheme.error, e
);
} else {
eprintln!("Redirection error: {}", e);
}
return 1;
}
}
_ => {
}
}
}
shell_state.last_exit_code = exit_code;
exit_code
} else {
execute_subshell(*body.clone(), shell_state)
}
}
_ => {
eprintln!("Unsupported compound command type");
1
}
}
}
fn has_stdout_redirection(redirections: &[Redirection]) -> bool {
redirections.iter().any(|r| match r {
Redirection::Output(_) | Redirection::OutputClobber(_) | Redirection::Append(_) => true,
Redirection::FdOutput(1, _) | Redirection::FdAppend(1, _) => true,
Redirection::FdDuplicate(1, _) | Redirection::FdClose(1) => true,
_ => false,
})
}
pub(crate) fn execute_compound_in_pipeline(
compound_ast: &Ast,
shell_state: &mut ShellState,
stdin: Option<File>,
is_first: bool,
is_last: bool,
redirections: &[Redirection],
) -> (i32, Option<File>) {
match compound_ast {
Ast::Subshell { body } | Ast::CommandGroup { body } => {
let mut subshell_state = shell_state.clone();
let mut _stdin_file = stdin;
if let Some(ref f) = _stdin_file {
let fd = f.as_raw_fd();
subshell_state.stdin_override = Some(fd);
} else if !is_first && subshell_state.stdin_override.is_none() {
if let Ok(f) = File::open("/dev/null") {
subshell_state.stdin_override = Some(f.as_raw_fd());
_stdin_file = Some(f);
}
}
let capture_buffer = if (!is_last || shell_state.capture_output.is_some())
&& !has_stdout_redirection(redirections)
{
let buffer = Rc::new(RefCell::new(Vec::new()));
subshell_state.capture_output = Some(buffer.clone());
Some(buffer)
} else {
None
};
let exit_code = if matches!(compound_ast, Ast::CommandGroup { .. }) {
if let Err(e) = subshell_state.fd_table.borrow_mut().save_all_fds() {
eprintln!("Error saving FDs: {}", e);
return (1, None);
}
if let Some(ref f) = _stdin_file {
unsafe {
libc::dup2(f.as_raw_fd(), 0);
}
}
if let Err(e) = apply_redirections(redirections, &mut subshell_state, None) {
if subshell_state.colors_enabled {
eprintln!("{}{}\u{001b}[0m", subshell_state.color_scheme.error, e);
} else {
eprintln!("{}", e);
}
subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
return (1, None);
}
let code = execute(*body.clone(), &mut subshell_state);
if let Err(e) = subshell_state.fd_table.borrow_mut().restore_all_fds() {
eprintln!("Error restoring FDs: {}", e);
}
code
} else {
if let Err(e) = subshell_state.fd_table.borrow_mut().save_all_fds() {
eprintln!("Error saving FDs: {}", e);
return (1, None);
}
if let Some(ref f) = _stdin_file {
unsafe {
libc::dup2(f.as_raw_fd(), 0);
}
}
if let Err(e) = apply_redirections(redirections, &mut subshell_state, None) {
eprintln!("{}", e);
subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
return (1, None);
}
let code = execute(*body.clone(), &mut subshell_state);
subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
code
};
let mut next_stdout = None;
if let Some(buffer) = capture_buffer {
let captured = buffer.borrow().clone();
if !is_last {
use std::io::Write;
let (reader, mut writer) = match pipe() {
Ok((r, w)) => (r, w),
Err(e) => {
eprintln!("Error creating pipe for compound command: {}", e);
return (exit_code, None);
}
};
if let Err(e) = writer.write_all(&captured) {
eprintln!("Error writing to pipe: {}", e);
}
drop(writer);
next_stdout = Some(unsafe { File::from_raw_fd(reader.into_raw_fd()) });
}
if let Some(ref parent_capture) = shell_state.capture_output {
parent_capture.borrow_mut().extend_from_slice(&captured);
}
}
shell_state.last_exit_code = exit_code;
(exit_code, next_stdout)
}
_ => {
eprintln!("Unsupported compound command in pipeline");
(1, None)
}
}
}