use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Write, pipe};
use std::os::fd::AsRawFd;
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
use super::expansion::expand_variables_in_string;
use crate::parser::Redirection;
use crate::state::ShellState;
fn write_file_with_noclobber(
path: &str,
data: &[u8],
noclobber: bool,
force_clobber: bool,
shell_state: &ShellState,
) -> Result<(), String> {
use std::fs::OpenOptions;
if noclobber && !force_clobber {
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.map_err(|e| {
if e.kind() == std::io::ErrorKind::AlreadyExists {
if shell_state.colors_enabled {
format!(
"{}cannot overwrite existing file '{}' (noclobber is set)\x1b[0m",
shell_state.color_scheme.error, path
)
} else {
format!(
"cannot overwrite existing file '{}' (noclobber is set)",
path
)
}
} else if shell_state.colors_enabled {
format!(
"{}Cannot create {}: {}\x1b[0m",
shell_state.color_scheme.error, path, e
)
} else {
format!("Cannot create {}: {}", path, e)
}
})?;
file.write_all(data).map_err(|e| {
if shell_state.colors_enabled {
format!(
"{}Failed to write to {}: {}\x1b[0m",
shell_state.color_scheme.error, path, e
)
} else {
format!("Failed to write to {}: {}", path, e)
}
})?;
} else {
std::fs::write(path, data).map_err(|e| {
if shell_state.colors_enabled {
format!(
"{}Cannot write to {}: {}\x1b[0m",
shell_state.color_scheme.error, path, e
)
} else {
format!("Cannot write to {}: {}", path, e)
}
})?;
}
Ok(())
}
pub(crate) fn collect_here_document_content(
delimiter: &str,
shell_state: &mut ShellState,
) -> String {
if let Some(content) = shell_state.pending_heredoc_content.take() {
return content;
}
let stdin = std::io::stdin();
let mut reader = BufReader::new(stdin.lock());
let mut content = String::new();
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line) {
Ok(0) => {
break;
}
Ok(_) => {
let line_content = line.trim_end();
if line_content == delimiter {
break;
} else {
content.push_str(&line);
}
}
Err(e) => {
if shell_state.colors_enabled {
eprintln!(
"{}Error reading here-document content: {}\x1b[0m",
shell_state.color_scheme.error, e
);
} else {
eprintln!("Error reading here-document content: {}", e);
}
break;
}
}
}
content
}
pub fn apply_redirections(
redirections: &[Redirection],
shell_state: &mut ShellState,
mut command: Option<&mut Command>,
) -> Result<(), String> {
for redir in redirections {
match redir {
Redirection::Input(file) => {
apply_input_redirection(0, file, shell_state, command.as_deref_mut())?;
}
Redirection::Output(file) => {
apply_output_redirection(
1,
file,
false,
false,
shell_state,
command.as_deref_mut(),
)?;
}
Redirection::OutputClobber(file) => {
apply_output_redirection(
1,
file,
false,
true,
shell_state,
command.as_deref_mut(),
)?;
}
Redirection::Append(file) => {
apply_output_redirection(
1,
file,
true,
false,
shell_state,
command.as_deref_mut(),
)?;
}
Redirection::FdInput(fd, file) => {
apply_input_redirection(*fd, file, shell_state, command.as_deref_mut())?;
}
Redirection::FdOutput(fd, file) => {
apply_output_redirection(
*fd,
file,
false,
false,
shell_state,
command.as_deref_mut(),
)?;
}
Redirection::FdOutputClobber(fd, file) => {
apply_output_redirection(
*fd,
file,
false,
true,
shell_state,
command.as_deref_mut(),
)?;
}
Redirection::FdAppend(fd, file) => {
apply_output_redirection(
*fd,
file,
true,
false,
shell_state,
command.as_deref_mut(),
)?;
}
Redirection::FdDuplicate(target_fd, source_fd) => {
apply_fd_duplication(*target_fd, *source_fd, shell_state, command.as_deref_mut())?;
}
Redirection::FdClose(fd) => {
apply_fd_close(*fd, shell_state, command.as_deref_mut())?;
}
Redirection::FdInputOutput(fd, file) => {
apply_fd_input_output(*fd, file, shell_state, command.as_deref_mut())?;
}
Redirection::HereDoc(delimiter, quoted_str) => {
let quoted = quoted_str == "true";
apply_heredoc_redirection(
0,
delimiter,
quoted,
shell_state,
command.as_deref_mut(),
)?;
}
Redirection::HereString(content) => {
apply_herestring_redirection(0, content, shell_state, command.as_deref_mut())?;
}
}
}
Ok(())
}
fn apply_input_redirection(
fd: i32,
file: &str,
shell_state: &mut ShellState,
command: Option<&mut Command>,
) -> Result<(), String> {
let expanded_file = expand_variables_in_string(file, shell_state);
let file_handle =
File::open(&expanded_file).map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?;
if fd == 0 {
if let Some(cmd) = command {
cmd.stdin(Stdio::from(file_handle));
} else {
shell_state.fd_table.borrow_mut().open_fd(
0,
&expanded_file,
true, false, false, false, false, )?;
let raw_fd = shell_state.fd_table.borrow().get_raw_fd(0);
if let Some(rfd) = raw_fd
&& rfd != 0
{
unsafe {
if libc::dup2(rfd, 0) < 0 {
return Err(format!("Failed to dup2 fd {} to 0", rfd));
}
}
}
}
} else {
let fd_file = File::open(&expanded_file)
.map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?;
shell_state.fd_table.borrow_mut().open_fd(
fd,
&expanded_file,
true, false, false, false, false, )?;
if let Some(cmd) = command {
let target_fd = fd;
unsafe {
cmd.pre_exec(move || {
let raw_fd = fd_file.as_raw_fd();
if raw_fd != target_fd {
let result = libc::dup2(raw_fd, target_fd);
if result < 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(())
});
}
}
}
Ok(())
}
fn apply_output_redirection(
fd: i32,
file: &str,
append: bool,
force_clobber: bool,
shell_state: &mut ShellState,
command: Option<&mut Command>,
) -> Result<(), String> {
let expanded_file = expand_variables_in_string(file, shell_state);
let file_handle = if append {
OpenOptions::new()
.append(true)
.create(true)
.open(&expanded_file)
.map_err(|e| {
if shell_state.colors_enabled {
format!(
"{}Cannot open {}: {}\x1b[0m",
shell_state.color_scheme.error, expanded_file, e
)
} else {
format!("Cannot open {}: {}", expanded_file, e)
}
})?
} else if shell_state.options.noclobber && !force_clobber {
OpenOptions::new()
.write(true)
.create_new(true)
.open(&expanded_file)
.map_err(|e| {
if e.kind() == std::io::ErrorKind::AlreadyExists {
if shell_state.colors_enabled {
format!(
"{}cannot overwrite existing file '{}' (noclobber is set)\x1b[0m",
shell_state.color_scheme.error, expanded_file
)
} else {
format!(
"cannot overwrite existing file '{}' (noclobber is set)",
expanded_file
)
}
} else if shell_state.colors_enabled {
format!(
"{}Cannot create {}: {}\x1b[0m",
shell_state.color_scheme.error, expanded_file, e
)
} else {
format!("Cannot create {}: {}", expanded_file, e)
}
})?
} else {
File::create(&expanded_file).map_err(|e| {
if shell_state.colors_enabled {
format!(
"{}Cannot create {}: {}\x1b[0m",
shell_state.color_scheme.error, expanded_file, e
)
} else {
format!("Cannot create {}: {}", expanded_file, e)
}
})?
};
if let Some(cmd) = command {
if fd == 1 {
cmd.stdout(Stdio::from(file_handle));
} else if fd == 2 {
cmd.stderr(Stdio::from(file_handle));
} else {
shell_state.fd_table.borrow_mut().open_fd(
fd,
&expanded_file,
false, true, append,
!append, false, )?;
}
} else {
if shell_state.options.noclobber && !force_clobber && !append {
if std::path::Path::new(&expanded_file).exists() {
let error_msg = if shell_state.colors_enabled {
format!(
"{}cannot overwrite existing file '{}' (noclobber is set)\x1b[0m",
shell_state.color_scheme.error, expanded_file
)
} else {
format!(
"cannot overwrite existing file '{}' (noclobber is set)",
expanded_file
)
};
return Err(error_msg);
}
}
shell_state.fd_table.borrow_mut().open_fd(
fd,
&expanded_file,
false, true, append,
!append, shell_state.options.noclobber && !force_clobber && !append, )?;
let raw_fd = shell_state.fd_table.borrow().get_raw_fd(fd);
if let Some(rfd) = raw_fd {
if rfd != fd {
unsafe {
if libc::dup2(rfd, fd) < 0 {
return Err(format!("Failed to dup2 fd {} to {}", rfd, fd));
}
}
}
}
}
Ok(())
}
fn apply_fd_duplication(
target_fd: i32,
source_fd: i32,
shell_state: &mut ShellState,
_command: Option<&mut Command>,
) -> Result<(), String> {
if shell_state.fd_table.borrow().is_closed(source_fd) {
let error_msg = format!("File descriptor {} is closed", source_fd);
if shell_state.colors_enabled {
eprintln!(
"{}Redirection error: {}\x1b[0m",
shell_state.color_scheme.error, error_msg
);
} else {
eprintln!("Redirection error: {}", error_msg);
}
return Err(error_msg);
}
shell_state
.fd_table
.borrow_mut()
.duplicate_fd(source_fd, target_fd)?;
Ok(())
}
fn apply_fd_close(
fd: i32,
shell_state: &mut ShellState,
command: Option<&mut Command>,
) -> Result<(), String> {
shell_state.fd_table.borrow_mut().close_fd(fd)?;
if let Some(cmd) = command {
match fd {
0 => {
cmd.stdin(Stdio::null());
}
1 => {
cmd.stdout(Stdio::null());
}
2 => {
cmd.stderr(Stdio::null());
}
_ => {
}
}
}
Ok(())
}
fn apply_fd_input_output(
fd: i32,
file: &str,
shell_state: &mut ShellState,
_command: Option<&mut Command>,
) -> Result<(), String> {
let expanded_file = expand_variables_in_string(file, shell_state);
shell_state.fd_table.borrow_mut().open_fd(
fd,
&expanded_file,
true, true, false, false, false, )?;
Ok(())
}
fn apply_heredoc_redirection(
fd: i32,
delimiter: &str,
quoted: bool,
shell_state: &mut ShellState,
command: Option<&mut Command>,
) -> Result<(), String> {
let here_doc_content = collect_here_document_content(delimiter, shell_state);
let expanded_content = if quoted {
here_doc_content
} else {
expand_variables_in_string(&here_doc_content, shell_state)
};
let (reader, mut writer) =
pipe().map_err(|e| format!("Failed to create pipe for here-document: {}", e))?;
writeln!(writer, "{}", expanded_content)
.map_err(|e| format!("Failed to write here-document content: {}", e))?;
if fd == 0
&& let Some(cmd) = command
{
cmd.stdin(Stdio::from(reader));
}
Ok(())
}
fn apply_herestring_redirection(
fd: i32,
content: &str,
shell_state: &mut ShellState,
command: Option<&mut Command>,
) -> Result<(), String> {
let expanded_content = expand_variables_in_string(content, shell_state);
let (reader, mut writer) =
pipe().map_err(|e| format!("Failed to create pipe for here-string: {}", e))?;
write!(writer, "{}", expanded_content)
.map_err(|e| format!("Failed to write here-string content: {}", e))?;
if fd == 0
&& let Some(cmd) = command
{
cmd.stdin(Stdio::from(reader));
}
Ok(())
}
pub(crate) fn write_file_with_noclobber_public(
path: &str,
data: &[u8],
noclobber: bool,
force_clobber: bool,
shell_state: &ShellState,
) -> Result<(), String> {
write_file_with_noclobber(path, data, noclobber, force_clobber, shell_state)
}