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_bg;
43mod builtin_break;
44mod builtin_cd;
45mod builtin_colon;
46mod builtin_continue;
47mod builtin_declare;
48mod builtin_dirs;
49mod builtin_env;
50mod builtin_exit;
51mod builtin_export;
52mod builtin_fg;
53mod builtin_help;
54mod builtin_jobs;
55mod builtin_kill;
56mod builtin_popd;
57mod builtin_pushd;
58mod builtin_pwd;
59mod builtin_return;
60mod builtin_set;
61mod builtin_set_color_scheme;
62mod builtin_set_colors;
63mod builtin_set_condensed;
64mod builtin_shift;
65mod builtin_source;
66mod builtin_test;
67mod builtin_times;
68mod builtin_trap;
69mod builtin_type;
70mod builtin_unalias;
71mod builtin_unset;
72mod builtin_wait;
73
74pub trait Builtin {
75 fn name(&self) -> &'static str;
76 fn names(&self) -> Vec<&'static str>;
77 fn description(&self) -> &'static str;
78 fn run(
79 &self,
80 cmd: &ShellCommand,
81 shell_state: &mut ShellState,
82 output_writer: &mut dyn Write,
83 ) -> i32;
84}
85
86fn get_builtins() -> Vec<Box<dyn Builtin>> {
101 vec![
102 Box::new(builtin_cd::CdBuiltin),
103 Box::new(builtin_pwd::PwdBuiltin),
104 Box::new(builtin_env::EnvBuiltin),
105 Box::new(builtin_exit::ExitBuiltin),
106 Box::new(builtin_help::HelpBuiltin),
107 Box::new(builtin_source::SourceBuiltin),
108 Box::new(builtin_export::ExportBuiltin),
109 Box::new(builtin_unset::UnsetBuiltin),
110 Box::new(builtin_pushd::PushdBuiltin),
111 Box::new(builtin_popd::PopdBuiltin),
112 Box::new(builtin_dirs::DirsBuiltin),
113 Box::new(builtin_alias::AliasBuiltin),
114 Box::new(builtin_unalias::UnaliasBuiltin),
115 Box::new(builtin_test::TestBuiltin),
116 Box::new(builtin_set::SetBuiltin),
117 Box::new(builtin_set_colors::SetColorsBuiltin),
118 Box::new(builtin_set_color_scheme::SetColorSchemeBuiltin),
119 Box::new(builtin_set_condensed::SetCondensedBuiltin),
120 Box::new(builtin_shift::ShiftBuiltin),
121 Box::new(builtin_declare::DeclareBuiltin),
122 Box::new(builtin_times::TimesBuiltin),
123 Box::new(builtin_trap::TrapBuiltin),
124 Box::new(builtin_type::TypeBuiltin),
125 Box::new(builtin_return::ReturnBuiltin),
126 Box::new(builtin_break::BreakBuiltin),
127 Box::new(builtin_continue::ContinueBuiltin),
128 Box::new(builtin_colon::ColonBuiltin),
129 Box::new(builtin_jobs::JobsBuiltin),
130 Box::new(builtin_fg::FgBuiltin),
131 Box::new(builtin_bg::BgBuiltin),
132 Box::new(builtin_kill::KillBuiltin),
133 Box::new(builtin_wait::WaitBuiltin),
134 ]
135}
136
137pub fn is_builtin(cmd: &str) -> bool {
138 get_builtins().iter().any(|b| b.names().contains(&cmd))
139}
140
141pub fn get_builtin_commands() -> Vec<String> {
142 let builtins = get_builtins();
143 let mut commands = Vec::new();
144 for b in builtins {
145 for &name in &b.names() {
146 commands.push(name.to_string());
147 }
148 }
149 commands
150}
151
152pub fn execute_builtin(
175 cmd: &ShellCommand,
176 shell_state: &mut ShellState,
177 output_override: Option<Box<dyn Write>>,
178) -> i32 {
179 let colors_enabled = shell_state.colors_enabled;
181 let error_color = shell_state.color_scheme.error.clone();
182 let print_error = move |msg: &str| {
183 if colors_enabled {
184 eprintln!("{}{}\x1b[0m", error_color, msg);
185 } else {
186 eprintln!("{}", msg);
187 }
188 };
189
190 if let Some(mut output_writer) = output_override {
192 let builtins = get_builtins();
193 if let Some(builtin) = builtins
194 .into_iter()
195 .find(|b| b.names().contains(&cmd.args[0].as_str()))
196 {
197 return builtin.run(cmd, shell_state, &mut *output_writer);
198 } else {
199 return 1;
200 }
201 }
202
203 use crate::parser::Redirection;
205
206 let redirections = cmd.redirections.clone();
208
209 let mut files_to_expand: Vec<String> = Vec::new();
212 for redir in &redirections {
213 match redir {
214 Redirection::Input(file)
215 | Redirection::Output(file)
216 | Redirection::OutputClobber(file)
217 | Redirection::Append(file)
218 | Redirection::FdInput(_, file)
219 | Redirection::FdOutput(_, file)
220 | Redirection::FdOutputClobber(_, file)
221 | Redirection::FdAppend(_, file)
222 | Redirection::FdInputOutput(_, file) => {
223 files_to_expand.push(file.clone());
224 }
225 _ => {
226 files_to_expand.push(String::new()); }
228 }
229 }
230
231 let mut expanded_files: Vec<String> = Vec::new();
233 for f in &files_to_expand {
234 if f.is_empty() {
235 expanded_files.push(String::new());
236 } else {
237 expanded_files.push(crate::executor::expand_variables_in_string(f, shell_state));
238 }
239 }
240
241 let mut expanded_redirections: Vec<(Redirection, Option<String>)> = Vec::new();
243 for (i, redir) in redirections.iter().enumerate() {
244 let expanded_file = if expanded_files[i].is_empty() {
245 None
246 } else {
247 Some(expanded_files[i].clone())
248 };
249 expanded_redirections.push((redir.clone(), expanded_file));
250 }
251
252 if let Err(e) = shell_state.fd_table.borrow_mut().save_all_fds() {
254 print_error(&format!("Failed to save file descriptors: {}", e));
255 return 1;
256 }
257
258 for (redir, expanded_file) in &expanded_redirections {
260 let result = match redir {
261 Redirection::Input(_) => {
262 let file = expanded_file.as_ref().unwrap();
263 shell_state.fd_table.borrow_mut().open_fd(
264 0, file, true, false, false, false, false, )
270 }
271 Redirection::Output(_) | Redirection::OutputClobber(_) => {
272 let file = expanded_file.as_ref().unwrap();
273 shell_state.fd_table.borrow_mut().open_fd(
274 1, file, false, true, false, true, false, )
280 }
281 Redirection::Append(_) => {
282 let file = expanded_file.as_ref().unwrap();
283 shell_state.fd_table.borrow_mut().open_fd(
284 1, file, false, true, true, false, false, )
290 }
291 Redirection::FdInput(fd, _) => {
292 let file = expanded_file.as_ref().unwrap();
293 shell_state.fd_table.borrow_mut().open_fd(
294 *fd, file, true, false, false, false, false, )
300 }
301 Redirection::FdOutput(fd, _) | Redirection::FdOutputClobber(fd, _) => {
302 let file = expanded_file.as_ref().unwrap();
303 shell_state.fd_table.borrow_mut().open_fd(
304 *fd, file, false, true, false, true, false, )
310 }
311 Redirection::FdAppend(fd, _) => {
312 let file = expanded_file.as_ref().unwrap();
313 shell_state.fd_table.borrow_mut().open_fd(
314 *fd, file, false, true, true, false, false, )
320 }
321 Redirection::FdDuplicate(target_fd, source_fd) => shell_state
322 .fd_table
323 .borrow_mut()
324 .duplicate_fd(*source_fd, *target_fd),
325 Redirection::FdClose(fd) => shell_state.fd_table.borrow_mut().close_fd(*fd),
326 Redirection::FdInputOutput(fd, _) => {
327 let file = expanded_file.as_ref().unwrap();
328 shell_state.fd_table.borrow_mut().open_fd(
329 *fd, file, true, true, false, false, false, )
335 }
336 Redirection::HereDoc(_, _) | Redirection::HereString(_) => Ok(()),
339 };
340
341 if let Err(e) = result {
342 print_error(&format!("Redirection error: {}", e));
343 let _ = shell_state.fd_table.borrow_mut().restore_all_fds();
345 return 1;
346 }
347 }
348
349 let mut output_writer: Box<dyn Write> = {
351 let raw_fd = shell_state.fd_table.borrow().get_raw_fd(1);
352 match raw_fd {
353 Some(fd) => {
354 let dup_fd = unsafe { libc::dup(fd) };
357 if dup_fd >= 0 {
358 let file = unsafe { std::fs::File::from_raw_fd(dup_fd) };
359 Box::new(ColoredWriter::new(file))
360 } else {
361 let err = io::Error::last_os_error();
363 if err.raw_os_error() == Some(libc::EBADF) {
364 Box::new(BadFdWriter)
367 } else {
368 print_error(&format!("Failed to duplicate stdout: {}", err));
370 let _ = shell_state.fd_table.borrow_mut().restore_all_fds();
371 return 1;
372 }
373 }
374 }
375 None => {
376 Box::new(BadFdWriter)
378 }
379 }
380 };
381
382 let builtins = get_builtins();
384 let exit_code = if let Some(builtin) = builtins
385 .into_iter()
386 .find(|b| b.names().contains(&cmd.args[0].as_str()))
387 {
388 builtin.run(cmd, shell_state, &mut *output_writer)
389 } else {
390 1
391 };
392
393 if let Err(e) = shell_state.fd_table.borrow_mut().restore_all_fds() {
395 print_error(&format!("Failed to restore file descriptors: {}", e));
396 return 1;
397 }
398
399 exit_code
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn test_is_builtin() {
408 assert!(is_builtin("cd"));
409 assert!(is_builtin("pwd"));
410 assert!(is_builtin("env"));
411 assert!(is_builtin("exit"));
412 assert!(is_builtin("help"));
413 assert!(is_builtin("alias"));
414 assert!(is_builtin("unalias"));
415 assert!(is_builtin("test"));
416 assert!(is_builtin("["));
417 assert!(is_builtin("."));
418 assert!(!is_builtin("ls"));
419 assert!(!is_builtin("grep"));
420 assert!(!is_builtin("echo"));
421 }
422
423 #[test]
424 fn test_execute_builtin_unknown() {
425 let cmd = ShellCommand {
426 args: vec!["unknown".to_string()],
427 redirections: Vec::new(),
428 compound: None,
429 };
430 let mut shell_state = ShellState::new();
431 let exit_code = execute_builtin(&cmd, &mut shell_state, None);
432 assert_eq!(exit_code, 1);
433 }
434
435 #[test]
436 fn test_get_builtin_commands() {
437 let commands = get_builtin_commands();
438 assert!(commands.contains(&"cd".to_string()));
439 assert!(commands.contains(&"pwd".to_string()));
440 assert!(commands.contains(&"env".to_string()));
441 assert!(commands.contains(&"exit".to_string()));
442 assert!(commands.contains(&"help".to_string()));
443 assert!(commands.contains(&"source".to_string()));
444 assert!(commands.contains(&"export".to_string()));
445 assert!(commands.contains(&"unset".to_string()));
446 assert!(commands.contains(&"pushd".to_string()));
447 assert!(commands.contains(&"popd".to_string()));
448 assert!(commands.contains(&"dirs".to_string()));
449 assert!(commands.contains(&"alias".to_string()));
450 assert!(commands.contains(&"unalias".to_string()));
451 assert!(commands.contains(&"test".to_string()));
452 assert!(commands.contains(&"[".to_string()));
453 assert!(commands.contains(&".".to_string()));
454 assert!(commands.contains(&"set_colors".to_string()));
455 assert!(commands.contains(&"set_color_scheme".to_string()));
456 assert!(commands.contains(&"set_condensed".to_string()));
457 assert!(commands.contains(&"return".to_string()));
458 assert!(commands.contains(&"break".to_string()));
459 assert!(commands.contains(&"continue".to_string()));
460 assert!(commands.contains(&"set".to_string()));
461 assert!(commands.contains(&":".to_string()));
462 assert!(commands.contains(&"times".to_string()));
463 assert!(commands.contains(&"jobs".to_string()));
464 assert!(commands.contains(&"fg".to_string()));
465 assert!(commands.contains(&"bg".to_string()));
466 assert!(commands.contains(&"kill".to_string()));
467 assert!(commands.contains(&"wait".to_string()));
468 assert_eq!(commands.len(), 34);
469 }
470}