1use std::cell::RefCell;
2use std::fs::{File, OpenOptions};
3use std::io::{BufRead, BufReader, Write, pipe};
4use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
5use std::os::unix::process::CommandExt;
6use std::process::{Command, Stdio};
7use std::rc::Rc;
8
9use super::parser::{Ast, Redirection, ShellCommand};
10use super::state::ShellState;
11
12const MAX_SUBSHELL_DEPTH: usize = 100;
14
15fn execute_and_capture_output(ast: Ast, shell_state: &mut ShellState) -> Result<String, String> {
18 let (reader, writer) = pipe().map_err(|e| format!("Failed to create pipe: {}", e))?;
20
21 match &ast {
26 Ast::Pipeline(commands) => {
27 if commands.is_empty() {
29 return Ok(String::new());
30 }
31
32 if commands.len() == 1 {
33 let cmd = &commands[0];
35 if cmd.args.is_empty() {
36 return Ok(String::new());
37 }
38
39 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
41 let expanded_args = expand_wildcards(&var_expanded_args)
42 .map_err(|e| format!("Wildcard expansion failed: {}", e))?;
43
44 if expanded_args.is_empty() {
45 return Ok(String::new());
46 }
47
48 if shell_state.get_function(&expanded_args[0]).is_some() {
50 let previous_capture = shell_state.capture_output.clone();
52
53 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
55 shell_state.capture_output = Some(capture_buffer.clone());
56
57 let function_call_ast = Ast::FunctionCall {
59 name: expanded_args[0].clone(),
60 args: expanded_args[1..].to_vec(),
61 };
62
63 let exit_code = execute(function_call_ast, shell_state);
64
65 let captured = capture_buffer.borrow().clone();
67 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
68
69 shell_state.capture_output = previous_capture;
71
72 if exit_code == 0 {
73 Ok(output)
74 } else {
75 Err(format!("Function failed with exit code {}", exit_code))
76 }
77 } else if crate::builtins::is_builtin(&expanded_args[0]) {
78 let temp_cmd = ShellCommand {
79 args: expanded_args,
80 redirections: cmd.redirections.clone(),
81 compound: None,
82 };
83
84 let exit_code = crate::builtins::execute_builtin(
86 &temp_cmd,
87 shell_state,
88 Some(Box::new(writer)),
89 );
90
91 drop(temp_cmd); let mut output = String::new();
94 use std::io::Read;
95 let mut reader = reader;
96 reader
97 .read_to_string(&mut output)
98 .map_err(|e| format!("Failed to read output: {}", e))?;
99
100 if exit_code == 0 {
101 Ok(output.trim_end().to_string())
102 } else {
103 Err(format!("Command failed with exit code {}", exit_code))
104 }
105 } else {
106 drop(writer); let mut command = Command::new(&expanded_args[0]);
110 command.args(&expanded_args[1..]);
111 command.stdout(Stdio::piped());
112 command.stderr(Stdio::null()); let child_env = shell_state.get_env_for_child();
116 command.env_clear();
117 for (key, value) in child_env {
118 command.env(key, value);
119 }
120
121 let output = command
122 .output()
123 .map_err(|e| format!("Failed to execute command: {}", e))?;
124
125 if output.status.success() {
126 Ok(String::from_utf8_lossy(&output.stdout)
127 .trim_end()
128 .to_string())
129 } else {
130 Err(format!(
131 "Command failed with exit code {}",
132 output.status.code().unwrap_or(1)
133 ))
134 }
135 }
136 } else {
137 drop(writer); let previous_capture = shell_state.capture_output.clone();
142
143 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
145 shell_state.capture_output = Some(capture_buffer.clone());
146
147 let exit_code = execute_pipeline(commands, shell_state);
149
150 let captured = capture_buffer.borrow().clone();
152 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
153
154 shell_state.capture_output = previous_capture;
156
157 if exit_code == 0 {
158 Ok(output)
159 } else {
160 Err(format!("Pipeline failed with exit code {}", exit_code))
161 }
162 }
163 }
164 _ => {
165 drop(writer);
167
168 let previous_capture = shell_state.capture_output.clone();
170
171 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
173 shell_state.capture_output = Some(capture_buffer.clone());
174
175 let exit_code = execute(ast, shell_state);
177
178 let captured = capture_buffer.borrow().clone();
180 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
181
182 shell_state.capture_output = previous_capture;
184
185 if exit_code == 0 {
186 Ok(output)
187 } else {
188 Err(format!("Command failed with exit code {}", exit_code))
189 }
190 }
191 }
192}
193
194fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
195 let mut expanded_args = Vec::new();
196
197 for arg in args {
198 let expanded_arg = expand_variables_in_string(arg, shell_state);
200 expanded_args.push(expanded_arg);
201 }
202
203 expanded_args
204}
205
206pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
207 let mut result = String::new();
208 let mut chars = input.chars().peekable();
209
210 while let Some(ch) = chars.next() {
211 if ch == '$' {
212 if let Some(&'(') = chars.peek() {
214 chars.next(); if let Some(&'(') = chars.peek() {
218 chars.next(); let mut arithmetic_expr = String::new();
221 let mut paren_depth = 1;
222 let mut found_closing = false;
223
224 while let Some(c) = chars.next() {
225 if c == '(' {
226 paren_depth += 1;
227 arithmetic_expr.push(c);
228 } else if c == ')' {
229 paren_depth -= 1;
230 if paren_depth == 0 {
231 if let Some(&')') = chars.peek() {
233 chars.next(); found_closing = true;
235 break;
236 } else {
237 result.push_str("$((");
239 result.push_str(&arithmetic_expr);
240 result.push(')');
241 break;
242 }
243 }
244 arithmetic_expr.push(c);
245 } else {
246 arithmetic_expr.push(c);
247 }
248 }
249
250 if found_closing {
251 let mut expanded_expr = String::new();
255 let mut expr_chars = arithmetic_expr.chars().peekable();
256
257 while let Some(ch) = expr_chars.next() {
258 if ch == '$' {
259 let mut var_name = String::new();
261 if let Some(&c) = expr_chars.peek() {
262 if c == '?'
263 || c == '$'
264 || c == '0'
265 || c == '#'
266 || c == '*'
267 || c == '@'
268 || c.is_ascii_digit()
269 {
270 var_name.push(c);
271 expr_chars.next();
272 } else {
273 while let Some(&c) = expr_chars.peek() {
274 if c.is_alphanumeric() || c == '_' {
275 var_name.push(c);
276 expr_chars.next();
277 } else {
278 break;
279 }
280 }
281 }
282 }
283
284 if !var_name.is_empty() {
285 if let Some(value) = shell_state.get_var(&var_name) {
286 expanded_expr.push_str(&value);
287 } else {
288 expanded_expr.push('0');
290 }
291 } else {
292 expanded_expr.push('$');
293 }
294 } else {
295 expanded_expr.push(ch);
296 }
297 }
298
299 match crate::arithmetic::evaluate_arithmetic_expression(
300 &expanded_expr,
301 shell_state,
302 ) {
303 Ok(value) => {
304 result.push_str(&value.to_string());
305 }
306 Err(e) => {
307 if shell_state.colors_enabled {
309 result.push_str(&format!(
310 "{}arithmetic error: {}{}",
311 shell_state.color_scheme.error, e, "\x1b[0m"
312 ));
313 } else {
314 result.push_str(&format!("arithmetic error: {}", e));
315 }
316 }
317 }
318 } else {
319 result.push_str("$((");
321 result.push_str(&arithmetic_expr);
322 }
324 continue;
325 }
326
327 let mut sub_command = String::new();
329 let mut paren_depth = 1;
330
331 for c in chars.by_ref() {
332 if c == '(' {
333 paren_depth += 1;
334 sub_command.push(c);
335 } else if c == ')' {
336 paren_depth -= 1;
337 if paren_depth == 0 {
338 break;
339 }
340 sub_command.push(c);
341 } else {
342 sub_command.push(c);
343 }
344 }
345
346 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
349 let expanded_tokens = match crate::lexer::expand_aliases(
351 tokens,
352 shell_state,
353 &mut std::collections::HashSet::new(),
354 ) {
355 Ok(t) => t,
356 Err(_) => {
357 result.push_str("$(");
359 result.push_str(&sub_command);
360 result.push(')');
361 continue;
362 }
363 };
364
365 match crate::parser::parse(expanded_tokens) {
366 Ok(ast) => {
367 match execute_and_capture_output(ast, shell_state) {
369 Ok(output) => {
370 result.push_str(&output);
371 }
372 Err(_) => {
373 result.push_str("$(");
375 result.push_str(&sub_command);
376 result.push(')');
377 }
378 }
379 }
380 Err(_parse_err) => {
381 let tokens_str = sub_command.trim();
383 if tokens_str.contains(' ') {
384 let parts: Vec<&str> = tokens_str.split_whitespace().collect();
386 if let Some(first_token) = parts.first()
387 && shell_state.get_function(first_token).is_some()
388 {
389 let function_call = Ast::FunctionCall {
391 name: first_token.to_string(),
392 args: parts[1..].iter().map(|s| s.to_string()).collect(),
393 };
394 match execute_and_capture_output(function_call, shell_state) {
395 Ok(output) => {
396 result.push_str(&output);
397 continue;
398 }
399 Err(_) => {
400 }
402 }
403 }
404 }
405 result.push_str("$(");
407 result.push_str(&sub_command);
408 result.push(')');
409 }
410 }
411 } else {
412 result.push_str("$(");
414 result.push_str(&sub_command);
415 result.push(')');
416 }
417 } else {
418 let mut var_name = String::new();
420 let mut next_ch = chars.peek();
421
422 if let Some(&c) = next_ch {
424 if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
425 var_name.push(c);
426 chars.next(); } else if c.is_ascii_digit() {
428 var_name.push(c);
430 chars.next();
431 } else {
432 while let Some(&c) = next_ch {
434 if c.is_alphanumeric() || c == '_' {
435 var_name.push(c);
436 chars.next(); next_ch = chars.peek();
438 } else {
439 break;
440 }
441 }
442 }
443 }
444
445 if !var_name.is_empty() {
446 if let Some(value) = shell_state.get_var(&var_name) {
447 result.push_str(&value);
448 } else {
449 if var_name.chars().next().unwrap().is_ascii_digit()
452 || var_name == "?"
453 || var_name == "$"
454 || var_name == "0"
455 || var_name == "#"
456 || var_name == "*"
457 || var_name == "@"
458 {
459 } else {
461 result.push('$');
463 result.push_str(&var_name);
464 }
465 }
466 } else {
467 result.push('$');
468 }
469 }
470 } else if ch == '`' {
471 let mut sub_command = String::new();
473
474 for c in chars.by_ref() {
475 if c == '`' {
476 break;
477 }
478 sub_command.push(c);
479 }
480
481 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
483 let expanded_tokens = match crate::lexer::expand_aliases(
485 tokens,
486 shell_state,
487 &mut std::collections::HashSet::new(),
488 ) {
489 Ok(t) => t,
490 Err(_) => {
491 result.push('`');
493 result.push_str(&sub_command);
494 result.push('`');
495 continue;
496 }
497 };
498
499 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
500 match execute_and_capture_output(ast, shell_state) {
502 Ok(output) => {
503 result.push_str(&output);
504 }
505 Err(_) => {
506 result.push('`');
508 result.push_str(&sub_command);
509 result.push('`');
510 }
511 }
512 } else {
513 result.push('`');
515 result.push_str(&sub_command);
516 result.push('`');
517 }
518 } else {
519 result.push('`');
521 result.push_str(&sub_command);
522 result.push('`');
523 }
524 } else {
525 result.push(ch);
526 }
527 }
528
529 result
530}
531
532fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
533 let mut expanded_args = Vec::new();
534
535 for arg in args {
536 if arg.contains('*') || arg.contains('?') || arg.contains('[') {
537 match glob::glob(arg) {
539 Ok(paths) => {
540 let mut matches: Vec<String> = paths
541 .filter_map(|p| p.ok())
542 .map(|p| p.to_string_lossy().to_string())
543 .collect();
544 if matches.is_empty() {
545 expanded_args.push(arg.clone());
547 } else {
548 matches.sort();
550 expanded_args.extend(matches);
551 }
552 }
553 Err(_e) => {
554 expanded_args.push(arg.clone());
556 }
557 }
558 } else {
559 expanded_args.push(arg.clone());
560 }
561 }
562 Ok(expanded_args)
563}
564
565fn collect_here_document_content(delimiter: &str, shell_state: &mut ShellState) -> String {
569 if let Some(content) = shell_state.pending_heredoc_content.take() {
571 return content;
572 }
573
574 let stdin = std::io::stdin();
576 let mut reader = BufReader::new(stdin.lock());
577 let mut content = String::new();
578 let mut line = String::new();
579
580 loop {
581 line.clear();
582 match reader.read_line(&mut line) {
583 Ok(0) => {
584 break;
586 }
587 Ok(_) => {
588 let line_content = line.trim_end();
590 if line_content == delimiter {
591 break;
593 } else {
594 content.push_str(&line);
596 }
597 }
598 Err(e) => {
599 if shell_state.colors_enabled {
600 eprintln!(
601 "{}Error reading here-document content: {}\x1b[0m",
602 shell_state.color_scheme.error, e
603 );
604 } else {
605 eprintln!("Error reading here-document content: {}", e);
606 }
607 break;
608 }
609 }
610 }
611
612 content
613}
614
615fn apply_redirections(
626 redirections: &[Redirection],
627 shell_state: &mut ShellState,
628 mut command: Option<&mut Command>,
629) -> Result<(), String> {
630 for redir in redirections {
632 match redir {
633 Redirection::Input(file) => {
634 apply_input_redirection(0, file, shell_state, command.as_deref_mut())?;
635 }
636 Redirection::Output(file) => {
637 apply_output_redirection(1, file, false, shell_state, command.as_deref_mut())?;
638 }
639 Redirection::Append(file) => {
640 apply_output_redirection(1, file, true, shell_state, command.as_deref_mut())?;
641 }
642 Redirection::FdInput(fd, file) => {
643 apply_input_redirection(*fd, file, shell_state, command.as_deref_mut())?;
644 }
645 Redirection::FdOutput(fd, file) => {
646 apply_output_redirection(*fd, file, false, shell_state, command.as_deref_mut())?;
647 }
648 Redirection::FdAppend(fd, file) => {
649 apply_output_redirection(*fd, file, true, shell_state, command.as_deref_mut())?;
650 }
651 Redirection::FdDuplicate(target_fd, source_fd) => {
652 apply_fd_duplication(*target_fd, *source_fd, shell_state, command.as_deref_mut())?;
653 }
654 Redirection::FdClose(fd) => {
655 apply_fd_close(*fd, shell_state, command.as_deref_mut())?;
656 }
657 Redirection::FdInputOutput(fd, file) => {
658 apply_fd_input_output(*fd, file, shell_state, command.as_deref_mut())?;
659 }
660 Redirection::HereDoc(delimiter, quoted_str) => {
661 let quoted = quoted_str == "true";
662 apply_heredoc_redirection(
663 0,
664 delimiter,
665 quoted,
666 shell_state,
667 command.as_deref_mut(),
668 )?;
669 }
670 Redirection::HereString(content) => {
671 apply_herestring_redirection(0, content, shell_state, command.as_deref_mut())?;
672 }
673 }
674 }
675 Ok(())
676}
677
678fn apply_input_redirection(
680 fd: i32,
681 file: &str,
682 shell_state: &mut ShellState,
683 command: Option<&mut Command>,
684) -> Result<(), String> {
685 let expanded_file = expand_variables_in_string(file, shell_state);
686
687 let file_handle =
689 File::open(&expanded_file).map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?;
690
691 if fd == 0 {
692 if let Some(cmd) = command {
694 cmd.stdin(Stdio::from(file_handle));
695 } else {
696 shell_state.fd_table.borrow_mut().open_fd(
698 0,
699 &expanded_file,
700 true, false, false, false, )?;
705
706 let raw_fd = shell_state.fd_table.borrow().get_raw_fd(0);
708 if let Some(rfd) = raw_fd {
709 if rfd != 0 {
710 unsafe {
711 if libc::dup2(rfd, 0) < 0 {
712 return Err(format!("Failed to dup2 fd {} to 0", rfd));
713 }
714 }
715 }
716 }
717 }
718 } else {
719 let fd_file = File::open(&expanded_file)
722 .map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?;
723
724 shell_state.fd_table.borrow_mut().open_fd(
726 fd,
727 &expanded_file,
728 true, false, false, false, )?;
733
734 if let Some(cmd) = command {
736 let target_fd = fd;
739 unsafe {
740 cmd.pre_exec(move || {
741 let raw_fd = fd_file.as_raw_fd();
742
743 if raw_fd != target_fd {
746 let result = libc::dup2(raw_fd, target_fd);
747 if result < 0 {
748 return Err(std::io::Error::last_os_error());
749 }
750 }
753 Ok(())
754 });
755 }
756 }
757 }
758
759 Ok(())
760}
761
762fn apply_output_redirection(
764 fd: i32,
765 file: &str,
766 append: bool,
767 shell_state: &mut ShellState,
768 command: Option<&mut Command>,
769) -> Result<(), String> {
770 let expanded_file = expand_variables_in_string(file, shell_state);
771
772 let file_handle = if append {
774 OpenOptions::new()
775 .append(true)
776 .create(true)
777 .open(&expanded_file)
778 .map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?
779 } else {
780 File::create(&expanded_file)
781 .map_err(|e| format!("Cannot create {}: {}", expanded_file, e))?
782 };
783
784 if let Some(cmd) = command {
785 if fd == 1 {
786 cmd.stdout(Stdio::from(file_handle));
788 } else if fd == 2 {
789 cmd.stderr(Stdio::from(file_handle));
791 } else {
792 shell_state.fd_table.borrow_mut().open_fd(
797 fd,
798 &expanded_file,
799 false, true, append,
802 !append, )?;
804 }
805 } else {
806 shell_state.fd_table.borrow_mut().open_fd(
809 fd,
810 &expanded_file,
811 false, true, append,
814 !append, )?;
816
817 let raw_fd = shell_state.fd_table.borrow().get_raw_fd(fd);
820 if let Some(rfd) = raw_fd {
821 if rfd != fd {
823 unsafe {
824 if libc::dup2(rfd, fd) < 0 {
825 return Err(format!("Failed to dup2 fd {} to {}", rfd, fd));
826 }
827 }
828 }
829 }
830 }
831
832 Ok(())
833}
834
835fn apply_fd_duplication(
837 target_fd: i32,
838 source_fd: i32,
839 shell_state: &mut ShellState,
840 _command: Option<&mut Command>,
841) -> Result<(), String> {
842 if shell_state.fd_table.borrow().is_closed(source_fd) {
844 let error_msg = format!("File descriptor {} is closed", source_fd);
845 if shell_state.colors_enabled {
846 eprintln!(
847 "{}Redirection error: {}\x1b[0m",
848 shell_state.color_scheme.error, error_msg
849 );
850 } else {
851 eprintln!("Redirection error: {}", error_msg);
852 }
853 return Err(error_msg);
854 }
855
856 shell_state
858 .fd_table
859 .borrow_mut()
860 .duplicate_fd(source_fd, target_fd)?;
861 Ok(())
862}
863
864fn apply_fd_close(
866 fd: i32,
867 shell_state: &mut ShellState,
868 command: Option<&mut Command>,
869) -> Result<(), String> {
870 shell_state.fd_table.borrow_mut().close_fd(fd)?;
872
873 if let Some(cmd) = command {
876 match fd {
877 0 => {
878 cmd.stdin(Stdio::null());
880 }
881 1 => {
882 cmd.stdout(Stdio::null());
884 }
885 2 => {
886 cmd.stderr(Stdio::null());
888 }
889 _ => {
890 }
893 }
894 }
895
896 Ok(())
897}
898
899fn apply_fd_input_output(
901 fd: i32,
902 file: &str,
903 shell_state: &mut ShellState,
904 _command: Option<&mut Command>,
905) -> Result<(), String> {
906 let expanded_file = expand_variables_in_string(file, shell_state);
907
908 shell_state.fd_table.borrow_mut().open_fd(
910 fd,
911 &expanded_file,
912 true, true, false, false, )?;
917
918 Ok(())
919}
920
921fn apply_heredoc_redirection(
923 fd: i32,
924 delimiter: &str,
925 quoted: bool,
926 shell_state: &mut ShellState,
927 command: Option<&mut Command>,
928) -> Result<(), String> {
929 let here_doc_content = collect_here_document_content(delimiter, shell_state);
930
931 let expanded_content = if quoted {
933 here_doc_content
934 } else {
935 expand_variables_in_string(&here_doc_content, shell_state)
936 };
937
938 let (reader, mut writer) =
940 pipe().map_err(|e| format!("Failed to create pipe for here-document: {}", e))?;
941
942 writeln!(writer, "{}", expanded_content)
943 .map_err(|e| format!("Failed to write here-document content: {}", e))?;
944
945 if fd == 0 {
947 if let Some(cmd) = command {
948 cmd.stdin(Stdio::from(reader));
949 }
950 }
951
952 Ok(())
953}
954
955fn apply_herestring_redirection(
957 fd: i32,
958 content: &str,
959 shell_state: &mut ShellState,
960 command: Option<&mut Command>,
961) -> Result<(), String> {
962 let expanded_content = expand_variables_in_string(content, shell_state);
963
964 let (reader, mut writer) =
966 pipe().map_err(|e| format!("Failed to create pipe for here-string: {}", e))?;
967
968 write!(writer, "{}", expanded_content)
969 .map_err(|e| format!("Failed to write here-string content: {}", e))?;
970
971 if fd == 0 {
973 if let Some(cmd) = command {
974 cmd.stdin(Stdio::from(reader));
975 }
976 }
977
978 Ok(())
979}
980
981pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
984 let saved_exit_code = shell_state.last_exit_code;
986
987 let result = match crate::lexer::lex(trap_cmd, shell_state) {
993 Ok(tokens) => {
994 match crate::lexer::expand_aliases(
995 tokens,
996 shell_state,
997 &mut std::collections::HashSet::new(),
998 ) {
999 Ok(expanded_tokens) => {
1000 match crate::parser::parse(expanded_tokens) {
1001 Ok(ast) => execute(ast, shell_state),
1002 Err(_) => {
1003 saved_exit_code
1005 }
1006 }
1007 }
1008 Err(_) => {
1009 saved_exit_code
1011 }
1012 }
1013 }
1014 Err(_) => {
1015 saved_exit_code
1017 }
1018 };
1019
1020 shell_state.last_exit_code = saved_exit_code;
1022
1023 result
1024}
1025
1026pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
1027 match ast {
1028 Ast::Assignment { var, value } => {
1029 let expanded_value = expand_variables_in_string(&value, shell_state);
1031 shell_state.set_var(&var, expanded_value);
1032 0
1033 }
1034 Ast::LocalAssignment { var, value } => {
1035 let expanded_value = expand_variables_in_string(&value, shell_state);
1037 shell_state.set_local_var(&var, expanded_value);
1038 0
1039 }
1040 Ast::Pipeline(commands) => {
1041 if commands.is_empty() {
1042 return 0;
1043 }
1044
1045 if commands.len() == 1 {
1046 execute_single_command(&commands[0], shell_state)
1048 } else {
1049 execute_pipeline(&commands, shell_state)
1051 }
1052 }
1053 Ast::Sequence(asts) => {
1054 let mut exit_code = 0;
1055 for ast in asts {
1056 exit_code = execute(ast, shell_state);
1057
1058 if shell_state.is_returning() {
1060 return exit_code;
1061 }
1062
1063 if shell_state.exit_requested {
1065 return shell_state.exit_code;
1066 }
1067 }
1068 exit_code
1069 }
1070 Ast::If {
1071 branches,
1072 else_branch,
1073 } => {
1074 for (condition, then_branch) in branches {
1075 let cond_exit = execute(*condition, shell_state);
1076 if cond_exit == 0 {
1077 let exit_code = execute(*then_branch, shell_state);
1078
1079 if shell_state.is_returning() {
1081 return exit_code;
1082 }
1083
1084 return exit_code;
1085 }
1086 }
1087 if let Some(else_b) = else_branch {
1088 let exit_code = execute(*else_b, shell_state);
1089
1090 if shell_state.is_returning() {
1092 return exit_code;
1093 }
1094
1095 exit_code
1096 } else {
1097 0
1098 }
1099 }
1100 Ast::Case {
1101 word,
1102 cases,
1103 default,
1104 } => {
1105 for (patterns, branch) in cases {
1106 for pattern in &patterns {
1107 if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
1108 if glob_pattern.matches(&word) {
1109 let exit_code = execute(branch, shell_state);
1110
1111 if shell_state.is_returning() {
1113 return exit_code;
1114 }
1115
1116 return exit_code;
1117 }
1118 } else {
1119 if &word == pattern {
1121 let exit_code = execute(branch, shell_state);
1122
1123 if shell_state.is_returning() {
1125 return exit_code;
1126 }
1127
1128 return exit_code;
1129 }
1130 }
1131 }
1132 }
1133 if let Some(def) = default {
1134 let exit_code = execute(*def, shell_state);
1135
1136 if shell_state.is_returning() {
1138 return exit_code;
1139 }
1140
1141 exit_code
1142 } else {
1143 0
1144 }
1145 }
1146 Ast::For {
1147 variable,
1148 items,
1149 body,
1150 } => {
1151 let mut exit_code = 0;
1152
1153 for item in items {
1155 crate::state::process_pending_signals(shell_state);
1157
1158 if shell_state.exit_requested {
1160 return shell_state.exit_code;
1161 }
1162
1163 shell_state.set_var(&variable, item.clone());
1165
1166 exit_code = execute(*body.clone(), shell_state);
1168
1169 if shell_state.is_returning() {
1171 return exit_code;
1172 }
1173
1174 if shell_state.exit_requested {
1176 return shell_state.exit_code;
1177 }
1178 }
1179
1180 exit_code
1181 }
1182 Ast::While { condition, body } => {
1183 let mut exit_code = 0;
1184
1185 loop {
1187 let cond_exit = execute(*condition.clone(), shell_state);
1189
1190 if shell_state.is_returning() {
1192 return cond_exit;
1193 }
1194
1195 if shell_state.exit_requested {
1197 return shell_state.exit_code;
1198 }
1199
1200 if cond_exit != 0 {
1202 break;
1203 }
1204
1205 exit_code = execute(*body.clone(), shell_state);
1207
1208 if shell_state.is_returning() {
1210 return exit_code;
1211 }
1212
1213 if shell_state.exit_requested {
1215 return shell_state.exit_code;
1216 }
1217 }
1218
1219 exit_code
1220 }
1221 Ast::FunctionDefinition { name, body } => {
1222 shell_state.define_function(name.clone(), *body);
1224 0
1225 }
1226 Ast::FunctionCall { name, args } => {
1227 if let Some(function_body) = shell_state.get_function(&name).cloned() {
1228 if shell_state.function_depth >= shell_state.max_recursion_depth {
1230 eprintln!(
1231 "Function recursion limit ({}) exceeded",
1232 shell_state.max_recursion_depth
1233 );
1234 return 1;
1235 }
1236
1237 shell_state.enter_function();
1239
1240 let old_positional = shell_state.positional_params.clone();
1242
1243 shell_state.set_positional_params(args.clone());
1245
1246 let exit_code = execute(function_body, shell_state);
1248
1249 if shell_state.is_returning() {
1251 let return_value = shell_state.get_return_value().unwrap_or(0);
1252
1253 shell_state.set_positional_params(old_positional);
1255
1256 shell_state.exit_function();
1258
1259 shell_state.clear_return();
1261
1262 return return_value;
1264 }
1265
1266 shell_state.set_positional_params(old_positional);
1268
1269 shell_state.exit_function();
1271
1272 exit_code
1273 } else {
1274 eprintln!("Function '{}' not found", name);
1275 1
1276 }
1277 }
1278 Ast::Return { value } => {
1279 if shell_state.function_depth == 0 {
1281 eprintln!("Return statement outside of function");
1282 return 1;
1283 }
1284
1285 let exit_code = if let Some(ref val) = value {
1287 val.parse::<i32>().unwrap_or(0)
1288 } else {
1289 0
1290 };
1291
1292 shell_state.set_return(exit_code);
1294
1295 exit_code
1297 }
1298 Ast::And { left, right } => {
1299 let left_exit = execute(*left, shell_state);
1301
1302 if shell_state.is_returning() {
1304 return left_exit;
1305 }
1306
1307 if left_exit == 0 {
1309 execute(*right, shell_state)
1310 } else {
1311 left_exit
1312 }
1313 }
1314 Ast::Or { left, right } => {
1315 let left_exit = execute(*left, shell_state);
1317
1318 if shell_state.is_returning() {
1320 return left_exit;
1321 }
1322
1323 if left_exit != 0 {
1325 execute(*right, shell_state)
1326 } else {
1327 left_exit
1328 }
1329 }
1330 Ast::Subshell { body } => execute_subshell(*body, shell_state),
1331 Ast::CommandGroup { body } => execute(*body, shell_state),
1332 }
1333}
1334
1335fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
1336 if let Some(ref compound_ast) = cmd.compound {
1338 return execute_compound_with_redirections(compound_ast, shell_state, &cmd.redirections);
1340 }
1341
1342 if cmd.args.is_empty() {
1343 if !cmd.redirections.is_empty() {
1345 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, None) {
1346 if shell_state.colors_enabled {
1347 eprintln!(
1348 "{}Redirection error: {}\x1b[0m",
1349 shell_state.color_scheme.error, e
1350 );
1351 } else {
1352 eprintln!("Redirection error: {}", e);
1353 }
1354 return 1;
1355 }
1356 }
1357 return 0;
1358 }
1359
1360 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1362 let expanded_args = match expand_wildcards(&var_expanded_args) {
1363 Ok(args) => args,
1364 Err(_) => return 1,
1365 };
1366
1367 if expanded_args.is_empty() {
1368 return 0;
1369 }
1370
1371 if shell_state.get_function(&expanded_args[0]).is_some() {
1373 let function_call = Ast::FunctionCall {
1375 name: expanded_args[0].clone(),
1376 args: expanded_args[1..].to_vec(),
1377 };
1378 return execute(function_call, shell_state);
1379 }
1380
1381 if crate::builtins::is_builtin(&expanded_args[0]) {
1382 let temp_cmd = ShellCommand {
1384 args: expanded_args,
1385 redirections: cmd.redirections.clone(),
1386 compound: None,
1387 };
1388
1389 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1391 struct CaptureWriter {
1393 buffer: Rc<RefCell<Vec<u8>>>,
1394 }
1395 impl std::io::Write for CaptureWriter {
1396 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1397 self.buffer.borrow_mut().extend_from_slice(buf);
1398 Ok(buf.len())
1399 }
1400 fn flush(&mut self) -> std::io::Result<()> {
1401 Ok(())
1402 }
1403 }
1404 let writer = CaptureWriter {
1405 buffer: capture_buffer.clone(),
1406 };
1407 crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
1408 } else {
1409 crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
1410 }
1411 } else {
1412 let mut env_assignments = Vec::new();
1415 let mut command_start_idx = 0;
1416
1417 for (idx, arg) in expanded_args.iter().enumerate() {
1418 if let Some(eq_pos) = arg.find('=')
1420 && eq_pos > 0
1421 {
1422 let var_part = &arg[..eq_pos];
1423 if var_part
1425 .chars()
1426 .next()
1427 .map(|c| c.is_alphabetic() || c == '_')
1428 .unwrap_or(false)
1429 && var_part.chars().all(|c| c.is_alphanumeric() || c == '_')
1430 {
1431 env_assignments.push(arg.clone());
1432 command_start_idx = idx + 1;
1433 continue;
1434 }
1435 }
1436 break;
1438 }
1439
1440 let has_command = command_start_idx < expanded_args.len();
1442
1443 if !has_command {
1446 for assignment in &env_assignments {
1447 if let Some(eq_pos) = assignment.find('=') {
1448 let var_name = &assignment[..eq_pos];
1449 let var_value = &assignment[eq_pos + 1..];
1450 shell_state.set_var(var_name, var_value.to_string());
1451 }
1452 }
1453
1454 if !cmd.redirections.is_empty() {
1456 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, None) {
1457 if shell_state.colors_enabled {
1458 eprintln!(
1459 "{}Redirection error: {}\x1b[0m",
1460 shell_state.color_scheme.error, e
1461 );
1462 } else {
1463 eprintln!("Redirection error: {}", e);
1464 }
1465 return 1;
1466 }
1467 }
1468 return 0;
1469 }
1470
1471 let mut command = Command::new(&expanded_args[command_start_idx]);
1473 command.args(&expanded_args[command_start_idx + 1..]);
1474
1475 if let Some(fd) = shell_state.stdin_override {
1477 unsafe {
1478 let dup_fd = libc::dup(fd);
1479 if dup_fd >= 0 {
1480 command.stdin(Stdio::from_raw_fd(dup_fd));
1481 }
1482 }
1483 }
1484
1485 let mut child_env = shell_state.get_env_for_child();
1487
1488 for assignment in env_assignments {
1490 if let Some(eq_pos) = assignment.find('=') {
1491 let var_name = assignment[..eq_pos].to_string();
1492 let var_value = assignment[eq_pos + 1..].to_string();
1493 child_env.insert(var_name, var_value);
1494 }
1495 }
1496
1497 command.env_clear();
1498 for (key, value) in child_env {
1499 command.env(key, value);
1500 }
1501
1502 let capturing = shell_state.capture_output.is_some();
1504 if capturing {
1505 command.stdout(Stdio::piped());
1506 }
1507
1508 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, Some(&mut command)) {
1510 if shell_state.colors_enabled {
1511 eprintln!(
1512 "{}Redirection error: {}\x1b[0m",
1513 shell_state.color_scheme.error, e
1514 );
1515 } else {
1516 eprintln!("Redirection error: {}", e);
1517 }
1518 return 1;
1519 }
1520
1521 let custom_fds: Vec<(i32, RawFd)> = {
1525 let fd_table = shell_state.fd_table.borrow();
1526 let mut fds = Vec::new();
1527
1528 for fd_num in 3..=9 {
1529 if fd_table.is_open(fd_num) {
1530 if let Some(raw_fd) = fd_table.get_raw_fd(fd_num) {
1531 fds.push((fd_num, raw_fd));
1532 }
1533 }
1534 }
1535
1536 fds
1537 };
1538
1539 if !custom_fds.is_empty() {
1541 unsafe {
1542 command.pre_exec(move || {
1543 for (target_fd, source_fd) in &custom_fds {
1544 let result = libc::dup2(*source_fd, *target_fd);
1545 if result < 0 {
1546 return Err(std::io::Error::last_os_error());
1547 }
1548 }
1549 Ok(())
1550 });
1551 }
1552 }
1553
1554 match command.spawn() {
1558 Ok(mut child) => {
1559 if capturing {
1561 if let Some(mut stdout) = child.stdout.take() {
1562 use std::io::Read;
1563 let mut output = Vec::new();
1564 if stdout.read_to_end(&mut output).is_ok() {
1565 if let Some(ref capture_buffer) = shell_state.capture_output {
1566 capture_buffer.borrow_mut().extend_from_slice(&output);
1567 }
1568 }
1569 }
1570 }
1571
1572 match child.wait() {
1573 Ok(status) => status.code().unwrap_or(0),
1574 Err(e) => {
1575 if shell_state.colors_enabled {
1576 eprintln!(
1577 "{}Error waiting for command: {}\x1b[0m",
1578 shell_state.color_scheme.error, e
1579 );
1580 } else {
1581 eprintln!("Error waiting for command: {}", e);
1582 }
1583 1
1584 }
1585 }
1586 }
1587 Err(e) => {
1588 if shell_state.colors_enabled {
1589 eprintln!(
1590 "{}Command spawn error: {}\x1b[0m",
1591 shell_state.color_scheme.error, e
1592 );
1593 } else {
1594 eprintln!("Command spawn error: {}", e);
1595 }
1596 1
1597 }
1598 }
1599 }
1600}
1601
1602fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
1603 let mut exit_code = 0;
1604 let mut previous_stdout: Option<File> = None;
1605
1606 for (i, cmd) in commands.iter().enumerate() {
1607 let is_last = i == commands.len() - 1;
1608
1609 if let Some(ref compound_ast) = cmd.compound {
1610 let (com_exit_code, com_stdout) = execute_compound_in_pipeline(
1612 compound_ast,
1613 shell_state,
1614 previous_stdout.take(),
1615 i == 0,
1616 is_last,
1617 &cmd.redirections,
1618 );
1619 exit_code = com_exit_code;
1620 previous_stdout = com_stdout;
1621 continue;
1622 }
1623
1624 if cmd.args.is_empty() {
1625 continue;
1626 }
1627
1628 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1630 let expanded_args = match expand_wildcards(&var_expanded_args) {
1631 Ok(args) => args,
1632 Err(_) => return 1,
1633 };
1634
1635 if expanded_args.is_empty() {
1636 continue;
1637 }
1638
1639 if crate::builtins::is_builtin(&expanded_args[0]) {
1640 let temp_cmd = ShellCommand {
1643 args: expanded_args,
1644 redirections: cmd.redirections.clone(),
1645 compound: None,
1646 };
1647 if !is_last {
1648 let (reader, writer) = match pipe() {
1650 Ok((r, w)) => (unsafe { File::from_raw_fd(r.into_raw_fd()) }, w),
1651 Err(e) => {
1652 if shell_state.colors_enabled {
1653 eprintln!(
1654 "{}Error creating pipe for builtin: {}\x1b[0m",
1655 shell_state.color_scheme.error, e
1656 );
1657 } else {
1658 eprintln!("Error creating pipe for builtin: {}", e);
1659 }
1660 return 1;
1661 }
1662 };
1663 exit_code = crate::builtins::execute_builtin(
1665 &temp_cmd,
1666 shell_state,
1667 Some(Box::new(writer)),
1668 );
1669 previous_stdout = Some(reader);
1671 } else {
1672 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1674 struct CaptureWriter {
1676 buffer: Rc<RefCell<Vec<u8>>>,
1677 }
1678 impl std::io::Write for CaptureWriter {
1679 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1680 self.buffer.borrow_mut().extend_from_slice(buf);
1681 Ok(buf.len())
1682 }
1683 fn flush(&mut self) -> std::io::Result<()> {
1684 Ok(())
1685 }
1686 }
1687 let writer = CaptureWriter {
1688 buffer: capture_buffer.clone(),
1689 };
1690 exit_code = crate::builtins::execute_builtin(
1691 &temp_cmd,
1692 shell_state,
1693 Some(Box::new(writer)),
1694 );
1695 } else {
1696 exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
1698 }
1699 previous_stdout = None;
1700 }
1701 } else {
1702 let mut command = Command::new(&expanded_args[0]);
1703 command.args(&expanded_args[1..]);
1704
1705 let child_env = shell_state.get_env_for_child();
1707 command.env_clear();
1708 for (key, value) in child_env {
1709 command.env(key, value);
1710 }
1711
1712 if let Some(prev) = previous_stdout.take() {
1714 command.stdin(Stdio::from(prev));
1715 } else if i > 0 {
1716 command.stdin(Stdio::null());
1720 } else if let Some(fd) = shell_state.stdin_override {
1721 unsafe {
1724 let dup_fd = libc::dup(fd);
1725 if dup_fd >= 0 {
1726 command.stdin(Stdio::from_raw_fd(dup_fd));
1727 }
1728 }
1729 }
1730
1731 if !is_last {
1733 command.stdout(Stdio::piped());
1734 } else if shell_state.capture_output.is_some() {
1735 command.stdout(Stdio::piped());
1737 }
1738
1739 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, Some(&mut command)) {
1741 if shell_state.colors_enabled {
1742 eprintln!(
1743 "{}Redirection error: {}\x1b[0m",
1744 shell_state.color_scheme.error, e
1745 );
1746 } else {
1747 eprintln!("Redirection error: {}", e);
1748 }
1749 return 1;
1750 }
1751
1752 match command.spawn() {
1753 Ok(mut child) => {
1754 if !is_last {
1755 previous_stdout = child
1756 .stdout
1757 .take()
1758 .map(|s| unsafe { File::from_raw_fd(s.into_raw_fd()) });
1759 } else if shell_state.capture_output.is_some() {
1760 if let Some(mut stdout) = child.stdout.take() {
1762 use std::io::Read;
1763 let mut output = Vec::new();
1764 if stdout.read_to_end(&mut output).is_ok()
1765 && let Some(ref capture_buffer) = shell_state.capture_output
1766 {
1767 capture_buffer.borrow_mut().extend_from_slice(&output);
1768 }
1769 }
1770 }
1771 match child.wait() {
1772 Ok(status) => {
1773 exit_code = status.code().unwrap_or(0);
1774 }
1775 Err(e) => {
1776 if shell_state.colors_enabled {
1777 eprintln!(
1778 "{}Error waiting for command: {}\x1b[0m",
1779 shell_state.color_scheme.error, e
1780 );
1781 } else {
1782 eprintln!("Error waiting for command: {}", e);
1783 }
1784 exit_code = 1;
1785 }
1786 }
1787 }
1788 Err(e) => {
1789 if shell_state.colors_enabled {
1790 eprintln!(
1791 "{}Error spawning command '{}{}",
1792 shell_state.color_scheme.error,
1793 expanded_args[0],
1794 &format!("': {}\x1b[0m", e)
1795 );
1796 } else {
1797 eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1798 }
1799 exit_code = 1;
1800 }
1801 }
1802 }
1803 }
1804
1805 exit_code
1806}
1807
1808fn execute_subshell(body: Ast, shell_state: &mut ShellState) -> i32 {
1826 if shell_state.subshell_depth >= MAX_SUBSHELL_DEPTH {
1828 if shell_state.colors_enabled {
1829 eprintln!(
1830 "{}Subshell nesting limit ({}) exceeded\x1b[0m",
1831 shell_state.color_scheme.error, MAX_SUBSHELL_DEPTH
1832 );
1833 } else {
1834 eprintln!("Subshell nesting limit ({}) exceeded", MAX_SUBSHELL_DEPTH);
1835 }
1836 shell_state.last_exit_code = 1;
1837 return 1;
1838 }
1839
1840 let original_dir = std::env::current_dir().ok();
1842
1843 let mut subshell_state = shell_state.clone();
1845
1846 match shell_state.fd_table.borrow().deep_clone() {
1850 Ok(new_fd_table) => {
1851 subshell_state.fd_table = Rc::new(RefCell::new(new_fd_table));
1852 }
1853 Err(e) => {
1854 if shell_state.colors_enabled {
1855 eprintln!(
1856 "{}Failed to clone file descriptor table: {}\x1b[0m",
1857 shell_state.color_scheme.error, e
1858 );
1859 } else {
1860 eprintln!("Failed to clone file descriptor table: {}", e);
1861 }
1862 return 1;
1863 }
1864 }
1865
1866 subshell_state.subshell_depth = shell_state.subshell_depth + 1;
1868
1869 let parent_traps = shell_state.trap_handlers.lock().unwrap().clone();
1871 subshell_state.trap_handlers = std::sync::Arc::new(std::sync::Mutex::new(parent_traps));
1872
1873 let exit_code = execute(body, &mut subshell_state);
1875
1876 let final_exit_code = if subshell_state.exit_requested {
1879 subshell_state.exit_code
1881 } else if subshell_state.is_returning() {
1882 subshell_state.get_return_value().unwrap_or(exit_code)
1885 } else {
1886 exit_code
1887 };
1888
1889 subshell_state.fd_table.borrow_mut().clear();
1892
1893 if let Some(dir) = original_dir {
1895 let _ = std::env::set_current_dir(dir);
1896 }
1897
1898 shell_state.last_exit_code = final_exit_code;
1900
1901 final_exit_code
1903}
1904
1905fn execute_compound_with_redirections(
1915 compound_ast: &Ast,
1916 shell_state: &mut ShellState,
1917 redirections: &[Redirection],
1918) -> i32 {
1919 match compound_ast {
1920 Ast::CommandGroup { body } => {
1921 if let Err(e) = shell_state.fd_table.borrow_mut().save_all_fds() {
1923 eprintln!("Error saving FDs: {}", e);
1924 return 1;
1925 }
1926
1927 if let Err(e) = apply_redirections(redirections, shell_state, None) {
1929 if shell_state.colors_enabled {
1930 eprintln!("{}{}\u{001b}[0m", shell_state.color_scheme.error, e);
1931 } else {
1932 eprintln!("{}", e);
1933 }
1934 shell_state.fd_table.borrow_mut().restore_all_fds().ok();
1935 return 1;
1936 }
1937
1938 let exit_code = execute(*body.clone(), shell_state);
1940
1941 if let Err(e) = shell_state.fd_table.borrow_mut().restore_all_fds() {
1943 eprintln!("Error restoring FDs: {}", e);
1944 }
1945
1946 exit_code
1947 }
1948 Ast::Subshell { body } => {
1949 let has_output_redir = redirections.iter().any(|r| {
1956 matches!(
1957 r,
1958 Redirection::Output(_)
1959 | Redirection::Append(_)
1960 | Redirection::FdOutput(_, _)
1961 | Redirection::FdAppend(_, _)
1962 )
1963 });
1964
1965 if has_output_redir {
1966 let mut subshell_state = shell_state.clone();
1968
1969 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
1971 subshell_state.capture_output = Some(capture_buffer.clone());
1972
1973 let exit_code = execute(*body.clone(), &mut subshell_state);
1975
1976 let output = capture_buffer.borrow().clone();
1978
1979 for redir in redirections {
1981 match redir {
1982 Redirection::Output(file) => {
1983 let expanded_file = expand_variables_in_string(file, shell_state);
1984 if let Err(e) = std::fs::write(&expanded_file, &output) {
1985 if shell_state.colors_enabled {
1986 eprintln!(
1987 "{}Redirection error: {}\x1b[0m",
1988 shell_state.color_scheme.error, e
1989 );
1990 } else {
1991 eprintln!("Redirection error: {}", e);
1992 }
1993 return 1;
1994 }
1995 }
1996 Redirection::Append(file) => {
1997 let expanded_file = expand_variables_in_string(file, shell_state);
1998 use std::fs::OpenOptions;
1999 let mut file_handle = match OpenOptions::new()
2000 .append(true)
2001 .create(true)
2002 .open(&expanded_file)
2003 {
2004 Ok(f) => f,
2005 Err(e) => {
2006 if shell_state.colors_enabled {
2007 eprintln!(
2008 "{}Redirection error: {}\x1b[0m",
2009 shell_state.color_scheme.error, e
2010 );
2011 } else {
2012 eprintln!("Redirection error: {}", e);
2013 }
2014 return 1;
2015 }
2016 };
2017 if let Err(e) = file_handle.write_all(&output) {
2018 if shell_state.colors_enabled {
2019 eprintln!(
2020 "{}Redirection error: {}\x1b[0m",
2021 shell_state.color_scheme.error, e
2022 );
2023 } else {
2024 eprintln!("Redirection error: {}", e);
2025 }
2026 return 1;
2027 }
2028 }
2029 _ => {
2030 }
2033 }
2034 }
2035
2036 shell_state.last_exit_code = exit_code;
2037 exit_code
2038 } else {
2039 execute_subshell(*body.clone(), shell_state)
2041 }
2042 }
2043 _ => {
2044 eprintln!("Unsupported compound command type");
2045 1
2046 }
2047 }
2048}
2049
2050fn has_stdout_redirection(redirections: &[Redirection]) -> bool {
2053 redirections.iter().any(|r| match r {
2054 Redirection::Output(_) | Redirection::Append(_) => true,
2056 Redirection::FdOutput(1, _) | Redirection::FdAppend(1, _) => true,
2058 Redirection::FdDuplicate(1, _) | Redirection::FdClose(1) => true,
2060 _ => false,
2062 })
2063}
2064
2065fn execute_compound_in_pipeline(
2076 compound_ast: &Ast,
2077 shell_state: &mut ShellState,
2078 stdin: Option<File>,
2079 is_first: bool,
2080 is_last: bool,
2081 redirections: &[Redirection],
2082) -> (i32, Option<File>) {
2083 match compound_ast {
2084 Ast::Subshell { body } | Ast::CommandGroup { body } => {
2085 let mut subshell_state = shell_state.clone();
2087
2088 let mut _stdin_file = stdin;
2091
2092 if let Some(ref f) = _stdin_file {
2093 let fd = f.as_raw_fd();
2094 subshell_state.stdin_override = Some(fd);
2095 } else if !is_first && subshell_state.stdin_override.is_none() {
2096 if let Ok(f) = File::open("/dev/null") {
2098 subshell_state.stdin_override = Some(f.as_raw_fd());
2099 _stdin_file = Some(f);
2100 }
2101 }
2102
2103 let capture_buffer = if (!is_last || shell_state.capture_output.is_some())
2106 && !has_stdout_redirection(redirections)
2107 {
2108 let buffer = Rc::new(RefCell::new(Vec::new()));
2109 subshell_state.capture_output = Some(buffer.clone());
2110 Some(buffer)
2111 } else {
2112 None
2113 };
2114
2115 let exit_code = if matches!(compound_ast, Ast::CommandGroup { .. }) {
2117 if let Err(e) = subshell_state.fd_table.borrow_mut().save_all_fds() {
2119 eprintln!("Error saving FDs: {}", e);
2120 return (1, None);
2121 }
2122
2123 if let Some(ref f) = _stdin_file {
2125 unsafe {
2126 libc::dup2(f.as_raw_fd(), 0);
2127 }
2128 }
2129
2130 if let Err(e) = apply_redirections(redirections, &mut subshell_state, None) {
2132 if subshell_state.colors_enabled {
2133 eprintln!("{}{}\u{001b}[0m", subshell_state.color_scheme.error, e);
2134 } else {
2135 eprintln!("{}", e);
2136 }
2137 subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
2138 return (1, None);
2139 }
2140
2141 let code = execute(*body.clone(), &mut subshell_state);
2143
2144 if let Err(e) = subshell_state.fd_table.borrow_mut().restore_all_fds() {
2146 eprintln!("Error restoring FDs: {}", e);
2147 }
2148 code
2149 } else {
2150 if let Err(e) = subshell_state.fd_table.borrow_mut().save_all_fds() {
2152 eprintln!("Error saving FDs: {}", e);
2153 return (1, None);
2154 }
2155
2156 if let Some(ref f) = _stdin_file {
2158 unsafe {
2159 libc::dup2(f.as_raw_fd(), 0);
2160 }
2161 }
2162
2163 if let Err(e) = apply_redirections(redirections, &mut subshell_state, None) {
2164 eprintln!("{}", e);
2165 subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
2166 return (1, None);
2167 }
2168 let code = execute(*body.clone(), &mut subshell_state);
2169 subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
2170 code
2171 };
2172
2173 let mut next_stdout = None;
2175 if let Some(buffer) = capture_buffer {
2176 let captured = buffer.borrow().clone();
2177
2178 if !is_last {
2180 use std::io::Write;
2181 let (reader, mut writer) = match pipe() {
2182 Ok((r, w)) => (r, w),
2183 Err(e) => {
2184 eprintln!("Error creating pipe for compound command: {}", e);
2185 return (exit_code, None);
2186 }
2187 };
2188 if let Err(e) = writer.write_all(&captured) {
2189 eprintln!("Error writing to pipe: {}", e);
2190 }
2191 drop(writer); next_stdout = Some(unsafe { File::from_raw_fd(reader.into_raw_fd()) });
2194 }
2195
2196 if let Some(ref parent_capture) = shell_state.capture_output {
2198 parent_capture.borrow_mut().extend_from_slice(&captured);
2199 }
2200 }
2201
2202 shell_state.last_exit_code = exit_code;
2203 (exit_code, next_stdout)
2204 }
2205 _ => {
2206 eprintln!("Unsupported compound command in pipeline");
2207 (1, None)
2208 }
2209 }
2210}
2211
2212#[cfg(test)]
2213mod tests {
2214 use super::*;
2215 use std::sync::Mutex;
2216
2217 static ENV_LOCK: Mutex<()> = Mutex::new(());
2219
2220 #[test]
2221 fn test_execute_single_command_builtin() {
2222 let cmd = ShellCommand {
2223 args: vec!["true".to_string()],
2224 redirections: Vec::new(),
2225 compound: None,
2226 };
2227 let mut shell_state = ShellState::new();
2228 let exit_code = execute_single_command(&cmd, &mut shell_state);
2229 assert_eq!(exit_code, 0);
2230 }
2231
2232 #[test]
2234 fn test_execute_single_command_external() {
2235 let cmd = ShellCommand {
2236 args: vec!["true".to_string()], redirections: Vec::new(),
2238 compound: None,
2239 };
2240 let mut shell_state = ShellState::new();
2241 let exit_code = execute_single_command(&cmd, &mut shell_state);
2242 assert_eq!(exit_code, 0);
2243 }
2244
2245 #[test]
2246 fn test_execute_single_command_external_nonexistent() {
2247 let cmd = ShellCommand {
2248 args: vec!["nonexistent_command".to_string()],
2249 redirections: Vec::new(),
2250 compound: None,
2251 };
2252 let mut shell_state = ShellState::new();
2253 let exit_code = execute_single_command(&cmd, &mut shell_state);
2254 assert_eq!(exit_code, 1); }
2256
2257 #[test]
2258 fn test_execute_pipeline() {
2259 let commands = vec![
2260 ShellCommand {
2261 args: vec!["printf".to_string(), "hello".to_string()],
2262 redirections: Vec::new(),
2263 compound: None,
2264 },
2265 ShellCommand {
2266 args: vec!["cat".to_string()], redirections: Vec::new(),
2268 compound: None,
2269 },
2270 ];
2271 let mut shell_state = ShellState::new();
2272 let exit_code = execute_pipeline(&commands, &mut shell_state);
2273 assert_eq!(exit_code, 0);
2274 }
2275
2276 #[test]
2277 fn test_execute_empty_pipeline() {
2278 let commands = vec![];
2279 let mut shell_state = ShellState::new();
2280 let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
2281 assert_eq!(exit_code, 0);
2282 }
2283
2284 #[test]
2285 fn test_execute_single_command() {
2286 let ast = Ast::Pipeline(vec![ShellCommand {
2287 args: vec!["true".to_string()],
2288 redirections: Vec::new(),
2289 compound: None,
2290 }]);
2291 let mut shell_state = ShellState::new();
2292 let exit_code = execute(ast, &mut shell_state);
2293 assert_eq!(exit_code, 0);
2294 }
2295
2296 #[test]
2297 fn test_execute_function_definition() {
2298 let ast = Ast::FunctionDefinition {
2299 name: "test_func".to_string(),
2300 body: Box::new(Ast::Pipeline(vec![ShellCommand {
2301 args: vec!["echo".to_string(), "hello".to_string()],
2302 redirections: Vec::new(),
2303 compound: None,
2304 }])),
2305 };
2306 let mut shell_state = ShellState::new();
2307 let exit_code = execute(ast, &mut shell_state);
2308 assert_eq!(exit_code, 0);
2309
2310 assert!(shell_state.get_function("test_func").is_some());
2312 }
2313
2314 #[test]
2315 fn test_execute_function_call() {
2316 let mut shell_state = ShellState::new();
2318 shell_state.define_function(
2319 "test_func".to_string(),
2320 Ast::Pipeline(vec![ShellCommand {
2321 args: vec!["echo".to_string(), "hello".to_string()],
2322 redirections: Vec::new(),
2323 compound: None,
2324 }]),
2325 );
2326
2327 let ast = Ast::FunctionCall {
2329 name: "test_func".to_string(),
2330 args: vec![],
2331 };
2332 let exit_code = execute(ast, &mut shell_state);
2333 assert_eq!(exit_code, 0);
2334 }
2335
2336 #[test]
2337 fn test_execute_function_call_with_args() {
2338 let mut shell_state = ShellState::new();
2340 shell_state.define_function(
2341 "test_func".to_string(),
2342 Ast::Pipeline(vec![ShellCommand {
2343 args: vec!["echo".to_string(), "arg1".to_string()],
2344 redirections: Vec::new(),
2345 compound: None,
2346 }]),
2347 );
2348
2349 let ast = Ast::FunctionCall {
2351 name: "test_func".to_string(),
2352 args: vec!["hello".to_string()],
2353 };
2354 let exit_code = execute(ast, &mut shell_state);
2355 assert_eq!(exit_code, 0);
2356 }
2357
2358 #[test]
2359 fn test_execute_nonexistent_function() {
2360 let mut shell_state = ShellState::new();
2361 let ast = Ast::FunctionCall {
2362 name: "nonexistent".to_string(),
2363 args: vec![],
2364 };
2365 let exit_code = execute(ast, &mut shell_state);
2366 assert_eq!(exit_code, 1); }
2368
2369 #[test]
2370 fn test_execute_function_integration() {
2371 let mut shell_state = ShellState::new();
2373
2374 let define_ast = Ast::FunctionDefinition {
2376 name: "hello".to_string(),
2377 body: Box::new(Ast::Pipeline(vec![ShellCommand {
2378 args: vec!["printf".to_string(), "Hello from function".to_string()],
2379 redirections: Vec::new(),
2380 compound: None,
2381 }])),
2382 };
2383 let exit_code = execute(define_ast, &mut shell_state);
2384 assert_eq!(exit_code, 0);
2385
2386 let call_ast = Ast::FunctionCall {
2388 name: "hello".to_string(),
2389 args: vec![],
2390 };
2391 let exit_code = execute(call_ast, &mut shell_state);
2392 assert_eq!(exit_code, 0);
2393 }
2394
2395 #[test]
2396 fn test_execute_function_with_local_variables() {
2397 let mut shell_state = ShellState::new();
2398
2399 shell_state.set_var("global_var", "global_value".to_string());
2401
2402 let define_ast = Ast::FunctionDefinition {
2404 name: "test_func".to_string(),
2405 body: Box::new(Ast::Sequence(vec![
2406 Ast::LocalAssignment {
2407 var: "local_var".to_string(),
2408 value: "local_value".to_string(),
2409 },
2410 Ast::Assignment {
2411 var: "global_var".to_string(),
2412 value: "modified_in_function".to_string(),
2413 },
2414 Ast::Pipeline(vec![ShellCommand {
2415 args: vec!["printf".to_string(), "success".to_string()],
2416 redirections: Vec::new(),
2417 compound: None,
2418 }]),
2419 ])),
2420 };
2421 let exit_code = execute(define_ast, &mut shell_state);
2422 assert_eq!(exit_code, 0);
2423
2424 assert_eq!(
2426 shell_state.get_var("global_var"),
2427 Some("global_value".to_string())
2428 );
2429
2430 let call_ast = Ast::FunctionCall {
2432 name: "test_func".to_string(),
2433 args: vec![],
2434 };
2435 let exit_code = execute(call_ast, &mut shell_state);
2436 assert_eq!(exit_code, 0);
2437
2438 assert_eq!(
2440 shell_state.get_var("global_var"),
2441 Some("modified_in_function".to_string())
2442 );
2443 }
2444
2445 #[test]
2446 fn test_execute_nested_function_calls() {
2447 let mut shell_state = ShellState::new();
2448
2449 shell_state.set_var("global_var", "global".to_string());
2451
2452 let outer_func = Ast::FunctionDefinition {
2454 name: "outer".to_string(),
2455 body: Box::new(Ast::Sequence(vec![
2456 Ast::Assignment {
2457 var: "global_var".to_string(),
2458 value: "outer_modified".to_string(),
2459 },
2460 Ast::FunctionCall {
2461 name: "inner".to_string(),
2462 args: vec![],
2463 },
2464 Ast::Pipeline(vec![ShellCommand {
2465 args: vec!["printf".to_string(), "outer_done".to_string()],
2466 redirections: Vec::new(),
2467 compound: None,
2468 }]),
2469 ])),
2470 };
2471
2472 let inner_func = Ast::FunctionDefinition {
2474 name: "inner".to_string(),
2475 body: Box::new(Ast::Sequence(vec![
2476 Ast::Assignment {
2477 var: "global_var".to_string(),
2478 value: "inner_modified".to_string(),
2479 },
2480 Ast::Pipeline(vec![ShellCommand {
2481 args: vec!["printf".to_string(), "inner_done".to_string()],
2482 redirections: Vec::new(),
2483 compound: None,
2484 }]),
2485 ])),
2486 };
2487
2488 execute(outer_func, &mut shell_state);
2490 execute(inner_func, &mut shell_state);
2491
2492 shell_state.set_var("global_var", "initial".to_string());
2494
2495 let call_ast = Ast::FunctionCall {
2497 name: "outer".to_string(),
2498 args: vec![],
2499 };
2500 let exit_code = execute(call_ast, &mut shell_state);
2501 assert_eq!(exit_code, 0);
2502
2503 assert_eq!(
2506 shell_state.get_var("global_var"),
2507 Some("inner_modified".to_string())
2508 );
2509 }
2510
2511 #[test]
2512 fn test_here_string_execution() {
2513 let cmd = ShellCommand {
2515 args: vec!["cat".to_string()],
2516 redirections: Vec::new(),
2517 compound: None,
2518 };
2520
2521 assert_eq!(cmd.args, vec!["cat"]);
2524 }
2526
2527 #[test]
2528 fn test_here_document_execution() {
2529 let cmd = ShellCommand {
2531 args: vec!["cat".to_string()],
2532 redirections: Vec::new(),
2533 compound: None,
2534 };
2536
2537 assert_eq!(cmd.args, vec!["cat"]);
2540 }
2542
2543 #[test]
2544 fn test_here_document_with_variable_expansion() {
2545 let mut shell_state = ShellState::new();
2547 shell_state.set_var("PWD", "/test/path".to_string());
2548
2549 let content = "Working dir: $PWD";
2551 let expanded = expand_variables_in_string(content, &mut shell_state);
2552
2553 assert_eq!(expanded, "Working dir: /test/path");
2554 }
2555
2556 #[test]
2557 fn test_here_document_with_command_substitution_builtin() {
2558 let mut shell_state = ShellState::new();
2560 shell_state.set_var("PWD", "/test/dir".to_string());
2561
2562 let content = "Current directory: `pwd`";
2564 let expanded = expand_variables_in_string(content, &mut shell_state);
2565
2566 assert!(expanded.contains("Current directory: "));
2568 }
2569
2570 #[test]
2575 fn test_fd_output_redirection() {
2576 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2577
2578 use std::time::{SystemTime, UNIX_EPOCH};
2580 let timestamp = SystemTime::now()
2581 .duration_since(UNIX_EPOCH)
2582 .unwrap()
2583 .as_nanos();
2584 let temp_file = format!("/tmp/rush_test_fd_out_{}.txt", timestamp);
2585
2586 let cmd = ShellCommand {
2588 args: vec![
2589 "sh".to_string(),
2590 "-c".to_string(),
2591 "echo error >&2".to_string(),
2592 ],
2593 redirections: vec![Redirection::FdOutput(2, temp_file.clone())],
2594 compound: None,
2595 };
2596
2597 let mut shell_state = ShellState::new();
2598 let exit_code = execute_single_command(&cmd, &mut shell_state);
2599 assert_eq!(exit_code, 0);
2600
2601 let content = std::fs::read_to_string(&temp_file).unwrap();
2603 assert_eq!(content.trim(), "error");
2604
2605 let _ = std::fs::remove_file(&temp_file);
2607 }
2608
2609 #[test]
2610 fn test_fd_input_redirection() {
2611 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2612
2613 use std::time::{SystemTime, UNIX_EPOCH};
2615 let timestamp = SystemTime::now()
2616 .duration_since(UNIX_EPOCH)
2617 .unwrap()
2618 .as_nanos();
2619 let temp_file = format!("/tmp/rush_test_fd_in_{}.txt", timestamp);
2620
2621 std::fs::write(&temp_file, "test input\n").unwrap();
2622 std::thread::sleep(std::time::Duration::from_millis(10));
2623
2624 let cmd = ShellCommand {
2627 args: vec!["cat".to_string()],
2628 compound: None,
2629 redirections: vec![
2630 Redirection::FdInput(3, temp_file.clone()),
2631 Redirection::Input(temp_file.clone()),
2632 ],
2633 };
2634
2635 let mut shell_state = ShellState::new();
2636 let exit_code = execute_single_command(&cmd, &mut shell_state);
2637 assert_eq!(exit_code, 0);
2638
2639 let _ = std::fs::remove_file(&temp_file);
2641 }
2642
2643 #[test]
2644 fn test_fd_append_redirection() {
2645 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2646
2647 use std::time::{SystemTime, UNIX_EPOCH};
2649 let timestamp = SystemTime::now()
2650 .duration_since(UNIX_EPOCH)
2651 .unwrap()
2652 .as_nanos();
2653 let temp_file = format!("/tmp/rush_test_fd_append_{}.txt", timestamp);
2654
2655 std::fs::write(&temp_file, "first line\n").unwrap();
2656 std::thread::sleep(std::time::Duration::from_millis(10));
2657
2658 let cmd = ShellCommand {
2660 args: vec![
2661 "sh".to_string(),
2662 "-c".to_string(),
2663 "echo second line >&2".to_string(),
2664 ],
2665 redirections: vec![Redirection::FdAppend(2, temp_file.clone())],
2666 compound: None,
2667 };
2668
2669 let mut shell_state = ShellState::new();
2670 let exit_code = execute_single_command(&cmd, &mut shell_state);
2671 assert_eq!(exit_code, 0);
2672
2673 let content = std::fs::read_to_string(&temp_file).unwrap();
2675 assert!(content.contains("first line"));
2676 assert!(content.contains("second line"));
2677
2678 let _ = std::fs::remove_file(&temp_file);
2680 }
2681
2682 #[test]
2683 fn test_fd_duplication_stderr_to_stdout() {
2684 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2685
2686 use std::time::{SystemTime, UNIX_EPOCH};
2688 let timestamp = SystemTime::now()
2689 .duration_since(UNIX_EPOCH)
2690 .unwrap()
2691 .as_nanos();
2692 let temp_file = format!("/tmp/rush_test_fd_dup_{}.txt", timestamp);
2693
2694 let cmd = ShellCommand {
2698 args: vec![
2699 "sh".to_string(),
2700 "-c".to_string(),
2701 "echo test; echo error >&2".to_string(),
2702 ],
2703 compound: None,
2704 redirections: vec![Redirection::Output(temp_file.clone())],
2705 };
2706
2707 let mut shell_state = ShellState::new();
2708 let exit_code = execute_single_command(&cmd, &mut shell_state);
2709 assert_eq!(exit_code, 0);
2710
2711 assert!(std::path::Path::new(&temp_file).exists());
2713 let content = std::fs::read_to_string(&temp_file).unwrap();
2714 assert!(content.contains("test"));
2715
2716 let _ = std::fs::remove_file(&temp_file);
2718 }
2719
2720 #[test]
2721 fn test_fd_close() {
2722 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2723
2724 let cmd = ShellCommand {
2726 args: vec!["sh".to_string(), "-c".to_string(), "echo test".to_string()],
2727 redirections: vec![Redirection::FdClose(2)],
2728 compound: None,
2729 };
2730
2731 let mut shell_state = ShellState::new();
2732 let exit_code = execute_single_command(&cmd, &mut shell_state);
2733 assert_eq!(exit_code, 0);
2734
2735 assert!(shell_state.fd_table.borrow().is_closed(2));
2737 }
2738
2739 #[test]
2740 fn test_fd_read_write() {
2741 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2742
2743 use std::time::{SystemTime, UNIX_EPOCH};
2745 let timestamp = SystemTime::now()
2746 .duration_since(UNIX_EPOCH)
2747 .unwrap()
2748 .as_nanos();
2749 let temp_file = format!("/tmp/rush_test_fd_rw_{}.txt", timestamp);
2750
2751 std::fs::write(&temp_file, "initial content\n").unwrap();
2752 std::thread::sleep(std::time::Duration::from_millis(10));
2753
2754 let cmd = ShellCommand {
2756 args: vec!["cat".to_string()],
2757 compound: None,
2758 redirections: vec![
2759 Redirection::FdInputOutput(3, temp_file.clone()),
2760 Redirection::Input(temp_file.clone()),
2761 ],
2762 };
2763
2764 let mut shell_state = ShellState::new();
2765 let exit_code = execute_single_command(&cmd, &mut shell_state);
2766 assert_eq!(exit_code, 0);
2767
2768 let _ = std::fs::remove_file(&temp_file);
2770 }
2771
2772 #[test]
2773 fn test_multiple_fd_redirections() {
2774 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2775
2776 use std::time::{SystemTime, UNIX_EPOCH};
2778 let timestamp = SystemTime::now()
2779 .duration_since(UNIX_EPOCH)
2780 .unwrap()
2781 .as_nanos();
2782 let out_file = format!("/tmp/rush_test_fd_multi_out_{}.txt", timestamp);
2783 let err_file = format!("/tmp/rush_test_fd_multi_err_{}.txt", timestamp);
2784
2785 let cmd = ShellCommand {
2787 args: vec![
2788 "sh".to_string(),
2789 "-c".to_string(),
2790 "echo stdout; echo stderr >&2".to_string(),
2791 ],
2792 redirections: vec![
2793 Redirection::FdOutput(2, err_file.clone()),
2794 Redirection::Output(out_file.clone()),
2795 ],
2796 compound: None,
2797 };
2798
2799 let mut shell_state = ShellState::new();
2800 let exit_code = execute_single_command(&cmd, &mut shell_state);
2801 assert_eq!(exit_code, 0);
2802
2803 assert!(std::path::Path::new(&out_file).exists());
2805 assert!(std::path::Path::new(&err_file).exists());
2806
2807 let out_content = std::fs::read_to_string(&out_file).unwrap();
2809 let err_content = std::fs::read_to_string(&err_file).unwrap();
2810 assert!(out_content.contains("stdout"));
2811 assert!(err_content.contains("stderr"));
2812
2813 let _ = std::fs::remove_file(&out_file);
2815 let _ = std::fs::remove_file(&err_file);
2816 }
2817
2818 #[test]
2819 fn test_fd_swap_pattern() {
2820 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2821
2822 use std::time::{SystemTime, UNIX_EPOCH};
2824 let timestamp = SystemTime::now()
2825 .duration_since(UNIX_EPOCH)
2826 .unwrap()
2827 .as_nanos();
2828 let temp_file = format!("/tmp/rush_test_fd_swap_{}.txt", timestamp);
2829
2830 let cmd = ShellCommand {
2833 args: vec!["sh".to_string(), "-c".to_string(), "echo test".to_string()],
2834 redirections: vec![
2835 Redirection::FdOutput(3, temp_file.clone()), Redirection::FdClose(3), Redirection::Output(temp_file.clone()), ],
2839 compound: None,
2840 };
2841
2842 let mut shell_state = ShellState::new();
2843 let exit_code = execute_single_command(&cmd, &mut shell_state);
2844 assert_eq!(exit_code, 0);
2845
2846 assert!(shell_state.fd_table.borrow().is_closed(3));
2848
2849 let _ = std::fs::remove_file(&temp_file);
2851 }
2852
2853 #[test]
2854 fn test_fd_redirection_with_pipes() {
2855 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2856
2857 use std::time::{SystemTime, UNIX_EPOCH};
2859 let timestamp = SystemTime::now()
2860 .duration_since(UNIX_EPOCH)
2861 .unwrap()
2862 .as_nanos();
2863 let temp_file = format!("/tmp/rush_test_fd_pipe_{}.txt", timestamp);
2864
2865 let commands = vec![
2868 ShellCommand {
2869 args: vec!["echo".to_string(), "piped output".to_string()],
2870 redirections: vec![],
2871 compound: None,
2872 },
2873 ShellCommand {
2874 args: vec!["cat".to_string()],
2875 compound: None,
2876 redirections: vec![Redirection::Output(temp_file.clone())],
2877 },
2878 ];
2879
2880 let mut shell_state = ShellState::new();
2881 let exit_code = execute_pipeline(&commands, &mut shell_state);
2882 assert_eq!(exit_code, 0);
2883
2884 let content = std::fs::read_to_string(&temp_file).unwrap();
2886 assert!(content.contains("piped output"));
2887
2888 let _ = std::fs::remove_file(&temp_file);
2890 }
2891
2892 #[test]
2893 fn test_fd_error_invalid_fd_number() {
2894 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2895
2896 use std::time::{SystemTime, UNIX_EPOCH};
2898 let timestamp = SystemTime::now()
2899 .duration_since(UNIX_EPOCH)
2900 .unwrap()
2901 .as_nanos();
2902 let temp_file = format!("/tmp/rush_test_fd_invalid_{}.txt", timestamp);
2903
2904 let cmd = ShellCommand {
2906 args: vec!["echo".to_string(), "test".to_string()],
2907 compound: None,
2908 redirections: vec![Redirection::FdOutput(1025, temp_file.clone())],
2909 };
2910
2911 let mut shell_state = ShellState::new();
2912 let exit_code = execute_single_command(&cmd, &mut shell_state);
2913
2914 assert_eq!(exit_code, 1);
2916
2917 let _ = std::fs::remove_file(&temp_file);
2919 }
2920
2921 #[test]
2922 fn test_fd_error_duplicate_closed_fd() {
2923 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2924
2925 let cmd = ShellCommand {
2927 args: vec!["echo".to_string(), "test".to_string()],
2928 compound: None,
2929 redirections: vec![
2930 Redirection::FdClose(3),
2931 Redirection::FdDuplicate(2, 3), ],
2933 };
2934
2935 let mut shell_state = ShellState::new();
2936 let exit_code = execute_single_command(&cmd, &mut shell_state);
2937
2938 assert_eq!(exit_code, 1);
2940 }
2941
2942 #[test]
2943 fn test_fd_error_file_permission() {
2944 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2945
2946 let cmd = ShellCommand {
2948 args: vec!["echo".to_string(), "test".to_string()],
2949 redirections: vec![Redirection::FdOutput(2, "/proc/version".to_string())],
2950 compound: None,
2951 };
2952
2953 let mut shell_state = ShellState::new();
2954 let exit_code = execute_single_command(&cmd, &mut shell_state);
2955
2956 assert_eq!(exit_code, 1);
2958 }
2959
2960 #[test]
2961 fn test_fd_redirection_order() {
2962 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2963
2964 use std::time::{SystemTime, UNIX_EPOCH};
2966 let timestamp = SystemTime::now()
2967 .duration_since(UNIX_EPOCH)
2968 .unwrap()
2969 .as_nanos();
2970 let file1 = format!("/tmp/rush_test_fd_order1_{}.txt", timestamp);
2971 let file2 = format!("/tmp/rush_test_fd_order2_{}.txt", timestamp);
2972
2973 let cmd = ShellCommand {
2976 args: vec!["echo".to_string(), "test".to_string()],
2977 compound: None,
2978 redirections: vec![
2979 Redirection::Output(file1.clone()),
2980 Redirection::Output(file2.clone()),
2981 ],
2982 };
2983
2984 let mut shell_state = ShellState::new();
2985 let exit_code = execute_single_command(&cmd, &mut shell_state);
2986 assert_eq!(exit_code, 0);
2987
2988 let content2 = std::fs::read_to_string(&file2).unwrap();
2990 assert!(content2.contains("test"));
2991
2992 let _ = std::fs::remove_file(&file1);
2994 let _ = std::fs::remove_file(&file2);
2995 }
2996
2997 #[test]
2998 fn test_fd_builtin_with_redirection() {
2999 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3000
3001 use std::time::{SystemTime, UNIX_EPOCH};
3003 let timestamp = SystemTime::now()
3004 .duration_since(UNIX_EPOCH)
3005 .unwrap()
3006 .as_nanos();
3007 let temp_file = format!("/tmp/rush_test_fd_builtin_{}.txt", timestamp);
3008
3009 let cmd = ShellCommand {
3011 args: vec!["echo".to_string(), "builtin test".to_string()],
3012 redirections: vec![Redirection::Output(temp_file.clone())],
3013 compound: None,
3014 };
3015
3016 let mut shell_state = ShellState::new();
3017 let exit_code = execute_single_command(&cmd, &mut shell_state);
3018 assert_eq!(exit_code, 0);
3019
3020 let content = std::fs::read_to_string(&temp_file).unwrap();
3022 assert!(content.contains("builtin test"));
3023
3024 let _ = std::fs::remove_file(&temp_file);
3026 }
3027
3028 #[test]
3029 fn test_fd_variable_expansion_in_filename() {
3030 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3031
3032 use std::time::{SystemTime, UNIX_EPOCH};
3034 let timestamp = SystemTime::now()
3035 .duration_since(UNIX_EPOCH)
3036 .unwrap()
3037 .as_nanos();
3038 let temp_file = format!("/tmp/rush_test_fd_var_{}.txt", timestamp);
3039
3040 let mut shell_state = ShellState::new();
3042 shell_state.set_var("OUTFILE", temp_file.clone());
3043
3044 let cmd = ShellCommand {
3046 args: vec!["echo".to_string(), "variable test".to_string()],
3047 compound: None,
3048 redirections: vec![Redirection::Output("$OUTFILE".to_string())],
3049 };
3050
3051 let exit_code = execute_single_command(&cmd, &mut shell_state);
3052 assert_eq!(exit_code, 0);
3053
3054 let content = std::fs::read_to_string(&temp_file).unwrap();
3056 assert!(content.contains("variable test"));
3057
3058 let _ = std::fs::remove_file(&temp_file);
3060 }
3061}