1use super::parser::Ast;
2use lazy_static::lazy_static;
3use std::cell::RefCell;
4use std::collections::{HashMap, HashSet, VecDeque};
5use std::env;
6use std::fs::{File, OpenOptions};
7use std::io::IsTerminal;
8use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
9use std::process::Stdio;
10use std::rc::Rc;
11use std::sync::{Arc, Mutex};
12use std::time::Instant;
13
14lazy_static! {
15 pub static ref SIGNAL_QUEUE: Arc<Mutex<VecDeque<SignalEvent>>> =
18 Arc::new(Mutex::new(VecDeque::new()));
19}
20
21const MAX_SIGNAL_QUEUE_SIZE: usize = 100;
23
24#[derive(Debug, Clone)]
26pub struct SignalEvent {
27 pub signal_name: String,
29 pub signal_number: i32,
31 pub timestamp: Instant,
33}
34
35impl SignalEvent {
36 pub fn new(signal_name: String, signal_number: i32) -> Self {
37 Self {
38 signal_name,
39 signal_number,
40 timestamp: Instant::now(),
41 }
42 }
43}
44
45#[derive(Debug)]
47pub enum FileDescriptor {
48 File(File),
50 Duplicate(RawFd),
52 Closed,
54}
55
56impl FileDescriptor {
57 pub fn try_clone(&self) -> Result<Self, String> {
58 match self {
59 FileDescriptor::File(f) => {
60 let new_file = f
61 .try_clone()
62 .map_err(|e| format!("Failed to clone file: {}", e))?;
63 Ok(FileDescriptor::File(new_file))
64 }
65 FileDescriptor::Duplicate(fd) => Ok(FileDescriptor::Duplicate(*fd)),
66 FileDescriptor::Closed => Ok(FileDescriptor::Closed),
67 }
68 }
69}
70
71#[derive(Debug)]
73pub struct FileDescriptorTable {
74 fds: HashMap<i32, FileDescriptor>,
76 saved_fds: HashMap<i32, RawFd>,
78}
79
80impl FileDescriptorTable {
81 pub fn new() -> Self {
83 Self {
84 fds: HashMap::new(),
85 saved_fds: HashMap::new(),
86 }
87 }
88
89 pub fn open_fd(
103 &mut self,
104 fd_num: i32,
105 path: &str,
106 read: bool,
107 write: bool,
108 append: bool,
109 truncate: bool,
110 ) -> Result<(), String> {
111 if !(0..=1024).contains(&fd_num) {
113 return Err(format!("Invalid file descriptor number: {}", fd_num));
114 }
115
116 let file = OpenOptions::new()
118 .read(read)
119 .write(write)
120 .append(append)
121 .truncate(truncate)
122 .create(write || append)
123 .open(path)
124 .map_err(|e| format!("Cannot open {}: {}", path, e))?;
125
126 self.fds.insert(fd_num, FileDescriptor::File(file));
128 Ok(())
129 }
130
131 pub fn duplicate_fd(&mut self, source_fd: i32, target_fd: i32) -> Result<(), String> {
141 if !(0..=1024).contains(&source_fd) {
143 return Err(format!("Invalid source file descriptor: {}", source_fd));
144 }
145 if !(0..=1024).contains(&target_fd) {
146 return Err(format!("Invalid target file descriptor: {}", target_fd));
147 }
148
149 if source_fd == target_fd {
151 return Ok(());
152 }
153
154 let raw_fd = match self.get_raw_fd(source_fd) {
156 Some(fd) => fd,
157 None => {
158 return Err(format!(
159 "File descriptor {} is not open or is closed",
160 source_fd
161 ));
162 }
163 };
164
165 self.fds
167 .insert(target_fd, FileDescriptor::Duplicate(raw_fd));
168 Ok(())
169 }
170
171 pub fn close_fd(&mut self, fd_num: i32) -> Result<(), String> {
180 if !(0..=1024).contains(&fd_num) {
182 return Err(format!("Invalid file descriptor number: {}", fd_num));
183 }
184
185 self.fds.insert(fd_num, FileDescriptor::Closed);
187 Ok(())
188 }
189
190 pub fn save_fd(&mut self, fd_num: i32) -> Result<(), String> {
199 if !(0..=1024).contains(&fd_num) {
201 return Err(format!("Invalid file descriptor number: {}", fd_num));
202 }
203
204 let saved_fd = unsafe {
206 let raw_fd = fd_num as RawFd;
207 libc::dup(raw_fd)
208 };
209
210 if saved_fd < 0 {
211 return Err(format!("Failed to save file descriptor {}", fd_num));
212 }
213
214 self.saved_fds.insert(fd_num, saved_fd);
215 Ok(())
216 }
217
218 pub fn restore_fd(&mut self, fd_num: i32) -> Result<(), String> {
227 if !(0..=1024).contains(&fd_num) {
229 return Err(format!("Invalid file descriptor number: {}", fd_num));
230 }
231
232 if let Some(saved_fd) = self.saved_fds.remove(&fd_num) {
234 unsafe {
236 let result = libc::dup2(saved_fd, fd_num as RawFd);
237 libc::close(saved_fd); if result < 0 {
240 return Err(format!("Failed to restore file descriptor {}", fd_num));
241 }
242 }
243
244 self.fds.remove(&fd_num);
246 }
247
248 Ok(())
249 }
250
251 pub fn deep_clone(&self) -> Result<Self, String> {
254 let mut new_fds = HashMap::new();
255 for (fd, descriptor) in &self.fds {
256 new_fds.insert(*fd, descriptor.try_clone()?);
257 }
258
259 Ok(Self {
260 fds: new_fds,
261 saved_fds: self.saved_fds.clone(),
262 })
263 }
264
265 pub fn save_all_fds(&mut self) -> Result<(), String> {
271 let fd_nums: Vec<i32> = self.fds.keys().copied().collect();
273 for fd_num in fd_nums {
274 self.save_fd(fd_num)?;
275 }
276
277 for fd in 0..=2 {
280 if !self.fds.contains_key(&fd) {
281 let _ = self.save_fd(fd);
283 }
284 }
285 Ok(())
286 }
287
288 pub fn restore_all_fds(&mut self) -> Result<(), String> {
294 let fd_nums: Vec<i32> = self.saved_fds.keys().copied().collect();
296 for fd_num in fd_nums {
297 self.restore_fd(fd_num)?;
298 }
299 Ok(())
300 }
301
302 #[allow(dead_code)]
311 pub fn get_stdio(&self, fd_num: i32) -> Option<Stdio> {
312 match self.fds.get(&fd_num) {
313 Some(FileDescriptor::File(file)) => {
314 let raw_fd = file.as_raw_fd();
316 let dup_fd = unsafe { libc::dup(raw_fd) };
317 if dup_fd >= 0 {
318 let file = unsafe { File::from_raw_fd(dup_fd) };
319 Some(Stdio::from(file))
320 } else {
321 None
322 }
323 }
324 Some(FileDescriptor::Duplicate(raw_fd)) => {
325 let dup_fd = unsafe { libc::dup(*raw_fd) };
327 if dup_fd >= 0 {
328 let file = unsafe { File::from_raw_fd(dup_fd) };
329 Some(Stdio::from(file))
330 } else {
331 None
332 }
333 }
334 Some(FileDescriptor::Closed) | None => None,
335 }
336 }
337
338 pub fn get_raw_fd(&self, fd_num: i32) -> Option<RawFd> {
347 match self.fds.get(&fd_num) {
348 Some(FileDescriptor::File(file)) => Some(file.as_raw_fd()),
349 Some(FileDescriptor::Duplicate(raw_fd)) => Some(*raw_fd),
350 Some(FileDescriptor::Closed) => None,
351 None => {
352 if fd_num >= 0 && fd_num <= 2 {
354 Some(fd_num as RawFd)
355 } else {
356 None
357 }
358 }
359 }
360 }
361
362 pub fn is_open(&self, fd_num: i32) -> bool {
371 matches!(
372 self.fds.get(&fd_num),
373 Some(FileDescriptor::File(_)) | Some(FileDescriptor::Duplicate(_))
374 )
375 }
376
377 pub fn is_closed(&self, fd_num: i32) -> bool {
386 matches!(self.fds.get(&fd_num), Some(FileDescriptor::Closed))
387 }
388
389 pub fn clear(&mut self) {
391 self.fds.clear();
392 self.saved_fds.clear();
393 }
394}
395
396impl Default for FileDescriptorTable {
397 fn default() -> Self {
398 Self::new()
399 }
400}
401
402#[derive(Debug, Clone)]
403pub struct ColorScheme {
404 pub prompt: String,
406 pub error: String,
408 pub success: String,
410 pub builtin: String,
412 pub directory: String,
414}
415
416impl Default for ColorScheme {
417 fn default() -> Self {
418 Self {
419 prompt: "\x1b[32m".to_string(), error: "\x1b[31m".to_string(), success: "\x1b[32m".to_string(), builtin: "\x1b[36m".to_string(), directory: "\x1b[34m".to_string(), }
425 }
426}
427
428#[derive(Debug, Clone)]
429pub struct ShellState {
430 pub variables: HashMap<String, String>,
432 pub exported: HashSet<String>,
434 pub last_exit_code: i32,
436 pub shell_pid: u32,
438 pub script_name: String,
440 pub dir_stack: Vec<String>,
442 pub aliases: HashMap<String, String>,
444 pub colors_enabled: bool,
446 pub color_scheme: ColorScheme,
448 pub positional_params: Vec<String>,
450 pub functions: HashMap<String, Ast>,
452 pub local_vars: Vec<HashMap<String, String>>,
454 pub function_depth: usize,
456 pub max_recursion_depth: usize,
458 pub returning: bool,
460 pub return_value: Option<i32>,
462 pub loop_depth: usize,
464 pub breaking: bool,
466 pub break_level: usize,
468 pub continuing: bool,
470 pub continue_level: usize,
472 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
474 pub condensed_cwd: bool,
476 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
478 pub exit_trap_executed: bool,
480 pub exit_requested: bool,
482 pub exit_code: i32,
484 #[allow(dead_code)]
487 pub pending_signals: bool,
488 pub pending_heredoc_content: Option<String>,
490 pub collecting_heredoc: Option<(String, String, String)>, pub fd_table: Rc<RefCell<FileDescriptorTable>>,
494 pub subshell_depth: usize,
496 pub stdin_override: Option<RawFd>,
498}
499
500impl ShellState {
501 pub fn new() -> Self {
502 let shell_pid = std::process::id();
503
504 let no_color = env::var("NO_COLOR").is_ok();
506
507 let rush_colors = env::var("RUSH_COLORS")
509 .map(|v| v.to_lowercase())
510 .unwrap_or_else(|_| "auto".to_string());
511
512 let colors_enabled = match rush_colors.as_str() {
513 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
514 "0" | "false" | "off" | "disable" => false,
515 "auto" => !no_color && std::io::stdout().is_terminal(),
516 _ => !no_color && std::io::stdout().is_terminal(),
517 };
518
519 let rush_condensed = env::var("RUSH_CONDENSED")
521 .map(|v| v.to_lowercase())
522 .unwrap_or_else(|_| "true".to_string());
523
524 let condensed_cwd = match rush_condensed.as_str() {
525 "1" | "true" | "on" | "enable" => true,
526 "0" | "false" | "off" | "disable" => false,
527 _ => true, };
529
530 Self {
531 variables: HashMap::new(),
532 exported: HashSet::new(),
533 last_exit_code: 0,
534 shell_pid,
535 script_name: "rush".to_string(),
536 dir_stack: Vec::new(),
537 aliases: HashMap::new(),
538 colors_enabled,
539 color_scheme: ColorScheme::default(),
540 positional_params: Vec::new(),
541 functions: HashMap::new(),
542 local_vars: Vec::new(),
543 function_depth: 0,
544 max_recursion_depth: 500, returning: false,
546 return_value: None,
547 loop_depth: 0,
548 breaking: false,
549 break_level: 0,
550 continuing: false,
551 continue_level: 0,
552 capture_output: None,
553 condensed_cwd,
554 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
555 exit_trap_executed: false,
556 exit_requested: false,
557 exit_code: 0,
558 pending_signals: false,
559 pending_heredoc_content: None,
560 collecting_heredoc: None,
561 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
562 subshell_depth: 0,
563 stdin_override: None,
564 }
565 }
566
567 pub fn get_var(&self, name: &str) -> Option<String> {
569 match name {
571 "?" => Some(self.last_exit_code.to_string()),
572 "$" => Some(self.shell_pid.to_string()),
573 "0" => Some(self.script_name.clone()),
574 "*" => {
575 if self.positional_params.is_empty() {
577 Some("".to_string())
578 } else {
579 Some(self.positional_params.join(" "))
580 }
581 }
582 "@" => {
583 if self.positional_params.is_empty() {
585 Some("".to_string())
586 } else {
587 Some(self.positional_params.join(" "))
588 }
589 }
590 "#" => Some(self.positional_params.len().to_string()),
591 _ => {
592 if let Ok(index) = name.parse::<usize>()
594 && index > 0
595 && index <= self.positional_params.len()
596 {
597 return Some(self.positional_params[index - 1].clone());
598 }
599
600 for scope in self.local_vars.iter().rev() {
603 if let Some(value) = scope.get(name) {
604 return Some(value.clone());
605 }
606 }
607
608 if let Some(value) = self.variables.get(name) {
610 Some(value.clone())
611 } else {
612 env::var(name).ok()
614 }
615 }
616 }
617 }
618
619 pub fn set_var(&mut self, name: &str, value: String) {
621 for scope in self.local_vars.iter_mut().rev() {
624 if scope.contains_key(name) {
625 scope.insert(name.to_string(), value);
626 return;
627 }
628 }
629
630 self.variables.insert(name.to_string(), value);
632 }
633
634 pub fn unset_var(&mut self, name: &str) {
636 self.variables.remove(name);
637 self.exported.remove(name);
638 }
639
640 pub fn export_var(&mut self, name: &str) {
642 if self.variables.contains_key(name) {
643 self.exported.insert(name.to_string());
644 }
645 }
646
647 pub fn set_exported_var(&mut self, name: &str, value: String) {
649 self.set_var(name, value);
650 self.export_var(name);
651 }
652
653 pub fn get_env_for_child(&self) -> HashMap<String, String> {
655 let mut child_env = HashMap::new();
656
657 for (key, value) in env::vars() {
659 child_env.insert(key, value);
660 }
661
662 for var_name in &self.exported {
664 if let Some(value) = self.variables.get(var_name) {
665 child_env.insert(var_name.clone(), value.clone());
666 }
667 }
668
669 child_env
670 }
671
672 pub fn set_last_exit_code(&mut self, code: i32) {
674 self.last_exit_code = code;
675 }
676
677 pub fn set_script_name(&mut self, name: &str) {
679 self.script_name = name.to_string();
680 }
681
682 pub fn get_condensed_cwd(&self) -> String {
684 match env::current_dir() {
685 Ok(path) => {
686 let path_str = path.to_string_lossy();
687 let components: Vec<&str> = path_str.split('/').collect();
688 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
689 return "/".to_string();
690 }
691 let mut result = String::new();
692 for (i, comp) in components.iter().enumerate() {
693 if comp.is_empty() {
694 continue; }
696 if i == components.len() - 1 {
697 result.push('/');
698 result.push_str(comp);
699 } else {
700 result.push('/');
701 if let Some(first) = comp.chars().next() {
702 result.push(first);
703 }
704 }
705 }
706 if result.is_empty() {
707 "/".to_string()
708 } else {
709 result
710 }
711 }
712 Err(_) => "/?".to_string(), }
714 }
715
716 pub fn get_full_cwd(&self) -> String {
718 match env::current_dir() {
719 Ok(path) => path.to_string_lossy().to_string(),
720 Err(_) => "/?".to_string(), }
722 }
723
724 pub fn get_user_hostname(&self) -> String {
726 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
727
728 if let Ok(hostname) = env::var("HOSTNAME")
730 && !hostname.trim().is_empty()
731 {
732 return format!("{}@{}", user, hostname);
733 }
734
735 let hostname = match std::process::Command::new("hostname").output() {
737 Ok(output) if output.status.success() => {
738 String::from_utf8_lossy(&output.stdout).trim().to_string()
739 }
740 _ => "hostname".to_string(), };
742
743 if hostname != "hostname" {
745 unsafe {
746 env::set_var("HOSTNAME", &hostname);
747 }
748 }
749
750 format!("{}@{}", user, hostname)
751 }
752
753 pub fn get_prompt(&self) -> String {
755 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
756 let prompt_char = if user == "root" { "#" } else { "$" };
757 let cwd = if self.condensed_cwd {
758 self.get_condensed_cwd()
759 } else {
760 self.get_full_cwd()
761 };
762 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
763 }
764
765 pub fn set_alias(&mut self, name: &str, value: String) {
767 self.aliases.insert(name.to_string(), value);
768 }
769
770 pub fn get_alias(&self, name: &str) -> Option<&String> {
772 self.aliases.get(name)
773 }
774
775 pub fn remove_alias(&mut self, name: &str) {
777 self.aliases.remove(name);
778 }
779
780 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
782 &self.aliases
783 }
784
785 pub fn set_positional_params(&mut self, params: Vec<String>) {
787 self.positional_params = params;
788 }
789
790 #[allow(dead_code)]
792 pub fn get_positional_params(&self) -> &[String] {
793 &self.positional_params
794 }
795
796 pub fn shift_positional_params(&mut self, count: usize) {
798 if count > 0 {
799 for _ in 0..count {
800 if !self.positional_params.is_empty() {
801 self.positional_params.remove(0);
802 }
803 }
804 }
805 }
806
807 #[allow(dead_code)]
809 pub fn push_positional_param(&mut self, param: String) {
810 self.positional_params.push(param);
811 }
812
813 pub fn define_function(&mut self, name: String, body: Ast) {
815 self.functions.insert(name, body);
816 }
817
818 pub fn get_function(&self, name: &str) -> Option<&Ast> {
820 self.functions.get(name)
821 }
822
823 #[allow(dead_code)]
825 pub fn remove_function(&mut self, name: &str) {
826 self.functions.remove(name);
827 }
828
829 #[allow(dead_code)]
831 pub fn get_function_names(&self) -> Vec<&String> {
832 self.functions.keys().collect()
833 }
834
835 pub fn push_local_scope(&mut self) {
837 self.local_vars.push(HashMap::new());
838 }
839
840 pub fn pop_local_scope(&mut self) {
842 if !self.local_vars.is_empty() {
843 self.local_vars.pop();
844 }
845 }
846
847 pub fn set_local_var(&mut self, name: &str, value: String) {
849 if let Some(current_scope) = self.local_vars.last_mut() {
850 current_scope.insert(name.to_string(), value);
851 } else {
852 self.set_var(name, value);
854 }
855 }
856
857 pub fn enter_function(&mut self) {
859 self.function_depth += 1;
860 if self.function_depth > self.local_vars.len() {
861 self.push_local_scope();
862 }
863 }
864
865 pub fn exit_function(&mut self) {
867 if self.function_depth > 0 {
868 self.function_depth -= 1;
869 if self.function_depth == self.local_vars.len() - 1 {
870 self.pop_local_scope();
871 }
872 }
873 }
874
875 pub fn set_return(&mut self, value: i32) {
877 self.returning = true;
878 self.return_value = Some(value);
879 }
880
881 pub fn clear_return(&mut self) {
883 self.returning = false;
884 self.return_value = None;
885 }
886
887 pub fn is_returning(&self) -> bool {
889 self.returning
890 }
891
892 pub fn get_return_value(&self) -> Option<i32> {
894 self.return_value
895 }
896
897 pub fn enter_loop(&mut self) {
899 self.loop_depth += 1;
900 }
901
902 pub fn exit_loop(&mut self) {
904 if self.loop_depth > 0 {
905 self.loop_depth -= 1;
906 }
907 }
908
909 pub fn set_break(&mut self, level: usize) {
911 self.breaking = true;
912 self.break_level = level;
913 }
914
915 pub fn clear_break(&mut self) {
917 self.breaking = false;
918 self.break_level = 0;
919 }
920
921 pub fn is_breaking(&self) -> bool {
923 self.breaking
924 }
925
926 pub fn get_break_level(&self) -> usize {
928 self.break_level
929 }
930
931 pub fn decrement_break_level(&mut self) {
933 if self.break_level > 0 {
934 self.break_level -= 1;
935 }
936 if self.break_level == 0 {
937 self.breaking = false;
938 }
939 }
940
941 pub fn set_continue(&mut self, level: usize) {
943 self.continuing = true;
944 self.continue_level = level;
945 }
946
947 pub fn clear_continue(&mut self) {
949 self.continuing = false;
950 self.continue_level = 0;
951 }
952
953 pub fn is_continuing(&self) -> bool {
955 self.continuing
956 }
957
958 pub fn get_continue_level(&self) -> usize {
960 self.continue_level
961 }
962
963 pub fn decrement_continue_level(&mut self) {
965 if self.continue_level > 0 {
966 self.continue_level -= 1;
967 }
968 if self.continue_level == 0 {
969 self.continuing = false;
970 }
971 }
972
973 pub fn set_trap(&mut self, signal: &str, command: String) {
975 if let Ok(mut handlers) = self.trap_handlers.lock() {
976 handlers.insert(signal.to_uppercase(), command);
977 }
978 }
979
980 pub fn get_trap(&self, signal: &str) -> Option<String> {
982 if let Ok(handlers) = self.trap_handlers.lock() {
983 handlers.get(&signal.to_uppercase()).cloned()
984 } else {
985 None
986 }
987 }
988
989 pub fn remove_trap(&mut self, signal: &str) {
991 if let Ok(mut handlers) = self.trap_handlers.lock() {
992 handlers.remove(&signal.to_uppercase());
993 }
994 }
995
996 pub fn get_all_traps(&self) -> HashMap<String, String> {
998 if let Ok(handlers) = self.trap_handlers.lock() {
999 handlers.clone()
1000 } else {
1001 HashMap::new()
1002 }
1003 }
1004
1005 #[allow(dead_code)]
1007 pub fn clear_traps(&mut self) {
1008 if let Ok(mut handlers) = self.trap_handlers.lock() {
1009 handlers.clear();
1010 }
1011 }
1012}
1013
1014pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
1017 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
1018 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
1020 queue.pop_front();
1021 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
1022 }
1023
1024 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
1025 }
1026}
1027
1028pub fn process_pending_signals(shell_state: &mut ShellState) {
1031 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
1033 while let Some(signal_event) = queue.pop_front() {
1035 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
1037 && !trap_cmd.is_empty()
1038 {
1039 if shell_state.colors_enabled {
1041 eprintln!(
1042 "{}Signal {} (signal {}) received at {:?}\x1b[0m",
1043 shell_state.color_scheme.builtin,
1044 signal_event.signal_name,
1045 signal_event.signal_number,
1046 signal_event.timestamp
1047 );
1048 } else {
1049 eprintln!(
1050 "Signal {} (signal {}) received at {:?}",
1051 signal_event.signal_name,
1052 signal_event.signal_number,
1053 signal_event.timestamp
1054 );
1055 }
1056
1057 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
1060 }
1061 }
1062 }
1063}
1064
1065impl Default for ShellState {
1066 fn default() -> Self {
1067 Self::new()
1068 }
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use std::sync::Mutex;
1075
1076 static FILE_LOCK: Mutex<()> = Mutex::new(());
1078
1079 #[test]
1080 fn test_shell_state_basic() {
1081 let mut state = ShellState::new();
1082 state.set_var("TEST_VAR", "test_value".to_string());
1083 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
1084 }
1085
1086 #[test]
1087 fn test_special_variables() {
1088 let mut state = ShellState::new();
1089 state.set_last_exit_code(42);
1090 state.set_script_name("test_script");
1091
1092 assert_eq!(state.get_var("?"), Some("42".to_string()));
1093 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
1094 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
1095 }
1096
1097 #[test]
1098 fn test_export_variable() {
1099 let mut state = ShellState::new();
1100 state.set_var("EXPORT_VAR", "export_value".to_string());
1101 state.export_var("EXPORT_VAR");
1102
1103 let child_env = state.get_env_for_child();
1104 assert_eq!(
1105 child_env.get("EXPORT_VAR"),
1106 Some(&"export_value".to_string())
1107 );
1108 }
1109
1110 #[test]
1111 fn test_unset_variable() {
1112 let mut state = ShellState::new();
1113 state.set_var("UNSET_VAR", "value".to_string());
1114 state.export_var("UNSET_VAR");
1115
1116 assert!(state.variables.contains_key("UNSET_VAR"));
1117 assert!(state.exported.contains("UNSET_VAR"));
1118
1119 state.unset_var("UNSET_VAR");
1120
1121 assert!(!state.variables.contains_key("UNSET_VAR"));
1122 assert!(!state.exported.contains("UNSET_VAR"));
1123 }
1124
1125 #[test]
1126 fn test_get_user_hostname() {
1127 let state = ShellState::new();
1128 let user_hostname = state.get_user_hostname();
1129 assert!(user_hostname.contains('@'));
1131 }
1132
1133 #[test]
1134 fn test_get_prompt() {
1135 let state = ShellState::new();
1136 let prompt = state.get_prompt();
1137 assert!(prompt.ends_with(" $ "));
1139 assert!(prompt.contains('@'));
1140 }
1141
1142 #[test]
1143 fn test_positional_parameters() {
1144 let mut state = ShellState::new();
1145 state.set_positional_params(vec![
1146 "arg1".to_string(),
1147 "arg2".to_string(),
1148 "arg3".to_string(),
1149 ]);
1150
1151 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1152 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1153 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1154 assert_eq!(state.get_var("4"), None);
1155 assert_eq!(state.get_var("#"), Some("3".to_string()));
1156 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
1157 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
1158 }
1159
1160 #[test]
1161 fn test_positional_parameters_empty() {
1162 let mut state = ShellState::new();
1163 state.set_positional_params(vec![]);
1164
1165 assert_eq!(state.get_var("1"), None);
1166 assert_eq!(state.get_var("#"), Some("0".to_string()));
1167 assert_eq!(state.get_var("*"), Some("".to_string()));
1168 assert_eq!(state.get_var("@"), Some("".to_string()));
1169 }
1170
1171 #[test]
1172 fn test_shift_positional_params() {
1173 let mut state = ShellState::new();
1174 state.set_positional_params(vec![
1175 "arg1".to_string(),
1176 "arg2".to_string(),
1177 "arg3".to_string(),
1178 ]);
1179
1180 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1181 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1182 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1183
1184 state.shift_positional_params(1);
1185
1186 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
1187 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
1188 assert_eq!(state.get_var("3"), None);
1189 assert_eq!(state.get_var("#"), Some("2".to_string()));
1190
1191 state.shift_positional_params(2);
1192
1193 assert_eq!(state.get_var("1"), None);
1194 assert_eq!(state.get_var("#"), Some("0".to_string()));
1195 }
1196
1197 #[test]
1198 fn test_push_positional_param() {
1199 let mut state = ShellState::new();
1200 state.set_positional_params(vec!["arg1".to_string()]);
1201
1202 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1203 assert_eq!(state.get_var("#"), Some("1".to_string()));
1204
1205 state.push_positional_param("arg2".to_string());
1206
1207 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1208 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1209 assert_eq!(state.get_var("#"), Some("2".to_string()));
1210 }
1211
1212 #[test]
1213 fn test_local_variable_scoping() {
1214 let mut state = ShellState::new();
1215
1216 state.set_var("global_var", "global_value".to_string());
1218 assert_eq!(
1219 state.get_var("global_var"),
1220 Some("global_value".to_string())
1221 );
1222
1223 state.push_local_scope();
1225
1226 state.set_local_var("global_var", "local_value".to_string());
1228 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
1229
1230 state.set_local_var("local_var", "local_only".to_string());
1232 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
1233
1234 state.pop_local_scope();
1236
1237 assert_eq!(
1239 state.get_var("global_var"),
1240 Some("global_value".to_string())
1241 );
1242 assert_eq!(state.get_var("local_var"), None);
1243 }
1244
1245 #[test]
1246 fn test_nested_local_scopes() {
1247 let mut state = ShellState::new();
1248
1249 state.set_var("test_var", "global".to_string());
1251
1252 state.push_local_scope();
1254 state.set_local_var("test_var", "level1".to_string());
1255 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1256
1257 state.push_local_scope();
1259 state.set_local_var("test_var", "level2".to_string());
1260 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
1261
1262 state.pop_local_scope();
1264 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1265
1266 state.pop_local_scope();
1268 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1269 }
1270
1271 #[test]
1272 fn test_variable_set_in_local_scope() {
1273 let mut state = ShellState::new();
1274
1275 state.set_var("test_var", "global".to_string());
1277 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1278
1279 state.push_local_scope();
1281 state.set_local_var("test_var", "local".to_string());
1282 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
1283
1284 state.pop_local_scope();
1286 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1287 }
1288
1289 #[test]
1290 fn test_condensed_cwd_environment_variable() {
1291 let state = ShellState::new();
1293 assert!(state.condensed_cwd);
1294
1295 unsafe {
1297 env::set_var("RUSH_CONDENSED", "true");
1298 }
1299 let state = ShellState::new();
1300 assert!(state.condensed_cwd);
1301
1302 unsafe {
1304 env::set_var("RUSH_CONDENSED", "false");
1305 }
1306 let state = ShellState::new();
1307 assert!(!state.condensed_cwd);
1308
1309 unsafe {
1311 env::remove_var("RUSH_CONDENSED");
1312 }
1313 }
1314
1315 #[test]
1316 fn test_get_full_cwd() {
1317 let state = ShellState::new();
1318 let full_cwd = state.get_full_cwd();
1319 assert!(!full_cwd.is_empty());
1320 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
1322 }
1323
1324 #[test]
1325 fn test_prompt_with_condensed_setting() {
1326 let mut state = ShellState::new();
1327
1328 assert!(state.condensed_cwd);
1330 let prompt_condensed = state.get_prompt();
1331 assert!(prompt_condensed.contains('@'));
1332
1333 state.condensed_cwd = false;
1335 let prompt_full = state.get_prompt();
1336 assert!(prompt_full.contains('@'));
1337
1338 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
1340 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
1341 }
1342
1343 #[test]
1346 fn test_fd_table_creation() {
1347 let fd_table = FileDescriptorTable::new();
1348 assert!(!fd_table.is_open(0));
1349 assert!(!fd_table.is_open(1));
1350 assert!(!fd_table.is_open(2));
1351 }
1352
1353 #[test]
1354 fn test_fd_table_open_file() {
1355 let mut fd_table = FileDescriptorTable::new();
1356
1357 let temp_file = "/tmp/rush_test_fd_open.txt";
1359 std::fs::write(temp_file, "test content").unwrap();
1360
1361 let result = fd_table.open_fd(3, temp_file, true, false, false, false);
1363 assert!(result.is_ok());
1364 assert!(fd_table.is_open(3));
1365
1366 let _ = std::fs::remove_file(temp_file);
1368 }
1369
1370 #[test]
1371 fn test_fd_table_open_file_for_writing() {
1372 let mut fd_table = FileDescriptorTable::new();
1373
1374 let temp_file = "/tmp/rush_test_fd_write.txt";
1376
1377 let result = fd_table.open_fd(4, temp_file, false, true, false, true);
1379 assert!(result.is_ok());
1380 assert!(fd_table.is_open(4));
1381
1382 let _ = std::fs::remove_file(temp_file);
1384 }
1385
1386 #[test]
1387 fn test_fd_table_invalid_fd_number() {
1388 let mut fd_table = FileDescriptorTable::new();
1389
1390 let result = fd_table.open_fd(-1, "/tmp/test.txt", true, false, false, false);
1392 assert!(result.is_err());
1393 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1394
1395 let result = fd_table.open_fd(1025, "/tmp/test.txt", true, false, false, false);
1396 assert!(result.is_err());
1397 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1398 }
1399
1400 #[test]
1401 fn test_fd_table_duplicate_fd() {
1402 let mut fd_table = FileDescriptorTable::new();
1403
1404 let temp_file = "/tmp/rush_test_fd_dup.txt";
1406 std::fs::write(temp_file, "test content").unwrap();
1407
1408 fd_table
1410 .open_fd(3, temp_file, true, false, false, false)
1411 .unwrap();
1412 assert!(fd_table.is_open(3));
1413
1414 let result = fd_table.duplicate_fd(3, 4);
1416 assert!(result.is_ok());
1417 assert!(fd_table.is_open(4));
1418
1419 let _ = std::fs::remove_file(temp_file);
1421 }
1422
1423 #[test]
1424 fn test_fd_table_duplicate_to_self() {
1425 let mut fd_table = FileDescriptorTable::new();
1426
1427 let temp_file = "/tmp/rush_test_fd_dup_self.txt";
1429 std::fs::write(temp_file, "test content").unwrap();
1430
1431 fd_table
1433 .open_fd(3, temp_file, true, false, false, false)
1434 .unwrap();
1435
1436 let result = fd_table.duplicate_fd(3, 3);
1438 assert!(result.is_ok());
1439 assert!(fd_table.is_open(3));
1440
1441 let _ = std::fs::remove_file(temp_file);
1443 }
1444
1445 #[test]
1446 fn test_fd_table_duplicate_closed_fd() {
1447 let mut fd_table = FileDescriptorTable::new();
1448
1449 let result = fd_table.duplicate_fd(3, 4);
1451 assert!(result.is_err());
1452 assert!(result.unwrap_err().contains("not open"));
1453 }
1454
1455 #[test]
1456 fn test_fd_table_close_fd() {
1457 let mut fd_table = FileDescriptorTable::new();
1458
1459 let temp_file = "/tmp/rush_test_fd_close.txt";
1461 std::fs::write(temp_file, "test content").unwrap();
1462
1463 fd_table
1465 .open_fd(3, temp_file, true, false, false, false)
1466 .unwrap();
1467 assert!(fd_table.is_open(3));
1468
1469 let result = fd_table.close_fd(3);
1471 assert!(result.is_ok());
1472 assert!(fd_table.is_closed(3));
1473 assert!(!fd_table.is_open(3));
1474
1475 let _ = std::fs::remove_file(temp_file);
1477 }
1478
1479 #[test]
1480 fn test_fd_table_save_and_restore() {
1481 let mut fd_table = FileDescriptorTable::new();
1482
1483 let result = fd_table.save_fd(0);
1485 assert!(result.is_ok());
1486
1487 let result = fd_table.restore_fd(0);
1489 assert!(result.is_ok());
1490 }
1491
1492 #[test]
1493 fn test_fd_table_save_all_and_restore_all() {
1494 let _lock = FILE_LOCK.lock().unwrap();
1495 let mut fd_table = FileDescriptorTable::new();
1496
1497 use std::time::{SystemTime, UNIX_EPOCH};
1499 let timestamp = SystemTime::now()
1500 .duration_since(UNIX_EPOCH)
1501 .unwrap()
1502 .as_nanos();
1503 let temp_file1 = format!("/tmp/rush_test_fd_save1_{}.txt", timestamp);
1504 let temp_file2 = format!("/tmp/rush_test_fd_save2_{}.txt", timestamp);
1505
1506 std::fs::write(&temp_file1, "test content 1").unwrap();
1507 std::fs::write(&temp_file2, "test content 2").unwrap();
1508
1509 let f1 = File::open(&temp_file1).unwrap();
1513 let f2 = File::open(&temp_file2).unwrap();
1514 unsafe {
1515 libc::dup2(f1.as_raw_fd(), 50);
1516 libc::dup2(f2.as_raw_fd(), 51);
1517 }
1518
1519 fd_table
1520 .open_fd(50, &temp_file1, true, false, false, false)
1521 .unwrap();
1522 fd_table
1523 .open_fd(51, &temp_file2, true, false, false, false)
1524 .unwrap();
1525
1526 let result = fd_table.save_all_fds();
1528 assert!(result.is_ok());
1529
1530 let result = fd_table.restore_all_fds();
1532 assert!(result.is_ok());
1533
1534 unsafe {
1536 libc::close(50);
1537 libc::close(51);
1538 }
1539 let _ = std::fs::remove_file(&temp_file1);
1540 let _ = std::fs::remove_file(&temp_file2);
1541 }
1542
1543 #[test]
1544 fn test_fd_table_clear() {
1545 let mut fd_table = FileDescriptorTable::new();
1546
1547 let temp_file = "/tmp/rush_test_fd_clear.txt";
1549 std::fs::write(temp_file, "test content").unwrap();
1550
1551 fd_table
1557 .open_fd(50, temp_file, true, false, false, false)
1558 .unwrap();
1559 assert!(fd_table.is_open(50));
1560
1561 fd_table.clear();
1563 assert!(!fd_table.is_open(3));
1564
1565 let _ = std::fs::remove_file(temp_file);
1567 }
1568
1569 #[test]
1570 fn test_fd_table_get_stdio() {
1571 let mut fd_table = FileDescriptorTable::new();
1572
1573 let temp_file = "/tmp/rush_test_fd_stdio.txt";
1575 std::fs::write(temp_file, "test content").unwrap();
1576
1577 fd_table
1579 .open_fd(3, temp_file, true, false, false, false)
1580 .unwrap();
1581
1582 let stdio = fd_table.get_stdio(3);
1584 assert!(stdio.is_some());
1585
1586 let stdio = fd_table.get_stdio(5);
1588 assert!(stdio.is_none());
1589
1590 let _ = std::fs::remove_file(temp_file);
1592 }
1593
1594 #[test]
1595 fn test_fd_table_multiple_operations() {
1596 let mut fd_table = FileDescriptorTable::new();
1597
1598 let temp_file1 = "/tmp/rush_test_fd_multi1.txt";
1600 let temp_file2 = "/tmp/rush_test_fd_multi2.txt";
1601 std::fs::write(temp_file1, "test content 1").unwrap();
1602 std::fs::write(temp_file2, "test content 2").unwrap();
1603
1604 fd_table
1606 .open_fd(3, temp_file1, true, false, false, false)
1607 .unwrap();
1608 assert!(fd_table.is_open(3));
1609
1610 fd_table.duplicate_fd(3, 4).unwrap();
1612 assert!(fd_table.is_open(4));
1613
1614 fd_table
1616 .open_fd(5, temp_file2, true, false, false, false)
1617 .unwrap();
1618 assert!(fd_table.is_open(5));
1619
1620 fd_table.close_fd(4).unwrap();
1622 assert!(fd_table.is_closed(4));
1623 assert!(!fd_table.is_open(4));
1624
1625 assert!(fd_table.is_open(3));
1627 assert!(fd_table.is_open(5));
1628
1629 let _ = std::fs::remove_file(temp_file1);
1631 let _ = std::fs::remove_file(temp_file2);
1632 }
1633
1634 #[test]
1635 fn test_shell_state_has_fd_table() {
1636 let state = ShellState::new();
1637 let fd_table = state.fd_table.borrow();
1638 assert!(!fd_table.is_open(3));
1639 }
1640
1641 #[test]
1642 fn test_shell_state_fd_table_operations() {
1643 let state = ShellState::new();
1644
1645 let temp_file = "/tmp/rush_test_state_fd.txt";
1647 std::fs::write(temp_file, "test content").unwrap();
1648
1649 {
1651 let mut fd_table = state.fd_table.borrow_mut();
1652 fd_table
1653 .open_fd(3, temp_file, true, false, false, false)
1654 .unwrap();
1655 }
1656
1657 {
1659 let fd_table = state.fd_table.borrow();
1660 assert!(fd_table.is_open(3));
1661 }
1662
1663 let _ = std::fs::remove_file(temp_file);
1665 }
1666}