1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::io::{Read, Write};
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use normalize_path::NormalizePath;
8use tokio::sync::Mutex;
9
10use super::arithmetic::Evaluatable;
11use super::env::{EnvironmentLookup, EnvironmentScope, ShellEnvironment};
12use super::interp::{self, Execute, ExecutionParameters};
13use super::options::RuntimeOptions;
14use super::results::ExecutionSpawnResult;
15use super::sys::fs::PathExt;
16use super::variables::{self, ShellVariable};
17use super::{
18 ExecutionControlFlow, ExecutionResult, ProcessGroupPolicy, history, interfaces, pathcache,
19 pathsearch, scripts, trace_categories, wellknownvars,
20};
21use super::{
22 builtins, commands, completion, env, error, expansion, functions, jobs, keywords, openfiles,
23 prompt, sys::users, traps,
24};
25
26pub type KeyBindingsHelper = Arc<Mutex<dyn interfaces::KeyBindings>>;
28
29pub type ErrorFormatterHelper = Arc<Mutex<dyn error::ErrorFormatter>>;
31
32pub type ShellFd = i32;
34
35pub struct Shell {
37 pub traps: traps::TrapHandlerConfig,
39
40 open_files: openfiles::OpenFiles,
42
43 working_dir: PathBuf,
45
46 pub env: ShellEnvironment,
48
49 funcs: functions::FunctionEnv,
51
52 pub options: RuntimeOptions,
54
55 pub jobs: jobs::JobManager,
57
58 pub aliases: HashMap<String, String>,
60
61 last_exit_status: u8,
63
64 pub last_pipeline_statuses: Vec<u8>,
66
67 depth: usize,
69
70 pub shell_name: Option<String>,
72
73 version: Option<String>,
75
76 pub positional_parameters: Vec<String>,
78
79 product_display_str: Option<String>,
81
82 script_call_stack: scripts::CallStack,
84
85 function_call_stack: functions::CallStack,
87
88 pub directory_stack: Vec<PathBuf>,
90
91 current_line_number: u32,
93
94 pub completion_config: completion::Config,
96
97 builtins: HashMap<String, builtins::Registration>,
99
100 pub program_location_cache: pathcache::PathCache,
102
103 last_stopwatch_time: std::time::SystemTime,
105
106 last_stopwatch_offset: u32,
108
109 key_bindings: Option<KeyBindingsHelper>,
111
112 history: Option<history::History>,
114
115 error_formatter: ErrorFormatterHelper,
117}
118
119impl Clone for Shell {
120 fn clone(&self) -> Self {
121 Self {
122 traps: self.traps.clone(),
123 open_files: self.open_files.clone(),
124 working_dir: self.working_dir.clone(),
125 env: self.env.clone(),
126 funcs: self.funcs.clone(),
127 options: self.options.clone(),
128 jobs: jobs::JobManager::new(),
129 aliases: self.aliases.clone(),
130 last_exit_status: self.last_exit_status,
131 last_pipeline_statuses: self.last_pipeline_statuses.clone(),
132 positional_parameters: self.positional_parameters.clone(),
133 shell_name: self.shell_name.clone(),
134 version: self.version.clone(),
135 product_display_str: self.product_display_str.clone(),
136 function_call_stack: self.function_call_stack.clone(),
137 script_call_stack: self.script_call_stack.clone(),
138 directory_stack: self.directory_stack.clone(),
139 current_line_number: self.current_line_number,
140 completion_config: self.completion_config.clone(),
141 builtins: self.builtins.clone(),
142 program_location_cache: self.program_location_cache.clone(),
143 last_stopwatch_time: self.last_stopwatch_time,
144 last_stopwatch_offset: self.last_stopwatch_offset,
145 key_bindings: self.key_bindings.clone(),
146 history: self.history.clone(),
147 error_formatter: self.error_formatter.clone(),
148 depth: self.depth + 1,
149 }
150 }
151}
152
153impl AsRef<Self> for Shell {
154 fn as_ref(&self) -> &Self {
155 self
156 }
157}
158
159impl AsMut<Self> for Shell {
160 fn as_mut(&mut self) -> &mut Self {
161 self
162 }
163}
164
165pub use shell_builder::State as ShellBuilderState;
166
167impl<S: shell_builder::IsComplete> ShellBuilder<S> {
168 pub async fn build(self) -> Result<Shell, error::Error> {
170 let options = self.build_settings();
171
172 Shell::new(options).await
173 }
174}
175
176impl<S: shell_builder::State> ShellBuilder<S> {
177 pub fn disable_option(mut self, option: impl Into<String>) -> Self {
179 self.disabled_options.push(option.into());
180 self
181 }
182
183 pub fn enable_option(mut self, option: impl Into<String>) -> Self {
185 self.enabled_options.push(option.into());
186 self
187 }
188
189 pub fn disable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
191 self.disabled_options
192 .extend(options.into_iter().map(Into::into));
193 self
194 }
195
196 pub fn enable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
198 self.enabled_options
199 .extend(options.into_iter().map(Into::into));
200 self
201 }
202
203 pub fn disable_shopt_option(mut self, option: impl Into<String>) -> Self {
205 self.disabled_shopt_options.push(option.into());
206 self
207 }
208
209 pub fn enable_shopt_option(mut self, option: impl Into<String>) -> Self {
211 self.enabled_shopt_options.push(option.into());
212 self
213 }
214
215 pub fn disable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
217 self.disabled_shopt_options
218 .extend(options.into_iter().map(Into::into));
219 self
220 }
221
222 pub fn enable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
224 self.enabled_shopt_options
225 .extend(options.into_iter().map(Into::into));
226 self
227 }
228
229 pub fn builtin(mut self, name: impl Into<String>, reg: builtins::Registration) -> Self {
231 self.builtins.insert(name.into(), reg);
232 self
233 }
234
235 pub fn builtins(
237 mut self,
238 builtins: impl IntoIterator<Item = (String, builtins::Registration)>,
239 ) -> Self {
240 self.builtins.extend(builtins);
241 self
242 }
243}
244
245#[derive(Default, bon::Builder)]
247#[builder(
248 builder_type(
249 name = ShellBuilder,
250 doc {
251 }),
253 finish_fn(
254 name = build_settings,
255 vis = "pub(self)",
256 ),
257 start_fn(
258 vis = "pub(self)"
259 )
260)]
261pub struct CreateOptions {
262 #[builder(field)]
264 pub disabled_options: Vec<String>,
265 #[builder(field)]
267 pub enabled_options: Vec<String>,
268 #[builder(field)]
270 pub disabled_shopt_options: Vec<String>,
271 #[builder(field)]
273 pub enabled_shopt_options: Vec<String>,
274 #[builder(field)]
276 pub builtins: HashMap<String, builtins::Registration>,
277 pub working_dir: Option<PathBuf>,
285 #[builder(default)]
287 pub disallow_overwriting_regular_files_via_output_redirection: bool,
288 #[builder(default)]
290 pub do_not_execute_commands: bool,
291 #[builder(default)]
293 pub exit_after_one_command: bool,
294 #[builder(default)]
296 pub interactive: bool,
297 #[builder(default)]
299 pub login: bool,
300 #[builder(default)]
302 pub no_editing: bool,
303 #[builder(default)]
305 pub no_profile: bool,
306 #[builder(default)]
308 pub no_rc: bool,
309 pub rc_file: Option<PathBuf>,
311 #[builder(default)]
313 pub do_not_inherit_env: bool,
314 pub fds: Option<HashMap<ShellFd, openfiles::OpenFile>>,
316 #[builder(default)]
318 pub posix: bool,
319 #[builder(default)]
321 pub print_commands_and_arguments: bool,
322 #[builder(default)]
324 pub read_commands_from_stdin: bool,
325 pub shell_name: Option<String>,
327 pub shell_product_display_str: Option<String>,
329 #[builder(default)]
331 pub sh_mode: bool,
332 #[builder(default)]
334 pub treat_unset_variables_as_error: bool,
335 #[builder(default)]
337 pub verbose: bool,
338 #[builder(default)]
340 pub command_string_mode: bool,
341 pub max_function_call_depth: Option<usize>,
343 pub key_bindings: Option<KeyBindingsHelper>,
345 pub error_formatter: Option<ErrorFormatterHelper>,
347 pub shell_version: Option<String>,
349}
350
351impl Shell {
352 pub fn builder() -> ShellBuilder<shell_builder::Empty> {
355 CreateOptions::builder().builtins(crate::builtins::default_builtins(
356 crate::builtins::BuiltinSet::BashMode,
357 ))
358 }
359
360 pub async fn new(options: CreateOptions) -> Result<Self, error::Error> {
366 let mut shell = Self {
368 traps: traps::TrapHandlerConfig::default(),
369 open_files: openfiles::OpenFiles::new(),
370 working_dir: if let Some(ref dir) = options.working_dir {
372 dir.clone()
374 } else {
375 std::env::current_dir()?
377 },
378 env: env::ShellEnvironment::new(),
379 funcs: functions::FunctionEnv::default(),
380 options: RuntimeOptions::defaults_from(&options),
381 jobs: jobs::JobManager::new(),
382 aliases: HashMap::default(),
383 last_exit_status: 0,
384 last_pipeline_statuses: vec![0],
385 positional_parameters: vec![],
386 shell_name: options.shell_name,
387 version: options.shell_version,
388 product_display_str: options.shell_product_display_str,
389 function_call_stack: functions::CallStack::new(),
390 script_call_stack: scripts::CallStack::new(),
391 directory_stack: vec![],
392 current_line_number: 0,
393 completion_config: completion::Config::default(),
394 builtins: options.builtins,
395 program_location_cache: pathcache::PathCache::default(),
396 last_stopwatch_time: std::time::SystemTime::now(),
397 last_stopwatch_offset: 0,
398 key_bindings: options.key_bindings,
399 history: None,
400 error_formatter: options
401 .error_formatter
402 .unwrap_or_else(|| Arc::new(Mutex::new(error::DefaultErrorFormatter::new()))),
403 depth: 0,
404 };
405
406 if let Some(fds) = options.fds {
408 shell.open_files.update_from(fds.into_iter());
409 }
410
411 shell.options.extended_globbing = true;
414
415 wellknownvars::initialize_vars(&mut shell, options.do_not_inherit_env)?;
417
418 if shell.options.enable_command_history {
420 if let Some(history_path) = shell.history_file_path() {
421 let mut options = std::fs::File::options();
422 options.read(true);
423
424 if let Ok(history_file) =
425 shell.open_file(&options, history_path, &shell.default_exec_params())
426 {
427 shell.history = Some(history::History::import(history_file)?);
428 }
429 }
430
431 if shell.history.is_none() {
432 shell.history = Some(history::History::default());
433 }
434 }
435
436 shell
438 .load_config(
439 options.no_profile,
440 options.no_rc,
441 options.rc_file.as_deref(),
442 )
443 .await?;
444
445 Ok(shell)
446 }
447
448 pub const fn current_line_number(&self) -> u32 {
450 self.current_line_number
451 }
452
453 pub const fn version(&self) -> &Option<String> {
455 &self.version
456 }
457
458 pub const fn last_result(&self) -> u8 {
460 self.last_exit_status
461 }
462
463 pub const fn function_call_stack(&self) -> &functions::CallStack {
465 &self.function_call_stack
466 }
467
468 pub const fn script_call_stack(&self) -> &scripts::CallStack {
470 &self.script_call_stack
471 }
472
473 pub const fn last_exit_status_mut(&mut self) -> &mut u8 {
475 &mut self.last_exit_status
476 }
477
478 pub const fn key_bindings(&self) -> &Option<KeyBindingsHelper> {
480 &self.key_bindings
481 }
482
483 pub const fn builtins(&self) -> &HashMap<String, builtins::Registration> {
485 &self.builtins
486 }
487
488 pub fn working_dir(&self) -> &Path {
490 &self.working_dir
491 }
492
493 pub(crate) const fn working_dir_mut(&mut self) -> &mut PathBuf {
496 &mut self.working_dir
497 }
498
499 pub const fn product_display_str(&self) -> &Option<String> {
501 &self.product_display_str
502 }
503
504 pub const fn funcs(&self) -> &functions::FunctionEnv {
506 &self.funcs
507 }
508
509 pub fn undefine_func(&mut self, name: &str) -> bool {
516 self.funcs.remove(name).is_some()
517 }
518
519 pub fn define_func(
527 &mut self,
528 name: impl Into<String>,
529 definition: crate::parser::ast::FunctionDefinition,
530 ) {
531 self.funcs.update(name.into(), definition.into());
532 }
533
534 pub fn func_mut(&mut self, name: &str) -> Option<&mut functions::Registration> {
541 self.funcs.get_mut(name)
542 }
543
544 pub fn define_func_from_str(
552 &mut self,
553 name: impl Into<String>,
554 body_text: &str,
555 ) -> Result<(), error::Error> {
556 let name = name.into();
557
558 let mut parser = create_parser(body_text.as_bytes(), &self.parser_options());
559 let func_body = parser.parse_function_parens_and_body().map_err(|e| {
560 error::Error::from(error::ErrorKind::FunctionParseError(name.clone(), e))
561 })?;
562
563 let def = crate::parser::ast::FunctionDefinition {
564 fname: name.clone().into(),
565 body: func_body,
566 source: String::new(),
567 };
568
569 self.define_func(name, def);
570
571 Ok(())
572 }
573
574 pub const fn last_stopwatch_time(&self) -> std::time::SystemTime {
576 self.last_stopwatch_time
577 }
578
579 pub const fn last_stopwatch_offset(&self) -> u32 {
581 self.last_stopwatch_offset
582 }
583
584 async fn load_config(
585 &mut self,
586 skip_profile: bool,
587 skip_rc: bool,
588 rc_file: Option<&Path>,
589 ) -> Result<(), error::Error> {
590 let mut params = self.default_exec_params();
591 params.process_group_policy = interp::ProcessGroupPolicy::SameProcessGroup;
592
593 if self.options.login_shell {
594 if skip_profile {
596 return Ok(());
597 }
598
599 self.source_if_exists(Path::new("/etc/profile"), ¶ms)
608 .await?;
609 if let Some(home_path) = self.home_dir() {
610 let sourced = !self.options.sh_mode
613 && (self
614 .source_if_exists(home_path.join(".bash_profile").as_path(), ¶ms)
615 .await?
616 || self
617 .source_if_exists(home_path.join(".bash_login").as_path(), ¶ms)
618 .await?);
619
620 if !sourced {
621 self.source_if_exists(home_path.join(".profile").as_path(), ¶ms)
622 .await?;
623 }
624 }
625 } else if self.options.interactive {
626 if skip_rc || self.options.sh_mode {
628 return Ok(());
629 }
630
631 if let Some(rc_file) = rc_file {
633 self.source_if_exists(rc_file, ¶ms).await?;
635 } else {
636 self.source_if_exists(Path::new("/etc/bash.bashrc"), ¶ms)
643 .await?;
644 if let Some(home_path) = self.home_dir() {
645 self.source_if_exists(home_path.join(".bashrc").as_path(), ¶ms)
646 .await?;
647 self.source_if_exists(home_path.join(".brushrc").as_path(), ¶ms)
648 .await?;
649 }
650 }
651 } else {
652 let env_var_name = if self.options.sh_mode {
653 "ENV"
654 } else {
655 "BASH_ENV"
656 };
657
658 if self.env.is_set(env_var_name) {
659 return error::unimp(
663 "load config from $ENV/BASH_ENV for non-interactive, non-login shell",
664 );
665 }
666 }
667
668 Ok(())
669 }
670
671 async fn source_if_exists(
672 &mut self,
673 path: impl AsRef<Path>,
674 params: &ExecutionParameters,
675 ) -> Result<bool, error::Error> {
676 let path = path.as_ref();
677 if path.exists() {
678 self.source_script(path, std::iter::empty::<String>(), params)
679 .await?;
680 Ok(true)
681 } else {
682 tracing::debug!("skipping non-existent file: {}", path.display());
683 Ok(false)
684 }
685 }
686
687 pub async fn source_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
695 &mut self,
696 path: P,
697 args: I,
698 params: &ExecutionParameters,
699 ) -> Result<ExecutionResult, error::Error> {
700 self.parse_and_execute_script_file(path.as_ref(), args, params, scripts::CallType::Sourced)
701 .await
702 }
703
704 async fn parse_and_execute_script_file<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
713 &mut self,
714 path: P,
715 args: I,
716 params: &ExecutionParameters,
717 call_type: scripts::CallType,
718 ) -> Result<ExecutionResult, error::Error> {
719 let path = path.as_ref();
720 tracing::debug!("sourcing: {}", path.display());
721
722 let mut options = std::fs::File::options();
723 options.read(true);
724
725 let opened_file: openfiles::OpenFile = self
726 .open_file(&options, path, params)
727 .map_err(|e| error::ErrorKind::FailedSourcingFile(path.to_owned(), e))?;
728
729 if opened_file.is_dir() {
730 return Err(error::ErrorKind::FailedSourcingFile(
731 path.to_owned(),
732 std::io::Error::from(std::io::ErrorKind::IsADirectory),
733 )
734 .into());
735 }
736
737 let source_info = crate::parser::SourceInfo {
738 source: path.to_string_lossy().to_string(),
739 };
740
741 let mut result = self
742 .source_file(opened_file, &source_info, args, params, call_type)
743 .await?;
744
745 if matches!(
749 result.next_control_flow,
750 ExecutionControlFlow::ReturnFromFunctionOrScript
751 ) {
752 result.next_control_flow = ExecutionControlFlow::Normal;
753 }
754
755 Ok(result)
756 }
757
758 async fn source_file<F: Read, S: AsRef<str>, I: Iterator<Item = S>>(
768 &mut self,
769 file: F,
770 source_info: &crate::parser::SourceInfo,
771 args: I,
772 params: &ExecutionParameters,
773 call_type: scripts::CallType,
774 ) -> Result<ExecutionResult, error::Error> {
775 let mut reader = std::io::BufReader::new(file);
776 let mut parser =
777 crate::parser::Parser::new(&mut reader, &self.parser_options(), source_info);
778
779 tracing::debug!(target: trace_categories::PARSE, "Parsing sourced file: {}", source_info.source);
780 let parse_result = parser.parse_program();
781
782 let mut other_positional_parameters: Vec<_> = args.map(|s| s.as_ref().to_owned()).collect();
783 let mut other_shell_name = Some(source_info.source.clone());
784 let positional_params_given = !other_positional_parameters.is_empty();
785
786 std::mem::swap(&mut self.shell_name, &mut other_shell_name);
788
789 if positional_params_given {
792 std::mem::swap(
793 &mut self.positional_parameters,
794 &mut other_positional_parameters,
795 );
796 }
797
798 self.script_call_stack
799 .push(call_type, source_info.source.clone());
800
801 let result = self
802 .run_parsed_result(parse_result, source_info, params)
803 .await;
804
805 self.script_call_stack.pop();
806
807 std::mem::swap(&mut self.shell_name, &mut other_shell_name);
809
810 if positional_params_given {
812 std::mem::swap(
813 &mut self.positional_parameters,
814 &mut other_positional_parameters,
815 );
816 }
817
818 result
819 }
820
821 pub async fn invoke_function<N: AsRef<str>, I: IntoIterator<Item = A>, A: AsRef<str>>(
829 &mut self,
830 name: N,
831 args: I,
832 params: &ExecutionParameters,
833 ) -> Result<u8, error::Error> {
834 let name = name.as_ref();
835 let command_name = String::from(name);
836
837 let func_registration = self
838 .funcs
839 .get(name)
840 .ok_or_else(|| error::ErrorKind::FunctionNotFound(name.to_owned()))?;
841
842 let func = func_registration.definition.clone();
843
844 let context = commands::ExecutionContext {
845 shell: self,
846 command_name,
847 params: params.clone(),
848 };
849
850 let command_args = args
851 .into_iter()
852 .map(|s| commands::CommandArg::String(String::from(s.as_ref())))
853 .collect::<Vec<_>>();
854
855 match commands::invoke_shell_function(func, context, &command_args).await? {
856 ExecutionSpawnResult::StartedProcess(_) => {
857 error::unimp("child spawned from function invocation")
858 }
859 ExecutionSpawnResult::Completed(result) => Ok(result.exit_code.into()),
860 }
861 }
862
863 pub async fn exec<S: Into<String>>(
870 &mut self,
871 command: S,
872 params: &ExecutionParameters,
873 ) -> Result<ExecutionResult, error::Error> {
874 self.current_line_number += 1;
878
879 let parse_result = self.parse_string(command.into());
880 let source_info = crate::parser::SourceInfo {
881 source: String::from("main"),
882 };
883 self.run_parsed_result(parse_result, &source_info, params)
884 .await
885 }
886
887 pub fn stream<S: Into<String>>(
895 &mut self,
896 command: S,
897 params: &ExecutionParameters,
898 ) -> Result<(
899 tokio_stream::wrappers::ReceiverStream<super::results::StreamingOutput>,
900 tokio::sync::mpsc::Sender<Vec<u8>>,
901 ), error::Error> {
902 use std::io::{Read, Write};
903 use super::results::StreamingOutput;
904
905 let command = command.into();
906
907 let program = self.parse_string(&command).map_err(|e| {
909 error::Error::from(error::ErrorKind::ParseError(
910 e,
911 crate::parser::SourceInfo { source: String::from("streaming") },
912 ))
913 })?;
914
915 let (stdin_reader, stdin_writer) =
917 std::io::pipe().map_err(|e| error::Error::from(error::ErrorKind::IoError(e)))?;
918 let (stdout_reader, stdout_writer) =
919 std::io::pipe().map_err(|e| error::Error::from(error::ErrorKind::IoError(e)))?;
920 let (stderr_reader, stderr_writer) =
921 std::io::pipe().map_err(|e| error::Error::from(error::ErrorKind::IoError(e)))?;
922
923 let (output_tx, output_rx) = tokio::sync::mpsc::channel::<StreamingOutput>(64);
925
926 let (stdin_tx, mut stdin_rx) = tokio::sync::mpsc::channel::<Vec<u8>>(64);
928
929 let mut subshell = self.clone();
931 let mut exec_params = params.clone();
932 exec_params.set_fd(openfiles::OpenFiles::STDIN_FD, openfiles::OpenFile::PipeReader(stdin_reader));
933 exec_params.set_fd(openfiles::OpenFiles::STDOUT_FD, openfiles::OpenFile::PipeWriter(stdout_writer));
934 exec_params.set_fd(openfiles::OpenFiles::STDERR_FD, openfiles::OpenFile::PipeWriter(stderr_writer));
935
936 tokio::spawn(async move {
938 let _ = subshell.run_program(program, &exec_params).await;
939 });
941
942 tokio::task::spawn_blocking(move || {
944 let mut w = stdin_writer;
945 while let Some(data) = stdin_rx.blocking_recv() {
946 if w.write_all(&data).is_err() {
947 break;
948 }
949 }
950 });
952
953 let tx1 = output_tx.clone();
955 tokio::task::spawn_blocking(move || {
956 let mut buf = [0u8; 4096];
957 let mut r = stdout_reader;
958 loop {
959 match r.read(&mut buf) {
960 Ok(0) => break,
961 Ok(n) => { let _ = tx1.blocking_send(StreamingOutput::stdout(buf[..n].to_vec())); }
962 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
963 Err(_) => break,
964 }
965 }
966 });
967
968 tokio::task::spawn_blocking(move || {
970 let mut buf = [0u8; 4096];
971 let mut r = stderr_reader;
972 loop {
973 match r.read(&mut buf) {
974 Ok(0) => break,
975 Ok(n) => { let _ = output_tx.blocking_send(StreamingOutput::stderr(buf[..n].to_vec())); }
976 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
977 Err(_) => break,
978 }
979 }
980 });
981
982 Ok((tokio_stream::wrappers::ReceiverStream::new(output_rx), stdin_tx))
983 }
984
985 pub fn parse<R: Read>(
988 &self,
989 reader: R,
990 ) -> Result<crate::parser::ast::Program, crate::parser::ParseError> {
991 let mut parser = create_parser(reader, &self.parser_options());
992
993 tracing::debug!(target: trace_categories::PARSE, "Parsing reader as program...");
994 parser.parse_program()
995 }
996
997 pub fn parse_string<S: Into<String>>(
1004 &self,
1005 s: S,
1006 ) -> Result<crate::parser::ast::Program, crate::parser::ParseError> {
1007 parse_string_impl(s.into(), self.parser_options())
1008 }
1009
1010 pub async fn basic_expand_string<S: AsRef<str>>(
1016 &mut self,
1017 params: &ExecutionParameters,
1018 s: S,
1019 ) -> Result<String, error::Error> {
1020 let result = expansion::basic_expand_str(self, params, s.as_ref()).await?;
1021 Ok(result)
1022 }
1023
1024 pub async fn full_expand_and_split_string<S: AsRef<str>>(
1031 &mut self,
1032 params: &ExecutionParameters,
1033 s: S,
1034 ) -> Result<Vec<String>, error::Error> {
1035 let result = expansion::full_expand_and_split_str(self, params, s.as_ref()).await?;
1036 Ok(result)
1037 }
1038
1039 pub fn default_exec_params(&self) -> ExecutionParameters {
1041 ExecutionParameters::default()
1042 }
1043
1044 pub async fn run_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
1051 &mut self,
1052 script_path: P,
1053 args: I,
1054 ) -> Result<ExecutionResult, error::Error> {
1055 let params = self.default_exec_params();
1056 let result = self
1057 .parse_and_execute_script_file(
1058 script_path.as_ref(),
1059 args,
1060 ¶ms,
1061 scripts::CallType::Executed,
1062 )
1063 .await?;
1064
1065 let _ = self.on_exit().await;
1066
1067 Ok(result)
1068 }
1069
1070 pub async fn on_exit(&mut self) -> Result<(), error::Error> {
1072 self.invoke_exit_trap_handler_if_registered().await?;
1073
1074 Ok(())
1075 }
1076
1077 async fn invoke_exit_trap_handler_if_registered(
1078 &mut self,
1079 ) -> Result<ExecutionResult, error::Error> {
1080 let Some(handler) = self.traps.handlers.get(&traps::TrapSignal::Exit).cloned() else {
1081 return Ok(ExecutionResult::success());
1082 };
1083
1084 let mut params = self.default_exec_params();
1086 params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
1087
1088 let orig_last_exit_status = self.last_exit_status;
1089 self.traps.handler_depth += 1;
1090
1091 let result = self.exec(handler, ¶ms).await;
1092
1093 self.traps.handler_depth -= 1;
1094 self.last_exit_status = orig_last_exit_status;
1095
1096 result
1097 }
1098
1099 pub(crate) async fn run_parsed_result(
1100 &mut self,
1101 parse_result: Result<crate::parser::ast::Program, crate::parser::ParseError>,
1102 source_info: &crate::parser::SourceInfo,
1103 params: &ExecutionParameters,
1104 ) -> Result<ExecutionResult, error::Error> {
1105 let result = match parse_result {
1107 Ok(prog) => self.run_program(prog, params).await,
1108 Err(parse_err) => Err(error::Error::from(error::ErrorKind::ParseError(
1109 parse_err,
1110 source_info.clone(),
1111 ))
1112 .into_fatal()),
1113 };
1114
1115 match result {
1117 Ok(result) => Ok(result),
1118 Err(err) => {
1119 let _ = self.display_error(&mut params.stderr(self), &err).await;
1120
1121 let result = err.into_result(self);
1122 *self.last_exit_status_mut() = result.exit_code.into();
1123
1124 Ok(result)
1125 }
1126 }
1127 }
1128
1129 pub async fn run_program(
1136 &mut self,
1137 program: crate::parser::ast::Program,
1138 params: &ExecutionParameters,
1139 ) -> Result<ExecutionResult, error::Error> {
1140 program.execute(self, params).await
1141 }
1142
1143 const fn default_prompt(&self) -> &'static str {
1144 if self.options.sh_mode {
1145 "$ "
1146 } else {
1147 "brush$ "
1148 }
1149 }
1150
1151 pub async fn compose_precmd_prompt(&mut self) -> Result<String, error::Error> {
1153 self.expand_prompt_var("PS0", "").await
1154 }
1155
1156 pub async fn compose_prompt(&mut self) -> Result<String, error::Error> {
1158 self.expand_prompt_var("PS1", self.default_prompt()).await
1159 }
1160
1161 pub async fn compose_alt_side_prompt(&mut self) -> Result<String, error::Error> {
1163 self.expand_prompt_var("BRUSH_PS_ALT", "").await
1165 }
1166
1167 pub async fn compose_continuation_prompt(&mut self) -> Result<String, error::Error> {
1169 self.expand_prompt_var("PS2", "> ").await
1170 }
1171
1172 async fn expand_prompt_var(
1173 &mut self,
1174 var_name: &str,
1175 default: &str,
1176 ) -> Result<String, error::Error> {
1177 let prompt_spec = self.parameter_or_default(var_name, default);
1184 if prompt_spec.is_empty() {
1185 return Ok(String::new());
1186 }
1187
1188 let params = self.default_exec_params();
1190 prompt::expand_prompt(self, ¶ms, prompt_spec.into_owned()).await
1191 }
1192
1193 fn parameter_or_default<'a>(&'a self, name: &str, default: &'a str) -> Cow<'a, str> {
1194 self.env_str(name).unwrap_or_else(|| default.into())
1195 }
1196
1197 pub const fn parser_options(&self) -> crate::parser::ParserOptions {
1200 crate::parser::ParserOptions {
1201 enable_extended_globbing: self.options.extended_globbing,
1202 posix_mode: self.options.posix_mode,
1203 sh_mode: self.options.sh_mode,
1204 tilde_expansion: true,
1205 }
1206 }
1207
1208 pub fn in_sourced_script(&self) -> bool {
1210 self.script_call_stack.in_sourced_script()
1211 }
1212
1213 pub fn in_function(&self) -> bool {
1215 !self.function_call_stack.is_empty()
1216 }
1217
1218 pub(crate) fn enter_function(
1226 &mut self,
1227 name: &str,
1228 function_def: &Arc<crate::parser::ast::FunctionDefinition>,
1229 ) -> Result<(), error::Error> {
1230 if let Some(max_call_depth) = self.options.max_function_call_depth
1231 && self.function_call_stack.depth() >= max_call_depth {
1232 return Err(error::ErrorKind::MaxFunctionCallDepthExceeded.into());
1233 }
1234
1235 if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1236 let depth = self.function_call_stack.depth();
1237 let prefix = repeated_char_str(' ', depth);
1238 tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
1239 }
1240
1241 self.function_call_stack.push(name, function_def);
1242 self.env.push_scope(env::EnvironmentScope::Local);
1243
1244 Ok(())
1245 }
1246
1247 pub(crate) fn leave_function(&mut self) -> Result<(), error::Error> {
1250 self.env.pop_scope(env::EnvironmentScope::Local)?;
1251
1252 if let Some(exited_call) = self.function_call_stack.pop()
1253 && tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1254 let depth = self.function_call_stack.depth();
1255 let prefix = repeated_char_str(' ', depth);
1256 tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func [depth={depth}]: {prefix}{}", exited_call.function_name);
1257 }
1258
1259 Ok(())
1260 }
1261
1262 pub fn history_file_path(&self) -> Option<PathBuf> {
1264 self.env_str("HISTFILE")
1265 .map(|s| PathBuf::from(s.into_owned()))
1266 }
1267
1268 pub fn history_time_format(&self) -> Option<String> {
1270 self.env_str("HISTTIMEFORMAT").map(|s| s.into_owned())
1271 }
1272
1273 pub fn save_history(&mut self) -> Result<(), error::Error> {
1275 if let Some(history_file_path) = self.history_file_path()
1276 && let Some(history) = &mut self.history {
1277 let write_timestamps = self.env.is_set("HISTTIMEFORMAT");
1279
1280 history.flush(
1282 history_file_path,
1283 true, true, write_timestamps,
1286 )?;
1287 }
1288
1289 Ok(())
1290 }
1291
1292 pub fn add_to_history(&mut self, command: &str) -> Result<(), error::Error> {
1294 if let Some(history) = &mut self.history {
1295 let command = command.trim();
1297
1298 if command.is_empty() {
1300 return Ok(());
1301 }
1302
1303 history.add(history::Item {
1305 id: 0,
1306 command_line: command.to_owned(),
1307 timestamp: Some(chrono::Utc::now()),
1308 dirty: true,
1309 })?;
1310 }
1311
1312 Ok(())
1313 }
1314
1315 pub fn env_str(&self, name: &str) -> Option<Cow<'_, str>> {
1322 self.env.get_str(name, self)
1323 }
1324
1325 pub fn env_var(&self, name: &str) -> Option<&ShellVariable> {
1331 self.env.get(name).map(|(_, var)| var)
1332 }
1333
1334 pub fn set_env_global(&mut self, name: &str, var: ShellVariable) -> Result<(), error::Error> {
1341 self.env.set_global(name, var)
1342 }
1343
1344 pub fn register_builtin<S: Into<String>>(
1351 &mut self,
1352 name: S,
1353 registration: builtins::Registration,
1354 ) {
1355 self.builtins.insert(name.into(), registration);
1356 }
1357
1358 pub fn builtin_mut(&mut self, name: &str) -> Option<&mut builtins::Registration> {
1365 self.builtins.get_mut(name)
1366 }
1367
1368 pub fn ifs(&self) -> Cow<'_, str> {
1370 self.env_str("IFS").unwrap_or_else(|| " \t\n".into())
1371 }
1372
1373 pub(crate) fn get_ifs_first_char(&self) -> char {
1375 self.ifs().chars().next().unwrap_or(' ')
1376 }
1377
1378 pub async fn complete(
1385 &mut self,
1386 input: &str,
1387 position: usize,
1388 ) -> Result<completion::Completions, error::Error> {
1389 let completion_config = self.completion_config.clone();
1390 completion_config
1391 .get_completions(self, input, position)
1392 .await
1393 }
1394
1395 pub fn find_executables_in_path<'a>(
1401 &'a self,
1402 filename: &'a str,
1403 ) -> impl Iterator<Item = PathBuf> + 'a {
1404 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1405 let paths = path_var.split(':').map(|s| s.to_owned());
1406
1407 pathsearch::search_for_executable(paths.into_iter(), filename)
1408 }
1409
1410 pub fn find_executables_in_path_with_prefix(
1417 &self,
1418 filename_prefix: &str,
1419 case_insensitive: bool,
1420 ) -> impl Iterator<Item = PathBuf> {
1421 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1422 let paths = path_var.split(':').map(|s| s.to_owned());
1423
1424 pathsearch::search_for_executable_with_prefix(
1425 paths.into_iter(),
1426 filename_prefix,
1427 case_insensitive,
1428 )
1429 }
1430
1431 pub fn find_first_executable_in_path<S: AsRef<str>>(
1438 &self,
1439 candidate_name: S,
1440 ) -> Option<PathBuf> {
1441 for dir_str in self.env_str("PATH").unwrap_or_default().split(':') {
1442 let candidate_path = Path::new(dir_str).join(candidate_name.as_ref());
1443 if candidate_path.executable() {
1444 return Some(candidate_path);
1445 }
1446 }
1447 None
1448 }
1449
1450 pub fn find_first_executable_in_path_using_cache<S: AsRef<str>>(
1458 &mut self,
1459 candidate_name: S,
1460 ) -> Option<PathBuf> {
1461 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
1462 Some(cached_path)
1463 } else if let Some(found_path) = self.find_first_executable_in_path(&candidate_name) {
1464 self.program_location_cache
1465 .set(&candidate_name, found_path.clone());
1466 Some(found_path)
1467 } else {
1468 None
1469 }
1470 }
1471
1472 pub fn absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
1478 let path = path.as_ref();
1479 if path.as_os_str().is_empty() || path.is_absolute() {
1480 path.to_owned()
1481 } else {
1482 self.working_dir().join(path)
1483 }
1484 }
1485
1486 pub(crate) fn open_file(
1494 &self,
1495 options: &std::fs::OpenOptions,
1496 path: impl AsRef<Path>,
1497 params: &ExecutionParameters,
1498 ) -> Result<openfiles::OpenFile, std::io::Error> {
1499 let path_to_open = self.absolute_path(path.as_ref());
1500
1501 if let Some(parent) = path_to_open.parent()
1505 && parent == Path::new("/dev/fd")
1506 && let Some(filename) = path_to_open.file_name()
1507 && let Ok(fd_num) = filename.to_string_lossy().to_string().parse::<ShellFd>()
1508 && let Some(open_file) = params.try_fd(self, fd_num) {
1509 return open_file.try_clone();
1510 }
1511
1512 Ok(options.open(path_to_open)?.into())
1513 }
1514
1515 pub fn set_working_dir(&mut self, target_dir: impl AsRef<Path>) -> Result<(), error::Error> {
1521 let abs_path = self.absolute_path(target_dir.as_ref());
1522
1523 match std::fs::metadata(&abs_path) {
1524 Ok(m) => {
1525 if !m.is_dir() {
1526 return Err(error::ErrorKind::NotADirectory(abs_path).into());
1527 }
1528 }
1529 Err(e) => {
1530 return Err(e.into());
1531 }
1532 }
1533
1534 let cleaned_path = abs_path.normalize();
1536
1537 let pwd = cleaned_path.to_string_lossy().to_string();
1538
1539 self.env.update_or_add(
1540 "PWD",
1541 variables::ShellValueLiteral::Scalar(pwd),
1542 |var| {
1543 var.export();
1544 Ok(())
1545 },
1546 EnvironmentLookup::Anywhere,
1547 EnvironmentScope::Global,
1548 )?;
1549 let oldpwd = std::mem::replace(self.working_dir_mut(), cleaned_path);
1550
1551 self.env.update_or_add(
1552 "OLDPWD",
1553 variables::ShellValueLiteral::Scalar(oldpwd.to_string_lossy().to_string()),
1554 |var| {
1555 var.export();
1556 Ok(())
1557 },
1558 EnvironmentLookup::Anywhere,
1559 EnvironmentScope::Global,
1560 )?;
1561
1562 Ok(())
1563 }
1564
1565 pub fn tilde_shorten(&self, s: String) -> String {
1571 if let Some(home_dir) = self.home_dir()
1572 && let Some(stripped) = s.strip_prefix(home_dir.to_string_lossy().as_ref()) {
1573 return format!("~{stripped}");
1574 }
1575 s
1576 }
1577
1578 pub(crate) fn home_dir(&self) -> Option<PathBuf> {
1580 if let Some(home) = self.env.get_str("HOME", self) {
1581 Some(PathBuf::from(home.to_string()))
1582 } else {
1583 users::get_current_user_home_dir()
1585 }
1586 }
1587
1588 pub fn replace_open_files(
1595 &mut self,
1596 open_fds: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
1597 ) {
1598 self.open_files = openfiles::OpenFiles::from(open_fds);
1599 }
1600
1601 pub(crate) const fn persistent_open_files(&self) -> &openfiles::OpenFiles {
1602 &self.open_files
1603 }
1604
1605 pub fn stdout(&self) -> impl std::io::Write {
1608 self.open_files.try_stdout().cloned().unwrap()
1609 }
1610
1611 pub fn stderr(&self) -> impl std::io::Write {
1614 self.open_files.try_stderr().cloned().unwrap()
1615 }
1616
1617 pub(crate) async fn trace_command<S: AsRef<str>>(
1623 &mut self,
1624 params: &ExecutionParameters,
1625 command: S,
1626 ) -> Result<(), error::Error> {
1627 let ps4 = self.as_mut().expand_prompt_var("PS4", "").await?;
1629 let mut prefix = ps4;
1630
1631 let additional_depth = self.script_call_stack.depth() + self.depth;
1633 if let Some(c) = prefix.chars().next() {
1634 for _ in 0..additional_depth {
1635 prefix.insert(0, c);
1636 }
1637 }
1638
1639 let mut trace_file = params.try_stderr(self);
1641
1642 if let Some((_, xtracefd_var)) = self.env.get("BASH_XTRACEFD") {
1644 let xtracefd_value = xtracefd_var.value().to_cow_str(self);
1645 if let Ok(fd) = xtracefd_value.parse::<ShellFd>()
1646 && let Some(file) = self.open_files.try_fd(fd) {
1647 trace_file = Some(file.clone());
1648 }
1649 }
1650
1651 if let Some(trace_file) = trace_file {
1653 let mut trace_file = trace_file.try_clone()?;
1654 writeln!(trace_file, "{prefix}{}", command.as_ref())?;
1655 }
1656
1657 Ok(())
1658 }
1659
1660 pub(crate) fn get_keywords(&self) -> Vec<String> {
1662 if self.options.sh_mode {
1663 keywords::SH_MODE_KEYWORDS.iter().cloned().collect()
1664 } else {
1665 keywords::KEYWORDS.iter().cloned().collect()
1666 }
1667 }
1668
1669 pub fn is_keyword(&self, s: &str) -> bool {
1675 if self.options.sh_mode {
1676 keywords::SH_MODE_KEYWORDS.contains(s)
1677 } else {
1678 keywords::KEYWORDS.contains(s)
1679 }
1680 }
1681
1682 pub fn check_for_completed_jobs(&mut self) -> Result<(), error::Error> {
1684 let results = self.jobs.poll()?;
1685
1686 if self.options.enable_job_control {
1687 for (job, _result) in results {
1688 writeln!(self.stderr(), "{job}")?;
1689 }
1690 }
1691
1692 Ok(())
1693 }
1694
1695 pub fn eval_arithmetic(
1697 &mut self,
1698 expr: &crate::parser::ast::ArithmeticExpr,
1699 ) -> Result<i64, error::Error> {
1700 Ok(expr.eval(self)?)
1701 }
1702
1703 pub fn set_edit_buffer(&mut self, contents: String, cursor: usize) -> Result<(), error::Error> {
1710 self.env
1711 .set_global("READLINE_LINE", ShellVariable::new(contents))?;
1712
1713 self.env
1714 .set_global("READLINE_POINT", ShellVariable::new(cursor.to_string()))?;
1715
1716 Ok(())
1717 }
1718
1719 pub fn pop_edit_buffer(&mut self) -> Result<Option<(String, usize)>, error::Error> {
1722 let line = self
1723 .env
1724 .unset("READLINE_LINE")?
1725 .map(|line| line.value().to_cow_str(self).to_string());
1726
1727 let point = self
1728 .env
1729 .unset("READLINE_POINT")?
1730 .and_then(|point| point.value().to_cow_str(self).parse::<usize>().ok())
1731 .unwrap_or(0);
1732
1733 if let Some(line) = line {
1734 Ok(Some((line, point)))
1735 } else {
1736 Ok(None)
1737 }
1738 }
1739
1740 pub const fn history(&self) -> Option<&history::History> {
1742 self.history.as_ref()
1743 }
1744
1745 pub const fn history_mut(&mut self) -> Option<&mut history::History> {
1747 self.history.as_mut()
1748 }
1749
1750 pub const fn is_subshell(&self) -> bool {
1752 self.depth > 0
1753 }
1754
1755 pub const fn depth(&self) -> usize {
1757 self.depth
1758 }
1759
1760 pub async fn display_error(
1767 &self,
1768 file: &mut impl std::io::Write,
1769 err: &error::Error,
1770 ) -> Result<(), error::Error> {
1771 let str = self.error_formatter.lock().await.format_error(err, self);
1772 write!(file, "{str}")?;
1773
1774 Ok(())
1775 }
1776}
1777
1778#[cached::proc_macro::cached(size = 64, result = true)]
1779fn parse_string_impl(
1780 s: String,
1781 parser_options: crate::parser::ParserOptions,
1782) -> Result<crate::parser::ast::Program, crate::parser::ParseError> {
1783 let mut parser = create_parser(s.as_bytes(), &parser_options);
1784
1785 tracing::debug!(target: trace_categories::PARSE, "Parsing string as program...");
1786 parser.parse_program()
1787}
1788
1789fn create_parser<R: Read>(
1790 r: R,
1791 parser_options: &crate::parser::ParserOptions,
1792) -> crate::parser::Parser<std::io::BufReader<R>> {
1793 let reader = std::io::BufReader::new(r);
1794 let source_info = crate::parser::SourceInfo {
1795 source: String::from("main"),
1796 };
1797
1798 crate::parser::Parser::new(reader, parser_options, &source_info)
1799}
1800
1801fn repeated_char_str(c: char, count: usize) -> String {
1802 (0..count).map(|_| c).collect()
1803}