1use std::borrow::Cow;
2use std::collections::{HashMap, VecDeque};
3use std::fmt::Write as _;
4use std::io::{Read, Write};
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8use normalize_path::NormalizePath;
9use rand::Rng;
10use tokio::sync::Mutex;
11
12use crate::arithmetic::Evaluatable;
13use crate::env::{EnvironmentLookup, EnvironmentScope, ShellEnvironment};
14use crate::interp::{self, Execute, ExecutionParameters, ExecutionResult};
15use crate::options::RuntimeOptions;
16use crate::sys::fs::PathExt;
17use crate::variables::{self, ShellValue, ShellVariable};
18use crate::{
19 builtins, commands, completion, env, error, expansion, functions, jobs, keywords, openfiles,
20 prompt, sys::users, traps,
21};
22use crate::{interfaces, pathcache, pathsearch, sys, trace_categories};
23
24const BASH_MAJOR: u32 = 5;
25const BASH_MINOR: u32 = 2;
26const BASH_PATCH: u32 = 15;
27const BASH_BUILD: u32 = 1;
28const BASH_RELEASE: &str = "release";
29const BASH_MACHINE: &str = "unknown";
30
31pub type KeyBindingsHelper = Arc<Mutex<dyn interfaces::KeyBindings>>;
33
34pub struct Shell {
36 pub traps: traps::TrapHandlerConfig,
40 open_files: openfiles::OpenFiles,
42 pub working_dir: PathBuf,
44 pub env: ShellEnvironment,
46 pub funcs: functions::FunctionEnv,
48 pub options: RuntimeOptions,
50 pub(crate) jobs: jobs::JobManager,
52 pub aliases: HashMap<String, String>,
54
55 pub last_exit_status: u8,
59
60 pub last_pipeline_statuses: Vec<u8>,
62
63 depth: usize,
65
66 pub shell_name: Option<String>,
68
69 pub positional_parameters: Vec<String>,
71
72 pub shell_product_display_str: Option<String>,
74
75 script_call_stack: VecDeque<(ScriptCallType, String)>,
77
78 function_call_stack: VecDeque<FunctionCall>,
80
81 pub directory_stack: Vec<PathBuf>,
83
84 current_line_number: u32,
86
87 pub completion_config: completion::Config,
89
90 pub builtins: HashMap<String, builtins::Registration>,
92
93 pub(crate) program_location_cache: pathcache::PathCache,
95
96 last_stopwatch_time: std::time::SystemTime,
98
99 last_stopwatch_offset: u32,
101
102 pub key_bindings: Option<KeyBindingsHelper>,
104}
105
106impl Clone for Shell {
107 fn clone(&self) -> Self {
108 Self {
109 traps: self.traps.clone(),
110 open_files: self.open_files.clone(),
111 working_dir: self.working_dir.clone(),
112 env: self.env.clone(),
113 funcs: self.funcs.clone(),
114 options: self.options.clone(),
115 jobs: jobs::JobManager::new(),
116 aliases: self.aliases.clone(),
117 last_exit_status: self.last_exit_status,
118 last_pipeline_statuses: self.last_pipeline_statuses.clone(),
119 positional_parameters: self.positional_parameters.clone(),
120 shell_name: self.shell_name.clone(),
121 shell_product_display_str: self.shell_product_display_str.clone(),
122 function_call_stack: self.function_call_stack.clone(),
123 script_call_stack: self.script_call_stack.clone(),
124 directory_stack: self.directory_stack.clone(),
125 current_line_number: self.current_line_number,
126 completion_config: self.completion_config.clone(),
127 builtins: self.builtins.clone(),
128 program_location_cache: self.program_location_cache.clone(),
129 last_stopwatch_time: self.last_stopwatch_time,
130 last_stopwatch_offset: self.last_stopwatch_offset,
131 key_bindings: self.key_bindings.clone(),
132 depth: self.depth + 1,
133 }
134 }
135}
136
137impl AsRef<Self> for Shell {
138 fn as_ref(&self) -> &Self {
139 self
140 }
141}
142
143impl AsMut<Self> for Shell {
144 fn as_mut(&mut self) -> &mut Self {
145 self
146 }
147}
148
149#[derive(Default)]
151pub struct CreateOptions {
152 pub disabled_shopt_options: Vec<String>,
154 pub enabled_shopt_options: Vec<String>,
156 pub disallow_overwriting_regular_files_via_output_redirection: bool,
158 pub do_not_execute_commands: bool,
160 pub exit_after_one_command: bool,
162 pub interactive: bool,
164 pub login: bool,
166 pub no_editing: bool,
168 pub no_profile: bool,
170 pub no_rc: bool,
172 pub rc_file: Option<PathBuf>,
174 pub do_not_inherit_env: bool,
176 pub posix: bool,
178 pub print_commands_and_arguments: bool,
180 pub read_commands_from_stdin: bool,
182 pub shell_name: Option<String>,
184 pub shell_product_display_str: Option<String>,
186 pub sh_mode: bool,
188 pub verbose: bool,
190 pub max_function_call_depth: Option<usize>,
192 pub key_bindings: Option<KeyBindingsHelper>,
194 pub shell_version: Option<String>,
196}
197
198#[derive(Clone, Debug)]
200pub enum ScriptCallType {
201 Sourced,
203 Executed,
205}
206
207#[derive(Clone, Debug)]
209pub struct FunctionCall {
210 function_name: String,
212 function_definition: Arc<brush_parser::ast::FunctionDefinition>,
214}
215
216impl Shell {
217 pub async fn new(options: &CreateOptions) -> Result<Self, error::Error> {
223 let mut shell = Self {
225 traps: traps::TrapHandlerConfig::default(),
226 open_files: openfiles::OpenFiles::default(),
227 working_dir: std::env::current_dir()?,
228 env: env::ShellEnvironment::new(),
229 funcs: functions::FunctionEnv::default(),
230 options: RuntimeOptions::defaults_from(options),
231 jobs: jobs::JobManager::new(),
232 aliases: HashMap::default(),
233 last_exit_status: 0,
234 last_pipeline_statuses: vec![0],
235 positional_parameters: vec![],
236 shell_name: options.shell_name.clone(),
237 shell_product_display_str: options.shell_product_display_str.clone(),
238 function_call_stack: VecDeque::new(),
239 script_call_stack: VecDeque::new(),
240 directory_stack: vec![],
241 current_line_number: 0,
242 completion_config: completion::Config::default(),
243 builtins: builtins::get_default_builtins(options),
244 program_location_cache: pathcache::PathCache::default(),
245 last_stopwatch_time: std::time::SystemTime::now(),
246 last_stopwatch_offset: 0,
247 key_bindings: options.key_bindings.clone(),
248 depth: 0,
249 };
250
251 shell.options.extended_globbing = true;
254
255 shell.initialize_vars(options)?;
257
258 shell.load_config(options).await?;
260
261 Ok(shell)
262 }
263
264 fn initialize_exported_func(
265 &mut self,
266 func_name: &str,
267 body_text: &str,
268 ) -> Result<(), error::Error> {
269 let mut parser = create_parser(body_text.as_bytes(), &self.parser_options());
270 let func_body = parser.parse_function_parens_and_body()?;
271
272 let func_def = brush_parser::ast::FunctionDefinition {
273 fname: func_name.to_owned(),
274 body: func_body,
275 source: String::new(),
276 };
277
278 let mut registration = functions::FunctionRegistration::from(func_def);
279 registration.export();
280
281 self.funcs.update(func_name.to_owned(), registration);
282
283 Ok(())
284 }
285
286 #[allow(clippy::too_many_lines)]
287 #[allow(clippy::unwrap_in_result)]
288 fn initialize_vars(&mut self, options: &CreateOptions) -> Result<(), error::Error> {
289 if !options.do_not_inherit_env {
291 for (k, v) in std::env::vars() {
292 if let Some(func_name) = k.strip_prefix("BASH_FUNC_") {
294 if let Some(func_name) = func_name.strip_suffix("%%") {
295 let _ = self.initialize_exported_func(func_name, v.as_str());
298 continue;
299 }
300 }
301
302 let mut var = ShellVariable::new(ShellValue::String(v));
303 var.export();
304 self.env.set_global(k, var)?;
305 }
306 }
307 let shell_version = options.shell_version.clone();
308 self.env.set_global(
309 "BRUSH_VERSION",
310 ShellVariable::new(shell_version.unwrap_or_default().into()),
311 )?;
312
313 if let Some(shell_name) = &options.shell_name {
317 self.env
318 .set_global("BASH", ShellVariable::new(shell_name.into()))?;
319 }
320
321 let mut bashopts_var = ShellVariable::new(ShellValue::Dynamic {
323 getter: |shell| shell.options.get_shopt_optstr().into(),
324 setter: |_| (),
325 });
326 bashopts_var.set_readonly();
327 self.env.set_global("BASHOPTS", bashopts_var)?;
328
329 #[cfg(not(target_family = "wasm"))]
331 {
332 let mut bashpid_var =
333 ShellVariable::new(ShellValue::String(std::process::id().to_string()));
334 bashpid_var.treat_as_integer();
335 self.env.set_global("BASHPID", bashpid_var)?;
336 }
337
338 self.env.set_global(
340 "BASH_ALIASES",
341 ShellVariable::new(ShellValue::Dynamic {
342 getter: |shell| {
343 let values = variables::ArrayLiteral(
344 shell
345 .aliases
346 .iter()
347 .map(|(k, v)| (Some(k.to_owned()), v.to_owned()))
348 .collect::<Vec<_>>(),
349 );
350
351 ShellValue::associative_array_from_literals(values).unwrap()
352 },
353 setter: |_| (),
354 }),
355 )?;
356
357 self.env.set_global(
363 "BASH_ARGV0",
364 ShellVariable::new(ShellValue::Dynamic {
365 getter: |shell| {
366 let argv0 = shell.shell_name.as_deref().unwrap_or_default();
367 argv0.to_string().into()
368 },
369 setter: |_| (),
371 }),
372 )?;
373
374 self.env.set_global(
376 "BASH_CMDS",
377 ShellVariable::new(ShellValue::Dynamic {
378 getter: |shell| shell.program_location_cache.to_value().unwrap(),
379 setter: |_| (),
380 }),
381 )?;
382
383 self.env.set_global(
389 "BASH_SOURCE",
390 ShellVariable::new(ShellValue::Dynamic {
391 getter: |shell| shell.get_bash_source_value(),
392 setter: |_| (),
393 }),
394 )?;
395
396 self.env.set_global(
398 "BASH_SUBSHELL",
399 ShellVariable::new(ShellValue::Dynamic {
400 getter: |shell| shell.depth.to_string().into(),
401 setter: |_| (),
402 }),
403 )?;
404
405 let mut bash_versinfo_var = ShellVariable::new(ShellValue::indexed_array_from_strs(
407 [
408 BASH_MAJOR.to_string().as_str(),
409 BASH_MINOR.to_string().as_str(),
410 BASH_PATCH.to_string().as_str(),
411 BASH_BUILD.to_string().as_str(),
412 BASH_RELEASE,
413 BASH_MACHINE,
414 ]
415 .as_slice(),
416 ));
417 bash_versinfo_var.set_readonly();
418 self.env.set_global("BASH_VERSINFO", bash_versinfo_var)?;
419
420 self.env.set_global(
423 "BASH_VERSION",
424 ShellVariable::new(
425 std::format!("{BASH_MAJOR}.{BASH_MINOR}.{BASH_PATCH}({BASH_BUILD})-{BASH_RELEASE}")
426 .into(),
427 ),
428 )?;
429
430 self.env.set_global(
432 "COMP_WORDBREAKS",
433 ShellVariable::new(" \t\n\"\'@><=;|&(:".into()),
434 )?;
435
436 self.env.set_global(
438 "DIRSTACK",
439 ShellVariable::new(ShellValue::Dynamic {
440 getter: |shell| {
441 shell
442 .directory_stack
443 .iter()
444 .map(|p| p.to_string_lossy().to_string())
445 .collect::<Vec<_>>()
446 .into()
447 },
448 setter: |_| (),
449 }),
450 )?;
451
452 self.env.set_global(
454 "EPOCHREALTIME",
455 ShellVariable::new(ShellValue::Dynamic {
456 getter: |_shell| {
457 let now = std::time::SystemTime::now();
458 let since_epoch = now
459 .duration_since(std::time::UNIX_EPOCH)
460 .unwrap_or_default();
461 since_epoch.as_secs_f64().to_string().into()
462 },
463 setter: |_| (),
464 }),
465 )?;
466
467 self.env.set_global(
469 "EPOCHSECONDS",
470 ShellVariable::new(ShellValue::Dynamic {
471 getter: |_shell| {
472 let now = std::time::SystemTime::now();
473 let since_epoch = now
474 .duration_since(std::time::UNIX_EPOCH)
475 .unwrap_or_default();
476 since_epoch.as_secs().to_string().into()
477 },
478 setter: |_| (),
479 }),
480 )?;
481
482 #[cfg(unix)]
484 {
485 let mut euid_var = ShellVariable::new(ShellValue::String(format!(
486 "{}",
487 uzers::get_effective_uid()
488 )));
489 euid_var.treat_as_integer().set_readonly();
490 self.env.set_global("EUID", euid_var)?;
491 }
492
493 self.env.set_global(
495 "FUNCNAME",
496 ShellVariable::new(ShellValue::Dynamic {
497 getter: |shell| shell.get_funcname_value(),
498 setter: |_| (),
499 }),
500 )?;
501
502 self.env.set_global(
506 "GROUPS",
507 ShellVariable::new(ShellValue::Dynamic {
508 getter: |_shell| {
509 let groups = get_current_user_gids();
510 ShellValue::indexed_array_from_strings(
511 groups.into_iter().map(|gid| gid.to_string()),
512 )
513 },
514 setter: |_| (),
515 }),
516 )?;
517
518 if !self.env.is_set("HISTFILE") {
522 if let Some(home_dir) = self.get_home_dir() {
523 let histfile = home_dir.join(".brush_history");
524 self.env.set_global(
525 "HISTFILE",
526 ShellVariable::new(ShellValue::String(histfile.to_string_lossy().to_string())),
527 )?;
528 }
529 }
530
531 self.env.set_global(
533 "HOSTNAME",
534 ShellVariable::new(
535 sys::network::get_hostname()
536 .unwrap_or_default()
537 .to_string_lossy()
538 .to_string()
539 .into(),
540 ),
541 )?;
542
543 #[cfg(unix)]
545 {
546 if let Ok(info) = nix::sys::utsname::uname() {
547 self.env.set_global(
548 "HOSTTYPE",
549 ShellVariable::new(info.machine().to_string_lossy().to_string().into()),
550 )?;
551 }
552 }
553
554 self.env
556 .set_global("IFS", ShellVariable::new(" \t\n".into()))?;
557
558 self.env.set_global(
560 "LINENO",
561 ShellVariable::new(ShellValue::Dynamic {
562 getter: |shell| shell.current_line_number.to_string().into(),
563 setter: |_| (),
564 }),
565 )?;
566
567 self.env
569 .set_global("MACHTYPE", ShellVariable::new(BASH_MACHINE.into()))?;
570
571 if !self.env.is_set("OLDPWD") {
573 let mut oldpwd_var =
574 ShellVariable::new(ShellValue::Unset(variables::ShellValueUnsetType::Untyped));
575 oldpwd_var.export();
576 self.env.set_global("OLDPWD", oldpwd_var)?;
577 }
578
579 self.env
581 .set_global("OPTERR", ShellVariable::new("1".into()))?;
582
583 let mut optind_var = ShellVariable::new("1".into());
585 optind_var.treat_as_integer();
586 self.env.set_global("OPTIND", optind_var)?;
587
588 let os_type = match std::env::consts::OS {
590 "linux" => "linux-gnu",
591 "windows" => "windows",
592 _ => "unknown",
593 };
594 self.env
595 .set_global("OSTYPE", ShellVariable::new(os_type.into()))?;
596
597 #[cfg(unix)]
599 if !self.env.is_set("PATH") {
600 let default_path_str = sys::fs::get_default_executable_search_paths().join(":");
601 self.env
602 .set_global("PATH", ShellVariable::new(default_path_str.into()))?;
603 }
604
605 self.env.set_global(
609 "PIPESTATUS",
610 ShellVariable::new(ShellValue::Dynamic {
611 getter: |shell| {
612 ShellValue::indexed_array_from_strings(
613 shell.last_pipeline_statuses.iter().map(|s| s.to_string()),
614 )
615 },
616 setter: |_| (),
617 }),
618 )?;
619
620 if let Some(ppid) = sys::terminal::get_parent_process_id() {
622 let mut ppid_var = ShellVariable::new(ppid.to_string().into());
623 ppid_var.treat_as_integer().set_readonly();
624 self.env.set_global("PPID", ppid_var)?;
625 }
626
627 let mut random_var = ShellVariable::new(ShellValue::Dynamic {
629 getter: get_random_value,
630 setter: |_| (),
631 });
632 random_var.treat_as_integer();
633 self.env.set_global("RANDOM", random_var)?;
634
635 self.env.set_global(
637 "SECONDS",
638 ShellVariable::new(ShellValue::Dynamic {
639 getter: |shell| {
640 let now = std::time::SystemTime::now();
641 let since_last = now
642 .duration_since(shell.last_stopwatch_time)
643 .unwrap_or_default();
644 let total_seconds =
645 since_last.as_secs() + u64::from(shell.last_stopwatch_offset);
646 total_seconds.to_string().into()
647 },
648 setter: |_| (),
650 }),
651 )?;
652
653 if let Ok(exe_path) = std::env::current_exe() {
655 self.env.set_global(
656 "SHELL",
657 ShellVariable::new(exe_path.to_string_lossy().to_string().into()),
658 )?;
659 }
660
661 let mut shellopts_var = ShellVariable::new(ShellValue::Dynamic {
663 getter: |shell| shell.options.get_set_o_optstr().into(),
664 setter: |_| (),
665 });
666 shellopts_var.set_readonly();
667 self.env.set_global("SHELLOPTS", shellopts_var)?;
668
669 let input_shlvl = self.get_env_str("SHLVL").unwrap_or_else(|| "0".into());
671 let updated_shlvl = input_shlvl.as_ref().parse::<u32>().unwrap_or(0) + 1;
672 let mut shlvl_var = ShellVariable::new(updated_shlvl.to_string().into());
673 shlvl_var.export();
674 self.env.set_global("SHLVL", shlvl_var)?;
675
676 let mut random_var = ShellVariable::new(ShellValue::Dynamic {
678 getter: get_srandom_value,
679 setter: |_| (),
680 });
681 random_var.treat_as_integer();
682 self.env.set_global("SRANDOM", random_var)?;
683
684 if options.interactive {
686 if !self.env.is_set("PS1") {
687 self.env
688 .set_global("PS1", ShellVariable::new(r"\s-\v\$ ".into()))?;
689 }
690
691 if !self.env.is_set("PS2") {
692 self.env
693 .set_global("PS2", ShellVariable::new("> ".into()))?;
694 }
695 }
696
697 if !self.env.is_set("PS4") {
699 self.env
700 .set_global("PS4", ShellVariable::new("+ ".into()))?;
701 }
702
703 let pwd = self.working_dir.to_string_lossy().to_string();
711 let mut pwd_var = ShellVariable::new(pwd.into());
712 pwd_var.export();
713 self.env.set_global("PWD", pwd_var)?;
714
715 #[cfg(unix)]
717 {
718 let mut uid_var =
719 ShellVariable::new(ShellValue::String(format!("{}", uzers::get_current_uid())));
720 uid_var.treat_as_integer().set_readonly();
721 self.env.set_global("UID", uid_var)?;
722 }
723
724 Ok(())
725 }
726
727 async fn load_config(&mut self, options: &CreateOptions) -> Result<(), error::Error> {
728 let mut params = self.default_exec_params();
729 params.process_group_policy = interp::ProcessGroupPolicy::SameProcessGroup;
730
731 if options.login {
732 if options.no_profile {
734 return Ok(());
735 }
736
737 self.source_if_exists(Path::new("/etc/profile"), ¶ms)
746 .await?;
747 if let Some(home_path) = self.get_home_dir() {
748 if options.sh_mode {
749 self.source_if_exists(home_path.join(".profile").as_path(), ¶ms)
750 .await?;
751 } else {
752 if !self
753 .source_if_exists(home_path.join(".bash_profile").as_path(), ¶ms)
754 .await?
755 {
756 if !self
757 .source_if_exists(home_path.join(".bash_login").as_path(), ¶ms)
758 .await?
759 {
760 self.source_if_exists(home_path.join(".profile").as_path(), ¶ms)
761 .await?;
762 }
763 }
764 }
765 }
766 } else {
767 if options.interactive {
768 if options.no_rc || options.sh_mode {
770 return Ok(());
771 }
772
773 if let Some(rc_file) = &options.rc_file {
775 self.source_if_exists(rc_file, ¶ms).await?;
777 } else {
778 self.source_if_exists(Path::new("/etc/bash.bashrc"), ¶ms)
785 .await?;
786 if let Some(home_path) = self.get_home_dir() {
787 self.source_if_exists(home_path.join(".bashrc").as_path(), ¶ms)
788 .await?;
789 self.source_if_exists(home_path.join(".brushrc").as_path(), ¶ms)
790 .await?;
791 }
792 }
793 } else {
794 let env_var_name = if options.sh_mode { "ENV" } else { "BASH_ENV" };
795
796 if self.env.is_set(env_var_name) {
797 return error::unimp(
801 "load config from $ENV/BASH_ENV for non-interactive, non-login shell",
802 );
803 }
804 }
805 }
806
807 Ok(())
808 }
809
810 async fn source_if_exists(
811 &mut self,
812 path: impl AsRef<Path>,
813 params: &ExecutionParameters,
814 ) -> Result<bool, error::Error> {
815 let path = path.as_ref();
816 if path.exists() {
817 self.source_script(path, std::iter::empty::<String>(), params)
818 .await?;
819 Ok(true)
820 } else {
821 tracing::debug!("skipping non-existent file: {}", path.display());
822 Ok(false)
823 }
824 }
825
826 pub async fn source_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
834 &mut self,
835 path: P,
836 args: I,
837 params: &ExecutionParameters,
838 ) -> Result<ExecutionResult, error::Error> {
839 self.parse_and_execute_script_file(path.as_ref(), args, params, ScriptCallType::Sourced)
840 .await
841 }
842
843 async fn parse_and_execute_script_file<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
852 &mut self,
853 path: P,
854 args: I,
855 params: &ExecutionParameters,
856 call_type: ScriptCallType,
857 ) -> Result<ExecutionResult, error::Error> {
858 let path = path.as_ref();
859 tracing::debug!("sourcing: {}", path.display());
860 let opened_file: openfiles::OpenFile = self
861 .open_file(path, params)
862 .map_err(|e| error::Error::FailedSourcingFile(path.to_owned(), e.into()))?;
863
864 if opened_file.is_dir() {
865 return Err(error::Error::FailedSourcingFile(
866 path.to_owned(),
867 error::Error::IsADirectory.into(),
868 ));
869 }
870
871 let source_info = brush_parser::SourceInfo {
872 source: path.to_string_lossy().to_string(),
873 };
874
875 self.source_file(opened_file, &source_info, args, params, call_type)
876 .await
877 }
878
879 async fn source_file<F: Read, S: AsRef<str>, I: Iterator<Item = S>>(
889 &mut self,
890 file: F,
891 source_info: &brush_parser::SourceInfo,
892 args: I,
893 params: &ExecutionParameters,
894 call_type: ScriptCallType,
895 ) -> Result<ExecutionResult, error::Error> {
896 let mut reader = std::io::BufReader::new(file);
897 let mut parser =
898 brush_parser::Parser::new(&mut reader, &self.parser_options(), source_info);
899
900 tracing::debug!(target: trace_categories::PARSE, "Parsing sourced file: {}", source_info.source);
901 let parse_result = parser.parse_program();
902
903 let mut other_positional_parameters = args.map(|s| s.as_ref().to_owned()).collect();
904 let mut other_shell_name = Some(source_info.source.clone());
905
906 std::mem::swap(&mut self.shell_name, &mut other_shell_name);
908 std::mem::swap(
909 &mut self.positional_parameters,
910 &mut other_positional_parameters,
911 );
912
913 self.script_call_stack
914 .push_front((call_type.clone(), source_info.source.clone()));
915
916 let result = self
917 .run_parsed_result(parse_result, source_info, params)
918 .await;
919
920 self.script_call_stack.pop_front();
921
922 std::mem::swap(&mut self.shell_name, &mut other_shell_name);
924 std::mem::swap(
925 &mut self.positional_parameters,
926 &mut other_positional_parameters,
927 );
928
929 result
930 }
931
932 pub async fn invoke_function(&mut self, name: &str, args: &[&str]) -> Result<u8, error::Error> {
939 let params = self.default_exec_params();
941
942 let command_name = String::from(name);
943
944 let func_registration = self
945 .funcs
946 .get(name)
947 .ok_or_else(|| error::Error::FunctionNotFound(name.to_owned()))?;
948
949 let func = func_registration.definition.clone();
950
951 let context = commands::ExecutionContext {
952 shell: self,
953 command_name,
954 params,
955 };
956
957 let command_args = args
958 .iter()
959 .map(|s| commands::CommandArg::String(String::from(*s)))
960 .collect::<Vec<_>>();
961
962 match commands::invoke_shell_function(func, context, &command_args).await? {
963 commands::CommandSpawnResult::SpawnedProcess(_) => {
964 error::unimp("child spawned from function invocation")
965 }
966 commands::CommandSpawnResult::ImmediateExit(code) => Ok(code),
967 commands::CommandSpawnResult::ExitShell(code) => Ok(code),
968 commands::CommandSpawnResult::ReturnFromFunctionOrScript(code) => Ok(code),
969 commands::CommandSpawnResult::BreakLoop(_)
970 | commands::CommandSpawnResult::ContinueLoop(_) => {
971 error::unimp("break or continue returned from function invocation")
972 }
973 }
974 }
975
976 pub async fn run_string<S: Into<String>>(
983 &mut self,
984 command: S,
985 params: &ExecutionParameters,
986 ) -> Result<ExecutionResult, error::Error> {
987 self.current_line_number += 1;
991
992 let parse_result = self.parse_string(command.into());
993 let source_info = brush_parser::SourceInfo {
994 source: String::from("main"),
995 };
996 self.run_parsed_result(parse_result, &source_info, params)
997 .await
998 }
999
1000 pub fn parse<R: Read>(
1003 &self,
1004 reader: R,
1005 ) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
1006 let mut parser = create_parser(reader, &self.parser_options());
1007
1008 tracing::debug!(target: trace_categories::PARSE, "Parsing reader as program...");
1009 parser.parse_program()
1010 }
1011
1012 pub fn parse_string<S: Into<String>>(
1019 &self,
1020 s: S,
1021 ) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
1022 parse_string_impl(s.into(), self.parser_options())
1023 }
1024
1025 pub async fn basic_expand_string<S: AsRef<str>>(
1031 &mut self,
1032 params: &ExecutionParameters,
1033 s: S,
1034 ) -> Result<String, error::Error> {
1035 let result = expansion::basic_expand_str(self, params, s.as_ref()).await?;
1036 Ok(result)
1037 }
1038
1039 pub async fn full_expand_and_split_string<S: AsRef<str>>(
1046 &mut self,
1047 params: &ExecutionParameters,
1048 s: S,
1049 ) -> Result<Vec<String>, error::Error> {
1050 let result = expansion::full_expand_and_split_str(self, params, s.as_ref()).await?;
1051 Ok(result)
1052 }
1053
1054 pub fn default_exec_params(&self) -> ExecutionParameters {
1056 ExecutionParameters {
1057 open_files: self.open_files.clone(),
1058 ..Default::default()
1059 }
1060 }
1061
1062 pub async fn run_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
1069 &mut self,
1070 script_path: P,
1071 args: I,
1072 ) -> Result<ExecutionResult, error::Error> {
1073 let params = self.default_exec_params();
1074 self.parse_and_execute_script_file(
1075 script_path.as_ref(),
1076 args,
1077 ¶ms,
1078 ScriptCallType::Executed,
1079 )
1080 .await
1081 }
1082
1083 async fn run_parsed_result(
1084 &mut self,
1085 parse_result: Result<brush_parser::ast::Program, brush_parser::ParseError>,
1086 source_info: &brush_parser::SourceInfo,
1087 params: &ExecutionParameters,
1088 ) -> Result<ExecutionResult, error::Error> {
1089 let error_prefix = if !source_info.source.is_empty() {
1090 format!("{}: ", source_info.source)
1091 } else {
1092 String::new()
1093 };
1094
1095 let result = match parse_result {
1096 Ok(prog) => match self.run_program(prog, params).await {
1097 Ok(result) => result,
1098 Err(e) => {
1099 tracing::error!("error: {:#}", e);
1100 self.last_exit_status = 1;
1101 ExecutionResult::new(1)
1102 }
1103 },
1104 Err(brush_parser::ParseError::ParsingNearToken(token_near_error)) => {
1105 let error_loc = &token_near_error.location().start;
1106
1107 tracing::error!(
1108 "{}syntax error near token `{}' (line {} col {})",
1109 error_prefix,
1110 token_near_error.to_str(),
1111 error_loc.line,
1112 error_loc.column,
1113 );
1114 self.last_exit_status = 2;
1115 ExecutionResult::new(2)
1116 }
1117 Err(brush_parser::ParseError::ParsingAtEndOfInput) => {
1118 tracing::error!("{}syntax error at end of input", error_prefix);
1119
1120 self.last_exit_status = 2;
1121 ExecutionResult::new(2)
1122 }
1123 Err(brush_parser::ParseError::Tokenizing { inner, position }) => {
1124 let mut error_message = error_prefix.clone();
1125 error_message.push_str(inner.to_string().as_str());
1126
1127 if let Some(position) = position {
1128 write!(
1129 error_message,
1130 " (detected near line {} column {})",
1131 position.line, position.column
1132 )?;
1133 }
1134
1135 tracing::error!("{}", error_message);
1136
1137 self.last_exit_status = 2;
1138 ExecutionResult::new(2)
1139 }
1140 };
1141
1142 Ok(result)
1143 }
1144
1145 pub async fn run_program(
1152 &mut self,
1153 program: brush_parser::ast::Program,
1154 params: &ExecutionParameters,
1155 ) -> Result<ExecutionResult, error::Error> {
1156 program.execute(self, params).await
1157 }
1158
1159 const fn default_prompt(&self) -> &'static str {
1160 if self.options.sh_mode {
1161 "$ "
1162 } else {
1163 "brush$ "
1164 }
1165 }
1166
1167 pub async fn compose_precmd_prompt(&mut self) -> Result<String, error::Error> {
1169 self.expand_prompt_var("PS0", "").await
1170 }
1171
1172 pub async fn compose_prompt(&mut self) -> Result<String, error::Error> {
1174 self.expand_prompt_var("PS1", self.default_prompt()).await
1175 }
1176
1177 #[allow(clippy::unused_async)]
1179 pub async fn compose_alt_side_prompt(&mut self) -> Result<String, error::Error> {
1180 self.expand_prompt_var("BRUSH_PS_ALT", "").await
1182 }
1183
1184 pub async fn compose_continuation_prompt(&mut self) -> Result<String, error::Error> {
1186 self.expand_prompt_var("PS2", "> ").await
1187 }
1188
1189 async fn expand_prompt_var(
1190 &mut self,
1191 var_name: &str,
1192 default: &str,
1193 ) -> Result<String, error::Error> {
1194 let prompt_spec = self.parameter_or_default(var_name, default);
1201 if prompt_spec.is_empty() {
1202 return Ok(String::new());
1203 }
1204
1205 let formatted_prompt = prompt::expand_prompt(self, prompt_spec.into_owned())?;
1207
1208 let params = self.default_exec_params();
1210 expansion::basic_expand_str(self, ¶ms, &formatted_prompt).await
1211 }
1212
1213 pub const fn last_result(&self) -> u8 {
1215 self.last_exit_status
1216 }
1217
1218 fn parameter_or_default<'a>(&'a self, name: &str, default: &'a str) -> Cow<'a, str> {
1219 self.get_env_str(name).unwrap_or_else(|| default.into())
1220 }
1221
1222 pub const fn parser_options(&self) -> brush_parser::ParserOptions {
1225 brush_parser::ParserOptions {
1226 enable_extended_globbing: self.options.extended_globbing,
1227 posix_mode: self.options.posix_mode,
1228 sh_mode: self.options.sh_mode,
1229 tilde_expansion: true,
1230 }
1231 }
1232
1233 pub(crate) fn in_sourced_script(&self) -> bool {
1235 self.script_call_stack
1236 .front()
1237 .is_some_and(|(ty, _)| matches!(ty, ScriptCallType::Sourced))
1238 }
1239
1240 pub(crate) fn in_function(&self) -> bool {
1242 !self.function_call_stack.is_empty()
1243 }
1244
1245 pub(crate) fn enter_function(
1253 &mut self,
1254 name: &str,
1255 function_def: &Arc<brush_parser::ast::FunctionDefinition>,
1256 ) -> Result<(), error::Error> {
1257 if let Some(max_call_depth) = self.options.max_function_call_depth {
1258 if self.function_call_stack.len() >= max_call_depth {
1259 return Err(error::Error::MaxFunctionCallDepthExceeded);
1260 }
1261 }
1262
1263 if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1264 let depth = self.function_call_stack.len();
1265 let prefix = repeated_char_str(' ', depth);
1266 tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
1267 }
1268
1269 self.function_call_stack.push_front(FunctionCall {
1270 function_name: name.to_owned(),
1271 function_definition: function_def.clone(),
1272 });
1273 self.env.push_scope(env::EnvironmentScope::Local);
1274 Ok(())
1275 }
1276
1277 pub(crate) fn leave_function(&mut self) -> Result<(), error::Error> {
1280 self.env.pop_scope(env::EnvironmentScope::Local)?;
1281
1282 if let Some(exited_call) = self.function_call_stack.pop_front() {
1283 if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1284 let depth = self.function_call_stack.len();
1285 let prefix = repeated_char_str(' ', depth);
1286 tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func [depth={depth}]: {prefix}{}", exited_call.function_name);
1287 }
1288 }
1289
1290 Ok(())
1291 }
1292
1293 fn get_funcname_value(&self) -> variables::ShellValue {
1294 if self.function_call_stack.is_empty() {
1295 ShellValue::Unset(variables::ShellValueUnsetType::IndexedArray)
1296 } else {
1297 self.function_call_stack
1298 .iter()
1299 .map(|s| s.function_name.as_str())
1300 .collect::<Vec<_>>()
1301 .into()
1302 }
1303 }
1304
1305 fn get_bash_source_value(&self) -> variables::ShellValue {
1306 if self.function_call_stack.is_empty() {
1307 self.script_call_stack
1308 .front()
1309 .map_or_else(Vec::new, |(_call_type, s)| vec![s.as_ref()])
1310 .into()
1311 } else {
1312 self.function_call_stack
1313 .iter()
1314 .map(|s| s.function_definition.source.as_ref())
1315 .collect::<Vec<_>>()
1316 .into()
1317 }
1318 }
1319
1320 pub fn get_history_file_path(&self) -> Option<PathBuf> {
1322 self.get_env_str("HISTFILE")
1323 .map(|s| PathBuf::from(s.into_owned()))
1324 }
1325
1326 pub(crate) const fn get_current_input_line_number(&self) -> u32 {
1328 self.current_line_number
1329 }
1330
1331 pub fn get_env_str(&self, name: &str) -> Option<Cow<'_, str>> {
1338 self.env.get_str(name, self)
1339 }
1340
1341 pub fn get_env_var(&self, name: &str) -> Option<&ShellVariable> {
1347 self.env.get(name).map(|(_, var)| var)
1348 }
1349
1350 pub fn set_env_global(&mut self, name: &str, var: ShellVariable) -> Result<(), error::Error> {
1357 self.env.set_global(name, var)
1358 }
1359
1360 pub fn register_builtin<S: Into<String>>(
1367 &mut self,
1368 name: S,
1369 registration: builtins::Registration,
1370 ) {
1371 self.builtins.insert(name.into(), registration);
1372 }
1373
1374 pub(crate) fn get_ifs(&self) -> Cow<'_, str> {
1376 self.get_env_str("IFS").unwrap_or_else(|| " \t\n".into())
1377 }
1378
1379 pub(crate) fn get_ifs_first_char(&self) -> char {
1381 self.get_ifs().chars().next().unwrap_or(' ')
1382 }
1383
1384 pub async fn get_completions(
1391 &mut self,
1392 input: &str,
1393 position: usize,
1394 ) -> Result<completion::Completions, error::Error> {
1395 let completion_config = self.completion_config.clone();
1396 completion_config
1397 .get_completions(self, input, position)
1398 .await
1399 }
1400
1401 pub fn find_executables_in_path<'a>(
1407 &'a self,
1408 filename: &'a str,
1409 ) -> impl Iterator<Item = PathBuf> + 'a {
1410 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1411 let paths = path_var.split(':').map(|s| s.to_owned());
1412
1413 pathsearch::search_for_executable(paths.into_iter(), filename)
1414 }
1415
1416 pub fn find_executables_in_path_with_prefix(
1423 &self,
1424 filename_prefix: &str,
1425 case_insensitive: bool,
1426 ) -> impl Iterator<Item = PathBuf> {
1427 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1428 let paths = path_var.split(':').map(|s| s.to_owned());
1429
1430 pathsearch::search_for_executable_with_prefix(
1431 paths.into_iter(),
1432 filename_prefix,
1433 case_insensitive,
1434 )
1435 }
1436
1437 pub fn find_first_executable_in_path<S: AsRef<str>>(
1444 &self,
1445 candidate_name: S,
1446 ) -> Option<PathBuf> {
1447 for dir_str in self.get_env_str("PATH").unwrap_or_default().split(':') {
1448 let candidate_path = Path::new(dir_str).join(candidate_name.as_ref());
1449 if candidate_path.executable() {
1450 return Some(candidate_path);
1451 }
1452 }
1453 None
1454 }
1455
1456 pub fn find_first_executable_in_path_using_cache<S: AsRef<str>>(
1464 &mut self,
1465 candidate_name: S,
1466 ) -> Option<PathBuf> {
1467 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
1468 Some(cached_path)
1469 } else if let Some(found_path) = self.find_first_executable_in_path(&candidate_name) {
1470 self.program_location_cache
1471 .set(&candidate_name, found_path.clone());
1472 Some(found_path)
1473 } else {
1474 None
1475 }
1476 }
1477
1478 pub fn get_absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
1484 let path = path.as_ref();
1485 if path.as_os_str().is_empty() || path.is_absolute() {
1486 path.to_owned()
1487 } else {
1488 self.working_dir.join(path)
1489 }
1490 }
1491
1492 pub(crate) fn open_file(
1499 &self,
1500 path: impl AsRef<Path>,
1501 params: &ExecutionParameters,
1502 ) -> Result<openfiles::OpenFile, error::Error> {
1503 let path_to_open = self.get_absolute_path(path.as_ref());
1504
1505 if let Some(parent) = path_to_open.parent() {
1509 if parent == Path::new("/dev/fd") {
1510 if let Some(filename) = path_to_open.file_name() {
1511 if let Ok(fd_num) = filename.to_string_lossy().to_string().parse::<u32>() {
1512 if let Some(open_file) = params.open_files.files.get(&fd_num) {
1513 return open_file.try_dup();
1514 }
1515 }
1516 }
1517 }
1518 }
1519
1520 Ok(std::fs::File::open(path_to_open)?.into())
1521 }
1522
1523 #[allow(dead_code)]
1529 pub(crate) fn replace_open_files(&mut self, open_files: openfiles::OpenFiles) {
1530 self.open_files = open_files;
1531 }
1532
1533 pub fn set_working_dir(&mut self, target_dir: impl AsRef<Path>) -> Result<(), error::Error> {
1539 let abs_path = self.get_absolute_path(target_dir.as_ref());
1540
1541 match std::fs::metadata(&abs_path) {
1542 Ok(m) => {
1543 if !m.is_dir() {
1544 return Err(error::Error::NotADirectory(abs_path));
1545 }
1546 }
1547 Err(e) => {
1548 return Err(e.into());
1549 }
1550 }
1551
1552 let cleaned_path = abs_path.normalize();
1554
1555 let pwd = cleaned_path.to_string_lossy().to_string();
1556
1557 self.env.update_or_add(
1558 "PWD",
1559 variables::ShellValueLiteral::Scalar(pwd),
1560 |var| {
1561 var.export();
1562 Ok(())
1563 },
1564 EnvironmentLookup::Anywhere,
1565 EnvironmentScope::Global,
1566 )?;
1567 let oldpwd = std::mem::replace(&mut self.working_dir, cleaned_path);
1568
1569 self.env.update_or_add(
1570 "OLDPWD",
1571 variables::ShellValueLiteral::Scalar(oldpwd.to_string_lossy().to_string()),
1572 |var| {
1573 var.export();
1574 Ok(())
1575 },
1576 EnvironmentLookup::Anywhere,
1577 EnvironmentScope::Global,
1578 )?;
1579
1580 Ok(())
1581 }
1582
1583 pub(crate) fn tilde_shorten(&self, s: String) -> String {
1589 if let Some(home_dir) = self.get_home_dir() {
1590 if let Some(stripped) = s.strip_prefix(home_dir.to_string_lossy().as_ref()) {
1591 return format!("~{stripped}");
1592 }
1593 }
1594 s
1595 }
1596
1597 pub(crate) fn get_home_dir(&self) -> Option<PathBuf> {
1599 Self::get_home_dir_with_env(&self.env, self)
1600 }
1601
1602 fn get_home_dir_with_env(env: &ShellEnvironment, shell: &Self) -> Option<PathBuf> {
1603 if let Some(home) = env.get_str("HOME", shell) {
1604 Some(PathBuf::from(home.to_string()))
1605 } else {
1606 users::get_current_user_home_dir()
1608 }
1609 }
1610
1611 pub fn stdout(&self) -> impl std::io::Write {
1614 self.open_files.files.get(&1).unwrap().try_dup().unwrap()
1615 }
1616
1617 pub fn stderr(&self) -> impl std::io::Write {
1620 self.open_files.files.get(&2).unwrap().try_dup().unwrap()
1621 }
1622
1623 pub(crate) async fn trace_command<S: AsRef<str>>(
1629 &mut self,
1630 command: S,
1631 ) -> Result<(), error::Error> {
1632 let ps4 = self.as_mut().expand_prompt_var("PS4", "").await?;
1633
1634 let mut prefix = ps4;
1635
1636 let additional_depth = self.script_call_stack.len() + self.depth;
1637 if let Some(c) = prefix.chars().next() {
1638 for _ in 0..additional_depth {
1639 prefix.insert(0, c);
1640 }
1641 }
1642
1643 writeln!(self.stderr(), "{prefix}{}", command.as_ref())?;
1644 Ok(())
1645 }
1646
1647 pub(crate) fn get_keywords(&self) -> Vec<String> {
1649 if self.options.sh_mode {
1650 keywords::SH_MODE_KEYWORDS.iter().cloned().collect()
1651 } else {
1652 keywords::KEYWORDS.iter().cloned().collect()
1653 }
1654 }
1655
1656 pub fn is_keyword(&self, s: &str) -> bool {
1662 if self.options.sh_mode {
1663 keywords::SH_MODE_KEYWORDS.contains(s)
1664 } else {
1665 keywords::KEYWORDS.contains(s)
1666 }
1667 }
1668
1669 pub fn check_for_completed_jobs(&mut self) -> Result<(), error::Error> {
1671 let results = self.jobs.poll()?;
1672
1673 if self.options.enable_job_control {
1674 for (job, _result) in results {
1675 writeln!(self.stderr(), "{job}")?;
1676 }
1677 }
1678
1679 Ok(())
1680 }
1681
1682 pub fn eval_arithmetic(
1684 &mut self,
1685 expr: &brush_parser::ast::ArithmeticExpr,
1686 ) -> Result<i64, error::Error> {
1687 Ok(expr.eval(self)?)
1688 }
1689
1690 pub fn set_edit_buffer(&mut self, contents: String, cursor: usize) -> Result<(), error::Error> {
1697 self.env
1698 .set_global("READLINE_LINE", ShellVariable::new(contents.into()))?;
1699
1700 self.env.set_global(
1701 "READLINE_POINT",
1702 ShellVariable::new(cursor.to_string().into()),
1703 )?;
1704
1705 Ok(())
1706 }
1707
1708 pub fn pop_edit_buffer(&mut self) -> Result<Option<(String, usize)>, error::Error> {
1711 let line = self
1712 .env
1713 .unset("READLINE_LINE")?
1714 .map(|line| line.value().to_cow_str(self).to_string());
1715
1716 let point = self
1717 .env
1718 .unset("READLINE_POINT")?
1719 .and_then(|point| point.value().to_cow_str(self).parse::<usize>().ok())
1720 .unwrap_or(0);
1721
1722 if let Some(line) = line {
1723 Ok(Some((line, point)))
1724 } else {
1725 Ok(None)
1726 }
1727 }
1728}
1729
1730#[cached::proc_macro::cached(size = 64, result = true)]
1731fn parse_string_impl(
1732 s: String,
1733 parser_options: brush_parser::ParserOptions,
1734) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
1735 let mut parser = create_parser(s.as_bytes(), &parser_options);
1736
1737 tracing::debug!(target: trace_categories::PARSE, "Parsing string as program...");
1738 parser.parse_program()
1739}
1740
1741fn create_parser<R: Read>(
1742 r: R,
1743 parser_options: &brush_parser::ParserOptions,
1744) -> brush_parser::Parser<std::io::BufReader<R>> {
1745 let reader = std::io::BufReader::new(r);
1746 let source_info = brush_parser::SourceInfo {
1747 source: String::from("main"),
1748 };
1749
1750 brush_parser::Parser::new(reader, parser_options, &source_info)
1751}
1752
1753fn repeated_char_str(c: char, count: usize) -> String {
1754 (0..count).map(|_| c).collect()
1755}
1756
1757fn get_random_value(_shell: &Shell) -> ShellValue {
1758 let mut rng = rand::rng();
1759 let num = rng.random_range(0..32768);
1760 let str = num.to_string();
1761 str.into()
1762}
1763
1764fn get_srandom_value(_shell: &Shell) -> ShellValue {
1765 let mut rng = rand::rng();
1766 let num: u32 = rng.random();
1767 let str = num.to_string();
1768 str.into()
1769}
1770
1771fn get_current_user_gids() -> Vec<u32> {
1773 let mut groups = sys::users::get_user_group_ids().unwrap_or_default();
1774
1775 if let Ok(gid) = sys::users::get_effective_gid() {
1778 if let Some(index) = groups.iter().position(|&g| g == gid) {
1779 if index > 0 {
1780 groups.remove(index);
1782 groups.insert(0, gid);
1783 }
1784 }
1785 }
1786
1787 groups
1788}