1use std::io::{self, Write};
2use std::os::unix::io::FromRawFd;
3
4use crate::parser::ShellCommand;
5use crate::state::ShellState;
6
7pub struct ColoredWriter<W: Write> {
9 inner: W,
10}
11
12impl<W: Write> ColoredWriter<W> {
13 pub fn new(inner: W) -> Self {
14 Self { inner }
15 }
16}
17
18impl<W: Write> Write for ColoredWriter<W> {
19 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
20 self.inner.write(buf)
21 }
22
23 fn flush(&mut self) -> io::Result<()> {
24 self.inner.flush()
25 }
26}
27
28pub struct BadFdWriter;
30
31impl Write for BadFdWriter {
32 fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
33 Err(io::Error::from_raw_os_error(libc::EBADF))
34 }
35
36 fn flush(&mut self) -> io::Result<()> {
37 Err(io::Error::from_raw_os_error(libc::EBADF))
38 }
39}
40
41mod builtin_alias;
42mod builtin_break;
43mod builtin_cd;
44mod builtin_continue;
45mod builtin_declare;
46mod builtin_dirs;
47mod builtin_env;
48mod builtin_exit;
49mod builtin_export;
50mod builtin_help;
51mod builtin_popd;
52mod builtin_pushd;
53mod builtin_pwd;
54mod builtin_return;
55mod builtin_set;
56mod builtin_set_color_scheme;
57mod builtin_set_colors;
58mod builtin_set_condensed;
59mod builtin_shift;
60mod builtin_source;
61mod builtin_test;
62mod builtin_trap;
63mod builtin_type;
64mod builtin_unalias;
65mod builtin_unset;
66
67pub trait Builtin {
68 fn name(&self) -> &'static str;
69 fn names(&self) -> Vec<&'static str>;
70 fn description(&self) -> &'static str;
71 fn run(
72 &self,
73 cmd: &ShellCommand,
74 shell_state: &mut ShellState,
75 output_writer: &mut dyn Write,
76 ) -> i32;
77}
78
79fn get_builtins() -> Vec<Box<dyn Builtin>> {
94 vec![
95 Box::new(builtin_cd::CdBuiltin),
96 Box::new(builtin_pwd::PwdBuiltin),
97 Box::new(builtin_env::EnvBuiltin),
98 Box::new(builtin_exit::ExitBuiltin),
99 Box::new(builtin_help::HelpBuiltin),
100 Box::new(builtin_source::SourceBuiltin),
101 Box::new(builtin_export::ExportBuiltin),
102 Box::new(builtin_unset::UnsetBuiltin),
103 Box::new(builtin_pushd::PushdBuiltin),
104 Box::new(builtin_popd::PopdBuiltin),
105 Box::new(builtin_dirs::DirsBuiltin),
106 Box::new(builtin_alias::AliasBuiltin),
107 Box::new(builtin_unalias::UnaliasBuiltin),
108 Box::new(builtin_test::TestBuiltin),
109 Box::new(builtin_set::SetBuiltin),
110 Box::new(builtin_set_colors::SetColorsBuiltin),
111 Box::new(builtin_set_color_scheme::SetColorSchemeBuiltin),
112 Box::new(builtin_set_condensed::SetCondensedBuiltin),
113 Box::new(builtin_shift::ShiftBuiltin),
114 Box::new(builtin_declare::DeclareBuiltin),
115 Box::new(builtin_trap::TrapBuiltin),
116 Box::new(builtin_type::TypeBuiltin),
117 Box::new(builtin_return::ReturnBuiltin),
118 Box::new(builtin_break::BreakBuiltin),
119 Box::new(builtin_continue::ContinueBuiltin),
120 ]
121}
122
123pub fn is_builtin(cmd: &str) -> bool {
124 get_builtins().iter().any(|b| b.names().contains(&cmd))
125}
126
127pub fn get_builtin_commands() -> Vec<String> {
128 let builtins = get_builtins();
129 let mut commands = Vec::new();
130 for b in builtins {
131 for &name in &b.names() {
132 commands.push(name.to_string());
133 }
134 }
135 commands
136}
137
138pub fn execute_builtin(
161 cmd: &ShellCommand,
162 shell_state: &mut ShellState,
163 output_override: Option<Box<dyn Write>>,
164) -> i32 {
165 let colors_enabled = shell_state.colors_enabled;
167 let error_color = shell_state.color_scheme.error.clone();
168 let print_error = move |msg: &str| {
169 if colors_enabled {
170 eprintln!("{}{}\x1b[0m", error_color, msg);
171 } else {
172 eprintln!("{}", msg);
173 }
174 };
175
176 if let Some(mut output_writer) = output_override {
178 let builtins = get_builtins();
179 if let Some(builtin) = builtins
180 .into_iter()
181 .find(|b| b.names().contains(&cmd.args[0].as_str()))
182 {
183 return builtin.run(cmd, shell_state, &mut *output_writer);
184 } else {
185 return 1;
186 }
187 }
188
189 use crate::parser::Redirection;
191
192 let redirections = cmd.redirections.clone();
194
195 let mut files_to_expand: Vec<String> = Vec::new();
198 for redir in &redirections {
199 match redir {
200 Redirection::Input(file)
201 | Redirection::Output(file)
202 | Redirection::OutputClobber(file)
203 | Redirection::Append(file)
204 | Redirection::FdInput(_, file)
205 | Redirection::FdOutput(_, file)
206 | Redirection::FdOutputClobber(_, file)
207 | Redirection::FdAppend(_, file)
208 | Redirection::FdInputOutput(_, file) => {
209 files_to_expand.push(file.clone());
210 }
211 _ => {
212 files_to_expand.push(String::new()); }
214 }
215 }
216
217 let mut expanded_files: Vec<String> = Vec::new();
219 for f in &files_to_expand {
220 if f.is_empty() {
221 expanded_files.push(String::new());
222 } else {
223 expanded_files.push(crate::executor::expand_variables_in_string(f, shell_state));
224 }
225 }
226
227 let mut expanded_redirections: Vec<(Redirection, Option<String>)> = Vec::new();
229 for (i, redir) in redirections.iter().enumerate() {
230 let expanded_file = if expanded_files[i].is_empty() {
231 None
232 } else {
233 Some(expanded_files[i].clone())
234 };
235 expanded_redirections.push((redir.clone(), expanded_file));
236 }
237
238 if let Err(e) = shell_state.fd_table.borrow_mut().save_all_fds() {
240 print_error(&format!("Failed to save file descriptors: {}", e));
241 return 1;
242 }
243
244 for (redir, expanded_file) in &expanded_redirections {
246 let result = match redir {
247 Redirection::Input(_) => {
248 let file = expanded_file.as_ref().unwrap();
249 shell_state.fd_table.borrow_mut().open_fd(
250 0, file, true, false, false, false, false, )
256 }
257 Redirection::Output(_) | Redirection::OutputClobber(_) => {
258 let file = expanded_file.as_ref().unwrap();
259 shell_state.fd_table.borrow_mut().open_fd(
260 1, file, false, true, false, true, false, )
266 }
267 Redirection::Append(_) => {
268 let file = expanded_file.as_ref().unwrap();
269 shell_state.fd_table.borrow_mut().open_fd(
270 1, file, false, true, true, false, false, )
276 }
277 Redirection::FdInput(fd, _) => {
278 let file = expanded_file.as_ref().unwrap();
279 shell_state.fd_table.borrow_mut().open_fd(
280 *fd, file, true, false, false, false, false, )
286 }
287 Redirection::FdOutput(fd, _) | Redirection::FdOutputClobber(fd, _) => {
288 let file = expanded_file.as_ref().unwrap();
289 shell_state.fd_table.borrow_mut().open_fd(
290 *fd, file, false, true, false, true, false, )
296 }
297 Redirection::FdAppend(fd, _) => {
298 let file = expanded_file.as_ref().unwrap();
299 shell_state.fd_table.borrow_mut().open_fd(
300 *fd, file, false, true, true, false, false, )
306 }
307 Redirection::FdDuplicate(target_fd, source_fd) => shell_state
308 .fd_table
309 .borrow_mut()
310 .duplicate_fd(*source_fd, *target_fd),
311 Redirection::FdClose(fd) => shell_state.fd_table.borrow_mut().close_fd(*fd),
312 Redirection::FdInputOutput(fd, _) => {
313 let file = expanded_file.as_ref().unwrap();
314 shell_state.fd_table.borrow_mut().open_fd(
315 *fd, file, true, true, false, false, false, )
321 }
322 Redirection::HereDoc(_, _) | Redirection::HereString(_) => Ok(()),
325 };
326
327 if let Err(e) = result {
328 print_error(&format!("Redirection error: {}", e));
329 let _ = shell_state.fd_table.borrow_mut().restore_all_fds();
331 return 1;
332 }
333 }
334
335 let mut output_writer: Box<dyn Write> = {
337 let raw_fd = shell_state.fd_table.borrow().get_raw_fd(1);
338 match raw_fd {
339 Some(fd) => {
340 let dup_fd = unsafe { libc::dup(fd) };
343 if dup_fd >= 0 {
344 let file = unsafe { std::fs::File::from_raw_fd(dup_fd) };
345 Box::new(ColoredWriter::new(file))
346 } else {
347 let err = io::Error::last_os_error();
349 if err.raw_os_error() == Some(libc::EBADF) {
350 Box::new(BadFdWriter)
353 } else {
354 print_error(&format!("Failed to duplicate stdout: {}", err));
356 let _ = shell_state.fd_table.borrow_mut().restore_all_fds();
357 return 1;
358 }
359 }
360 }
361 None => {
362 Box::new(BadFdWriter)
364 }
365 }
366 };
367
368 let builtins = get_builtins();
370 let exit_code = if let Some(builtin) = builtins
371 .into_iter()
372 .find(|b| b.names().contains(&cmd.args[0].as_str()))
373 {
374 builtin.run(cmd, shell_state, &mut *output_writer)
375 } else {
376 1
377 };
378
379 if let Err(e) = shell_state.fd_table.borrow_mut().restore_all_fds() {
381 print_error(&format!("Failed to restore file descriptors: {}", e));
382 return 1;
383 }
384
385 exit_code
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn test_is_builtin() {
394 assert!(is_builtin("cd"));
395 assert!(is_builtin("pwd"));
396 assert!(is_builtin("env"));
397 assert!(is_builtin("exit"));
398 assert!(is_builtin("help"));
399 assert!(is_builtin("alias"));
400 assert!(is_builtin("unalias"));
401 assert!(is_builtin("test"));
402 assert!(is_builtin("["));
403 assert!(is_builtin("."));
404 assert!(!is_builtin("ls"));
405 assert!(!is_builtin("grep"));
406 assert!(!is_builtin("echo"));
407 }
408
409 #[test]
410 fn test_execute_builtin_unknown() {
411 let cmd = ShellCommand {
412 args: vec!["unknown".to_string()],
413 redirections: Vec::new(),
414 compound: None,
415 };
416 let mut shell_state = ShellState::new();
417 let exit_code = execute_builtin(&cmd, &mut shell_state, None);
418 assert_eq!(exit_code, 1);
419 }
420
421 #[test]
422 fn test_get_builtin_commands() {
423 let commands = get_builtin_commands();
424 assert!(commands.contains(&"cd".to_string()));
425 assert!(commands.contains(&"pwd".to_string()));
426 assert!(commands.contains(&"env".to_string()));
427 assert!(commands.contains(&"exit".to_string()));
428 assert!(commands.contains(&"help".to_string()));
429 assert!(commands.contains(&"source".to_string()));
430 assert!(commands.contains(&"export".to_string()));
431 assert!(commands.contains(&"unset".to_string()));
432 assert!(commands.contains(&"pushd".to_string()));
433 assert!(commands.contains(&"popd".to_string()));
434 assert!(commands.contains(&"dirs".to_string()));
435 assert!(commands.contains(&"alias".to_string()));
436 assert!(commands.contains(&"unalias".to_string()));
437 assert!(commands.contains(&"test".to_string()));
438 assert!(commands.contains(&"[".to_string()));
439 assert!(commands.contains(&".".to_string()));
440 assert!(commands.contains(&"set_colors".to_string()));
441 assert!(commands.contains(&"set_color_scheme".to_string()));
442 assert!(commands.contains(&"set_condensed".to_string()));
443 assert!(commands.contains(&"return".to_string()));
444 assert!(commands.contains(&"break".to_string()));
445 assert!(commands.contains(&"continue".to_string()));
446 assert!(commands.contains(&"set".to_string()));
447 assert_eq!(commands.len(), 27);
448 }
449}