1use std::cell::RefCell;
2use std::fs::{File, OpenOptions};
3use std::io::{BufRead, BufReader, Write, pipe};
4use std::os::fd::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 }
696 } else {
697 shell_state.fd_table.borrow_mut().open_fd(
699 fd,
700 &expanded_file,
701 true, false, false, false, )?;
706 }
707
708 Ok(())
709}
710
711fn apply_output_redirection(
713 fd: i32,
714 file: &str,
715 append: bool,
716 shell_state: &mut ShellState,
717 command: Option<&mut Command>,
718) -> Result<(), String> {
719 let expanded_file = expand_variables_in_string(file, shell_state);
720
721 let file_handle = if append {
723 OpenOptions::new()
724 .append(true)
725 .create(true)
726 .open(&expanded_file)
727 .map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?
728 } else {
729 File::create(&expanded_file)
730 .map_err(|e| format!("Cannot create {}: {}", expanded_file, e))?
731 };
732
733 if fd == 1 {
734 if let Some(cmd) = command {
736 cmd.stdout(Stdio::from(file_handle));
737 }
738 } else if fd == 2 {
739 if let Some(cmd) = command {
741 cmd.stderr(Stdio::from(file_handle));
742 }
743 } else {
744 shell_state.fd_table.borrow_mut().open_fd(
746 fd,
747 &expanded_file,
748 false, true, append,
751 !append, )?;
753 }
754
755 Ok(())
756}
757
758fn apply_fd_duplication(
760 target_fd: i32,
761 source_fd: i32,
762 shell_state: &mut ShellState,
763 _command: Option<&mut Command>,
764) -> Result<(), String> {
765 if shell_state.fd_table.borrow().is_closed(source_fd) {
767 let error_msg = format!("File descriptor {} is closed", source_fd);
768 if shell_state.colors_enabled {
769 eprintln!(
770 "{}Redirection error: {}\x1b[0m",
771 shell_state.color_scheme.error, error_msg
772 );
773 } else {
774 eprintln!("Redirection error: {}", error_msg);
775 }
776 return Err(error_msg);
777 }
778
779 shell_state
781 .fd_table
782 .borrow_mut()
783 .duplicate_fd(source_fd, target_fd)?;
784 Ok(())
785}
786
787fn apply_fd_close(
789 fd: i32,
790 shell_state: &mut ShellState,
791 _command: Option<&mut Command>,
792) -> Result<(), String> {
793 shell_state.fd_table.borrow_mut().close_fd(fd)?;
795 Ok(())
796}
797
798fn apply_fd_input_output(
800 fd: i32,
801 file: &str,
802 shell_state: &mut ShellState,
803 _command: Option<&mut Command>,
804) -> Result<(), String> {
805 let expanded_file = expand_variables_in_string(file, shell_state);
806
807 shell_state.fd_table.borrow_mut().open_fd(
809 fd,
810 &expanded_file,
811 true, true, false, false, )?;
816
817 Ok(())
818}
819
820fn apply_heredoc_redirection(
822 fd: i32,
823 delimiter: &str,
824 quoted: bool,
825 shell_state: &mut ShellState,
826 command: Option<&mut Command>,
827) -> Result<(), String> {
828 let here_doc_content = collect_here_document_content(delimiter, shell_state);
829
830 let expanded_content = if quoted {
832 here_doc_content
833 } else {
834 expand_variables_in_string(&here_doc_content, shell_state)
835 };
836
837 let (reader, mut writer) =
839 pipe().map_err(|e| format!("Failed to create pipe for here-document: {}", e))?;
840
841 writeln!(writer, "{}", expanded_content)
842 .map_err(|e| format!("Failed to write here-document content: {}", e))?;
843
844 if fd == 0 {
846 if let Some(cmd) = command {
847 cmd.stdin(Stdio::from(reader));
848 }
849 }
850
851 Ok(())
852}
853
854fn apply_herestring_redirection(
856 fd: i32,
857 content: &str,
858 shell_state: &mut ShellState,
859 command: Option<&mut Command>,
860) -> Result<(), String> {
861 let expanded_content = expand_variables_in_string(content, shell_state);
862
863 let (reader, mut writer) =
865 pipe().map_err(|e| format!("Failed to create pipe for here-string: {}", e))?;
866
867 write!(writer, "{}", expanded_content)
868 .map_err(|e| format!("Failed to write here-string content: {}", e))?;
869
870 if fd == 0 {
872 if let Some(cmd) = command {
873 cmd.stdin(Stdio::from(reader));
874 }
875 }
876
877 Ok(())
878}
879
880fn apply_custom_fds_to_command(command: &mut Command, shell_state: &mut ShellState) {
899 let custom_fds: Vec<(i32, RawFd)> = {
901 let fd_table = shell_state.fd_table.borrow();
902 let mut fds = Vec::new();
903
904 for fd_num in 3..=9 {
905 if fd_table.is_open(fd_num) {
906 if let Some(raw_fd) = fd_table.get_raw_fd(fd_num) {
908 fds.push((fd_num, raw_fd));
909 }
910 }
911 }
912
913 fds
914 };
915
916 if !custom_fds.is_empty() {
918 unsafe {
919 command.pre_exec(move || {
920 for (target_fd, source_fd) in &custom_fds {
921 let result = libc::dup2(*source_fd, *target_fd);
923 if result < 0 {
924 return Err(std::io::Error::last_os_error());
925 }
926 }
927 Ok(())
928 });
929 }
930 }
931}
932
933pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
936 let saved_exit_code = shell_state.last_exit_code;
938
939 let result = match crate::lexer::lex(trap_cmd, shell_state) {
945 Ok(tokens) => {
946 match crate::lexer::expand_aliases(
947 tokens,
948 shell_state,
949 &mut std::collections::HashSet::new(),
950 ) {
951 Ok(expanded_tokens) => {
952 match crate::parser::parse(expanded_tokens) {
953 Ok(ast) => execute(ast, shell_state),
954 Err(_) => {
955 saved_exit_code
957 }
958 }
959 }
960 Err(_) => {
961 saved_exit_code
963 }
964 }
965 }
966 Err(_) => {
967 saved_exit_code
969 }
970 };
971
972 shell_state.last_exit_code = saved_exit_code;
974
975 result
976}
977
978pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
979 match ast {
980 Ast::Assignment { var, value } => {
981 let expanded_value = expand_variables_in_string(&value, shell_state);
983 shell_state.set_var(&var, expanded_value);
984 0
985 }
986 Ast::LocalAssignment { var, value } => {
987 let expanded_value = expand_variables_in_string(&value, shell_state);
989 shell_state.set_local_var(&var, expanded_value);
990 0
991 }
992 Ast::Pipeline(commands) => {
993 if commands.is_empty() {
994 return 0;
995 }
996
997 if commands.len() == 1 {
998 execute_single_command(&commands[0], shell_state)
1000 } else {
1001 execute_pipeline(&commands, shell_state)
1003 }
1004 }
1005 Ast::Sequence(asts) => {
1006 let mut exit_code = 0;
1007 for ast in asts {
1008 exit_code = execute(ast, shell_state);
1009
1010 if shell_state.is_returning() {
1012 return exit_code;
1013 }
1014
1015 if shell_state.exit_requested {
1017 return shell_state.exit_code;
1018 }
1019 }
1020 exit_code
1021 }
1022 Ast::If {
1023 branches,
1024 else_branch,
1025 } => {
1026 for (condition, then_branch) in branches {
1027 let cond_exit = execute(*condition, shell_state);
1028 if cond_exit == 0 {
1029 let exit_code = execute(*then_branch, shell_state);
1030
1031 if shell_state.is_returning() {
1033 return exit_code;
1034 }
1035
1036 return exit_code;
1037 }
1038 }
1039 if let Some(else_b) = else_branch {
1040 let exit_code = execute(*else_b, shell_state);
1041
1042 if shell_state.is_returning() {
1044 return exit_code;
1045 }
1046
1047 exit_code
1048 } else {
1049 0
1050 }
1051 }
1052 Ast::Case {
1053 word,
1054 cases,
1055 default,
1056 } => {
1057 for (patterns, branch) in cases {
1058 for pattern in &patterns {
1059 if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
1060 if glob_pattern.matches(&word) {
1061 let exit_code = execute(branch, shell_state);
1062
1063 if shell_state.is_returning() {
1065 return exit_code;
1066 }
1067
1068 return exit_code;
1069 }
1070 } else {
1071 if &word == pattern {
1073 let exit_code = execute(branch, shell_state);
1074
1075 if shell_state.is_returning() {
1077 return exit_code;
1078 }
1079
1080 return exit_code;
1081 }
1082 }
1083 }
1084 }
1085 if let Some(def) = default {
1086 let exit_code = execute(*def, shell_state);
1087
1088 if shell_state.is_returning() {
1090 return exit_code;
1091 }
1092
1093 exit_code
1094 } else {
1095 0
1096 }
1097 }
1098 Ast::For {
1099 variable,
1100 items,
1101 body,
1102 } => {
1103 let mut exit_code = 0;
1104
1105 for item in items {
1107 crate::state::process_pending_signals(shell_state);
1109
1110 if shell_state.exit_requested {
1112 return shell_state.exit_code;
1113 }
1114
1115 shell_state.set_var(&variable, item.clone());
1117
1118 exit_code = execute(*body.clone(), shell_state);
1120
1121 if shell_state.is_returning() {
1123 return exit_code;
1124 }
1125
1126 if shell_state.exit_requested {
1128 return shell_state.exit_code;
1129 }
1130 }
1131
1132 exit_code
1133 }
1134 Ast::While { condition, body } => {
1135 let mut exit_code = 0;
1136
1137 loop {
1139 let cond_exit = execute(*condition.clone(), shell_state);
1141
1142 if shell_state.is_returning() {
1144 return cond_exit;
1145 }
1146
1147 if shell_state.exit_requested {
1149 return shell_state.exit_code;
1150 }
1151
1152 if cond_exit != 0 {
1154 break;
1155 }
1156
1157 exit_code = execute(*body.clone(), shell_state);
1159
1160 if shell_state.is_returning() {
1162 return exit_code;
1163 }
1164
1165 if shell_state.exit_requested {
1167 return shell_state.exit_code;
1168 }
1169 }
1170
1171 exit_code
1172 }
1173 Ast::FunctionDefinition { name, body } => {
1174 shell_state.define_function(name.clone(), *body);
1176 0
1177 }
1178 Ast::FunctionCall { name, args } => {
1179 if let Some(function_body) = shell_state.get_function(&name).cloned() {
1180 if shell_state.function_depth >= shell_state.max_recursion_depth {
1182 eprintln!(
1183 "Function recursion limit ({}) exceeded",
1184 shell_state.max_recursion_depth
1185 );
1186 return 1;
1187 }
1188
1189 shell_state.enter_function();
1191
1192 let old_positional = shell_state.positional_params.clone();
1194
1195 shell_state.set_positional_params(args.clone());
1197
1198 let exit_code = execute(function_body, shell_state);
1200
1201 if shell_state.is_returning() {
1203 let return_value = shell_state.get_return_value().unwrap_or(0);
1204
1205 shell_state.set_positional_params(old_positional);
1207
1208 shell_state.exit_function();
1210
1211 shell_state.clear_return();
1213
1214 return return_value;
1216 }
1217
1218 shell_state.set_positional_params(old_positional);
1220
1221 shell_state.exit_function();
1223
1224 exit_code
1225 } else {
1226 eprintln!("Function '{}' not found", name);
1227 1
1228 }
1229 }
1230 Ast::Return { value } => {
1231 if shell_state.function_depth == 0 {
1233 eprintln!("Return statement outside of function");
1234 return 1;
1235 }
1236
1237 let exit_code = if let Some(ref val) = value {
1239 val.parse::<i32>().unwrap_or(0)
1240 } else {
1241 0
1242 };
1243
1244 shell_state.set_return(exit_code);
1246
1247 exit_code
1249 }
1250 Ast::And { left, right } => {
1251 let left_exit = execute(*left, shell_state);
1253
1254 if shell_state.is_returning() {
1256 return left_exit;
1257 }
1258
1259 if left_exit == 0 {
1261 execute(*right, shell_state)
1262 } else {
1263 left_exit
1264 }
1265 }
1266 Ast::Or { left, right } => {
1267 let left_exit = execute(*left, shell_state);
1269
1270 if shell_state.is_returning() {
1272 return left_exit;
1273 }
1274
1275 if left_exit != 0 {
1277 execute(*right, shell_state)
1278 } else {
1279 left_exit
1280 }
1281 }
1282 Ast::Subshell { body } => execute_subshell(*body, shell_state),
1283 }
1284}
1285
1286fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
1287 if let Some(ref compound_ast) = cmd.compound {
1289 return execute_compound_with_redirections(
1291 compound_ast,
1292 shell_state,
1293 &cmd.redirections,
1294 );
1295 }
1296
1297 if cmd.args.is_empty() {
1298 if !cmd.redirections.is_empty() {
1300 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, None) {
1301 if shell_state.colors_enabled {
1302 eprintln!(
1303 "{}Redirection error: {}\x1b[0m",
1304 shell_state.color_scheme.error, e
1305 );
1306 } else {
1307 eprintln!("Redirection error: {}", e);
1308 }
1309 return 1;
1310 }
1311 }
1312 return 0;
1313 }
1314
1315 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1317 let expanded_args = match expand_wildcards(&var_expanded_args) {
1318 Ok(args) => args,
1319 Err(_) => return 1,
1320 };
1321
1322 if expanded_args.is_empty() {
1323 return 0;
1324 }
1325
1326 if shell_state.get_function(&expanded_args[0]).is_some() {
1328 let function_call = Ast::FunctionCall {
1330 name: expanded_args[0].clone(),
1331 args: expanded_args[1..].to_vec(),
1332 };
1333 return execute(function_call, shell_state);
1334 }
1335
1336 if crate::builtins::is_builtin(&expanded_args[0]) {
1337 let temp_cmd = ShellCommand {
1339 args: expanded_args,
1340 redirections: cmd.redirections.clone(),
1341 compound: None,
1342 };
1343
1344 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1346 struct CaptureWriter {
1348 buffer: Rc<RefCell<Vec<u8>>>,
1349 }
1350 impl std::io::Write for CaptureWriter {
1351 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1352 self.buffer.borrow_mut().extend_from_slice(buf);
1353 Ok(buf.len())
1354 }
1355 fn flush(&mut self) -> std::io::Result<()> {
1356 Ok(())
1357 }
1358 }
1359 let writer = CaptureWriter {
1360 buffer: capture_buffer.clone(),
1361 };
1362 crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
1363 } else {
1364 crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
1365 }
1366 } else {
1367 let mut env_assignments = Vec::new();
1370 let mut command_start_idx = 0;
1371
1372 for (idx, arg) in expanded_args.iter().enumerate() {
1373 if let Some(eq_pos) = arg.find('=')
1375 && eq_pos > 0
1376 {
1377 let var_part = &arg[..eq_pos];
1378 if var_part
1380 .chars()
1381 .next()
1382 .map(|c| c.is_alphabetic() || c == '_')
1383 .unwrap_or(false)
1384 && var_part.chars().all(|c| c.is_alphanumeric() || c == '_')
1385 {
1386 env_assignments.push(arg.clone());
1387 command_start_idx = idx + 1;
1388 continue;
1389 }
1390 }
1391 break;
1393 }
1394
1395 let has_command = command_start_idx < expanded_args.len();
1397
1398 if !has_command {
1401 for assignment in &env_assignments {
1402 if let Some(eq_pos) = assignment.find('=') {
1403 let var_name = &assignment[..eq_pos];
1404 let var_value = &assignment[eq_pos + 1..];
1405 shell_state.set_var(var_name, var_value.to_string());
1406 }
1407 }
1408
1409 if !cmd.redirections.is_empty() {
1411 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, None) {
1412 if shell_state.colors_enabled {
1413 eprintln!(
1414 "{}Redirection error: {}\x1b[0m",
1415 shell_state.color_scheme.error, e
1416 );
1417 } else {
1418 eprintln!("Redirection error: {}", e);
1419 }
1420 return 1;
1421 }
1422 }
1423 return 0;
1424 }
1425
1426 let mut command = Command::new(&expanded_args[command_start_idx]);
1428 command.args(&expanded_args[command_start_idx + 1..]);
1429
1430 let mut child_env = shell_state.get_env_for_child();
1432
1433 for assignment in env_assignments {
1435 if let Some(eq_pos) = assignment.find('=') {
1436 let var_name = assignment[..eq_pos].to_string();
1437 let var_value = assignment[eq_pos + 1..].to_string();
1438 child_env.insert(var_name, var_value);
1439 }
1440 }
1441
1442 command.env_clear();
1443 for (key, value) in child_env {
1444 command.env(key, value);
1445 }
1446
1447 let capturing = shell_state.capture_output.is_some();
1449 if capturing {
1450 command.stdout(Stdio::piped());
1451 }
1452
1453 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, Some(&mut command)) {
1455 if shell_state.colors_enabled {
1456 eprintln!(
1457 "{}Redirection error: {}\x1b[0m",
1458 shell_state.color_scheme.error, e
1459 );
1460 } else {
1461 eprintln!("Redirection error: {}", e);
1462 }
1463 return 1;
1464 }
1465
1466 apply_custom_fds_to_command(&mut command, shell_state);
1469
1470 match command.spawn() {
1472 Ok(mut child) => {
1473 if capturing {
1475 if let Some(mut stdout) = child.stdout.take() {
1476 use std::io::Read;
1477 let mut output = Vec::new();
1478 if stdout.read_to_end(&mut output).is_ok() {
1479 if let Some(ref capture_buffer) = shell_state.capture_output {
1480 capture_buffer.borrow_mut().extend_from_slice(&output);
1481 }
1482 }
1483 }
1484 }
1485
1486 match child.wait() {
1487 Ok(status) => status.code().unwrap_or(0),
1488 Err(e) => {
1489 if shell_state.colors_enabled {
1490 eprintln!(
1491 "{}Error waiting for command: {}\x1b[0m",
1492 shell_state.color_scheme.error, e
1493 );
1494 } else {
1495 eprintln!("Error waiting for command: {}", e);
1496 }
1497 1
1498 }
1499 }
1500 }
1501 Err(e) => {
1502 if shell_state.colors_enabled {
1503 eprintln!(
1504 "{}Command spawn error: {}\x1b[0m",
1505 shell_state.color_scheme.error, e
1506 );
1507 } else {
1508 eprintln!("Command spawn error: {}", e);
1509 }
1510 1
1511 }
1512 }
1513 }
1514}
1515
1516fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
1517 let mut exit_code = 0;
1518 let mut previous_stdout = None;
1519
1520 for (i, cmd) in commands.iter().enumerate() {
1521 let is_last = i == commands.len() - 1;
1522
1523 if let Some(ref compound_ast) = cmd.compound {
1525 exit_code = execute_compound_in_pipeline(
1527 compound_ast,
1528 shell_state,
1529 is_last,
1530 &cmd.redirections,
1531 );
1532
1533 previous_stdout = None;
1536 continue;
1537 }
1538
1539 if cmd.args.is_empty() {
1540 continue;
1541 }
1542
1543 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1545 let expanded_args = match expand_wildcards(&var_expanded_args) {
1546 Ok(args) => args,
1547 Err(_) => return 1,
1548 };
1549
1550 if expanded_args.is_empty() {
1551 continue;
1552 }
1553
1554 if crate::builtins::is_builtin(&expanded_args[0]) {
1555 let temp_cmd = ShellCommand {
1558 args: expanded_args,
1559 redirections: cmd.redirections.clone(),
1560 compound: None,
1561 };
1562 if !is_last {
1563 let (reader, writer) = match pipe() {
1565 Ok(p) => p,
1566 Err(e) => {
1567 if shell_state.colors_enabled {
1568 eprintln!(
1569 "{}Error creating pipe for builtin: {}\x1b[0m",
1570 shell_state.color_scheme.error, e
1571 );
1572 } else {
1573 eprintln!("Error creating pipe for builtin: {}", e);
1574 }
1575 return 1;
1576 }
1577 };
1578 exit_code = crate::builtins::execute_builtin(
1580 &temp_cmd,
1581 shell_state,
1582 Some(Box::new(writer)),
1583 );
1584 previous_stdout = Some(Stdio::from(reader));
1586 } else {
1587 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1589 struct CaptureWriter {
1591 buffer: Rc<RefCell<Vec<u8>>>,
1592 }
1593 impl std::io::Write for CaptureWriter {
1594 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1595 self.buffer.borrow_mut().extend_from_slice(buf);
1596 Ok(buf.len())
1597 }
1598 fn flush(&mut self) -> std::io::Result<()> {
1599 Ok(())
1600 }
1601 }
1602 let writer = CaptureWriter {
1603 buffer: capture_buffer.clone(),
1604 };
1605 exit_code = crate::builtins::execute_builtin(
1606 &temp_cmd,
1607 shell_state,
1608 Some(Box::new(writer)),
1609 );
1610 } else {
1611 exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
1613 }
1614 previous_stdout = None;
1615 }
1616 } else {
1617 let mut command = Command::new(&expanded_args[0]);
1618 command.args(&expanded_args[1..]);
1619
1620 let child_env = shell_state.get_env_for_child();
1622 command.env_clear();
1623 for (key, value) in child_env {
1624 command.env(key, value);
1625 }
1626
1627 if let Some(prev) = previous_stdout.take() {
1629 command.stdin(prev);
1630 }
1631
1632 if !is_last {
1634 command.stdout(Stdio::piped());
1635 } else if shell_state.capture_output.is_some() {
1636 command.stdout(Stdio::piped());
1638 }
1639
1640 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, Some(&mut command)) {
1642 if shell_state.colors_enabled {
1643 eprintln!(
1644 "{}Redirection error: {}\x1b[0m",
1645 shell_state.color_scheme.error, e
1646 );
1647 } else {
1648 eprintln!("Redirection error: {}", e);
1649 }
1650 return 1;
1651 }
1652
1653 match command.spawn() {
1654 Ok(mut child) => {
1655 if !is_last {
1656 previous_stdout = child.stdout.take().map(Stdio::from);
1657 } else if shell_state.capture_output.is_some() {
1658 if let Some(mut stdout) = child.stdout.take() {
1660 use std::io::Read;
1661 let mut output = Vec::new();
1662 if stdout.read_to_end(&mut output).is_ok()
1663 && let Some(ref capture_buffer) = shell_state.capture_output
1664 {
1665 capture_buffer.borrow_mut().extend_from_slice(&output);
1666 }
1667 }
1668 }
1669 match child.wait() {
1670 Ok(status) => {
1671 exit_code = status.code().unwrap_or(0);
1672 }
1673 Err(e) => {
1674 if shell_state.colors_enabled {
1675 eprintln!(
1676 "{}Error waiting for command: {}\x1b[0m",
1677 shell_state.color_scheme.error, e
1678 );
1679 } else {
1680 eprintln!("Error waiting for command: {}", e);
1681 }
1682 exit_code = 1;
1683 }
1684 }
1685 }
1686 Err(e) => {
1687 if shell_state.colors_enabled {
1688 eprintln!(
1689 "{}Error spawning command '{}{}",
1690 shell_state.color_scheme.error,
1691 expanded_args[0],
1692 &format!("': {}\x1b[0m", e)
1693 );
1694 } else {
1695 eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1696 }
1697 exit_code = 1;
1698 }
1699 }
1700 }
1701 }
1702
1703 exit_code
1704}
1705
1706fn execute_subshell(body: Ast, shell_state: &mut ShellState) -> i32 {
1724 if shell_state.subshell_depth >= MAX_SUBSHELL_DEPTH {
1726 if shell_state.colors_enabled {
1727 eprintln!(
1728 "{}Subshell nesting limit ({}) exceeded\x1b[0m",
1729 shell_state.color_scheme.error, MAX_SUBSHELL_DEPTH
1730 );
1731 } else {
1732 eprintln!("Subshell nesting limit ({}) exceeded", MAX_SUBSHELL_DEPTH);
1733 }
1734 shell_state.last_exit_code = 1;
1735 return 1;
1736 }
1737
1738 let original_dir = std::env::current_dir().ok();
1740
1741 let mut subshell_state = shell_state.clone();
1743
1744 subshell_state.subshell_depth = shell_state.subshell_depth + 1;
1746
1747 let parent_traps = shell_state.trap_handlers.lock().unwrap().clone();
1749 subshell_state.trap_handlers = std::sync::Arc::new(std::sync::Mutex::new(parent_traps));
1750
1751 let exit_code = execute(body, &mut subshell_state);
1753
1754 let final_exit_code = if subshell_state.exit_requested {
1757 subshell_state.exit_code
1759 } else if subshell_state.is_returning() {
1760 subshell_state.get_return_value().unwrap_or(exit_code)
1763 } else {
1764 exit_code
1765 };
1766
1767 subshell_state.fd_table.borrow_mut().clear();
1770
1771 if let Some(dir) = original_dir {
1773 let _ = std::env::set_current_dir(dir);
1774 }
1775
1776 shell_state.last_exit_code = final_exit_code;
1778
1779 final_exit_code
1781}
1782
1783fn execute_compound_with_redirections(
1793 compound_ast: &Ast,
1794 shell_state: &mut ShellState,
1795 redirections: &[Redirection],
1796) -> i32 {
1797 match compound_ast {
1798 Ast::Subshell { body } => {
1799 let has_output_redir = redirections.iter().any(|r| {
1806 matches!(
1807 r,
1808 Redirection::Output(_)
1809 | Redirection::Append(_)
1810 | Redirection::FdOutput(_, _)
1811 | Redirection::FdAppend(_, _)
1812 )
1813 });
1814
1815 if has_output_redir {
1816 let mut subshell_state = shell_state.clone();
1818
1819 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
1821 subshell_state.capture_output = Some(capture_buffer.clone());
1822
1823 let exit_code = execute(*body.clone(), &mut subshell_state);
1825
1826 let output = capture_buffer.borrow().clone();
1828
1829 for redir in redirections {
1831 match redir {
1832 Redirection::Output(file) => {
1833 let expanded_file = expand_variables_in_string(file, shell_state);
1834 if let Err(e) = std::fs::write(&expanded_file, &output) {
1835 if shell_state.colors_enabled {
1836 eprintln!(
1837 "{}Redirection error: {}\x1b[0m",
1838 shell_state.color_scheme.error, e
1839 );
1840 } else {
1841 eprintln!("Redirection error: {}", e);
1842 }
1843 return 1;
1844 }
1845 }
1846 Redirection::Append(file) => {
1847 let expanded_file = expand_variables_in_string(file, shell_state);
1848 use std::fs::OpenOptions;
1849 let mut file_handle = match OpenOptions::new()
1850 .append(true)
1851 .create(true)
1852 .open(&expanded_file)
1853 {
1854 Ok(f) => f,
1855 Err(e) => {
1856 if shell_state.colors_enabled {
1857 eprintln!(
1858 "{}Redirection error: {}\x1b[0m",
1859 shell_state.color_scheme.error, e
1860 );
1861 } else {
1862 eprintln!("Redirection error: {}", e);
1863 }
1864 return 1;
1865 }
1866 };
1867 if let Err(e) = file_handle.write_all(&output) {
1868 if shell_state.colors_enabled {
1869 eprintln!(
1870 "{}Redirection error: {}\x1b[0m",
1871 shell_state.color_scheme.error, e
1872 );
1873 } else {
1874 eprintln!("Redirection error: {}", e);
1875 }
1876 return 1;
1877 }
1878 }
1879 _ => {
1880 }
1883 }
1884 }
1885
1886 shell_state.last_exit_code = exit_code;
1887 exit_code
1888 } else {
1889 execute_subshell(*body.clone(), shell_state)
1891 }
1892 }
1893 _ => {
1894 eprintln!("Unsupported compound command type");
1895 1
1896 }
1897 }
1898}
1899
1900fn execute_compound_in_pipeline(
1911 compound_ast: &Ast,
1912 shell_state: &mut ShellState,
1913 is_last: bool,
1914 _redirections: &[Redirection],
1915) -> i32 {
1916 match compound_ast {
1917 Ast::Subshell { body } => {
1918 let mut subshell_state = shell_state.clone();
1920
1921 if !is_last || shell_state.capture_output.is_some() {
1923 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
1925 subshell_state.capture_output = Some(capture_buffer.clone());
1926
1927 let exit_code = execute(*body.clone(), &mut subshell_state);
1929
1930 if let Some(ref parent_capture) = shell_state.capture_output {
1932 let captured = capture_buffer.borrow().clone();
1933 parent_capture.borrow_mut().extend_from_slice(&captured);
1934 }
1935
1936 shell_state.last_exit_code = exit_code;
1938
1939 exit_code
1940 } else {
1941 let exit_code = execute(*body.clone(), &mut subshell_state);
1943 shell_state.last_exit_code = exit_code;
1944 exit_code
1945 }
1946 }
1947 _ => {
1948 eprintln!("Unsupported compound command in pipeline");
1950 1
1951 }
1952 }
1953}
1954
1955#[cfg(test)]
1956mod tests {
1957 use super::*;
1958 use std::sync::Mutex;
1959
1960 static ENV_LOCK: Mutex<()> = Mutex::new(());
1962
1963 #[test]
1964 fn test_execute_single_command_builtin() {
1965 let cmd = ShellCommand {
1966 args: vec!["true".to_string()],
1967 redirections: Vec::new(),
1968 compound: None,
1969 };
1970 let mut shell_state = ShellState::new();
1971 let exit_code = execute_single_command(&cmd, &mut shell_state);
1972 assert_eq!(exit_code, 0);
1973 }
1974
1975 #[test]
1977 fn test_execute_single_command_external() {
1978 let cmd = ShellCommand {
1979 args: vec!["true".to_string()], redirections: Vec::new(),
1981 compound: None,
1982 };
1983 let mut shell_state = ShellState::new();
1984 let exit_code = execute_single_command(&cmd, &mut shell_state);
1985 assert_eq!(exit_code, 0);
1986 }
1987
1988 #[test]
1989 fn test_execute_single_command_external_nonexistent() {
1990 let cmd = ShellCommand {
1991 args: vec!["nonexistent_command".to_string()],
1992 redirections: Vec::new(),
1993 compound: None,
1994 };
1995 let mut shell_state = ShellState::new();
1996 let exit_code = execute_single_command(&cmd, &mut shell_state);
1997 assert_eq!(exit_code, 1); }
1999
2000 #[test]
2001 fn test_execute_pipeline() {
2002 let commands = vec![
2003 ShellCommand {
2004 args: vec!["printf".to_string(), "hello".to_string()],
2005 redirections: Vec::new(),
2006 compound: None,
2007 },
2008 ShellCommand {
2009 args: vec!["cat".to_string()], redirections: Vec::new(),
2011 compound: None,
2012 },
2013 ];
2014 let mut shell_state = ShellState::new();
2015 let exit_code = execute_pipeline(&commands, &mut shell_state);
2016 assert_eq!(exit_code, 0);
2017 }
2018
2019 #[test]
2020 fn test_execute_empty_pipeline() {
2021 let commands = vec![];
2022 let mut shell_state = ShellState::new();
2023 let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
2024 assert_eq!(exit_code, 0);
2025 }
2026
2027 #[test]
2028 fn test_execute_single_command() {
2029 let ast = Ast::Pipeline(vec![ShellCommand {
2030 args: vec!["true".to_string()],
2031 redirections: Vec::new(),
2032 compound: None,
2033 }]);
2034 let mut shell_state = ShellState::new();
2035 let exit_code = execute(ast, &mut shell_state);
2036 assert_eq!(exit_code, 0);
2037 }
2038
2039 #[test]
2040 fn test_execute_function_definition() {
2041 let ast = Ast::FunctionDefinition {
2042 name: "test_func".to_string(),
2043 body: Box::new(Ast::Pipeline(vec![ShellCommand {
2044 args: vec!["echo".to_string(), "hello".to_string()],
2045 redirections: Vec::new(),
2046 compound: None,
2047 }])),
2048 };
2049 let mut shell_state = ShellState::new();
2050 let exit_code = execute(ast, &mut shell_state);
2051 assert_eq!(exit_code, 0);
2052
2053 assert!(shell_state.get_function("test_func").is_some());
2055 }
2056
2057 #[test]
2058 fn test_execute_function_call() {
2059 let mut shell_state = ShellState::new();
2061 shell_state.define_function(
2062 "test_func".to_string(),
2063 Ast::Pipeline(vec![ShellCommand {
2064 args: vec!["echo".to_string(), "hello".to_string()],
2065 redirections: Vec::new(),
2066 compound: None,
2067 }]),
2068 );
2069
2070 let ast = Ast::FunctionCall {
2072 name: "test_func".to_string(),
2073 args: vec![],
2074 };
2075 let exit_code = execute(ast, &mut shell_state);
2076 assert_eq!(exit_code, 0);
2077 }
2078
2079 #[test]
2080 fn test_execute_function_call_with_args() {
2081 let mut shell_state = ShellState::new();
2083 shell_state.define_function(
2084 "test_func".to_string(),
2085 Ast::Pipeline(vec![ShellCommand {
2086 args: vec!["echo".to_string(), "arg1".to_string()],
2087 redirections: Vec::new(),
2088 compound: None,
2089 }]),
2090 );
2091
2092 let ast = Ast::FunctionCall {
2094 name: "test_func".to_string(),
2095 args: vec!["hello".to_string()],
2096 };
2097 let exit_code = execute(ast, &mut shell_state);
2098 assert_eq!(exit_code, 0);
2099 }
2100
2101 #[test]
2102 fn test_execute_nonexistent_function() {
2103 let mut shell_state = ShellState::new();
2104 let ast = Ast::FunctionCall {
2105 name: "nonexistent".to_string(),
2106 args: vec![],
2107 };
2108 let exit_code = execute(ast, &mut shell_state);
2109 assert_eq!(exit_code, 1); }
2111
2112 #[test]
2113 fn test_execute_function_integration() {
2114 let mut shell_state = ShellState::new();
2116
2117 let define_ast = Ast::FunctionDefinition {
2119 name: "hello".to_string(),
2120 body: Box::new(Ast::Pipeline(vec![ShellCommand {
2121 args: vec!["printf".to_string(), "Hello from function".to_string()],
2122 redirections: Vec::new(),
2123 compound: None,
2124 }])),
2125 };
2126 let exit_code = execute(define_ast, &mut shell_state);
2127 assert_eq!(exit_code, 0);
2128
2129 let call_ast = Ast::FunctionCall {
2131 name: "hello".to_string(),
2132 args: vec![],
2133 };
2134 let exit_code = execute(call_ast, &mut shell_state);
2135 assert_eq!(exit_code, 0);
2136 }
2137
2138 #[test]
2139 fn test_execute_function_with_local_variables() {
2140 let mut shell_state = ShellState::new();
2141
2142 shell_state.set_var("global_var", "global_value".to_string());
2144
2145 let define_ast = Ast::FunctionDefinition {
2147 name: "test_func".to_string(),
2148 body: Box::new(Ast::Sequence(vec![
2149 Ast::LocalAssignment {
2150 var: "local_var".to_string(),
2151 value: "local_value".to_string(),
2152 },
2153 Ast::Assignment {
2154 var: "global_var".to_string(),
2155 value: "modified_in_function".to_string(),
2156 },
2157 Ast::Pipeline(vec![ShellCommand {
2158 args: vec!["printf".to_string(), "success".to_string()],
2159 redirections: Vec::new(),
2160 compound: None,
2161 }]),
2162 ])),
2163 };
2164 let exit_code = execute(define_ast, &mut shell_state);
2165 assert_eq!(exit_code, 0);
2166
2167 assert_eq!(
2169 shell_state.get_var("global_var"),
2170 Some("global_value".to_string())
2171 );
2172
2173 let call_ast = Ast::FunctionCall {
2175 name: "test_func".to_string(),
2176 args: vec![],
2177 };
2178 let exit_code = execute(call_ast, &mut shell_state);
2179 assert_eq!(exit_code, 0);
2180
2181 assert_eq!(
2183 shell_state.get_var("global_var"),
2184 Some("modified_in_function".to_string())
2185 );
2186 }
2187
2188 #[test]
2189 fn test_execute_nested_function_calls() {
2190 let mut shell_state = ShellState::new();
2191
2192 shell_state.set_var("global_var", "global".to_string());
2194
2195 let outer_func = Ast::FunctionDefinition {
2197 name: "outer".to_string(),
2198 body: Box::new(Ast::Sequence(vec![
2199 Ast::Assignment {
2200 var: "global_var".to_string(),
2201 value: "outer_modified".to_string(),
2202 },
2203 Ast::FunctionCall {
2204 name: "inner".to_string(),
2205 args: vec![],
2206 },
2207 Ast::Pipeline(vec![ShellCommand {
2208 args: vec!["printf".to_string(), "outer_done".to_string()],
2209 redirections: Vec::new(),
2210 compound: None,
2211 }]),
2212 ])),
2213 };
2214
2215 let inner_func = Ast::FunctionDefinition {
2217 name: "inner".to_string(),
2218 body: Box::new(Ast::Sequence(vec![
2219 Ast::Assignment {
2220 var: "global_var".to_string(),
2221 value: "inner_modified".to_string(),
2222 },
2223 Ast::Pipeline(vec![ShellCommand {
2224 args: vec!["printf".to_string(), "inner_done".to_string()],
2225 redirections: Vec::new(),
2226 compound: None,
2227 }]),
2228 ])),
2229 };
2230
2231 execute(outer_func, &mut shell_state);
2233 execute(inner_func, &mut shell_state);
2234
2235 shell_state.set_var("global_var", "initial".to_string());
2237
2238 let call_ast = Ast::FunctionCall {
2240 name: "outer".to_string(),
2241 args: vec![],
2242 };
2243 let exit_code = execute(call_ast, &mut shell_state);
2244 assert_eq!(exit_code, 0);
2245
2246 assert_eq!(
2249 shell_state.get_var("global_var"),
2250 Some("inner_modified".to_string())
2251 );
2252 }
2253
2254 #[test]
2255 fn test_here_string_execution() {
2256 let cmd = ShellCommand {
2258 args: vec!["cat".to_string()],
2259 redirections: Vec::new(),
2260 compound: None,
2261 };
2263
2264 assert_eq!(cmd.args, vec!["cat"]);
2267 }
2269
2270 #[test]
2271 fn test_here_document_execution() {
2272 let cmd = ShellCommand {
2274 args: vec!["cat".to_string()],
2275 redirections: Vec::new(),
2276 compound: None,
2277 };
2279
2280 assert_eq!(cmd.args, vec!["cat"]);
2283 }
2285
2286 #[test]
2287 fn test_here_document_with_variable_expansion() {
2288 let mut shell_state = ShellState::new();
2290 shell_state.set_var("PWD", "/test/path".to_string());
2291
2292 let content = "Working dir: $PWD";
2294 let expanded = expand_variables_in_string(content, &mut shell_state);
2295
2296 assert_eq!(expanded, "Working dir: /test/path");
2297 }
2298
2299 #[test]
2300 fn test_here_document_with_command_substitution_builtin() {
2301 let mut shell_state = ShellState::new();
2303 shell_state.set_var("PWD", "/test/dir".to_string());
2304
2305 let content = "Current directory: `pwd`";
2307 let expanded = expand_variables_in_string(content, &mut shell_state);
2308
2309 assert!(expanded.contains("Current directory: "));
2311 }
2312
2313 #[test]
2318 fn test_fd_output_redirection() {
2319 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2320
2321 use std::time::{SystemTime, UNIX_EPOCH};
2323 let timestamp = SystemTime::now()
2324 .duration_since(UNIX_EPOCH)
2325 .unwrap()
2326 .as_nanos();
2327 let temp_file = format!("/tmp/rush_test_fd_out_{}.txt", timestamp);
2328
2329 let cmd = ShellCommand {
2331 args: vec![
2332 "sh".to_string(),
2333 "-c".to_string(),
2334 "echo error >&2".to_string(),
2335 ],
2336 redirections: vec![Redirection::FdOutput(2, temp_file.clone())],
2337 compound: None,
2338 };
2339
2340 let mut shell_state = ShellState::new();
2341 let exit_code = execute_single_command(&cmd, &mut shell_state);
2342 assert_eq!(exit_code, 0);
2343
2344 let content = std::fs::read_to_string(&temp_file).unwrap();
2346 assert_eq!(content.trim(), "error");
2347
2348 let _ = std::fs::remove_file(&temp_file);
2350 }
2351
2352 #[test]
2353 fn test_fd_input_redirection() {
2354 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2355
2356 use std::time::{SystemTime, UNIX_EPOCH};
2358 let timestamp = SystemTime::now()
2359 .duration_since(UNIX_EPOCH)
2360 .unwrap()
2361 .as_nanos();
2362 let temp_file = format!("/tmp/rush_test_fd_in_{}.txt", timestamp);
2363
2364 std::fs::write(&temp_file, "test input\n").unwrap();
2365 std::thread::sleep(std::time::Duration::from_millis(10));
2366
2367 let cmd = ShellCommand {
2370 args: vec!["cat".to_string()],
2371 compound: None,
2372 redirections: vec![
2373 Redirection::FdInput(3, temp_file.clone()),
2374 Redirection::Input(temp_file.clone()),
2375 ],
2376 };
2377
2378 let mut shell_state = ShellState::new();
2379 let exit_code = execute_single_command(&cmd, &mut shell_state);
2380 assert_eq!(exit_code, 0);
2381
2382 let _ = std::fs::remove_file(&temp_file);
2384 }
2385
2386 #[test]
2387 fn test_fd_append_redirection() {
2388 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2389
2390 use std::time::{SystemTime, UNIX_EPOCH};
2392 let timestamp = SystemTime::now()
2393 .duration_since(UNIX_EPOCH)
2394 .unwrap()
2395 .as_nanos();
2396 let temp_file = format!("/tmp/rush_test_fd_append_{}.txt", timestamp);
2397
2398 std::fs::write(&temp_file, "first line\n").unwrap();
2399 std::thread::sleep(std::time::Duration::from_millis(10));
2400
2401 let cmd = ShellCommand {
2403 args: vec![
2404 "sh".to_string(),
2405 "-c".to_string(),
2406 "echo second line >&2".to_string(),
2407 ],
2408 redirections: vec![Redirection::FdAppend(2, temp_file.clone())],
2409 compound: None,
2410 };
2411
2412 let mut shell_state = ShellState::new();
2413 let exit_code = execute_single_command(&cmd, &mut shell_state);
2414 assert_eq!(exit_code, 0);
2415
2416 let content = std::fs::read_to_string(&temp_file).unwrap();
2418 assert!(content.contains("first line"));
2419 assert!(content.contains("second line"));
2420
2421 let _ = std::fs::remove_file(&temp_file);
2423 }
2424
2425 #[test]
2426 fn test_fd_duplication_stderr_to_stdout() {
2427 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2428
2429 use std::time::{SystemTime, UNIX_EPOCH};
2431 let timestamp = SystemTime::now()
2432 .duration_since(UNIX_EPOCH)
2433 .unwrap()
2434 .as_nanos();
2435 let temp_file = format!("/tmp/rush_test_fd_dup_{}.txt", timestamp);
2436
2437 let cmd = ShellCommand {
2441 args: vec![
2442 "sh".to_string(),
2443 "-c".to_string(),
2444 "echo test; echo error >&2".to_string(),
2445 ],
2446 compound: None,
2447 redirections: vec![Redirection::Output(temp_file.clone())],
2448 };
2449
2450 let mut shell_state = ShellState::new();
2451 let exit_code = execute_single_command(&cmd, &mut shell_state);
2452 assert_eq!(exit_code, 0);
2453
2454 assert!(std::path::Path::new(&temp_file).exists());
2456 let content = std::fs::read_to_string(&temp_file).unwrap();
2457 assert!(content.contains("test"));
2458
2459 let _ = std::fs::remove_file(&temp_file);
2461 }
2462
2463 #[test]
2464 fn test_fd_close() {
2465 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2466
2467 let cmd = ShellCommand {
2469 args: vec!["sh".to_string(), "-c".to_string(), "echo test".to_string()],
2470 redirections: vec![Redirection::FdClose(2)],
2471 compound: None,
2472 };
2473
2474 let mut shell_state = ShellState::new();
2475 let exit_code = execute_single_command(&cmd, &mut shell_state);
2476 assert_eq!(exit_code, 0);
2477
2478 assert!(shell_state.fd_table.borrow().is_closed(2));
2480 }
2481
2482 #[test]
2483 fn test_fd_read_write() {
2484 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2485
2486 use std::time::{SystemTime, UNIX_EPOCH};
2488 let timestamp = SystemTime::now()
2489 .duration_since(UNIX_EPOCH)
2490 .unwrap()
2491 .as_nanos();
2492 let temp_file = format!("/tmp/rush_test_fd_rw_{}.txt", timestamp);
2493
2494 std::fs::write(&temp_file, "initial content\n").unwrap();
2495 std::thread::sleep(std::time::Duration::from_millis(10));
2496
2497 let cmd = ShellCommand {
2499 args: vec!["cat".to_string()],
2500 compound: None,
2501 redirections: vec![
2502 Redirection::FdInputOutput(3, temp_file.clone()),
2503 Redirection::Input(temp_file.clone()),
2504 ],
2505 };
2506
2507 let mut shell_state = ShellState::new();
2508 let exit_code = execute_single_command(&cmd, &mut shell_state);
2509 assert_eq!(exit_code, 0);
2510
2511 let _ = std::fs::remove_file(&temp_file);
2513 }
2514
2515 #[test]
2516 fn test_multiple_fd_redirections() {
2517 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2518
2519 use std::time::{SystemTime, UNIX_EPOCH};
2521 let timestamp = SystemTime::now()
2522 .duration_since(UNIX_EPOCH)
2523 .unwrap()
2524 .as_nanos();
2525 let out_file = format!("/tmp/rush_test_fd_multi_out_{}.txt", timestamp);
2526 let err_file = format!("/tmp/rush_test_fd_multi_err_{}.txt", timestamp);
2527
2528 let cmd = ShellCommand {
2530 args: vec![
2531 "sh".to_string(),
2532 "-c".to_string(),
2533 "echo stdout; echo stderr >&2".to_string(),
2534 ],
2535 redirections: vec![
2536 Redirection::FdOutput(2, err_file.clone()),
2537 Redirection::Output(out_file.clone()),
2538 ],
2539 compound: None,
2540 };
2541
2542 let mut shell_state = ShellState::new();
2543 let exit_code = execute_single_command(&cmd, &mut shell_state);
2544 assert_eq!(exit_code, 0);
2545
2546 assert!(std::path::Path::new(&out_file).exists());
2548 assert!(std::path::Path::new(&err_file).exists());
2549
2550 let out_content = std::fs::read_to_string(&out_file).unwrap();
2552 let err_content = std::fs::read_to_string(&err_file).unwrap();
2553 assert!(out_content.contains("stdout"));
2554 assert!(err_content.contains("stderr"));
2555
2556 let _ = std::fs::remove_file(&out_file);
2558 let _ = std::fs::remove_file(&err_file);
2559 }
2560
2561 #[test]
2562 fn test_fd_swap_pattern() {
2563 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2564
2565 use std::time::{SystemTime, UNIX_EPOCH};
2567 let timestamp = SystemTime::now()
2568 .duration_since(UNIX_EPOCH)
2569 .unwrap()
2570 .as_nanos();
2571 let temp_file = format!("/tmp/rush_test_fd_swap_{}.txt", timestamp);
2572
2573 let cmd = ShellCommand {
2576 args: vec!["sh".to_string(), "-c".to_string(), "echo test".to_string()],
2577 redirections: vec![
2578 Redirection::FdOutput(3, temp_file.clone()), Redirection::FdClose(3), Redirection::Output(temp_file.clone()), ],
2582 compound: None,
2583 };
2584
2585 let mut shell_state = ShellState::new();
2586 let exit_code = execute_single_command(&cmd, &mut shell_state);
2587 assert_eq!(exit_code, 0);
2588
2589 assert!(shell_state.fd_table.borrow().is_closed(3));
2591
2592 let _ = std::fs::remove_file(&temp_file);
2594 }
2595
2596 #[test]
2597 fn test_fd_redirection_with_pipes() {
2598 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2599
2600 use std::time::{SystemTime, UNIX_EPOCH};
2602 let timestamp = SystemTime::now()
2603 .duration_since(UNIX_EPOCH)
2604 .unwrap()
2605 .as_nanos();
2606 let temp_file = format!("/tmp/rush_test_fd_pipe_{}.txt", timestamp);
2607
2608 let commands = vec![
2611 ShellCommand {
2612 args: vec!["echo".to_string(), "piped output".to_string()],
2613 redirections: vec![],
2614 compound: None,
2615 },
2616 ShellCommand {
2617 args: vec!["cat".to_string()],
2618 compound: None,
2619 redirections: vec![Redirection::Output(temp_file.clone())],
2620 },
2621 ];
2622
2623 let mut shell_state = ShellState::new();
2624 let exit_code = execute_pipeline(&commands, &mut shell_state);
2625 assert_eq!(exit_code, 0);
2626
2627 let content = std::fs::read_to_string(&temp_file).unwrap();
2629 assert!(content.contains("piped output"));
2630
2631 let _ = std::fs::remove_file(&temp_file);
2633 }
2634
2635 #[test]
2636 fn test_fd_error_invalid_fd_number() {
2637 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2638
2639 use std::time::{SystemTime, UNIX_EPOCH};
2641 let timestamp = SystemTime::now()
2642 .duration_since(UNIX_EPOCH)
2643 .unwrap()
2644 .as_nanos();
2645 let temp_file = format!("/tmp/rush_test_fd_invalid_{}.txt", timestamp);
2646
2647 let cmd = ShellCommand {
2649 args: vec!["echo".to_string(), "test".to_string()],
2650 compound: None,
2651 redirections: vec![Redirection::FdOutput(10, temp_file.clone())],
2652 };
2653
2654 let mut shell_state = ShellState::new();
2655 let exit_code = execute_single_command(&cmd, &mut shell_state);
2656
2657 assert_eq!(exit_code, 1);
2659
2660 let _ = std::fs::remove_file(&temp_file);
2662 }
2663
2664 #[test]
2665 fn test_fd_error_duplicate_closed_fd() {
2666 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2667
2668 let cmd = ShellCommand {
2670 args: vec!["echo".to_string(), "test".to_string()],
2671 compound: None,
2672 redirections: vec![
2673 Redirection::FdClose(3),
2674 Redirection::FdDuplicate(2, 3), ],
2676 };
2677
2678 let mut shell_state = ShellState::new();
2679 let exit_code = execute_single_command(&cmd, &mut shell_state);
2680
2681 assert_eq!(exit_code, 1);
2683 }
2684
2685 #[test]
2686 fn test_fd_error_file_permission() {
2687 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2688
2689 let cmd = ShellCommand {
2691 args: vec!["echo".to_string(), "test".to_string()],
2692 redirections: vec![Redirection::FdOutput(2, "/proc/version".to_string())],
2693 compound: None,
2694 };
2695
2696 let mut shell_state = ShellState::new();
2697 let exit_code = execute_single_command(&cmd, &mut shell_state);
2698
2699 assert_eq!(exit_code, 1);
2701 }
2702
2703 #[test]
2704 fn test_fd_redirection_order() {
2705 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2706
2707 use std::time::{SystemTime, UNIX_EPOCH};
2709 let timestamp = SystemTime::now()
2710 .duration_since(UNIX_EPOCH)
2711 .unwrap()
2712 .as_nanos();
2713 let file1 = format!("/tmp/rush_test_fd_order1_{}.txt", timestamp);
2714 let file2 = format!("/tmp/rush_test_fd_order2_{}.txt", timestamp);
2715
2716 let cmd = ShellCommand {
2719 args: vec!["echo".to_string(), "test".to_string()],
2720 compound: None,
2721 redirections: vec![
2722 Redirection::Output(file1.clone()),
2723 Redirection::Output(file2.clone()),
2724 ],
2725 };
2726
2727 let mut shell_state = ShellState::new();
2728 let exit_code = execute_single_command(&cmd, &mut shell_state);
2729 assert_eq!(exit_code, 0);
2730
2731 let content2 = std::fs::read_to_string(&file2).unwrap();
2733 assert!(content2.contains("test"));
2734
2735 let _ = std::fs::remove_file(&file1);
2737 let _ = std::fs::remove_file(&file2);
2738 }
2739
2740 #[test]
2741 fn test_fd_builtin_with_redirection() {
2742 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2743
2744 use std::time::{SystemTime, UNIX_EPOCH};
2746 let timestamp = SystemTime::now()
2747 .duration_since(UNIX_EPOCH)
2748 .unwrap()
2749 .as_nanos();
2750 let temp_file = format!("/tmp/rush_test_fd_builtin_{}.txt", timestamp);
2751
2752 let cmd = ShellCommand {
2754 args: vec!["echo".to_string(), "builtin test".to_string()],
2755 redirections: vec![Redirection::Output(temp_file.clone())],
2756 compound: None,
2757 };
2758
2759 let mut shell_state = ShellState::new();
2760 let exit_code = execute_single_command(&cmd, &mut shell_state);
2761 assert_eq!(exit_code, 0);
2762
2763 let content = std::fs::read_to_string(&temp_file).unwrap();
2765 assert!(content.contains("builtin test"));
2766
2767 let _ = std::fs::remove_file(&temp_file);
2769 }
2770
2771 #[test]
2772 fn test_fd_variable_expansion_in_filename() {
2773 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2774
2775 use std::time::{SystemTime, UNIX_EPOCH};
2777 let timestamp = SystemTime::now()
2778 .duration_since(UNIX_EPOCH)
2779 .unwrap()
2780 .as_nanos();
2781 let temp_file = format!("/tmp/rush_test_fd_var_{}.txt", timestamp);
2782
2783 let mut shell_state = ShellState::new();
2785 shell_state.set_var("OUTFILE", temp_file.clone());
2786
2787 let cmd = ShellCommand {
2789 args: vec!["echo".to_string(), "variable test".to_string()],
2790 compound: None,
2791 redirections: vec![Redirection::Output("$OUTFILE".to_string())],
2792 };
2793
2794 let exit_code = execute_single_command(&cmd, &mut shell_state);
2795 assert_eq!(exit_code, 0);
2796
2797 let content = std::fs::read_to_string(&temp_file).unwrap();
2799 assert!(content.contains("variable test"));
2800
2801 let _ = std::fs::remove_file(&temp_file);
2803 }
2804}