1use brush_parser::ast::{self, CommandPrefixOrSuffixItem};
2use itertools::Itertools;
3use std::collections::VecDeque;
4use std::io::Write;
5#[cfg(target_os = "linux")]
6use std::os::fd::{AsFd, AsRawFd};
7#[cfg(unix)]
8use std::os::unix::process::ExitStatusExt;
9use std::path::{Path, PathBuf};
10use std::sync::Arc;
11
12use crate::arithmetic::{self, ExpandAndEvaluate};
13use crate::commands::{self, CommandArg, CommandSpawnResult};
14use crate::env::{EnvironmentLookup, EnvironmentScope};
15use crate::openfiles::{OpenFile, OpenFiles};
16use crate::shell::Shell;
17use crate::variables::{
18 ArrayLiteral, ShellValue, ShellValueLiteral, ShellValueUnsetType, ShellVariable,
19};
20use crate::{error, expansion, extendedtests, jobs, openfiles, processes, sys, timing, traps};
21
22#[derive(Debug, Default)]
24pub struct ExecutionResult {
25 pub exit_code: u8,
27 pub exit_shell: bool,
29 pub return_from_function_or_script: bool,
31 pub break_loop: Option<u8>,
33 pub continue_loop: Option<u8>,
35}
36
37impl From<processes::ProcessWaitResult> for ExecutionResult {
38 fn from(wait_result: processes::ProcessWaitResult) -> Self {
39 match wait_result {
40 processes::ProcessWaitResult::Completed(output) => output.into(),
41 processes::ProcessWaitResult::Stopped => Self::stopped(),
42 }
43 }
44}
45
46impl From<std::process::Output> for ExecutionResult {
47 fn from(output: std::process::Output) -> ExecutionResult {
48 if let Some(code) = output.status.code() {
49 #[allow(clippy::cast_sign_loss)]
50 return Self::new((code & 0xFF) as u8);
51 }
52
53 #[cfg(unix)]
54 if let Some(signal) = output.status.signal() {
55 #[allow(clippy::cast_sign_loss)]
56 return Self::new((signal & 0xFF) as u8 + 128);
57 }
58
59 tracing::error!("unhandled process exit");
60 Self::new(127)
61 }
62}
63
64impl ExecutionResult {
65 pub fn new(exit_code: u8) -> ExecutionResult {
70 ExecutionResult {
71 exit_code,
72 ..ExecutionResult::default()
73 }
74 }
75
76 pub fn success() -> ExecutionResult {
78 Self::new(0)
79 }
80
81 pub fn is_success(&self) -> bool {
83 self.exit_code == 0
84 }
85
86 pub fn stopped() -> ExecutionResult {
88 const SIGTSTP: std::os::raw::c_int = 20;
90
91 #[allow(clippy::cast_possible_truncation)]
92 Self::new(128 + SIGTSTP as u8)
93 }
94}
95
96struct PipelineExecutionContext<'a> {
98 shell: &'a mut Shell,
100
101 current_pipeline_index: usize,
102 pipeline_len: usize,
103 output_pipes: &'a mut Vec<sys::pipes::PipeReader>,
104
105 process_group_id: Option<i32>,
106}
107
108#[derive(Clone, Default)]
110pub struct ExecutionParameters {
111 pub open_files: openfiles::OpenFiles,
113 pub process_group_policy: ProcessGroupPolicy,
115}
116
117impl ExecutionParameters {
118 pub fn stdin(&self) -> openfiles::OpenFile {
120 self.fd(0).unwrap()
121 }
122
123 pub fn stdout(&self) -> openfiles::OpenFile {
125 self.fd(1).unwrap()
126 }
127
128 pub fn stderr(&self) -> openfiles::OpenFile {
130 self.fd(2).unwrap()
131 }
132
133 #[allow(clippy::unwrap_in_result)]
135 pub fn fd(&self, fd: u32) -> Option<openfiles::OpenFile> {
136 self.open_files.files.get(&fd).map(|f| f.try_dup().unwrap())
137 }
138}
139
140#[derive(Clone, Debug, Default)]
141pub enum ProcessGroupPolicy {
143 #[default]
145 NewProcessGroup,
146 SameProcessGroup,
148}
149
150#[async_trait::async_trait]
151pub trait Execute {
152 async fn execute(
153 &self,
154 shell: &mut Shell,
155 params: &ExecutionParameters,
156 ) -> Result<ExecutionResult, error::Error>;
157}
158
159#[async_trait::async_trait]
160trait ExecuteInPipeline {
161 async fn execute_in_pipeline(
162 &self,
163 context: &mut PipelineExecutionContext,
164 params: ExecutionParameters,
165 ) -> Result<CommandSpawnResult, error::Error>;
166}
167
168#[async_trait::async_trait]
169impl Execute for ast::Program {
170 async fn execute(
171 &self,
172 shell: &mut Shell,
173 params: &ExecutionParameters,
174 ) -> Result<ExecutionResult, error::Error> {
175 let mut result = ExecutionResult::success();
176
177 for command in &self.complete_commands {
178 result = command.execute(shell, params).await?;
179 if result.exit_shell || result.return_from_function_or_script {
180 break;
181 }
182 }
183
184 shell.last_exit_status = result.exit_code;
185 Ok(result)
186 }
187}
188
189#[async_trait::async_trait]
190impl Execute for ast::CompoundList {
191 async fn execute(
192 &self,
193 shell: &mut Shell,
194 params: &ExecutionParameters,
195 ) -> Result<ExecutionResult, error::Error> {
196 let mut result = ExecutionResult::success();
197
198 for ast::CompoundListItem(ao_list, sep) in &self.0 {
199 let run_async = matches!(sep, ast::SeparatorOperator::Async);
200
201 if run_async {
202 let job = spawn_ao_list_in_task(ao_list, shell, params);
206 let job_formatted = job.to_pid_style_string();
207
208 if shell.options.interactive {
209 writeln!(shell.stderr(), "{job_formatted}")?;
210 }
211
212 result = ExecutionResult::success();
213 } else {
214 result = ao_list.execute(shell, params).await?;
215 }
216
217 if result.exit_shell || result.return_from_function_or_script {
219 break;
220 }
221
222 if result.continue_loop.is_some() || result.break_loop.is_some() {
224 break;
225 }
226 }
227
228 shell.last_exit_status = result.exit_code;
229 Ok(result)
230 }
231}
232
233fn spawn_ao_list_in_task<'a>(
234 ao_list: &ast::AndOrList,
235 shell: &'a mut Shell,
236 params: &ExecutionParameters,
237) -> &'a jobs::Job {
238 let mut cloned_shell = shell.clone();
240 let cloned_params = params.clone();
241 let cloned_ao_list = ao_list.clone();
242
243 cloned_shell.options.interactive = false;
245
246 let join_handle = tokio::spawn(async move {
247 cloned_ao_list
248 .execute(&mut cloned_shell, &cloned_params)
249 .await
250 });
251
252 let job = shell.jobs.add_as_current(jobs::Job::new(
253 [jobs::JobTask::Internal(join_handle)],
254 ao_list.to_string(),
255 jobs::JobState::Running,
256 ));
257
258 job
259}
260
261#[async_trait::async_trait]
262impl Execute for ast::AndOrList {
263 async fn execute(
264 &self,
265 shell: &mut Shell,
266 params: &ExecutionParameters,
267 ) -> Result<ExecutionResult, error::Error> {
268 let mut result = self.first.execute(shell, params).await?;
269
270 for next_ao in &self.additional {
271 if result.exit_shell || result.return_from_function_or_script {
273 break;
274 }
275
276 if result.continue_loop.is_some() || result.break_loop.is_some() {
279 break;
280 }
281
282 let (is_and, pipeline) = match next_ao {
283 ast::AndOr::And(p) => (true, p),
284 ast::AndOr::Or(p) => (false, p),
285 };
286
287 if is_and {
292 if !result.is_success() {
293 continue;
294 }
295 } else if result.is_success() {
296 continue;
297 }
298
299 result = pipeline.execute(shell, params).await?;
300 }
301
302 Ok(result)
303 }
304}
305
306#[async_trait::async_trait]
307impl Execute for ast::Pipeline {
308 async fn execute(
309 &self,
310 shell: &mut Shell,
311 params: &ExecutionParameters,
312 ) -> Result<ExecutionResult, error::Error> {
313 let stopwatch = self
315 .timed
316 .is_some()
317 .then(timing::start_timing)
318 .transpose()?;
319
320 let spawn_results = spawn_pipeline_processes(self, shell, params).await?;
323
324 let mut result =
326 wait_for_pipeline_processes_and_update_status(self, spawn_results, shell).await?;
327
328 if self.bang {
330 result.exit_code = if result.exit_code == 0 { 1 } else { 0 };
331 }
332
333 shell.last_exit_status = result.exit_code;
335
336 if let Some(timed) = &self.timed {
338 if let Some(stderr) = params.open_files.stderr() {
339 let timing = stopwatch.unwrap().stop()?;
340
341 match timed {
342 ast::PipelineTimed::Timed => {
343 std::write!(
344 stderr.to_owned(),
345 "\nreal\t{}\nuser\t{}\nsys\t{}\n",
346 timing::format_duration_non_posixly(&timing.wall),
347 timing::format_duration_non_posixly(&timing.user),
348 timing::format_duration_non_posixly(&timing.system),
349 )?;
350 }
351 ast::PipelineTimed::TimedWithPosixOutput => {
352 std::write!(
353 stderr.to_owned(),
354 "real {}\nuser {}\nsys {}\n",
355 timing::format_duration_posixly(&timing.wall),
356 timing::format_duration_posixly(&timing.user),
357 timing::format_duration_posixly(&timing.system),
358 )?;
359 }
360 }
361 }
362 }
363
364 Ok(result)
365 }
366}
367
368async fn spawn_pipeline_processes(
369 pipeline: &ast::Pipeline,
370 shell: &mut Shell,
371 params: &ExecutionParameters,
372) -> Result<VecDeque<CommandSpawnResult>, error::Error> {
373 let pipeline_len = pipeline.seq.len();
374 let mut output_pipes = vec![];
375 let mut spawn_results = VecDeque::new();
376 let mut process_group_id: Option<i32> = None;
377
378 for (current_pipeline_index, command) in pipeline.seq.iter().enumerate() {
379 let run_in_current_shell = pipeline_len == 1
388 || (current_pipeline_index == pipeline_len - 1
389 && shell.options.run_last_pipeline_cmd_in_current_shell
390 && !shell.options.enable_job_control);
391
392 if !run_in_current_shell {
393 let mut subshell = shell.clone();
394 let mut pipeline_context = PipelineExecutionContext {
395 shell: &mut subshell,
396 current_pipeline_index,
397 pipeline_len,
398 output_pipes: &mut output_pipes,
399 process_group_id,
400 };
401
402 let mut cmd_params = params.clone();
403
404 if current_pipeline_index > 0 {
406 cmd_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
407 }
408
409 spawn_results.push_back(
410 command
411 .execute_in_pipeline(&mut pipeline_context, cmd_params)
412 .await?,
413 );
414 process_group_id = pipeline_context.process_group_id;
415 } else {
416 let mut pipeline_context = PipelineExecutionContext {
417 shell,
418 current_pipeline_index,
419 pipeline_len,
420 output_pipes: &mut output_pipes,
421 process_group_id,
422 };
423
424 spawn_results.push_back(
425 command
426 .execute_in_pipeline(&mut pipeline_context, params.clone())
427 .await?,
428 );
429 process_group_id = pipeline_context.process_group_id;
430 }
431 }
432
433 Ok(spawn_results)
434}
435
436async fn wait_for_pipeline_processes_and_update_status(
437 pipeline: &ast::Pipeline,
438 mut process_spawn_results: VecDeque<CommandSpawnResult>,
439 shell: &mut Shell,
440) -> Result<ExecutionResult, error::Error> {
441 let mut result = ExecutionResult::success();
442 let mut stopped_children = vec![];
443
444 shell.last_pipeline_statuses.clear();
446
447 while let Some(child) = process_spawn_results.pop_front() {
448 match child.wait(!stopped_children.is_empty()).await? {
449 commands::CommandWaitResult::CommandCompleted(current_result) => {
450 result = current_result;
451 shell.last_exit_status = result.exit_code;
452 shell.last_pipeline_statuses.push(result.exit_code);
453 }
454 commands::CommandWaitResult::CommandStopped(current_result, child) => {
455 result = current_result;
456 shell.last_exit_status = result.exit_code;
457 shell.last_pipeline_statuses.push(result.exit_code);
458
459 stopped_children.push(jobs::JobTask::External(child));
460 }
461 }
462 }
463
464 if shell.options.interactive {
465 sys::terminal::move_self_to_foreground()?;
466 }
467
468 if !stopped_children.is_empty() {
471 let job = shell.jobs.add_as_current(jobs::Job::new(
472 stopped_children,
473 pipeline.to_string(),
474 jobs::JobState::Stopped,
475 ));
476
477 let formatted = job.to_string();
478
479 writeln!(shell.stderr(), "\r{formatted}")?;
481 }
482
483 Ok(result)
484}
485
486#[async_trait::async_trait]
487impl ExecuteInPipeline for ast::Command {
488 async fn execute_in_pipeline(
489 &self,
490 pipeline_context: &mut PipelineExecutionContext,
491 mut params: ExecutionParameters,
492 ) -> Result<CommandSpawnResult, error::Error> {
493 if pipeline_context.shell.options.do_not_execute_commands {
494 return Ok(CommandSpawnResult::ImmediateExit(0));
495 }
496
497 match self {
498 ast::Command::Simple(simple) => {
499 simple.execute_in_pipeline(pipeline_context, params).await
500 }
501 ast::Command::Compound(compound, redirects) => {
502 setup_pipeline_redirection(&mut params.open_files, pipeline_context)?;
504
505 if let Some(redirects) = redirects {
507 for redirect in &redirects.0 {
508 setup_redirect(pipeline_context.shell, &mut params, redirect).await?;
509 }
510 }
511
512 let result = compound.execute(pipeline_context.shell, ¶ms).await?;
513 if result.exit_shell {
514 Ok(CommandSpawnResult::ExitShell(result.exit_code))
515 } else if result.return_from_function_or_script {
516 Ok(CommandSpawnResult::ReturnFromFunctionOrScript(
517 result.exit_code,
518 ))
519 } else if let Some(count) = result.break_loop {
520 Ok(CommandSpawnResult::BreakLoop(count))
521 } else if let Some(count) = result.continue_loop {
522 Ok(CommandSpawnResult::ContinueLoop(count))
523 } else {
524 Ok(CommandSpawnResult::ImmediateExit(result.exit_code))
525 }
526 }
527 ast::Command::Function(func) => {
528 let result = func.execute(pipeline_context.shell, ¶ms).await?;
529 Ok(CommandSpawnResult::ImmediateExit(result.exit_code))
530 }
531 ast::Command::ExtendedTest(e) => {
532 let result =
533 if extendedtests::eval_extended_test_expr(e, pipeline_context.shell, ¶ms)
534 .await?
535 {
536 0
537 } else {
538 1
539 };
540 Ok(CommandSpawnResult::ImmediateExit(result))
541 }
542 }
543 }
544}
545
546enum WhileOrUntil {
547 While,
548 Until,
549}
550
551#[async_trait::async_trait]
552impl Execute for ast::CompoundCommand {
553 async fn execute(
554 &self,
555 shell: &mut Shell,
556 params: &ExecutionParameters,
557 ) -> Result<ExecutionResult, error::Error> {
558 match self {
559 ast::CompoundCommand::BraceGroup(ast::BraceGroupCommand(g)) => {
560 g.execute(shell, params).await
561 }
562 ast::CompoundCommand::Subshell(ast::SubshellCommand(s)) => {
563 let mut subshell = shell.clone();
565 let subshell_result = s.execute(&mut subshell, params).await?;
566
567 Ok(ExecutionResult::new(subshell_result.exit_code))
570 }
571 ast::CompoundCommand::ForClause(f) => f.execute(shell, params).await,
572 ast::CompoundCommand::CaseClause(c) => c.execute(shell, params).await,
573 ast::CompoundCommand::IfClause(i) => i.execute(shell, params).await,
574 ast::CompoundCommand::WhileClause(w) => {
575 (WhileOrUntil::While, w).execute(shell, params).await
576 }
577 ast::CompoundCommand::UntilClause(u) => {
578 (WhileOrUntil::Until, u).execute(shell, params).await
579 }
580 ast::CompoundCommand::Arithmetic(a) => a.execute(shell, params).await,
581 ast::CompoundCommand::ArithmeticForClause(a) => a.execute(shell, params).await,
582 }
583 }
584}
585
586#[async_trait::async_trait]
587impl Execute for ast::ForClauseCommand {
588 async fn execute(
589 &self,
590 shell: &mut Shell,
591 params: &ExecutionParameters,
592 ) -> Result<ExecutionResult, error::Error> {
593 let mut result = ExecutionResult::success();
594
595 let mut expanded_values = vec![];
598 if let Some(unexpanded_values) = &self.values {
599 for value in unexpanded_values {
600 let mut expanded =
601 expansion::full_expand_and_split_word(shell, params, value).await?;
602 expanded_values.append(&mut expanded);
603 }
604 } else {
605 expanded_values.extend_from_slice(&shell.positional_parameters);
607 }
608
609 for value in expanded_values {
610 if shell.options.print_commands_and_arguments {
611 if let Some(unexpanded_values) = &self.values {
612 shell
613 .trace_command(std::format!(
614 "for {} in {}",
615 self.variable_name,
616 unexpanded_values.iter().join(" ")
617 ))
618 .await?;
619 } else {
620 shell
621 .trace_command(std::format!("for {}", self.variable_name,))
622 .await?;
623 }
624 }
625
626 shell.env.update_or_add(
628 &self.variable_name,
629 ShellValueLiteral::Scalar(value),
630 |_| Ok(()),
631 EnvironmentLookup::Anywhere,
632 EnvironmentScope::Global,
633 )?;
634
635 result = self.body.0.execute(shell, params).await?;
636 if result.exit_shell || result.return_from_function_or_script {
637 break;
638 }
639
640 if let Some(continue_count) = &result.continue_loop {
641 if *continue_count == 0 {
642 result.continue_loop = None;
643 } else {
644 result.continue_loop = Some(*continue_count - 1);
645 break;
646 }
647 }
648 if let Some(break_count) = &result.break_loop {
649 if *break_count == 0 {
650 result.break_loop = None;
651 } else {
652 result.break_loop = Some(*break_count - 1);
653 }
654 break;
655 }
656 }
657
658 shell.last_exit_status = result.exit_code;
659 Ok(result)
660 }
661}
662
663#[async_trait::async_trait]
664impl Execute for ast::CaseClauseCommand {
665 async fn execute(
666 &self,
667 shell: &mut Shell,
668 params: &ExecutionParameters,
669 ) -> Result<ExecutionResult, error::Error> {
670 if shell.options.print_commands_and_arguments {
673 shell
674 .trace_command(std::format!("case {} in", &self.value))
675 .await?;
676 }
677
678 let expanded_value = expansion::basic_expand_word(shell, params, &self.value).await?;
679 let mut result: ExecutionResult = ExecutionResult::success();
680 let mut force_execute_next_case = false;
681
682 for case in &self.cases {
683 if force_execute_next_case {
684 force_execute_next_case = false;
685 } else {
686 let mut matches = false;
687 for pattern in &case.patterns {
688 let expanded_pattern = expansion::basic_expand_pattern(shell, params, pattern)
689 .await?
690 .set_extended_globbing(shell.options.extended_globbing)
691 .set_case_insensitive(shell.options.case_insensitive_conditionals);
692
693 if expanded_pattern.exactly_matches(expanded_value.as_str())? {
694 matches = true;
695 break;
696 }
697 }
698
699 if !matches {
700 continue;
701 }
702 }
703
704 result = if let Some(case_cmd) = &case.cmd {
705 case_cmd.execute(shell, params).await?
706 } else {
707 ExecutionResult::success()
708 };
709
710 match case.post_action {
711 ast::CaseItemPostAction::ExitCase => break,
712 ast::CaseItemPostAction::UnconditionallyExecuteNextCaseItem => {
713 force_execute_next_case = true;
714 }
715 ast::CaseItemPostAction::ContinueEvaluatingCases => (),
716 }
717 }
718
719 shell.last_exit_status = result.exit_code;
720
721 Ok(result)
722 }
723}
724
725#[async_trait::async_trait]
726impl Execute for ast::IfClauseCommand {
727 async fn execute(
728 &self,
729 shell: &mut Shell,
730 params: &ExecutionParameters,
731 ) -> Result<ExecutionResult, error::Error> {
732 let condition = self.condition.execute(shell, params).await?;
733
734 if condition.is_success() {
735 return self.then.execute(shell, params).await;
736 }
737
738 if let Some(elses) = &self.elses {
739 for else_clause in elses {
740 match &else_clause.condition {
741 Some(else_condition) => {
742 let else_condition_result = else_condition.execute(shell, params).await?;
743 if else_condition_result.is_success() {
744 return else_clause.body.execute(shell, params).await;
745 }
746 }
747 None => {
748 return else_clause.body.execute(shell, params).await;
749 }
750 }
751 }
752 }
753
754 let result = ExecutionResult::success();
755 shell.last_exit_status = result.exit_code;
756
757 Ok(result)
758 }
759}
760
761#[async_trait::async_trait]
762impl Execute for (WhileOrUntil, &ast::WhileOrUntilClauseCommand) {
763 async fn execute(
764 &self,
765 shell: &mut Shell,
766 params: &ExecutionParameters,
767 ) -> Result<ExecutionResult, error::Error> {
768 let is_while = match self.0 {
769 WhileOrUntil::While => true,
770 WhileOrUntil::Until => false,
771 };
772 let test_condition = &self.1 .0;
773 let body = &self.1 .1;
774
775 let mut result = ExecutionResult::success();
776
777 loop {
778 let condition_result = test_condition.execute(shell, params).await?;
779
780 if condition_result.exit_shell || condition_result.return_from_function_or_script {
781 result.exit_code = condition_result.exit_code;
782 result.exit_shell = condition_result.exit_shell;
783 result.return_from_function_or_script =
784 condition_result.return_from_function_or_script;
785 break;
786 }
787
788 if condition_result.is_success() != is_while {
789 break;
790 }
791
792 result = body.0.execute(shell, params).await?;
793 if result.exit_shell || result.return_from_function_or_script {
794 break;
795 }
796
797 if let Some(continue_count) = &result.continue_loop {
798 if *continue_count == 0 {
799 result.continue_loop = None;
800 } else {
801 result.continue_loop = Some(*continue_count - 1);
802 break;
803 }
804 }
805 if let Some(break_count) = &result.break_loop {
806 if *break_count == 0 {
807 result.break_loop = None;
808 } else {
809 result.break_loop = Some(*break_count - 1);
810 }
811 break;
812 }
813 }
814
815 Ok(result)
816 }
817}
818
819#[async_trait::async_trait]
820impl Execute for ast::ArithmeticCommand {
821 async fn execute(
822 &self,
823 shell: &mut Shell,
824 params: &ExecutionParameters,
825 ) -> Result<ExecutionResult, error::Error> {
826 let value = self.expr.eval(shell, params, true).await?;
827 let result = if value != 0 {
828 ExecutionResult::success()
829 } else {
830 ExecutionResult::new(1)
831 };
832
833 shell.last_exit_status = result.exit_code;
834
835 Ok(result)
836 }
837}
838
839#[async_trait::async_trait]
840impl Execute for ast::ArithmeticForClauseCommand {
841 async fn execute(
842 &self,
843 shell: &mut Shell,
844 params: &ExecutionParameters,
845 ) -> Result<ExecutionResult, error::Error> {
846 let mut result = ExecutionResult::success();
847 if let Some(initializer) = &self.initializer {
848 initializer.eval(shell, params, true).await?;
849 }
850
851 loop {
852 if let Some(condition) = &self.condition {
853 if condition.eval(shell, params, true).await? == 0 {
854 break;
855 }
856 }
857
858 result = self.body.0.execute(shell, params).await?;
859 if result.exit_shell || result.return_from_function_or_script {
860 break;
861 }
862
863 if let Some(continue_count) = &result.continue_loop {
864 if *continue_count == 0 {
865 result.continue_loop = None;
866 } else {
867 result.continue_loop = Some(*continue_count - 1);
868 break;
869 }
870 }
871 if let Some(break_count) = &result.break_loop {
872 if *break_count == 0 {
873 result.break_loop = None;
874 } else {
875 result.break_loop = Some(*break_count - 1);
876 }
877 break;
878 }
879
880 if let Some(updater) = &self.updater {
881 updater.eval(shell, params, true).await?;
882 }
883 }
884
885 shell.last_exit_status = result.exit_code;
886 Ok(result)
887 }
888}
889
890#[async_trait::async_trait]
891impl Execute for ast::FunctionDefinition {
892 async fn execute(
893 &self,
894 shell: &mut Shell,
895 _params: &ExecutionParameters,
896 ) -> Result<ExecutionResult, error::Error> {
897 shell
898 .funcs
899 .update(self.fname.clone(), Arc::new(self.clone()));
900
901 let result = ExecutionResult::success();
902 shell.last_exit_status = result.exit_code;
903
904 Ok(result)
905 }
906}
907
908#[async_trait::async_trait]
909impl ExecuteInPipeline for ast::SimpleCommand {
910 #[allow(clippy::too_many_lines)] async fn execute_in_pipeline(
912 &self,
913 context: &mut PipelineExecutionContext,
914 mut params: ExecutionParameters,
915 ) -> Result<CommandSpawnResult, error::Error> {
916 let default_prefix = ast::CommandPrefix::default();
917 let prefix_items = self.prefix.as_ref().unwrap_or(&default_prefix);
918
919 let default_suffix = ast::CommandSuffix::default();
920 let suffix_items = self.suffix.as_ref().unwrap_or(&default_suffix);
921
922 let mut cmd_name_items = vec![];
923 if let Some(cmd_name) = &self.word_or_name {
924 cmd_name_items.push(CommandPrefixOrSuffixItem::Word(cmd_name.clone()));
925 }
926
927 let mut assignments = vec![];
928 let mut args: Vec<CommandArg> = vec![];
929 let mut invoking_declaration_builtin = false;
930
931 setup_pipeline_redirection(&mut params.open_files, context)?;
933
934 for item in prefix_items
935 .0
936 .iter()
937 .chain(cmd_name_items.iter())
938 .chain(suffix_items.0.iter())
939 {
940 match item {
941 CommandPrefixOrSuffixItem::IoRedirect(redirect) => {
942 match setup_redirect(context.shell, &mut params, redirect).await {
943 Err(e) => {
944 writeln!(params.stderr(), "error: {e}")?;
945 return Ok(CommandSpawnResult::ImmediateExit(1));
946 }
947 Ok(None) => {
948 return Ok(CommandSpawnResult::ImmediateExit(1));
950 }
951 Ok(_) => (),
952 }
953 }
954 CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => {
955 let (installed_fd_num, substitution_file) = setup_process_substitution(
956 context.shell,
957 &mut params,
958 kind,
959 subshell_command,
960 )?;
961
962 params
963 .open_files
964 .files
965 .insert(installed_fd_num, substitution_file);
966
967 args.push(CommandArg::String(std::format!(
968 "/dev/fd/{installed_fd_num}"
969 )));
970 }
971 CommandPrefixOrSuffixItem::AssignmentWord(assignment, word) => {
972 if args.is_empty() {
973 assignments.push(assignment);
976 } else {
977 if invoking_declaration_builtin {
978 let expanded =
982 expand_assignment(context.shell, ¶ms, assignment).await?;
983 args.push(CommandArg::Assignment(expanded));
984 } else {
985 let mut next_args =
989 expansion::full_expand_and_split_word(context.shell, ¶ms, word)
990 .await?
991 .into_iter()
992 .map(CommandArg::String)
993 .collect();
994 args.append(&mut next_args);
995 }
996 }
997 }
998 CommandPrefixOrSuffixItem::Word(arg) => {
999 let mut next_args =
1000 expansion::full_expand_and_split_word(context.shell, ¶ms, arg).await?;
1001
1002 if args.is_empty() {
1003 if let Some(cmd_name) = next_args.first() {
1004 if let Some(alias_value) = context.shell.aliases.get(cmd_name.as_str())
1005 {
1006 let mut alias_pieces: Vec<_> = alias_value
1011 .split_ascii_whitespace()
1012 .map(|i| i.to_owned())
1013 .collect();
1014
1015 next_args.remove(0);
1016 alias_pieces.append(&mut next_args);
1017
1018 next_args = alias_pieces;
1019 }
1020
1021 if context
1025 .shell
1026 .builtins
1027 .get(next_args[0].as_str())
1028 .is_some_and(|r| r.declaration_builtin)
1029 {
1030 invoking_declaration_builtin = true;
1031 }
1032 }
1033 }
1034
1035 let mut next_args = next_args.into_iter().map(CommandArg::String).collect();
1036 args.append(&mut next_args);
1037 }
1038 }
1039 }
1040
1041 if let Some(CommandArg::String(cmd_name)) = args.first().cloned() {
1043 context.shell.env.push_scope(EnvironmentScope::Command);
1047 for assignment in &assignments {
1048 apply_assignment(
1050 assignment,
1051 context.shell,
1052 ¶ms,
1053 true,
1054 Some(EnvironmentScope::Command),
1055 EnvironmentScope::Command,
1056 )
1057 .await?;
1058 }
1059
1060 if context.shell.options.print_commands_and_arguments {
1061 context
1062 .shell
1063 .trace_command(args.iter().map(|arg| arg.quote_for_tracing()).join(" "))
1064 .await?;
1065 }
1066
1067 if context.shell.traps.handler_depth == 0 {
1070 let debug_trap_handler = context
1071 .shell
1072 .traps
1073 .handlers
1074 .get(&traps::TrapSignal::Debug)
1075 .cloned();
1076 if let Some(debug_trap_handler) = debug_trap_handler {
1077 let handler_params = ExecutionParameters {
1080 open_files: params.open_files.clone(),
1081 process_group_policy: ProcessGroupPolicy::SameProcessGroup,
1082 };
1083
1084 let full_cmd = args.iter().map(|arg| arg.to_string()).join(" ");
1085
1086 context.shell.env.update_or_add(
1088 "BASH_COMMAND",
1089 ShellValueLiteral::Scalar(full_cmd),
1090 |_| Ok(()),
1091 EnvironmentLookup::Anywhere,
1092 EnvironmentScope::Global,
1093 )?;
1094
1095 context.shell.traps.handler_depth += 1;
1096
1097 let _ = context
1099 .shell
1100 .run_string(debug_trap_handler, &handler_params)
1101 .await?;
1102
1103 context.shell.traps.handler_depth -= 1;
1104 }
1105 }
1106
1107 let cmd_context = commands::ExecutionContext {
1108 shell: context.shell,
1109 command_name: cmd_name,
1110 params,
1111 };
1112
1113 let execution_result = commands::execute(
1115 cmd_context,
1116 &mut context.process_group_id,
1117 args,
1118 true, None,
1120 )
1121 .await;
1122
1123 context.shell.env.pop_scope(EnvironmentScope::Command)?;
1126
1127 execution_result
1128 } else {
1129 context.shell.last_exit_status = 0;
1131
1132 for assignment in assignments {
1134 apply_assignment(
1135 assignment,
1136 context.shell,
1137 ¶ms,
1138 false,
1139 None,
1140 EnvironmentScope::Global,
1141 )
1142 .await?;
1143 }
1144
1145 Ok(CommandSpawnResult::ImmediateExit(
1148 context.shell.last_exit_status,
1149 ))
1150 }
1151 }
1152}
1153
1154async fn expand_assignment(
1155 shell: &mut Shell,
1156 params: &ExecutionParameters,
1157 assignment: &ast::Assignment,
1158) -> Result<ast::Assignment, error::Error> {
1159 let value = expand_assignment_value(shell, params, &assignment.value).await?;
1160 Ok(ast::Assignment {
1161 name: basic_expand_assignment_name(shell, params, &assignment.name).await?,
1162 value,
1163 append: assignment.append,
1164 })
1165}
1166
1167async fn basic_expand_assignment_name(
1168 shell: &mut Shell,
1169 params: &ExecutionParameters,
1170 name: &ast::AssignmentName,
1171) -> Result<ast::AssignmentName, error::Error> {
1172 match name {
1173 ast::AssignmentName::VariableName(name) => {
1174 let expanded = expansion::basic_expand_str(shell, params, name).await?;
1175 Ok(ast::AssignmentName::VariableName(expanded))
1176 }
1177 ast::AssignmentName::ArrayElementName(name, index) => {
1178 let expanded_name = expansion::basic_expand_str(shell, params, name).await?;
1179 let expanded_index = expansion::basic_expand_str(shell, params, index).await?;
1180 Ok(ast::AssignmentName::ArrayElementName(
1181 expanded_name,
1182 expanded_index,
1183 ))
1184 }
1185 }
1186}
1187
1188async fn expand_assignment_value(
1189 shell: &mut Shell,
1190 params: &ExecutionParameters,
1191 value: &ast::AssignmentValue,
1192) -> Result<ast::AssignmentValue, error::Error> {
1193 let expanded = match value {
1194 ast::AssignmentValue::Scalar(s) => {
1195 let expanded_word = expansion::basic_expand_word(shell, params, s).await?;
1196 ast::AssignmentValue::Scalar(ast::Word {
1197 value: expanded_word,
1198 })
1199 }
1200 ast::AssignmentValue::Array(arr) => {
1201 let mut expanded_values = vec![];
1202 for (key, value) in arr {
1203 if let Some(k) = key {
1204 let expanded_key = expansion::basic_expand_word(shell, params, k).await?.into();
1205 let expanded_value = expansion::basic_expand_word(shell, params, value)
1206 .await?
1207 .into();
1208 expanded_values.push((Some(expanded_key), expanded_value));
1209 } else {
1210 let split_expanded_value =
1211 expansion::full_expand_and_split_word(shell, params, value).await?;
1212 for expanded_value in split_expanded_value {
1213 expanded_values.push((None, expanded_value.into()));
1214 }
1215 }
1216 }
1217
1218 ast::AssignmentValue::Array(expanded_values)
1219 }
1220 };
1221
1222 Ok(expanded)
1223}
1224
1225#[allow(clippy::too_many_lines)]
1226async fn apply_assignment(
1227 assignment: &ast::Assignment,
1228 shell: &mut Shell,
1229 params: &ExecutionParameters,
1230 mut export: bool,
1231 required_scope: Option<EnvironmentScope>,
1232 creation_scope: EnvironmentScope,
1233) -> Result<(), error::Error> {
1234 let mut array_index;
1237 let variable_name = match &assignment.name {
1238 ast::AssignmentName::VariableName(name) => {
1239 array_index = None;
1240 name
1241 }
1242 ast::AssignmentName::ArrayElementName(name, index) => {
1243 let expanded = expansion::basic_expand_str(shell, params, index).await?;
1244 array_index = Some(expanded);
1245 name
1246 }
1247 };
1248
1249 let new_value = match &assignment.value {
1251 ast::AssignmentValue::Scalar(unexpanded_value) => {
1252 let value = expansion::basic_expand_word(shell, params, unexpanded_value).await?;
1253 ShellValueLiteral::Scalar(value)
1254 }
1255 ast::AssignmentValue::Array(unexpanded_values) => {
1256 let mut elements = vec![];
1257 for (unexpanded_key, unexpanded_value) in unexpanded_values {
1258 let key = match unexpanded_key {
1259 Some(unexpanded_key) => {
1260 Some(expansion::basic_expand_word(shell, params, unexpanded_key).await?)
1261 }
1262 None => None,
1263 };
1264
1265 if key.is_some() {
1266 let value =
1267 expansion::basic_expand_word(shell, params, unexpanded_value).await?;
1268 elements.push((key, value));
1269 } else {
1270 let values =
1271 expansion::full_expand_and_split_word(shell, params, unexpanded_value)
1272 .await?;
1273 for value in values {
1274 elements.push((None, value));
1275 }
1276 }
1277 }
1278 ShellValueLiteral::Array(ArrayLiteral(elements))
1279 }
1280 };
1281
1282 if shell.options.print_commands_and_arguments {
1283 let op = if assignment.append { "+=" } else { "=" };
1284 shell
1285 .trace_command(std::format!("{}{op}{new_value}", assignment.name))
1286 .await?;
1287 }
1288
1289 if let Some(idx) = &array_index {
1291 let will_be_indexed_array = if let Some((_, existing_value)) = shell.env.get(variable_name)
1292 {
1293 matches!(
1294 existing_value.value(),
1295 ShellValue::IndexedArray(_) | ShellValue::Unset(ShellValueUnsetType::IndexedArray)
1296 )
1297 } else {
1298 true
1299 };
1300
1301 if will_be_indexed_array {
1302 array_index = Some(
1303 arithmetic::expand_and_eval(shell, params, idx.as_str(), false)
1304 .await?
1305 .to_string(),
1306 );
1307 }
1308 }
1309
1310 if let Some((existing_value_scope, existing_value)) = shell.env.get_mut(variable_name.as_str())
1312 {
1313 if required_scope.is_none() || Some(existing_value_scope) == required_scope {
1314 if let Some(array_index) = array_index {
1315 match new_value {
1316 ShellValueLiteral::Scalar(s) => {
1317 existing_value.assign_at_index(array_index, s, assignment.append)?;
1318 }
1319 ShellValueLiteral::Array(_) => {
1320 return error::unimp("replacing an array item with an array");
1321 }
1322 }
1323 } else {
1324 if !export
1325 && shell.options.export_variables_on_modification
1326 && !matches!(new_value, ShellValueLiteral::Array(_))
1327 {
1328 export = true;
1329 }
1330
1331 existing_value.assign(new_value, assignment.append)?;
1332 }
1333
1334 if export {
1335 existing_value.export();
1336 }
1337
1338 return Ok(());
1340 }
1341 }
1342
1343 let new_value = if let Some(array_index) = array_index {
1345 match new_value {
1346 ShellValueLiteral::Scalar(s) => {
1347 ShellValue::indexed_array_from_literals(ArrayLiteral(vec![(Some(array_index), s)]))
1348 }
1349 ShellValueLiteral::Array(_) => {
1350 return error::unimp("cannot assign list to array member");
1351 }
1352 }
1353 } else {
1354 match new_value {
1355 ShellValueLiteral::Scalar(s) => {
1356 export = export || shell.options.export_variables_on_modification;
1357 ShellValue::String(s)
1358 }
1359 ShellValueLiteral::Array(values) => ShellValue::indexed_array_from_literals(values),
1360 }
1361 };
1362
1363 let mut new_var = ShellVariable::new(new_value);
1364
1365 if export {
1366 new_var.export();
1367 }
1368
1369 shell.env.add(variable_name, new_var, creation_scope)
1370}
1371
1372fn setup_pipeline_redirection(
1373 open_files: &mut OpenFiles,
1374 context: &mut PipelineExecutionContext<'_>,
1375) -> Result<(), error::Error> {
1376 if context.current_pipeline_index > 0 {
1377 if let Some(preceding_output_reader) = context.output_pipes.pop() {
1379 open_files
1381 .files
1382 .insert(0, OpenFile::PipeReader(preceding_output_reader));
1383 } else {
1384 open_files.files.insert(0, OpenFile::Null);
1385 }
1386 }
1387
1388 if context.pipeline_len > 1 && context.current_pipeline_index < context.pipeline_len - 1 {
1391 let (reader, writer) = sys::pipes::pipe()?;
1393 context.output_pipes.push(reader);
1394 open_files.files.insert(1, OpenFile::PipeWriter(writer));
1395 }
1396
1397 Ok(())
1398}
1399
1400#[allow(clippy::too_many_lines)]
1401pub(crate) async fn setup_redirect(
1402 shell: &mut Shell,
1403 params: &'_ mut ExecutionParameters,
1404 redirect: &ast::IoRedirect,
1405) -> Result<Option<u32>, error::Error> {
1406 match redirect {
1407 ast::IoRedirect::OutputAndError(f, append) => {
1408 let mut expanded_fields =
1409 expansion::full_expand_and_split_word(shell, params, f).await?;
1410 if expanded_fields.len() != 1 {
1411 return Err(error::Error::InvalidRedirection);
1412 }
1413
1414 let expanded_file_path: PathBuf =
1415 shell.get_absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1416
1417 let opened_file = std::fs::File::options()
1418 .create(true)
1419 .write(true)
1420 .truncate(!*append)
1421 .append(*append)
1422 .open(expanded_file_path.as_path())
1423 .map_err(|err| {
1424 error::Error::RedirectionFailure(
1425 expanded_file_path.to_string_lossy().to_string(),
1426 err,
1427 )
1428 })?;
1429
1430 let stdout_file = OpenFile::File(opened_file);
1431 let stderr_file = stdout_file.try_dup()?;
1432
1433 params.open_files.files.insert(1, stdout_file);
1434 params.open_files.files.insert(2, stderr_file);
1435
1436 Ok(Some(1))
1437 }
1438 ast::IoRedirect::File(specified_fd_num, kind, target) => {
1439 let fd_num;
1440 let target_file;
1441 match target {
1442 ast::IoFileRedirectTarget::Filename(f) => {
1443 let mut options = std::fs::File::options();
1444
1445 let mut expanded_fields =
1446 expansion::full_expand_and_split_word(shell, params, f).await?;
1447
1448 if expanded_fields.len() != 1 {
1449 return Err(error::Error::InvalidRedirection);
1450 }
1451
1452 let expanded_file_path: PathBuf =
1453 shell.get_absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1454
1455 let default_fd_if_unspecified = get_default_fd_for_redirect_kind(kind);
1456 match kind {
1457 ast::IoFileRedirectKind::Read => {
1458 options.read(true);
1459 }
1460 ast::IoFileRedirectKind::Write => {
1461 if shell
1462 .options
1463 .disallow_overwriting_regular_files_via_output_redirection
1464 {
1465 if !expanded_file_path.is_file() {
1468 options.create(true);
1469 } else {
1470 options.create_new(true);
1471 }
1472 options.write(true);
1473 } else {
1474 options.create(true);
1475 options.write(true);
1476 options.truncate(true);
1477 }
1478 }
1479 ast::IoFileRedirectKind::Append => {
1480 options.create(true);
1481 options.append(true);
1482 }
1483 ast::IoFileRedirectKind::ReadAndWrite => {
1484 options.create(true);
1485 options.read(true);
1486 options.write(true);
1487 }
1488 ast::IoFileRedirectKind::Clobber => {
1489 options.create(true);
1490 options.write(true);
1491 options.truncate(true);
1492 }
1493 ast::IoFileRedirectKind::DuplicateInput => {
1494 options.read(true);
1495 }
1496 ast::IoFileRedirectKind::DuplicateOutput => {
1497 options.create(true);
1498 options.write(true);
1499 }
1500 }
1501
1502 fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1503
1504 let opened_file =
1505 options.open(expanded_file_path.as_path()).map_err(|err| {
1506 error::Error::RedirectionFailure(
1507 expanded_file_path.to_string_lossy().to_string(),
1508 err,
1509 )
1510 })?;
1511 target_file = OpenFile::File(opened_file);
1512 }
1513 ast::IoFileRedirectTarget::Fd(fd) => {
1514 let default_fd_if_unspecified = match kind {
1515 ast::IoFileRedirectKind::DuplicateInput => 0,
1516 ast::IoFileRedirectKind::DuplicateOutput => 1,
1517 _ => {
1518 return error::unimp("unexpected redirect kind");
1519 }
1520 };
1521
1522 fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1523
1524 if let Some(f) = params.open_files.files.get(fd) {
1525 target_file = f.try_dup()?;
1526 } else {
1527 tracing::error!("{}: Bad file descriptor", fd);
1528 return Ok(None);
1529 }
1530 }
1531 ast::IoFileRedirectTarget::ProcessSubstitution(substitution_kind, subshell_cmd) => {
1532 match kind {
1533 ast::IoFileRedirectKind::Read
1534 | ast::IoFileRedirectKind::Write
1535 | ast::IoFileRedirectKind::Append
1536 | ast::IoFileRedirectKind::ReadAndWrite
1537 | ast::IoFileRedirectKind::Clobber => {
1538 let (substitution_fd, substitution_file) = setup_process_substitution(
1539 shell,
1540 params,
1541 substitution_kind,
1542 subshell_cmd,
1543 )?;
1544
1545 target_file = substitution_file.try_dup()?;
1546 params
1547 .open_files
1548 .files
1549 .insert(substitution_fd, substitution_file);
1550
1551 fd_num = specified_fd_num
1552 .unwrap_or_else(|| get_default_fd_for_redirect_kind(kind));
1553 }
1554 _ => return error::unimp("invalid process substitution"),
1555 }
1556 }
1557 }
1558
1559 params.open_files.files.insert(fd_num, target_file);
1560 Ok(Some(fd_num))
1561 }
1562 ast::IoRedirect::HereDocument(fd_num, io_here) => {
1563 let fd_num = fd_num.unwrap_or(0);
1565
1566 let io_here_doc = if io_here.requires_expansion {
1568 expansion::basic_expand_word(shell, params, &io_here.doc).await?
1569 } else {
1570 io_here.doc.flatten()
1571 };
1572
1573 let f = setup_open_file_with_contents(io_here_doc.as_str())?;
1574
1575 params.open_files.files.insert(fd_num, f);
1576 Ok(Some(fd_num))
1577 }
1578 ast::IoRedirect::HereString(fd_num, word) => {
1579 let fd_num = fd_num.unwrap_or(0);
1581
1582 let mut expanded_word = expansion::basic_expand_word(shell, params, word).await?;
1583 expanded_word.push('\n');
1584
1585 let f = setup_open_file_with_contents(expanded_word.as_str())?;
1586
1587 params.open_files.files.insert(fd_num, f);
1588 Ok(Some(fd_num))
1589 }
1590 }
1591}
1592
1593fn get_default_fd_for_redirect_kind(kind: &ast::IoFileRedirectKind) -> u32 {
1594 match kind {
1595 ast::IoFileRedirectKind::Read => 0,
1596 ast::IoFileRedirectKind::Write => 1,
1597 ast::IoFileRedirectKind::Append => 1,
1598 ast::IoFileRedirectKind::ReadAndWrite => 0,
1599 ast::IoFileRedirectKind::Clobber => 1,
1600 ast::IoFileRedirectKind::DuplicateInput => 0,
1601 ast::IoFileRedirectKind::DuplicateOutput => 1,
1602 }
1603}
1604
1605fn setup_process_substitution(
1606 shell: &mut Shell,
1607 params: &mut ExecutionParameters,
1608 kind: &ast::ProcessSubstitutionKind,
1609 subshell_cmd: &ast::SubshellCommand,
1610) -> Result<(u32, OpenFile), error::Error> {
1611 let mut subshell = shell.clone();
1614
1615 let mut child_params = params.clone();
1617 child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
1618
1619 let (reader, writer) = sys::pipes::pipe()?;
1621
1622 let target_file = match kind {
1623 ast::ProcessSubstitutionKind::Read => {
1624 child_params
1625 .open_files
1626 .files
1627 .insert(1, openfiles::OpenFile::PipeWriter(writer));
1628 OpenFile::PipeReader(reader)
1629 }
1630 ast::ProcessSubstitutionKind::Write => {
1631 child_params
1632 .open_files
1633 .files
1634 .insert(0, openfiles::OpenFile::PipeReader(reader));
1635 OpenFile::PipeWriter(writer)
1636 }
1637 };
1638
1639 let subshell_cmd = subshell_cmd.to_owned();
1642 tokio::spawn(async move {
1643 let _ = subshell_cmd.0.execute(&mut subshell, &child_params).await;
1645 });
1646
1647 let mut candidate_fd_num = 63;
1650 while params.open_files.files.contains_key(&candidate_fd_num) {
1651 candidate_fd_num -= 1;
1652 if candidate_fd_num == 0 {
1653 return error::unimp("no available file descriptors");
1654 }
1655 }
1656
1657 Ok((candidate_fd_num, target_file))
1658}
1659
1660#[allow(unused_variables)]
1661fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Error> {
1662 let (reader, mut writer) = sys::pipes::pipe()?;
1663
1664 let bytes = contents.as_bytes();
1665 let len = i32::try_from(bytes.len())?;
1666
1667 #[cfg(target_os = "linux")]
1668 nix::fcntl::fcntl(
1669 reader.as_fd().as_raw_fd(),
1670 nix::fcntl::FcntlArg::F_SETPIPE_SZ(len),
1671 )?;
1672
1673 writer.write_all(bytes)?;
1674 drop(writer);
1675
1676 Ok(OpenFile::PipeReader(reader))
1677}