1use brush_parser::ast::{self, CommandPrefixOrSuffixItem};
2use itertools::Itertools;
3use std::collections::VecDeque;
4use std::io::Write;
5use std::path::{Path, PathBuf};
6
7use crate::arithmetic::{self, ExpandAndEvaluate};
8use crate::commands::{self, CommandArg};
9use crate::env::{EnvironmentLookup, EnvironmentScope};
10use crate::openfiles::{OpenFile, OpenFiles};
11use crate::results::{
12 ExecutionExitCode, ExecutionResult, ExecutionSpawnResult, ExecutionWaitResult,
13};
14use crate::shell::Shell;
15use crate::variables::{
16 ArrayLiteral, ShellValue, ShellValueLiteral, ShellValueUnsetType, ShellVariable,
17};
18use crate::{
19 ShellFd, error, expansion, extendedtests, extensions, ioutils, jobs, openfiles, sys, timing,
20};
21
22struct PipelineExecutionContext<'a, SE: extensions::ShellExtensions> {
24 shell: commands::ShellForCommand<'a, SE>,
26 process_group_id: Option<i32>,
28}
29
30#[derive(Clone, Default)]
32pub struct ExecutionParameters {
33 open_files: openfiles::OpenFiles,
35 pub process_group_policy: ProcessGroupPolicy,
37 pub suppress_errexit: bool,
40}
41
42impl ExecutionParameters {
43 pub fn stdin(
49 &self,
50 shell: &Shell<impl extensions::ShellExtensions>,
51 ) -> impl std::io::Read + 'static {
52 self.try_stdin(shell).unwrap_or_else(|| {
53 ioutils::FailingReaderWriter::new("standard input not available").into()
54 })
55 }
56
57 pub fn try_stdin(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Option<OpenFile> {
63 self.try_fd(shell, openfiles::OpenFiles::STDIN_FD)
64 }
65
66 pub fn stdout(
75 &self,
76 shell: &Shell<impl extensions::ShellExtensions>,
77 ) -> impl std::io::Write + 'static {
78 self.try_stdout(shell).unwrap_or_else(|| {
79 ioutils::FailingReaderWriter::new("standard output not available").into()
80 })
81 }
82
83 pub fn try_stdout(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Option<OpenFile> {
89 self.try_fd(shell, openfiles::OpenFiles::STDOUT_FD)
90 }
91
92 pub fn stderr(
100 &self,
101 shell: &Shell<impl extensions::ShellExtensions>,
102 ) -> impl std::io::Write + 'static {
103 self.try_stderr(shell).unwrap_or_else(|| {
104 ioutils::FailingReaderWriter::new("standard error not available").into()
105 })
106 }
107
108 pub fn try_stderr(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Option<OpenFile> {
114 self.try_fd(shell, openfiles::OpenFiles::STDERR_FD)
115 }
116
117 pub fn try_fd(
125 &self,
126 shell: &Shell<impl extensions::ShellExtensions>,
127 fd: ShellFd,
128 ) -> Option<openfiles::OpenFile> {
129 match self.open_files.fd_entry(fd) {
130 openfiles::OpenFileEntry::Open(f) => Some(f.clone()),
131 openfiles::OpenFileEntry::NotPresent => None,
132 openfiles::OpenFileEntry::NotSpecified => {
133 shell.persistent_open_files().try_fd(fd).cloned()
136 }
137 }
138 }
139
140 pub fn set_fd(&mut self, fd: ShellFd, file: openfiles::OpenFile) {
147 self.open_files.set_fd(fd, file);
148 }
149
150 pub fn iter_fds(
156 &self,
157 shell: &Shell<impl extensions::ShellExtensions>,
158 ) -> impl Iterator<Item = (ShellFd, openfiles::OpenFile)> {
159 let our_fds = self.open_files.iter_fds();
160 let shell_fds = shell
161 .persistent_open_files()
162 .iter_fds()
163 .filter(|(fd, _)| !self.open_files.contains_fd(*fd));
164
165 #[allow(clippy::needless_collect)]
166 let all_fds: Vec<_> = our_fds
167 .chain(shell_fds)
168 .map(|(fd, file)| (fd, file.clone()))
169 .collect();
170
171 all_fds.into_iter()
172 }
173}
174
175#[derive(Clone, Debug, Default)]
176pub enum ProcessGroupPolicy {
178 #[default]
180 NewProcessGroup,
181 SameProcessGroup,
183}
184
185#[async_trait::async_trait]
186pub trait Execute {
187 async fn execute(
188 &self,
189 shell: &mut Shell<impl extensions::ShellExtensions>,
190 params: &ExecutionParameters,
191 ) -> Result<ExecutionResult, error::Error>;
192}
193
194#[async_trait::async_trait]
195trait ExecuteInPipeline<SE: extensions::ShellExtensions> {
196 async fn execute_in_pipeline(
197 &self,
198 context: PipelineExecutionContext<'_, SE>,
199 params: ExecutionParameters,
200 ) -> Result<ExecutionSpawnResult, error::Error>;
201}
202
203#[async_trait::async_trait]
204impl Execute for ast::Program {
205 async fn execute(
206 &self,
207 shell: &mut Shell<impl extensions::ShellExtensions>,
208 params: &ExecutionParameters,
209 ) -> Result<ExecutionResult, error::Error> {
210 let mut result = ExecutionResult::success();
211
212 for command in &self.complete_commands {
213 match command.execute(shell, params).await {
217 Ok(exec_result) => result = exec_result,
218 Err(err) => {
219 let _ = shell.display_error(&mut params.stderr(shell), &err);
221 result = err.into_result(shell);
222 }
223 }
224
225 shell.set_last_exit_status(result.exit_code.into());
227
228 if !result.is_normal_flow() {
230 break;
231 }
232 }
233
234 Ok(result)
235 }
236}
237
238#[async_trait::async_trait]
239impl Execute for ast::CompoundList {
240 async fn execute(
241 &self,
242 shell: &mut Shell<impl extensions::ShellExtensions>,
243 params: &ExecutionParameters,
244 ) -> Result<ExecutionResult, error::Error> {
245 let mut result = ExecutionResult::success();
246
247 for ast::CompoundListItem(ao_list, sep) in &self.0 {
248 let run_async = matches!(sep, ast::SeparatorOperator::Async);
249
250 if run_async {
251 let job = spawn_ao_list_in_task(ao_list, shell, params);
255 let job_formatted = job.to_pid_style_string();
256
257 if shell.options().interactive && !shell.is_subshell() {
258 writeln!(params.stderr(shell), "{job_formatted}")?;
259 }
260
261 result = ExecutionResult::success();
262 } else {
263 result = ao_list.execute(shell, params).await?;
264
265 shell.set_last_exit_status(result.exit_code.into());
267 }
268
269 if !result.is_normal_flow() {
270 break;
271 }
272 }
273
274 Ok(result)
275 }
276}
277
278fn spawn_ao_list_in_task<'a, SE: extensions::ShellExtensions>(
279 ao_list: &ast::AndOrList,
280 shell: &'a mut Shell<SE>,
281 params: &ExecutionParameters,
282) -> &'a jobs::Job {
283 let mut cloned_shell = shell.clone();
285 let cloned_params = params.clone();
286 let cloned_ao_list = ao_list.clone();
287
288 cloned_shell.options_mut().interactive = false;
290
291 let join_handle = tokio::spawn(async move {
292 cloned_ao_list
293 .execute(&mut cloned_shell, &cloned_params)
294 .await
295 });
296
297 shell.jobs_mut().add_as_current(jobs::Job::new(
298 [jobs::JobTask::Internal(join_handle)],
299 ao_list.to_string(),
300 jobs::JobState::Running,
301 ))
302}
303
304#[async_trait::async_trait]
305impl Execute for ast::AndOrList {
306 async fn execute(
307 &self,
308 shell: &mut Shell<impl extensions::ShellExtensions>,
309 params: &ExecutionParameters,
310 ) -> Result<ExecutionResult, error::Error> {
311 let has_operators = !self.additional.is_empty();
312
313 let mut first_params = params.clone();
315 if has_operators {
316 first_params.suppress_errexit = true;
317 }
318
319 let mut result = self.first.execute(shell, &first_params).await?;
320
321 for (index, next_ao) in self.additional.iter().enumerate() {
322 if !result.is_normal_flow() {
324 break;
325 }
326
327 let (is_and, pipeline) = match next_ao {
328 ast::AndOr::And(p) => (true, p),
329 ast::AndOr::Or(p) => (false, p),
330 };
331
332 if is_and {
337 if !result.is_success() {
338 continue;
339 }
340 } else if result.is_success() {
341 continue;
342 }
343
344 let mut params = params.clone();
347
348 let is_last = index == self.additional.len() - 1;
349 if !is_last {
350 params.suppress_errexit = true;
351 }
352
353 result = pipeline.execute(shell, ¶ms).await?;
354 }
355
356 Ok(result)
357 }
358}
359
360#[async_trait::async_trait]
361impl Execute for ast::Pipeline {
362 async fn execute(
363 &self,
364 shell: &mut Shell<impl extensions::ShellExtensions>,
365 params: &ExecutionParameters,
366 ) -> Result<ExecutionResult, error::Error> {
367 let stopwatch = self
369 .timed
370 .is_some()
371 .then(timing::start_timing)
372 .transpose()?;
373
374 let mut params = params.clone();
375
376 if self.bang {
378 params.suppress_errexit = true;
379 }
380
381 let spawn_results = spawn_pipeline_processes(self, shell, ¶ms).await?;
384
385 let mut result =
387 wait_for_pipeline_processes_and_update_status(self, spawn_results, shell, ¶ms)
388 .await?;
389
390 if self.bang {
392 result.exit_code = ExecutionExitCode::from(if result.is_success() { 1 } else { 0 });
393 }
394
395 shell.set_last_exit_status(result.exit_code.into());
397
398 if !params.suppress_errexit && !self.bang {
400 shell.apply_errexit_if_enabled(&mut result);
401 }
402
403 if let (Some(timed), Some(stopwatch)) = (&self.timed, &stopwatch)
405 && let Some(mut stderr) = params.try_fd(shell, openfiles::OpenFiles::STDERR_FD)
406 {
407 let timing = stopwatch.stop()?;
408 if timed.is_posix_output() {
409 std::write!(
410 stderr,
411 "real {}\nuser {}\nsys {}\n",
412 timing::format_duration_posixly(&timing.wall),
413 timing::format_duration_posixly(&timing.user),
414 timing::format_duration_posixly(&timing.system),
415 )?;
416 } else {
417 std::write!(
418 stderr,
419 "\nreal\t{}\nuser\t{}\nsys\t{}\n",
420 timing::format_duration_non_posixly(&timing.wall),
421 timing::format_duration_non_posixly(&timing.user),
422 timing::format_duration_non_posixly(&timing.system),
423 )?;
424 }
425 }
426
427 Ok(result)
428 }
429}
430
431async fn spawn_pipeline_processes(
432 pipeline: &ast::Pipeline,
433 shell: &mut Shell<impl extensions::ShellExtensions>,
434 params: &ExecutionParameters,
435) -> Result<VecDeque<ExecutionSpawnResult>, error::Error> {
436 let pipeline_len = pipeline.seq.len();
437 let mut pipe_readers = vec![];
438 let mut pipe_writers = vec![];
439 let mut spawn_results = VecDeque::new();
440 let mut process_group_id: Option<i32> = None;
441
442 if pipeline_len > 1 {
445 for _ in 0..(pipeline_len - 1) {
446 let (reader, writer) = std::io::pipe()?;
447 pipe_readers.push(Some(reader.into()));
448 pipe_writers.push(Some(writer.into()));
449 }
450 pipe_readers.push(None);
453 }
454
455 for (current_pipeline_index, command) in pipeline.seq.iter().enumerate() {
456 let run_in_current_shell = pipeline_len == 1
465 || (current_pipeline_index == pipeline_len - 1
466 && shell.options().run_last_pipeline_cmd_in_current_shell
467 && !shell.options().enable_job_control);
468
469 let mut cmd_params = params.clone();
471
472 if let Some(Some(reader)) = pipe_readers.pop() {
474 cmd_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
475 }
476 if let Some(Some(writer)) = pipe_writers.pop() {
477 cmd_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
478 }
479
480 let pipeline_context = if !run_in_current_shell {
481 if current_pipeline_index > 0 {
483 cmd_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
484 }
485
486 PipelineExecutionContext {
487 shell: commands::ShellForCommand::OwnedShell {
488 target: Box::new(shell.clone()),
489 parent: shell,
490 },
491 process_group_id,
492 }
493 } else {
494 PipelineExecutionContext {
495 shell: commands::ShellForCommand::ParentShell(shell),
496 process_group_id,
497 }
498 };
499
500 let spawn_result = command
501 .execute_in_pipeline(pipeline_context, cmd_params)
502 .await?;
503
504 if let ExecutionSpawnResult::StartedProcess(child) = &spawn_result
506 && process_group_id.is_none()
507 {
508 process_group_id = child.pgid();
509 }
510
511 spawn_results.push_back(spawn_result);
512 }
513
514 Ok(spawn_results)
515}
516
517async fn wait_for_pipeline_processes_and_update_status(
518 pipeline: &ast::Pipeline,
519 mut process_spawn_results: VecDeque<ExecutionSpawnResult>,
520 shell: &mut Shell<impl extensions::ShellExtensions>,
521 params: &ExecutionParameters,
522) -> Result<ExecutionResult, error::Error> {
523 let mut result = ExecutionResult::success();
524 let mut stopped_children = vec![];
525 let mut last_failure_exit_code: Option<ExecutionExitCode> = None;
526
527 shell.last_pipeline_statuses_mut().clear();
529
530 while let Some(child) = process_spawn_results.pop_front() {
531 let wait_result = if !stopped_children.is_empty() {
532 child.poll().await?
533 } else {
534 child.wait().await?
535 };
536
537 match wait_result {
538 ExecutionWaitResult::Completed(current_result) => {
539 result = current_result;
540 shell.set_last_exit_status(result.exit_code.into());
541 shell
542 .last_pipeline_statuses_mut()
543 .push(result.exit_code.into());
544
545 if !result.is_success() {
547 last_failure_exit_code = Some(result.exit_code);
548 }
549 }
550 ExecutionWaitResult::Stopped(child) => {
551 result = ExecutionResult::stopped();
552 shell.set_last_exit_status(result.exit_code.into());
553 shell
554 .last_pipeline_statuses_mut()
555 .push(result.exit_code.into());
556
557 stopped_children.push(jobs::JobTask::External(child));
558 }
559 }
560 }
561
562 if shell.options().return_last_failure_from_pipeline
564 && let Some(failure_exit_code) = last_failure_exit_code
565 {
566 result.exit_code = failure_exit_code;
567 }
568
569 if shell.options().interactive {
570 sys::terminal::move_self_to_foreground()?;
571 }
572
573 if !stopped_children.is_empty() {
576 let job = shell.jobs_mut().add_as_current(jobs::Job::new(
577 stopped_children,
578 pipeline.to_string(),
579 jobs::JobState::Stopped,
580 ));
581
582 let formatted = job.to_string();
583
584 writeln!(params.stderr(shell), "\r{formatted}")?;
586 }
587
588 Ok(result)
589}
590
591#[async_trait::async_trait]
592impl<SE: extensions::ShellExtensions> ExecuteInPipeline<SE> for ast::Command {
593 async fn execute_in_pipeline(
594 &self,
595 mut pipeline_context: PipelineExecutionContext<'_, SE>,
596 mut params: ExecutionParameters,
597 ) -> Result<ExecutionSpawnResult, error::Error> {
598 if pipeline_context.shell.options().do_not_execute_commands {
599 return Ok(ExecutionSpawnResult::Completed(ExecutionResult::success()));
600 }
601
602 pipeline_context.shell.set_current_cmd(self);
604
605 match self {
606 Self::Simple(simple) => simple.execute_in_pipeline(pipeline_context, params).await,
607 Self::Compound(compound, redirects) => {
608 if let Some(redirects) = redirects {
610 for redirect in &redirects.0 {
611 setup_redirect(&mut pipeline_context.shell, &mut params, redirect).await?;
612 }
613 }
614
615 Ok(compound
616 .execute(&mut pipeline_context.shell, ¶ms)
617 .await?
618 .into())
619 }
620 Self::Function(func) => Ok(func
621 .execute(&mut pipeline_context.shell, ¶ms)
622 .await?
623 .into()),
624 Self::ExtendedTest(e, redirects) => {
625 if let Some(redirects) = redirects {
627 for redirect in &redirects.0 {
628 setup_redirect(&mut pipeline_context.shell, &mut params, redirect).await?;
629 }
630 }
631
632 let result = if extendedtests::eval_extended_test_expr(
634 &e.expr,
635 &mut pipeline_context.shell,
636 ¶ms,
637 )
638 .await?
639 {
640 0
641 } else {
642 1
643 };
644 Ok(ExecutionResult::new(result).into())
645 }
646 }
647 }
648}
649
650enum WhileOrUntil {
651 While,
652 Until,
653}
654
655#[async_trait::async_trait]
656impl Execute for ast::CompoundCommand {
657 async fn execute(
658 &self,
659 shell: &mut Shell<impl extensions::ShellExtensions>,
660 params: &ExecutionParameters,
661 ) -> Result<ExecutionResult, error::Error> {
662 match self {
663 Self::BraceGroup(ast::BraceGroupCommand { list, .. }) => {
664 list.execute(shell, params).await
665 }
666 Self::Subshell(ast::SubshellCommand { list, .. }) => {
667 let mut subshell = shell.clone();
670
671 let subshell_result = match list.execute(&mut subshell, params).await {
674 Ok(result) => result,
675 Err(error) => {
676 let mut stderr = params.stderr(shell);
678 let _ = shell.display_error(&mut stderr, &error);
679
680 error.into_result(&subshell)
682 }
683 };
684
685 Ok(ExecutionResult::from(subshell_result.exit_code))
688 }
689 Self::ForClause(f) => f.execute(shell, params).await,
690 Self::CaseClause(c) => c.execute(shell, params).await,
691 Self::IfClause(i) => i.execute(shell, params).await,
692 Self::WhileClause(w) => (WhileOrUntil::While, w).execute(shell, params).await,
693 Self::UntilClause(u) => (WhileOrUntil::Until, u).execute(shell, params).await,
694 Self::Arithmetic(a) => a.execute(shell, params).await,
695 Self::ArithmeticForClause(a) => a.execute(shell, params).await,
696 }
697 }
698}
699
700#[async_trait::async_trait]
701impl Execute for ast::ForClauseCommand {
702 async fn execute(
703 &self,
704 shell: &mut Shell<impl extensions::ShellExtensions>,
705 params: &ExecutionParameters,
706 ) -> Result<ExecutionResult, error::Error> {
707 let mut result = ExecutionResult::success();
708
709 let mut expanded_values = vec![];
712 if let Some(unexpanded_values) = &self.values {
713 for value in unexpanded_values {
714 let mut expanded =
715 expansion::full_expand_and_split_word(shell, params, value).await?;
716 expanded_values.append(&mut expanded);
717 }
718 } else {
719 expanded_values.extend_from_slice(shell.current_shell_args());
721 }
722
723 for value in expanded_values {
724 if shell.options().print_commands_and_arguments {
725 if let Some(unexpanded_values) = &self.values {
726 shell
727 .trace_command(
728 params,
729 std::format!(
730 "for {} in {}",
731 self.variable_name,
732 unexpanded_values.iter().join(" ")
733 ),
734 )
735 .await;
736 } else {
737 shell
738 .trace_command(params, std::format!("for {}", self.variable_name,))
739 .await;
740 }
741 }
742
743 shell.env_mut().update_or_add(
745 &self.variable_name,
746 ShellValueLiteral::Scalar(value),
747 |_| Ok(()),
748 EnvironmentLookup::Anywhere,
749 EnvironmentScope::Global,
750 )?;
751
752 result = self.body.list.execute(shell, params).await?;
753 if result.is_return_or_exit() {
754 break;
755 }
756
757 let is_break = result.is_break();
758
759 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
760
761 if is_break || result.is_continue() {
762 break;
763 }
764 }
765
766 shell.set_last_exit_status(result.exit_code.into());
767 Ok(result)
768 }
769}
770
771#[async_trait::async_trait]
772impl Execute for ast::CaseClauseCommand {
773 async fn execute(
774 &self,
775 shell: &mut Shell<impl extensions::ShellExtensions>,
776 params: &ExecutionParameters,
777 ) -> Result<ExecutionResult, error::Error> {
778 if shell.options().print_commands_and_arguments {
781 shell
782 .trace_command(params, std::format!("case {} in", &self.value))
783 .await;
784 }
785
786 let expanded_value = expansion::basic_expand_word(shell, params, &self.value).await?;
787 let mut result: ExecutionResult = ExecutionResult::success();
788 let mut force_execute_next_case = false;
789
790 for case in &self.cases {
791 if force_execute_next_case {
792 force_execute_next_case = false;
793 } else {
794 let mut matches = false;
795 for pattern in &case.patterns {
796 let expanded_pattern = expansion::basic_expand_pattern(shell, params, pattern)
797 .await?
798 .set_extended_globbing(shell.options().extended_globbing)
799 .set_case_insensitive(shell.options().case_insensitive_conditionals);
800
801 if expanded_pattern.exactly_matches(expanded_value.as_str())? {
802 matches = true;
803 break;
804 }
805 }
806
807 if !matches {
808 continue;
809 }
810 }
811
812 result = if let Some(case_cmd) = &case.cmd {
813 case_cmd.execute(shell, params).await?
814 } else {
815 ExecutionResult::success()
816 };
817
818 if !result.is_normal_flow() {
820 break;
821 }
822
823 match case.post_action {
824 ast::CaseItemPostAction::ExitCase => break,
825 ast::CaseItemPostAction::UnconditionallyExecuteNextCaseItem => {
826 force_execute_next_case = true;
827 }
828 ast::CaseItemPostAction::ContinueEvaluatingCases => (),
829 }
830 }
831
832 shell.set_last_exit_status(result.exit_code.into());
833
834 Ok(result)
835 }
836}
837
838#[async_trait::async_trait]
839impl Execute for ast::IfClauseCommand {
840 async fn execute(
841 &self,
842 shell: &mut Shell<impl extensions::ShellExtensions>,
843 params: &ExecutionParameters,
844 ) -> Result<ExecutionResult, error::Error> {
845 let mut condition_params = params.clone();
847 condition_params.suppress_errexit = true;
848 let condition = self.condition.execute(shell, &condition_params).await?;
849
850 if !condition.is_normal_flow() {
852 return Ok(condition);
853 }
854
855 if condition.is_success() {
856 return self.then.execute(shell, params).await;
857 }
858
859 if let Some(elses) = &self.elses {
860 for else_clause in elses {
861 match &else_clause.condition {
862 Some(else_condition) => {
863 let else_condition_result =
864 else_condition.execute(shell, &condition_params).await?;
865
866 if !else_condition_result.is_normal_flow() {
868 return Ok(else_condition_result);
869 }
870
871 if else_condition_result.is_success() {
872 return else_clause.body.execute(shell, params).await;
873 }
874 }
875 None => {
876 return else_clause.body.execute(shell, params).await;
877 }
878 }
879 }
880 }
881
882 let result = ExecutionResult::success();
885 shell.set_last_exit_status(result.exit_code.into());
886
887 Ok(result)
888 }
889}
890
891#[async_trait::async_trait]
892impl Execute for (WhileOrUntil, &ast::WhileOrUntilClauseCommand) {
893 async fn execute(
894 &self,
895 shell: &mut Shell<impl extensions::ShellExtensions>,
896 params: &ExecutionParameters,
897 ) -> Result<ExecutionResult, error::Error> {
898 let is_while = match self.0 {
899 WhileOrUntil::While => true,
900 WhileOrUntil::Until => false,
901 };
902 let test_condition = &self.1.0;
903 let body = &self.1.1;
904
905 let mut result = ExecutionResult::success();
906
907 let mut condition_params = params.clone();
909 condition_params.suppress_errexit = true;
910
911 loop {
912 let condition_result = test_condition.execute(shell, &condition_params).await?;
913
914 shell.set_last_exit_status(condition_result.exit_code.into());
916
917 if !condition_result.is_normal_flow() {
918 result = condition_result;
919
920 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
923 break;
924 }
925
926 if condition_result.is_success() != is_while {
927 break;
928 }
929
930 result = body.list.execute(shell, params).await?;
931 if result.is_return_or_exit() {
932 break;
933 }
934
935 let is_break = result.is_break();
936
937 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
938
939 if is_break || result.is_continue() {
940 break;
941 }
942 }
943
944 shell.set_last_exit_status(result.exit_code.into());
945 Ok(result)
946 }
947}
948
949#[async_trait::async_trait]
950impl Execute for ast::ArithmeticCommand {
951 async fn execute(
952 &self,
953 shell: &mut Shell<impl extensions::ShellExtensions>,
954 params: &ExecutionParameters,
955 ) -> Result<ExecutionResult, error::Error> {
956 let value = self.expr.eval(shell, params, true).await?;
957 let result = if value != 0 {
958 ExecutionResult::success()
959 } else {
960 ExecutionResult::general_error()
961 };
962
963 shell.set_last_exit_status(result.exit_code.into());
964
965 Ok(result)
966 }
967}
968
969#[async_trait::async_trait]
970impl Execute for ast::ArithmeticForClauseCommand {
971 async fn execute(
972 &self,
973 shell: &mut Shell<impl extensions::ShellExtensions>,
974 params: &ExecutionParameters,
975 ) -> Result<ExecutionResult, error::Error> {
976 let mut result = ExecutionResult::success();
977 if let Some(initializer) = &self.initializer {
978 initializer.eval(shell, params, true).await?;
979 }
980
981 loop {
982 if let Some(condition) = &self.condition {
983 if !condition.value.is_empty() && condition.eval(shell, params, true).await? == 0 {
985 break;
986 }
987 }
988
989 result = self.body.list.execute(shell, params).await?;
990 if result.is_return_or_exit() {
991 break;
992 }
993
994 let is_break = result.is_break();
995
996 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
997
998 if is_break || result.is_continue() {
999 break;
1000 }
1001
1002 if let Some(updater) = &self.updater {
1003 updater.eval(shell, params, true).await?;
1004 }
1005 }
1006
1007 shell.set_last_exit_status(result.exit_code.into());
1008 Ok(result)
1009 }
1010}
1011
1012#[async_trait::async_trait]
1013impl Execute for ast::FunctionDefinition {
1014 async fn execute(
1015 &self,
1016 shell: &mut Shell<impl extensions::ShellExtensions>,
1017 _params: &ExecutionParameters,
1018 ) -> Result<ExecutionResult, error::Error> {
1019 let func_name = self.fname.value.clone();
1020
1021 if shell.options().posix_mode
1023 && shell
1024 .builtins()
1025 .get(&func_name)
1026 .is_some_and(|r| r.special_builtin)
1027 {
1028 return Err(
1029 error::Error::from(error::ErrorKind::FunctionNameShadowsSpecialBuiltin {
1030 name: func_name,
1031 })
1032 .into_fatal(),
1033 );
1034 }
1035
1036 let source_info = shell
1039 .call_stack()
1040 .current_frame()
1041 .map_or_else(crate::SourceInfo::default, |frame| {
1042 frame.adjusted_source_info()
1043 });
1044 shell.define_func(func_name, self.clone(), &source_info);
1045
1046 let result = ExecutionResult::success();
1047 shell.set_last_exit_status(result.exit_code.into());
1048
1049 Ok(result)
1050 }
1051}
1052
1053#[async_trait::async_trait]
1054#[allow(clippy::too_many_lines)]
1055impl<SE: extensions::ShellExtensions> ExecuteInPipeline<SE> for ast::SimpleCommand {
1056 async fn execute_in_pipeline(
1057 &self,
1058 mut context: PipelineExecutionContext<'_, SE>,
1059 mut params: ExecutionParameters,
1060 ) -> Result<ExecutionSpawnResult, error::Error> {
1061 let prefix_iter = self.prefix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1062 let suffix_iter = self.suffix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1063 let cmd_name_items = self
1064 .word_or_name
1065 .as_ref()
1066 .map(|won| CommandPrefixOrSuffixItem::Word(won.clone()));
1067
1068 let mut assignments = vec![];
1069 let mut args: Vec<CommandArg> = vec![];
1070 let mut command_takes_assignments = false;
1071
1072 let status_change_count_before_expansion = context.shell.last_exit_status_change_count();
1075
1076 for item in prefix_iter.chain(cmd_name_items.iter()).chain(suffix_iter) {
1077 match item {
1078 CommandPrefixOrSuffixItem::IoRedirect(redirect) => {
1079 if let Err(e) = setup_redirect(&mut context.shell, &mut params, redirect).await
1080 {
1081 writeln!(params.stderr(&context.shell), "error: {e}")?;
1082 return Ok(ExecutionResult::general_error().into());
1083 }
1084 }
1085 CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => {
1086 let (installed_fd_num, substitution_file) = setup_process_substitution(
1087 &context.shell,
1088 ¶ms,
1089 kind,
1090 subshell_command,
1091 )?;
1092
1093 params
1094 .open_files
1095 .set_fd(installed_fd_num, substitution_file);
1096
1097 args.push(CommandArg::String(std::format!(
1098 "/dev/fd/{installed_fd_num}"
1099 )));
1100 }
1101 CommandPrefixOrSuffixItem::AssignmentWord(assignment, word) => {
1102 if args.is_empty() {
1103 assignments.push(assignment);
1106 } else {
1107 if command_takes_assignments {
1108 let expanded =
1112 expand_assignment(&mut context.shell, ¶ms, assignment).await?;
1113 args.push(CommandArg::Assignment(expanded));
1114 } else {
1115 let mut next_args = expansion::full_expand_and_split_word(
1119 &mut context.shell,
1120 ¶ms,
1121 word,
1122 )
1123 .await?
1124 .into_iter()
1125 .map(CommandArg::String)
1126 .collect();
1127 args.append(&mut next_args);
1128 }
1129 }
1130 }
1131 CommandPrefixOrSuffixItem::Word(arg) => {
1132 let mut next_args =
1133 expansion::full_expand_and_split_word(&mut context.shell, ¶ms, arg)
1134 .await?;
1135
1136 if args.is_empty()
1137 && let Some(cmd_name) = next_args.first()
1138 {
1139 if let Some(alias_value) = context.shell.aliases().get(cmd_name.as_str()) {
1140 let mut alias_pieces: Vec<_> = alias_value
1145 .split_ascii_whitespace()
1146 .map(|i| i.to_owned())
1147 .collect();
1148
1149 next_args.remove(0);
1150 alias_pieces.append(&mut next_args);
1151
1152 next_args = alias_pieces;
1153 }
1154
1155 let first_arg = next_args[0].as_str();
1156
1157 if context
1160 .shell
1161 .builtins()
1162 .get(first_arg)
1163 .is_some_and(|r| !r.disabled && r.declaration_builtin)
1164 {
1165 command_takes_assignments = true;
1166 }
1167 }
1168
1169 let mut next_args = next_args.into_iter().map(CommandArg::String).collect();
1170 args.append(&mut next_args);
1171 }
1172 }
1173 }
1174
1175 if let Some(CommandArg::String(cmd_name)) = args.first().cloned() {
1177 let mut stderr = params.stderr(&context.shell);
1178
1179 let (owned_shell, parent_shell) = match context.shell {
1180 commands::ShellForCommand::ParentShell(shell) => (None, shell),
1181 commands::ShellForCommand::OwnedShell { target, parent } => (Some(target), parent),
1182 };
1183
1184 let shell = if let Some(owned_shell) = owned_shell {
1185 commands::ShellForCommand::OwnedShell {
1186 target: owned_shell,
1187 parent: parent_shell,
1188 }
1189 } else {
1190 commands::ShellForCommand::ParentShell(parent_shell)
1191 };
1192
1193 let context = PipelineExecutionContext {
1194 shell,
1195 process_group_id: context.process_group_id,
1196 };
1197
1198 match execute_command(context, params, cmd_name, assignments, args).await {
1199 Ok(result) => Ok(result),
1200 Err(err) => {
1201 let _ = parent_shell.display_error(&mut stderr, &err);
1202
1203 let result = err.into_result(parent_shell);
1204 Ok(result.into())
1205 }
1206 }
1207 } else {
1208 for assignment in assignments {
1210 apply_assignment(
1213 assignment,
1214 &mut context.shell,
1215 ¶ms,
1216 false,
1217 None,
1218 EnvironmentScope::Global,
1219 )
1220 .await?;
1221 }
1222
1223 if status_change_count_before_expansion == context.shell.last_exit_status_change_count()
1228 {
1229 context.shell.set_last_exit_status(0);
1230 }
1231
1232 Ok(ExecutionResult::new(context.shell.last_exit_status()).into())
1235 }
1236 }
1237}
1238
1239async fn execute_command(
1240 mut context: PipelineExecutionContext<'_, impl extensions::ShellExtensions>,
1241 params: ExecutionParameters,
1242 cmd_name: String,
1243 assignments: Vec<&ast::Assignment>,
1244 args: Vec<CommandArg>,
1245) -> Result<ExecutionSpawnResult, error::Error> {
1246 let mut guard = crate::env::ScopeGuard::new(&mut context.shell, EnvironmentScope::Command);
1250
1251 for assignment in &assignments {
1252 apply_assignment(
1254 assignment,
1255 guard.shell(),
1256 ¶ms,
1257 true,
1258 Some(EnvironmentScope::Command),
1259 EnvironmentScope::Command,
1260 )
1261 .await?;
1262 }
1263
1264 if guard.shell().options().print_commands_and_arguments {
1265 guard
1266 .shell()
1267 .trace_command(
1268 ¶ms,
1269 args.iter().map(|arg| arg.quote_for_tracing()).join(" "),
1270 )
1271 .await;
1272 }
1273
1274 guard.detach();
1275 drop(guard);
1276
1277 let mut cmd = commands::SimpleCommand::new(context.shell, params, cmd_name, args);
1279 cmd.process_group_id = context.process_group_id;
1280
1281 cmd.post_execute = Some(|shell| shell.env_mut().pop_scope(EnvironmentScope::Command));
1283
1284 let _ = commands::on_preexecute(&mut cmd).await;
1286
1287 cmd.execute().await
1290}
1291
1292async fn expand_assignment(
1293 shell: &mut Shell<impl extensions::ShellExtensions>,
1294 params: &ExecutionParameters,
1295 assignment: &ast::Assignment,
1296) -> Result<ast::Assignment, error::Error> {
1297 let value = expand_assignment_value(shell, params, &assignment.value).await?;
1298 Ok(ast::Assignment {
1299 name: basic_expand_assignment_name(shell, params, &assignment.name).await?,
1300 value,
1301 append: assignment.append,
1302 loc: assignment.loc.clone(),
1303 })
1304}
1305
1306async fn basic_expand_assignment_name(
1307 shell: &mut Shell<impl extensions::ShellExtensions>,
1308 params: &ExecutionParameters,
1309 name: &ast::AssignmentName,
1310) -> Result<ast::AssignmentName, error::Error> {
1311 match name {
1312 ast::AssignmentName::VariableName(name) => {
1313 let expanded = expansion::basic_expand_word(shell, params, name).await?;
1314 Ok(ast::AssignmentName::VariableName(expanded))
1315 }
1316 ast::AssignmentName::ArrayElementName(name, index) => {
1317 let expanded_name = expansion::basic_expand_word(shell, params, name).await?;
1318 let expanded_index = expansion::basic_expand_word(shell, params, index).await?;
1319 Ok(ast::AssignmentName::ArrayElementName(
1320 expanded_name,
1321 expanded_index,
1322 ))
1323 }
1324 }
1325}
1326
1327async fn expand_assignment_value(
1328 shell: &mut Shell<impl extensions::ShellExtensions>,
1329 params: &ExecutionParameters,
1330 value: &ast::AssignmentValue,
1331) -> Result<ast::AssignmentValue, error::Error> {
1332 let expanded = match value {
1333 ast::AssignmentValue::Scalar(s) => {
1334 let expanded_word = expansion::basic_expand_assignment_word(shell, params, s).await?;
1335 ast::AssignmentValue::Scalar(ast::Word::from(expanded_word))
1336 }
1337 ast::AssignmentValue::Array(arr) => {
1338 let mut expanded_values = vec![];
1339 for (key, value) in arr {
1340 if let Some(k) = key {
1341 let expanded_key = expansion::basic_expand_assignment_word(shell, params, k)
1342 .await?
1343 .into();
1344 let expanded_value =
1345 expansion::basic_expand_assignment_word(shell, params, value)
1346 .await?
1347 .into();
1348 expanded_values.push((Some(expanded_key), expanded_value));
1349 } else {
1350 let split_expanded_value =
1352 expansion::full_expand_and_split_word(shell, params, value).await?;
1353 for expanded_value in split_expanded_value {
1354 expanded_values.push((None, expanded_value.into()));
1355 }
1356 }
1357 }
1358
1359 ast::AssignmentValue::Array(expanded_values)
1360 }
1361 };
1362
1363 Ok(expanded)
1364}
1365
1366#[expect(clippy::too_many_lines)]
1367async fn apply_assignment(
1368 assignment: &ast::Assignment,
1369 shell: &mut Shell<impl extensions::ShellExtensions>,
1370 params: &ExecutionParameters,
1371 mut export: bool,
1372 required_scope: Option<EnvironmentScope>,
1373 creation_scope: EnvironmentScope,
1374) -> Result<(), error::Error> {
1375 let mut array_index;
1378 let variable_name = match &assignment.name {
1379 ast::AssignmentName::VariableName(name) => {
1380 array_index = None;
1381 name
1382 }
1383 ast::AssignmentName::ArrayElementName(name, index) => {
1384 let expanded = expansion::basic_expand_word(shell, params, index).await?;
1385 array_index = Some(expanded);
1386 name
1387 }
1388 };
1389
1390 let new_value = match &assignment.value {
1392 ast::AssignmentValue::Scalar(unexpanded_value) => {
1393 let value =
1394 expansion::basic_expand_assignment_word(shell, params, unexpanded_value).await?;
1395 ShellValueLiteral::Scalar(value)
1396 }
1397 ast::AssignmentValue::Array(unexpanded_values) => {
1398 let mut elements = vec![];
1399 for (unexpanded_key, unexpanded_value) in unexpanded_values {
1400 let key = match unexpanded_key {
1401 Some(unexpanded_key) => Some(
1402 expansion::basic_expand_assignment_word(shell, params, unexpanded_key)
1403 .await?,
1404 ),
1405 None => None,
1406 };
1407
1408 if key.is_some() {
1409 let value =
1410 expansion::basic_expand_assignment_word(shell, params, unexpanded_value)
1411 .await?;
1412 elements.push((key, value));
1413 } else {
1414 let values =
1416 expansion::full_expand_and_split_word(shell, params, unexpanded_value)
1417 .await?;
1418 for value in values {
1419 elements.push((None, value));
1420 }
1421 }
1422 }
1423 ShellValueLiteral::Array(ArrayLiteral(elements))
1424 }
1425 };
1426
1427 if shell.options().print_commands_and_arguments {
1428 let op = if assignment.append { "+=" } else { "=" };
1429 shell
1430 .trace_command(params, std::format!("{}{op}{new_value}", assignment.name))
1431 .await;
1432 }
1433
1434 if let Some(idx) = &array_index {
1436 let will_be_indexed_array = if let Some((_, existing_value)) =
1437 shell.env().get(variable_name)
1438 {
1439 matches!(
1440 existing_value.value(),
1441 ShellValue::IndexedArray(_) | ShellValue::Unset(ShellValueUnsetType::IndexedArray)
1442 )
1443 } else {
1444 true
1445 };
1446
1447 if will_be_indexed_array {
1448 array_index = Some(
1449 arithmetic::expand_and_eval(shell, params, idx.as_str(), false)
1450 .await?
1451 .to_string(),
1452 );
1453 }
1454 }
1455
1456 let export_variables_on_modification = shell.options().export_variables_on_modification;
1458
1459 if let Some((existing_value_scope, existing_value)) =
1461 shell.env_mut().get_mut(variable_name.as_str())
1462 && (required_scope.is_none() || Some(existing_value_scope) == required_scope)
1463 {
1464 if let Some(array_index) = array_index {
1465 match new_value {
1466 ShellValueLiteral::Scalar(s) => {
1467 existing_value.assign_at_index(array_index, s, assignment.append)?;
1468 }
1469 ShellValueLiteral::Array(_) => {
1470 return error::unimp("replacing an array item with an array");
1471 }
1472 }
1473 } else {
1474 if !export
1475 && export_variables_on_modification
1476 && !matches!(new_value, ShellValueLiteral::Array(_))
1477 {
1478 export = true;
1479 }
1480
1481 existing_value.assign(new_value, assignment.append)?;
1482 }
1483
1484 if export {
1485 existing_value.export();
1486 }
1487
1488 return Ok(());
1490 }
1491
1492 let new_value = if let Some(array_index) = array_index {
1494 match new_value {
1495 ShellValueLiteral::Scalar(s) => {
1496 ShellValue::indexed_array_from_literals(ArrayLiteral(vec![(Some(array_index), s)]))
1497 }
1498 ShellValueLiteral::Array(_) => {
1499 return error::unimp("cannot assign list to array member");
1500 }
1501 }
1502 } else {
1503 match new_value {
1504 ShellValueLiteral::Scalar(s) => {
1505 export = export || shell.options().export_variables_on_modification;
1506 ShellValue::String(s)
1507 }
1508 ShellValueLiteral::Array(values) => ShellValue::indexed_array_from_literals(values),
1509 }
1510 };
1511
1512 let mut new_var = ShellVariable::new(new_value);
1513
1514 if export {
1515 new_var.export();
1516 }
1517
1518 shell.env_mut().add(variable_name, new_var, creation_scope)
1519}
1520
1521#[expect(clippy::too_many_lines)]
1522pub(crate) async fn setup_redirect(
1523 shell: &mut Shell<impl extensions::ShellExtensions>,
1524 params: &'_ mut ExecutionParameters,
1525 redirect: &ast::IoRedirect,
1526) -> Result<(), error::Error> {
1527 match redirect {
1528 ast::IoRedirect::OutputAndError(f, append) => {
1529 let mut expanded_fields =
1530 expansion::full_expand_and_split_word(shell, params, f).await?;
1531 if expanded_fields.len() != 1 {
1532 return Err(error::ErrorKind::InvalidRedirection.into());
1533 }
1534
1535 let expanded_file_path = expanded_fields.remove(0);
1536 setup_redirect_output_and_error_to(shell, params, &expanded_file_path, *append)?;
1537 }
1538
1539 ast::IoRedirect::File(specified_fd_num, kind, target) => {
1540 match target {
1541 ast::IoFileRedirectTarget::Filename(f) => {
1542 let mut options = std::fs::File::options();
1543
1544 let mut expanded_fields =
1545 expansion::full_expand_and_split_word(shell, params, f).await?;
1546
1547 if expanded_fields.len() != 1 {
1548 return Err(error::ErrorKind::InvalidRedirection.into());
1549 }
1550
1551 let expanded_file_path: PathBuf =
1552 shell.absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1553
1554 let default_fd_if_unspecified = get_default_fd_for_redirect_kind(kind);
1555 match kind {
1556 ast::IoFileRedirectKind::Read => {
1557 options.read(true);
1558 }
1559 ast::IoFileRedirectKind::Write => {
1560 if shell
1561 .options()
1562 .disallow_overwriting_regular_files_via_output_redirection
1563 {
1564 if !expanded_file_path.is_file() {
1567 options.create(true);
1568 } else {
1569 options.create_new(true);
1570 }
1571 options.write(true);
1572 } else {
1573 options.create(true);
1574 options.write(true);
1575 options.truncate(true);
1576 }
1577 }
1578 ast::IoFileRedirectKind::Append => {
1579 options.create(true);
1580 options.append(true);
1581 }
1582 ast::IoFileRedirectKind::ReadAndWrite => {
1583 options.create(true);
1584 options.read(true);
1585 options.write(true);
1586 }
1587 ast::IoFileRedirectKind::Clobber => {
1588 options.create(true);
1589 options.write(true);
1590 options.truncate(true);
1591 }
1592 ast::IoFileRedirectKind::DuplicateInput => {
1593 options.read(true);
1594 }
1595 ast::IoFileRedirectKind::DuplicateOutput => {
1596 options.create(true);
1597 options.write(true);
1598 }
1599 }
1600
1601 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1602
1603 let opened_file = shell
1604 .open_file(&options, &expanded_file_path, params)
1605 .map_err(|err| {
1606 error::ErrorKind::RedirectionFailure(
1607 expanded_file_path.to_string_lossy().to_string(),
1608 err.to_string(),
1609 )
1610 })?;
1611
1612 params.open_files.set_fd(fd_num, opened_file);
1613 }
1614
1615 ast::IoFileRedirectTarget::Fd(fd) => {
1616 let default_fd_if_unspecified = match kind {
1617 ast::IoFileRedirectKind::DuplicateInput => 0,
1618 ast::IoFileRedirectKind::DuplicateOutput => 1,
1619 _ => {
1620 return error::unimp("unexpected redirect kind");
1621 }
1622 };
1623
1624 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1625
1626 if let Some(f) = params.try_fd(shell, *fd) {
1627 let target_file = f.try_clone()?;
1628
1629 params.open_files.set_fd(fd_num, target_file);
1630 } else {
1631 return Err(error::ErrorKind::BadFileDescriptor(*fd).into());
1632 }
1633 }
1634
1635 ast::IoFileRedirectTarget::Duplicate(word) => {
1636 let default_fd_if_unspecified = match kind {
1637 ast::IoFileRedirectKind::DuplicateInput => 0,
1638 ast::IoFileRedirectKind::DuplicateOutput => 1,
1639 _ => {
1640 return error::unimp("unexpected redirect kind");
1641 }
1642 };
1643
1644 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1645
1646 let mut expanded_fields =
1647 expansion::full_expand_and_split_word(shell, params, word).await?;
1648
1649 if expanded_fields.len() != 1 {
1650 return Err(error::ErrorKind::InvalidRedirection.into());
1651 }
1652
1653 let mut expanded = expanded_fields.remove(0);
1654
1655 let dash = if expanded.ends_with('-') {
1656 expanded.pop();
1657 true
1658 } else {
1659 false
1660 };
1661
1662 if expanded.is_empty() {
1663 } else if expanded.chars().all(|c: char| c.is_ascii_digit()) {
1665 let source_fd_num = expanded
1666 .parse::<ShellFd>()
1667 .map_err(|_| error::ErrorKind::InvalidRedirection)?;
1668
1669 let target_file = if let Some(f) = params.try_fd(shell, source_fd_num) {
1671 f.try_clone()?
1672 } else {
1673 return Err(error::ErrorKind::BadFileDescriptor(source_fd_num).into());
1674 };
1675
1676 params.open_files.set_fd(fd_num, target_file);
1677 } else if fd_num == 1 && !dash {
1678 setup_redirect_output_and_error_to(
1680 shell, params, &expanded, false, )?;
1682 } else {
1683 return Err(error::ErrorKind::InvalidRedirection.into());
1684 }
1685
1686 if dash {
1687 params.open_files.remove_fd(fd_num);
1689 }
1690 }
1691
1692 ast::IoFileRedirectTarget::ProcessSubstitution(substitution_kind, subshell_cmd) => {
1693 match kind {
1694 ast::IoFileRedirectKind::Read
1695 | ast::IoFileRedirectKind::Write
1696 | ast::IoFileRedirectKind::Append
1697 | ast::IoFileRedirectKind::ReadAndWrite
1698 | ast::IoFileRedirectKind::Clobber => {
1699 let (substitution_fd, substitution_file) = setup_process_substitution(
1700 shell,
1701 params,
1702 substitution_kind,
1703 subshell_cmd,
1704 )?;
1705
1706 let target_file = substitution_file.try_clone()?;
1707 params.open_files.set_fd(substitution_fd, substitution_file);
1708
1709 let fd_num = specified_fd_num
1710 .unwrap_or_else(|| get_default_fd_for_redirect_kind(kind));
1711
1712 params.open_files.set_fd(fd_num, target_file);
1713 }
1714 _ => return error::unimp("invalid process substitution"),
1715 }
1716 }
1717 }
1718 }
1719
1720 ast::IoRedirect::HereDocument(fd_num, io_here) => {
1721 let fd_num = fd_num.unwrap_or(0);
1723
1724 let io_here_doc = if io_here.requires_expansion {
1726 expansion::basic_expand_heredoc_word(shell, params, &io_here.doc).await?
1727 } else {
1728 io_here.doc.flatten()
1729 };
1730
1731 let f = setup_open_file_with_contents(io_here_doc.as_str())?;
1732
1733 params.open_files.set_fd(fd_num, f);
1734 }
1735
1736 ast::IoRedirect::HereString(fd_num, word) => {
1737 let fd_num = fd_num.unwrap_or(0);
1739
1740 let mut expanded_word = expansion::basic_expand_word(shell, params, word).await?;
1741 expanded_word.push('\n');
1742
1743 let f = setup_open_file_with_contents(expanded_word.as_str())?;
1744
1745 params.open_files.set_fd(fd_num, f);
1746 }
1747 }
1748
1749 Ok(())
1750}
1751
1752fn setup_redirect_output_and_error_to(
1761 shell: &Shell<impl extensions::ShellExtensions>,
1762 params: &mut ExecutionParameters,
1763 file_path: &str,
1764 append: bool,
1765) -> Result<(), error::Error> {
1766 let abs_file_path: PathBuf = shell.absolute_path(Path::new(file_path));
1767
1768 let mut file_options = std::fs::File::options();
1769 file_options
1770 .create(true)
1771 .write(true)
1772 .truncate(!append)
1773 .append(append);
1774
1775 let stdout_file = shell
1776 .open_file(&file_options, &abs_file_path, params)
1777 .map_err(|err| {
1778 error::ErrorKind::RedirectionFailure(
1779 abs_file_path.to_string_lossy().to_string(),
1780 err.to_string(),
1781 )
1782 })?;
1783
1784 let stderr_file = stdout_file.try_clone()?;
1785
1786 params.open_files.set_fd(OpenFiles::STDOUT_FD, stdout_file);
1787 params.open_files.set_fd(OpenFiles::STDERR_FD, stderr_file);
1788
1789 Ok(())
1790}
1791
1792const fn get_default_fd_for_redirect_kind(kind: &ast::IoFileRedirectKind) -> ShellFd {
1793 match kind {
1794 ast::IoFileRedirectKind::Read => 0,
1795 ast::IoFileRedirectKind::Write => 1,
1796 ast::IoFileRedirectKind::Append => 1,
1797 ast::IoFileRedirectKind::ReadAndWrite => 0,
1798 ast::IoFileRedirectKind::Clobber => 1,
1799 ast::IoFileRedirectKind::DuplicateInput => 0,
1800 ast::IoFileRedirectKind::DuplicateOutput => 1,
1801 }
1802}
1803
1804fn setup_process_substitution(
1805 shell: &Shell<impl extensions::ShellExtensions>,
1806 params: &ExecutionParameters,
1807 kind: &ast::ProcessSubstitutionKind,
1808 subshell_cmd: &ast::SubshellCommand,
1809) -> Result<(ShellFd, OpenFile), error::Error> {
1810 let mut subshell = shell.clone();
1813
1814 let mut child_params = params.clone();
1816 child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
1817
1818 let (reader, writer) = std::io::pipe()?;
1820 let (reader, writer) = (reader.into(), writer.into());
1821
1822 let target_file = match kind {
1823 ast::ProcessSubstitutionKind::Read => {
1824 child_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
1825 reader
1826 }
1827 ast::ProcessSubstitutionKind::Write => {
1828 child_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
1829 writer
1830 }
1831 };
1832
1833 let subshell_cmd = subshell_cmd.to_owned();
1836 tokio::spawn(async move {
1837 let _ = subshell_cmd
1839 .list
1840 .execute(&mut subshell, &child_params)
1841 .await;
1842 });
1843
1844 let mut candidate_fd_num = 63;
1847 while params.open_files.contains_fd(candidate_fd_num) {
1848 candidate_fd_num -= 1;
1849 if candidate_fd_num == 0 {
1850 return error::unimp("no available file descriptors");
1851 }
1852 }
1853
1854 Ok((candidate_fd_num, target_file))
1855}
1856
1857fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Error> {
1858 let (reader, mut writer) = std::io::pipe()?;
1859
1860 let bytes = contents.as_bytes();
1861
1862 #[cfg(target_os = "linux")]
1863 {
1864 use std::os::fd::AsFd as _;
1865
1866 let len = i32::try_from(bytes.len())
1867 .map_err(|_err| error::Error::from(error::ErrorKind::TooMuchData))?;
1868 nix::fcntl::fcntl(reader.as_fd(), nix::fcntl::FcntlArg::F_SETPIPE_SZ(len))?;
1869 }
1870
1871 writer.write_all(bytes)?;
1872 drop(writer);
1873
1874 Ok(reader.into())
1875}