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, valid_variable_name};
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_async_ao_list_in_task(ao_list, shell, params);
252 let job_formatted = job.to_pid_style_string();
253
254 if shell.options().interactive && !shell.is_subshell() {
255 writeln!(params.stderr(shell), "{job_formatted}")?;
256 }
257
258 result = ExecutionResult::success();
259 } else {
260 result = ao_list.execute(shell, params).await?;
261
262 shell.set_last_exit_status(result.exit_code.into());
264 }
265
266 if !result.is_normal_flow() {
267 break;
268 }
269 }
270
271 Ok(result)
272 }
273}
274
275fn spawn_async_ao_list_in_task<'a, SE: extensions::ShellExtensions>(
276 ao_list: &ast::AndOrList,
277 shell: &'a mut Shell<SE>,
278 params: &ExecutionParameters,
279) -> &'a jobs::Job {
280 let mut cloned_shell = shell.clone();
282 let mut cloned_params = params.clone();
283 let cloned_ao_list = ao_list.clone();
284
285 cloned_shell.options_mut().interactive = false;
287
288 if let Ok(null) = openfiles::null() {
290 cloned_params.set_fd(openfiles::OpenFiles::STDIN_FD, null);
291 }
292
293 let join_handle = tokio::spawn(async move {
294 cloned_ao_list
295 .execute(&mut cloned_shell, &cloned_params)
296 .await
297 });
298
299 shell.jobs_mut().add_as_current(jobs::Job::new(
300 [jobs::JobTask::Internal(join_handle)],
301 ao_list.to_string(),
302 jobs::JobState::Running,
303 ))
304}
305
306#[async_trait::async_trait]
307impl Execute for ast::AndOrList {
308 async fn execute(
309 &self,
310 shell: &mut Shell<impl extensions::ShellExtensions>,
311 params: &ExecutionParameters,
312 ) -> Result<ExecutionResult, error::Error> {
313 let has_operators = !self.additional.is_empty();
314
315 let mut first_params = params.clone();
317 if has_operators {
318 first_params.suppress_errexit = true;
319 }
320
321 let mut result = self.first.execute(shell, &first_params).await?;
322
323 for (index, next_ao) in self.additional.iter().enumerate() {
324 if !result.is_normal_flow() {
326 break;
327 }
328
329 let (is_and, pipeline) = match next_ao {
330 ast::AndOr::And(p) => (true, p),
331 ast::AndOr::Or(p) => (false, p),
332 };
333
334 if is_and {
339 if !result.is_success() {
340 continue;
341 }
342 } else if result.is_success() {
343 continue;
344 }
345
346 let mut params = params.clone();
349
350 let is_last = index == self.additional.len() - 1;
351 if !is_last {
352 params.suppress_errexit = true;
353 }
354
355 result = pipeline.execute(shell, ¶ms).await?;
356 }
357
358 Ok(result)
359 }
360}
361
362#[async_trait::async_trait]
363impl Execute for ast::Pipeline {
364 async fn execute(
365 &self,
366 shell: &mut Shell<impl extensions::ShellExtensions>,
367 params: &ExecutionParameters,
368 ) -> Result<ExecutionResult, error::Error> {
369 let stopwatch = self
371 .timed
372 .is_some()
373 .then(timing::start_timing)
374 .transpose()?;
375
376 let mut params = params.clone();
377
378 if self.bang {
380 params.suppress_errexit = true;
381 }
382
383 let spawn_results = spawn_pipeline_processes(self, shell, ¶ms).await?;
386
387 let mut result =
389 wait_for_pipeline_processes_and_update_status(self, spawn_results, shell, ¶ms)
390 .await?;
391
392 if self.bang {
394 result.exit_code = ExecutionExitCode::from(if result.is_success() { 1 } else { 0 });
395 }
396
397 shell.set_last_exit_status(result.exit_code.into());
399
400 if !result.is_success() && !params.suppress_errexit && !self.bang {
405 if shell.traps().handles(crate::traps::TrapSignal::Err) {
406 shell
407 .invoke_trap_handler(crate::traps::TrapSignal::Err, ¶ms)
408 .await?;
409 }
410 }
411
412 if !params.suppress_errexit && !self.bang {
414 shell.apply_errexit_if_enabled(&mut result);
415 }
416
417 if let (Some(timed), Some(stopwatch)) = (&self.timed, &stopwatch)
419 && let Some(mut stderr) = params.try_fd(shell, openfiles::OpenFiles::STDERR_FD)
420 {
421 let timing = stopwatch.stop()?;
422 if timed.is_posix_output() {
423 std::write!(
424 stderr,
425 "real {}\nuser {}\nsys {}\n",
426 timing::format_duration_posixly(&timing.wall),
427 timing::format_duration_posixly(&timing.user),
428 timing::format_duration_posixly(&timing.system),
429 )?;
430 } else {
431 std::write!(
432 stderr,
433 "\nreal\t{}\nuser\t{}\nsys\t{}\n",
434 timing::format_duration_non_posixly(&timing.wall),
435 timing::format_duration_non_posixly(&timing.user),
436 timing::format_duration_non_posixly(&timing.system),
437 )?;
438 }
439 }
440
441 Ok(result)
442 }
443}
444
445async fn spawn_pipeline_processes(
446 pipeline: &ast::Pipeline,
447 shell: &mut Shell<impl extensions::ShellExtensions>,
448 params: &ExecutionParameters,
449) -> Result<VecDeque<ExecutionSpawnResult>, error::Error> {
450 let pipeline_len = pipeline.seq.len();
451 let mut pipe_readers = vec![];
452 let mut pipe_writers = vec![];
453 let mut spawn_results = VecDeque::new();
454 let mut process_group_id: Option<i32> = None;
455
456 if pipeline_len > 1 {
459 for _ in 0..(pipeline_len - 1) {
460 let (reader, writer) = std::io::pipe()?;
461 pipe_readers.push(Some(reader.into()));
462 pipe_writers.push(Some(writer.into()));
463 }
464 pipe_readers.push(None);
467 }
468
469 for (current_pipeline_index, command) in pipeline.seq.iter().enumerate() {
470 let run_in_current_shell = pipeline_len == 1
479 || (current_pipeline_index == pipeline_len - 1
480 && shell.options().run_last_pipeline_cmd_in_current_shell
481 && !shell.options().enable_job_control);
482
483 let mut cmd_params = params.clone();
485
486 if let Some(Some(reader)) = pipe_readers.pop() {
488 cmd_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
489 }
490 if let Some(Some(writer)) = pipe_writers.pop() {
491 cmd_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
492 }
493
494 let pipeline_context = if !run_in_current_shell {
495 if current_pipeline_index > 0 {
497 cmd_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
498 }
499
500 PipelineExecutionContext {
501 shell: commands::ShellForCommand::OwnedShell {
502 target: Box::new(shell.clone()),
503 parent: shell,
504 },
505 process_group_id,
506 }
507 } else {
508 PipelineExecutionContext {
509 shell: commands::ShellForCommand::ParentShell(shell),
510 process_group_id,
511 }
512 };
513
514 let spawn_result = command
515 .execute_in_pipeline(pipeline_context, cmd_params)
516 .await?;
517
518 if let ExecutionSpawnResult::StartedProcess(child) = &spawn_result {
520 if process_group_id.is_none() {
521 process_group_id = child.pgid();
522 }
523 }
524
525 spawn_results.push_back(spawn_result);
526 }
527
528 Ok(spawn_results)
529}
530
531async fn wait_for_pipeline_processes_and_update_status(
532 pipeline: &ast::Pipeline,
533 mut process_spawn_results: VecDeque<ExecutionSpawnResult>,
534 shell: &mut Shell<impl extensions::ShellExtensions>,
535 params: &ExecutionParameters,
536) -> Result<ExecutionResult, error::Error> {
537 let mut result = ExecutionResult::success();
538 let mut stopped_children = vec![];
539 let mut last_failure_exit_code: Option<ExecutionExitCode> = None;
540
541 shell.last_pipeline_statuses_mut().clear();
543
544 while let Some(child) = process_spawn_results.pop_front() {
545 let wait_result = if !stopped_children.is_empty() {
546 child.poll().await?
547 } else {
548 child.wait().await?
549 };
550
551 match wait_result {
552 ExecutionWaitResult::Completed(current_result) => {
553 result = current_result;
554 shell.set_last_exit_status(result.exit_code.into());
555 shell
556 .last_pipeline_statuses_mut()
557 .push(result.exit_code.into());
558
559 if !result.is_success() {
561 last_failure_exit_code = Some(result.exit_code);
562 }
563 }
564 ExecutionWaitResult::Stopped(child) => {
565 result = ExecutionResult::stopped();
566 shell.set_last_exit_status(result.exit_code.into());
567 shell
568 .last_pipeline_statuses_mut()
569 .push(result.exit_code.into());
570
571 stopped_children.push(jobs::JobTask::External(child));
572 }
573 }
574 }
575
576 if shell.options().return_last_failure_from_pipeline {
578 if let Some(failure_exit_code) = last_failure_exit_code {
579 result.exit_code = failure_exit_code;
580 }
581 }
582
583 if shell.options().interactive {
584 sys::terminal::move_self_to_foreground()?;
585 }
586
587 if !stopped_children.is_empty() {
590 let job = shell.jobs_mut().add_as_current(jobs::Job::new(
591 stopped_children,
592 pipeline.to_string(),
593 jobs::JobState::Stopped,
594 ));
595
596 let formatted = job.to_string();
597
598 writeln!(params.stderr(shell), "\r{formatted}")?;
600 }
601
602 Ok(result)
603}
604
605#[async_trait::async_trait]
606impl<SE: extensions::ShellExtensions> ExecuteInPipeline<SE> for ast::Command {
607 async fn execute_in_pipeline(
608 &self,
609 mut pipeline_context: PipelineExecutionContext<'_, SE>,
610 mut params: ExecutionParameters,
611 ) -> Result<ExecutionSpawnResult, error::Error> {
612 if pipeline_context.shell.options().do_not_execute_commands {
613 return Ok(ExecutionSpawnResult::Completed(ExecutionResult::success()));
614 }
615
616 pipeline_context.shell.set_current_cmd(self);
618
619 match self {
620 Self::Simple(simple) => simple.execute_in_pipeline(pipeline_context, params).await,
621 Self::Compound(compound, redirects) => {
622 if let Some(redirects) = redirects {
624 for redirect in &redirects.0 {
625 setup_redirect(&mut pipeline_context.shell, &mut params, redirect).await?;
626 }
627 }
628
629 Ok(compound
630 .execute(&mut pipeline_context.shell, ¶ms)
631 .await?
632 .into())
633 }
634 Self::Function(func) => Ok(func
635 .execute(&mut pipeline_context.shell, ¶ms)
636 .await?
637 .into()),
638 Self::ExtendedTest(e, redirects) => {
639 if let Some(redirects) = redirects {
641 for redirect in &redirects.0 {
642 setup_redirect(&mut pipeline_context.shell, &mut params, redirect).await?;
643 }
644 }
645
646 let result = if extendedtests::eval_extended_test_expr(
648 &e.expr,
649 &mut pipeline_context.shell,
650 ¶ms,
651 )
652 .await?
653 {
654 0
655 } else {
656 1
657 };
658 Ok(ExecutionResult::new(result).into())
659 }
660 }
661 }
662}
663
664enum WhileOrUntil {
665 While,
666 Until,
667}
668
669#[async_trait::async_trait]
670impl Execute for ast::CompoundCommand {
671 async fn execute(
672 &self,
673 shell: &mut Shell<impl extensions::ShellExtensions>,
674 params: &ExecutionParameters,
675 ) -> Result<ExecutionResult, error::Error> {
676 match self {
677 Self::BraceGroup(ast::BraceGroupCommand { list, .. }) => {
678 list.execute(shell, params).await
679 }
680 Self::Subshell(ast::SubshellCommand { list, .. }) => {
681 let mut subshell = shell.clone();
684
685 let subshell_result = match list.execute(&mut subshell, params).await {
688 Ok(result) => result,
689 Err(error) => {
690 let mut stderr = params.stderr(shell);
692 let _ = shell.display_error(&mut stderr, &error);
693
694 error.into_result(&subshell)
696 }
697 };
698
699 Ok(ExecutionResult::from(subshell_result.exit_code))
702 }
703 Self::ForClause(f) => f.execute(shell, params).await,
704 Self::CaseClause(c) => c.execute(shell, params).await,
705 Self::IfClause(i) => i.execute(shell, params).await,
706 Self::WhileClause(w) => (WhileOrUntil::While, w).execute(shell, params).await,
707 Self::UntilClause(u) => (WhileOrUntil::Until, u).execute(shell, params).await,
708 Self::Arithmetic(a) => a.execute(shell, params).await,
709 Self::ArithmeticForClause(a) => a.execute(shell, params).await,
710 Self::Coprocess(c) => c.execute(shell, params).await,
711 }
712 }
713}
714
715#[async_trait::async_trait]
716impl Execute for ast::CoprocessCommand {
717 async fn execute(
718 &self,
719 shell: &mut Shell<impl extensions::ShellExtensions>,
720 params: &ExecutionParameters,
721 ) -> Result<ExecutionResult, error::Error> {
722 if shell.options().do_not_execute_commands {
723 return Ok(ExecutionResult::success());
724 }
725
726 let name = self
728 .name
729 .as_ref()
730 .map_or_else(|| "COPROC".to_string(), |w| w.to_string());
731
732 if !valid_variable_name(&name) {
733 writeln!(
734 params.stderr(shell),
735 "coproc {name}: not a valid identifier"
736 )?;
737 return Ok(ExecutionExitCode::GeneralError.into());
738 }
739
740 let (stdin_reader, stdin_writer) = std::io::pipe()?;
742 let (stdout_reader, stdout_writer) = std::io::pipe()?;
743
744 let stdout_fd = shell.open_files_mut().add(stdout_reader.into())?;
747 let stdin_fd = shell.open_files_mut().add(stdin_writer.into())?;
748
749 let mut child_shell = shell.clone();
751 child_shell.options_mut().interactive = false;
752
753 let mut child_params = params.clone();
755 child_params
756 .open_files
757 .set_fd(OpenFiles::STDIN_FD, stdin_reader.into());
758 child_params
759 .open_files
760 .set_fd(OpenFiles::STDOUT_FD, stdout_writer.into());
761
762 let body = self.body.clone();
763 let join_handle = tokio::spawn(async move {
764 let pipeline_context = PipelineExecutionContext {
765 shell: commands::ShellForCommand::ParentShell(&mut child_shell),
766 process_group_id: None,
767 };
768 let spawn_result = body
769 .execute_in_pipeline(pipeline_context, child_params)
770 .await?;
771 match spawn_result.wait().await? {
772 ExecutionWaitResult::Completed(result) => Ok(result),
773 ExecutionWaitResult::Stopped(_) => Ok(ExecutionResult::stopped()),
774 }
775 });
776
777 let job = shell.jobs_mut().add_as_current(jobs::Job::new(
778 [jobs::JobTask::Internal(join_handle)],
779 format!("coproc {name}"),
780 jobs::JobState::Running,
781 ));
782 let job_id = job.id;
783
784 let arr_value = ShellValue::from(vec![stdout_fd.to_string(), stdin_fd.to_string()]);
786 shell
787 .env_mut()
788 .set_global(name.clone(), ShellVariable::new(arr_value))?;
789
790 let pid_name = format!("{name}_PID");
792 shell
793 .env_mut()
794 .set_global(pid_name, ShellVariable::new(job_id.to_string()))?;
795
796 Ok(ExecutionResult::success())
797 }
798}
799
800#[async_trait::async_trait]
801impl Execute for ast::ForClauseCommand {
802 async fn execute(
803 &self,
804 shell: &mut Shell<impl extensions::ShellExtensions>,
805 params: &ExecutionParameters,
806 ) -> Result<ExecutionResult, error::Error> {
807 let mut result = ExecutionResult::success();
808
809 let mut expanded_values = vec![];
812 if let Some(unexpanded_values) = &self.values {
813 for value in unexpanded_values {
814 let mut expanded =
815 expansion::full_expand_and_split_word(shell, params, value).await?;
816 expanded_values.append(&mut expanded);
817 }
818 } else {
819 expanded_values.extend_from_slice(shell.current_shell_args());
821 }
822
823 for value in expanded_values {
824 if shell.options().print_commands_and_arguments {
825 if let Some(unexpanded_values) = &self.values {
826 shell
827 .trace_command(
828 params,
829 std::format!(
830 "for {} in {}",
831 self.variable_name,
832 unexpanded_values.iter().join(" ")
833 ),
834 )
835 .await;
836 } else {
837 shell
838 .trace_command(params, std::format!("for {}", self.variable_name))
839 .await;
840 }
841 }
842
843 shell.env_mut().update_or_add(
845 &self.variable_name,
846 ShellValueLiteral::Scalar(value),
847 |_| Ok(()),
848 EnvironmentLookup::Anywhere,
849 EnvironmentScope::Global,
850 )?;
851
852 result = self.body.list.execute(shell, params).await?;
853 if result.is_return_or_exit() {
854 break;
855 }
856
857 let is_break = result.is_break();
858
859 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
860
861 if is_break || result.is_continue() {
862 break;
863 }
864 }
865
866 shell.set_last_exit_status(result.exit_code.into());
867 Ok(result)
868 }
869}
870
871#[async_trait::async_trait]
872impl Execute for ast::CaseClauseCommand {
873 async fn execute(
874 &self,
875 shell: &mut Shell<impl extensions::ShellExtensions>,
876 params: &ExecutionParameters,
877 ) -> Result<ExecutionResult, error::Error> {
878 if shell.options().print_commands_and_arguments {
881 shell
882 .trace_command(params, std::format!("case {} in", &self.value))
883 .await;
884 }
885
886 let expanded_value = expansion::basic_expand_word(shell, params, &self.value).await?;
887 let mut result: ExecutionResult = ExecutionResult::success();
888 let mut force_execute_next_case = false;
889
890 for case in &self.cases {
891 if force_execute_next_case {
892 force_execute_next_case = false;
893 } else {
894 let mut matches = false;
895 for pattern in &case.patterns {
896 let expanded_pattern = expansion::basic_expand_pattern(shell, params, pattern)
897 .await?
898 .set_extended_globbing(shell.options().extended_globbing)
899 .set_case_insensitive(shell.options().case_insensitive_conditionals);
900
901 if expanded_pattern.exactly_matches(expanded_value.as_str())? {
902 matches = true;
903 break;
904 }
905 }
906
907 if !matches {
908 continue;
909 }
910 }
911
912 result = if let Some(case_cmd) = &case.cmd {
913 case_cmd.execute(shell, params).await?
914 } else {
915 ExecutionResult::success()
916 };
917
918 if !result.is_normal_flow() {
920 break;
921 }
922
923 match case.post_action {
924 ast::CaseItemPostAction::ExitCase => break,
925 ast::CaseItemPostAction::UnconditionallyExecuteNextCaseItem => {
926 force_execute_next_case = true;
927 }
928 ast::CaseItemPostAction::ContinueEvaluatingCases => (),
929 }
930 }
931
932 shell.set_last_exit_status(result.exit_code.into());
933
934 Ok(result)
935 }
936}
937
938#[async_trait::async_trait]
939impl Execute for ast::IfClauseCommand {
940 async fn execute(
941 &self,
942 shell: &mut Shell<impl extensions::ShellExtensions>,
943 params: &ExecutionParameters,
944 ) -> Result<ExecutionResult, error::Error> {
945 let mut condition_params = params.clone();
947 condition_params.suppress_errexit = true;
948 let condition = self.condition.execute(shell, &condition_params).await?;
949
950 if !condition.is_normal_flow() {
952 return Ok(condition);
953 }
954
955 if condition.is_success() {
956 return self.then.execute(shell, params).await;
957 }
958
959 if let Some(elses) = &self.elses {
960 for else_clause in elses {
961 match &else_clause.condition {
962 Some(else_condition) => {
963 let else_condition_result =
964 else_condition.execute(shell, &condition_params).await?;
965
966 if !else_condition_result.is_normal_flow() {
968 return Ok(else_condition_result);
969 }
970
971 if else_condition_result.is_success() {
972 return else_clause.body.execute(shell, params).await;
973 }
974 }
975 None => {
976 return else_clause.body.execute(shell, params).await;
977 }
978 }
979 }
980 }
981
982 let result = ExecutionResult::success();
985 shell.set_last_exit_status(result.exit_code.into());
986
987 Ok(result)
988 }
989}
990
991#[async_trait::async_trait]
992impl Execute for (WhileOrUntil, &ast::WhileOrUntilClauseCommand) {
993 async fn execute(
994 &self,
995 shell: &mut Shell<impl extensions::ShellExtensions>,
996 params: &ExecutionParameters,
997 ) -> Result<ExecutionResult, error::Error> {
998 let is_while = match self.0 {
999 WhileOrUntil::While => true,
1000 WhileOrUntil::Until => false,
1001 };
1002 let test_condition = &self.1.0;
1003 let body = &self.1.1;
1004
1005 let mut result = ExecutionResult::success();
1006
1007 let mut condition_params = params.clone();
1009 condition_params.suppress_errexit = true;
1010
1011 loop {
1012 let condition_result = test_condition.execute(shell, &condition_params).await?;
1013
1014 shell.set_last_exit_status(condition_result.exit_code.into());
1016
1017 if !condition_result.is_normal_flow() {
1018 result = condition_result;
1019
1020 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1023 break;
1024 }
1025
1026 if condition_result.is_success() != is_while {
1027 break;
1028 }
1029
1030 result = body.list.execute(shell, params).await?;
1031 if result.is_return_or_exit() {
1032 break;
1033 }
1034
1035 let is_break = result.is_break();
1036
1037 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1038
1039 if is_break || result.is_continue() {
1040 break;
1041 }
1042 }
1043
1044 shell.set_last_exit_status(result.exit_code.into());
1045 Ok(result)
1046 }
1047}
1048
1049#[async_trait::async_trait]
1050impl Execute for ast::ArithmeticCommand {
1051 async fn execute(
1052 &self,
1053 shell: &mut Shell<impl extensions::ShellExtensions>,
1054 params: &ExecutionParameters,
1055 ) -> Result<ExecutionResult, error::Error> {
1056 let value = self.expr.eval(shell, params, true).await?;
1057 let result = if value != 0 {
1058 ExecutionResult::success()
1059 } else {
1060 ExecutionResult::general_error()
1061 };
1062
1063 shell.set_last_exit_status(result.exit_code.into());
1064
1065 Ok(result)
1066 }
1067}
1068
1069#[async_trait::async_trait]
1070impl Execute for ast::ArithmeticForClauseCommand {
1071 async fn execute(
1072 &self,
1073 shell: &mut Shell<impl extensions::ShellExtensions>,
1074 params: &ExecutionParameters,
1075 ) -> Result<ExecutionResult, error::Error> {
1076 let mut result = ExecutionResult::success();
1077 if let Some(initializer) = &self.initializer {
1078 initializer.eval(shell, params, true).await?;
1079 }
1080
1081 loop {
1082 if let Some(condition) = &self.condition {
1083 if !condition.value.is_empty() && condition.eval(shell, params, true).await? == 0 {
1085 break;
1086 }
1087 }
1088
1089 result = self.body.list.execute(shell, params).await?;
1090 if result.is_return_or_exit() {
1091 break;
1092 }
1093
1094 let is_break = result.is_break();
1095
1096 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1097
1098 if is_break || result.is_continue() {
1099 break;
1100 }
1101
1102 if let Some(updater) = &self.updater {
1103 updater.eval(shell, params, true).await?;
1104 }
1105 }
1106
1107 shell.set_last_exit_status(result.exit_code.into());
1108 Ok(result)
1109 }
1110}
1111
1112#[async_trait::async_trait]
1113impl Execute for ast::FunctionDefinition {
1114 async fn execute(
1115 &self,
1116 shell: &mut Shell<impl extensions::ShellExtensions>,
1117 _params: &ExecutionParameters,
1118 ) -> Result<ExecutionResult, error::Error> {
1119 let func_name = self.fname.value.clone();
1120
1121 if shell.options().posix_mode
1123 && shell
1124 .builtins()
1125 .get(&func_name)
1126 .is_some_and(|r| r.special_builtin)
1127 {
1128 return Err(
1129 error::Error::from(error::ErrorKind::FunctionNameShadowsSpecialBuiltin {
1130 name: func_name,
1131 })
1132 .into_fatal(),
1133 );
1134 }
1135
1136 let source_info = shell
1139 .call_stack()
1140 .current_frame()
1141 .map_or_else(crate::SourceInfo::default, |frame| {
1142 frame.adjusted_source_info()
1143 });
1144 shell.define_func(func_name, self.clone(), &source_info);
1145
1146 let result = ExecutionResult::success();
1147 shell.set_last_exit_status(result.exit_code.into());
1148
1149 Ok(result)
1150 }
1151}
1152
1153#[async_trait::async_trait]
1154#[allow(clippy::too_many_lines)]
1155impl<SE: extensions::ShellExtensions> ExecuteInPipeline<SE> for ast::SimpleCommand {
1156 async fn execute_in_pipeline(
1157 &self,
1158 mut context: PipelineExecutionContext<'_, SE>,
1159 mut params: ExecutionParameters,
1160 ) -> Result<ExecutionSpawnResult, error::Error> {
1161 let prefix_iter = self.prefix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1162 let suffix_iter = self.suffix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1163 let cmd_name_items = self
1164 .word_or_name
1165 .as_ref()
1166 .map(|won| CommandPrefixOrSuffixItem::Word(won.clone()));
1167
1168 let mut assignments = vec![];
1169 let mut args: Vec<CommandArg> = vec![];
1170 let mut command_takes_assignments = false;
1171
1172 let status_change_count_before_expansion = context.shell.last_exit_status_change_count();
1175
1176 for item in prefix_iter.chain(cmd_name_items.iter()).chain(suffix_iter) {
1177 match item {
1178 CommandPrefixOrSuffixItem::IoRedirect(redirect) => {
1179 if let Err(e) = setup_redirect(&mut context.shell, &mut params, redirect).await
1180 {
1181 writeln!(params.stderr(&context.shell), "error: {e}")?;
1182 return Ok(ExecutionResult::general_error().into());
1183 }
1184 }
1185 CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => {
1186 let (installed_fd_num, substitution_file) = setup_process_substitution(
1187 &context.shell,
1188 ¶ms,
1189 kind,
1190 subshell_command,
1191 )?;
1192
1193 params
1194 .open_files
1195 .set_fd(installed_fd_num, substitution_file);
1196
1197 args.push(CommandArg::String(std::format!(
1198 "/dev/fd/{installed_fd_num}"
1199 )));
1200 }
1201 CommandPrefixOrSuffixItem::AssignmentWord(assignment, word) => {
1202 if args.is_empty() {
1203 assignments.push(assignment);
1206 } else {
1207 if command_takes_assignments {
1208 let expanded =
1212 expand_assignment(&mut context.shell, ¶ms, assignment).await?;
1213 args.push(CommandArg::Assignment(expanded));
1214 } else {
1215 let mut next_args = expansion::full_expand_and_split_word(
1219 &mut context.shell,
1220 ¶ms,
1221 word,
1222 )
1223 .await?
1224 .into_iter()
1225 .map(CommandArg::String)
1226 .collect();
1227 args.append(&mut next_args);
1228 }
1229 }
1230 }
1231 CommandPrefixOrSuffixItem::Word(arg) => {
1232 let mut next_args =
1233 expansion::full_expand_and_split_word(&mut context.shell, ¶ms, arg)
1234 .await?;
1235
1236 if args.is_empty() {
1237 if let Some(cmd_name) = next_args.first() {
1238 if let Some(alias_value) =
1239 context.shell.aliases().get(cmd_name.as_str())
1240 {
1241 let mut alias_pieces: Vec<_> = alias_value
1246 .split_ascii_whitespace()
1247 .map(|i| i.to_owned())
1248 .collect();
1249
1250 next_args.remove(0);
1251 alias_pieces.append(&mut next_args);
1252
1253 next_args = alias_pieces;
1254 }
1255
1256 let first_arg = next_args[0].as_str();
1257
1258 if context
1261 .shell
1262 .builtins()
1263 .get(first_arg)
1264 .is_some_and(|r| !r.disabled && r.declaration_builtin)
1265 {
1266 command_takes_assignments = true;
1267 }
1268 }
1269 }
1270
1271 let mut next_args = next_args.into_iter().map(CommandArg::String).collect();
1272 args.append(&mut next_args);
1273 }
1274 }
1275 }
1276
1277 if let Some(CommandArg::String(cmd_name)) = args.first().cloned() {
1279 let mut stderr = params.stderr(&context.shell);
1280
1281 let (owned_shell, parent_shell) = match context.shell {
1282 commands::ShellForCommand::ParentShell(shell) => (None, shell),
1283 commands::ShellForCommand::OwnedShell { target, parent } => (Some(target), parent),
1284 };
1285
1286 let shell = if let Some(owned_shell) = owned_shell {
1287 commands::ShellForCommand::OwnedShell {
1288 target: owned_shell,
1289 parent: parent_shell,
1290 }
1291 } else {
1292 commands::ShellForCommand::ParentShell(parent_shell)
1293 };
1294
1295 let context = PipelineExecutionContext {
1296 shell,
1297 process_group_id: context.process_group_id,
1298 };
1299
1300 match execute_command(context, params, cmd_name, assignments, args).await {
1301 Ok(result) => Ok(result),
1302 Err(err) => {
1303 let _ = parent_shell.display_error(&mut stderr, &err);
1304
1305 let result = err.into_result(parent_shell);
1306 Ok(result.into())
1307 }
1308 }
1309 } else {
1310 for assignment in assignments {
1312 apply_assignment(
1315 assignment,
1316 &mut context.shell,
1317 ¶ms,
1318 false,
1319 None,
1320 EnvironmentScope::Global,
1321 )
1322 .await?;
1323 }
1324
1325 context.shell.update_last_arg_variable(None);
1329
1330 if status_change_count_before_expansion == context.shell.last_exit_status_change_count()
1335 {
1336 context.shell.set_last_exit_status(0);
1337 }
1338
1339 Ok(ExecutionResult::new(context.shell.last_exit_status()).into())
1342 }
1343 }
1344}
1345
1346async fn execute_command(
1347 mut context: PipelineExecutionContext<'_, impl extensions::ShellExtensions>,
1348 params: ExecutionParameters,
1349 cmd_name: String,
1350 assignments: Vec<&ast::Assignment>,
1351 args: Vec<CommandArg>,
1352) -> Result<ExecutionSpawnResult, error::Error> {
1353 let mut guard = crate::env::ScopeGuard::new(&mut context.shell, EnvironmentScope::Command);
1357
1358 for assignment in &assignments {
1359 apply_assignment(
1361 assignment,
1362 guard.shell(),
1363 ¶ms,
1364 true,
1365 Some(EnvironmentScope::Command),
1366 EnvironmentScope::Command,
1367 )
1368 .await?;
1369 }
1370
1371 if guard.shell().options().print_commands_and_arguments {
1372 guard
1373 .shell()
1374 .trace_command(
1375 ¶ms,
1376 args.iter().map(|arg| arg.quote_for_tracing()).join(" "),
1377 )
1378 .await;
1379 }
1380
1381 guard.detach();
1382 drop(guard);
1383
1384 let mut cmd = commands::SimpleCommand::new(context.shell, params, cmd_name, args);
1386 cmd.process_group_id = context.process_group_id;
1387
1388 cmd.post_execute = Some(|shell| shell.env_mut().pop_scope(EnvironmentScope::Command));
1390
1391 let _ = commands::on_preexecute(&mut cmd).await;
1393
1394 cmd.execute().await
1397}
1398
1399async fn expand_assignment(
1400 shell: &mut Shell<impl extensions::ShellExtensions>,
1401 params: &ExecutionParameters,
1402 assignment: &ast::Assignment,
1403) -> Result<ast::Assignment, error::Error> {
1404 let value = expand_assignment_value(shell, params, &assignment.value).await?;
1405 Ok(ast::Assignment {
1406 name: basic_expand_assignment_name(shell, params, &assignment.name).await?,
1407 value,
1408 append: assignment.append,
1409 loc: assignment.loc.clone(),
1410 })
1411}
1412
1413async fn basic_expand_assignment_name(
1414 shell: &mut Shell<impl extensions::ShellExtensions>,
1415 params: &ExecutionParameters,
1416 name: &ast::AssignmentName,
1417) -> Result<ast::AssignmentName, error::Error> {
1418 match name {
1419 ast::AssignmentName::VariableName(name) => {
1420 let expanded = expansion::basic_expand_word(shell, params, name).await?;
1421 Ok(ast::AssignmentName::VariableName(expanded))
1422 }
1423 ast::AssignmentName::ArrayElementName(name, index) => {
1424 let expanded_name = expansion::basic_expand_word(shell, params, name).await?;
1425 let expanded_index = expansion::basic_expand_word(shell, params, index).await?;
1426 Ok(ast::AssignmentName::ArrayElementName(
1427 expanded_name,
1428 expanded_index,
1429 ))
1430 }
1431 }
1432}
1433
1434async fn expand_assignment_value(
1435 shell: &mut Shell<impl extensions::ShellExtensions>,
1436 params: &ExecutionParameters,
1437 value: &ast::AssignmentValue,
1438) -> Result<ast::AssignmentValue, error::Error> {
1439 let expanded = match value {
1440 ast::AssignmentValue::Scalar(s) => {
1441 let expanded_word = expansion::basic_expand_assignment_word(shell, params, s).await?;
1442 ast::AssignmentValue::Scalar(ast::Word::from(expanded_word))
1443 }
1444 ast::AssignmentValue::Array(arr) => {
1445 let mut expanded_values = vec![];
1446 for (key, value) in arr {
1447 if let Some(k) = key {
1448 let expanded_key = expansion::basic_expand_assignment_word(shell, params, k)
1449 .await?
1450 .into();
1451 let expanded_value =
1452 expansion::basic_expand_assignment_word(shell, params, value)
1453 .await?
1454 .into();
1455 expanded_values.push((Some(expanded_key), expanded_value));
1456 } else {
1457 let split_expanded_value =
1459 expansion::full_expand_and_split_word(shell, params, value).await?;
1460 for expanded_value in split_expanded_value {
1461 expanded_values.push((None, expanded_value.into()));
1462 }
1463 }
1464 }
1465
1466 ast::AssignmentValue::Array(expanded_values)
1467 }
1468 };
1469
1470 Ok(expanded)
1471}
1472
1473#[expect(clippy::too_many_lines)]
1474async fn apply_assignment(
1475 assignment: &ast::Assignment,
1476 shell: &mut Shell<impl extensions::ShellExtensions>,
1477 params: &ExecutionParameters,
1478 mut export: bool,
1479 required_scope: Option<EnvironmentScope>,
1480 creation_scope: EnvironmentScope,
1481) -> Result<(), error::Error> {
1482 let mut array_index;
1485 let variable_name = match &assignment.name {
1486 ast::AssignmentName::VariableName(name) => {
1487 array_index = None;
1488 name
1489 }
1490 ast::AssignmentName::ArrayElementName(name, index) => {
1491 let expanded = expansion::basic_expand_word(shell, params, index).await?;
1492 array_index = Some(expanded);
1493 name
1494 }
1495 };
1496
1497 let new_value = match &assignment.value {
1499 ast::AssignmentValue::Scalar(unexpanded_value) => {
1500 let value =
1501 expansion::basic_expand_assignment_word(shell, params, unexpanded_value).await?;
1502 ShellValueLiteral::Scalar(value)
1503 }
1504 ast::AssignmentValue::Array(unexpanded_values) => {
1505 let mut elements = vec![];
1506 for (unexpanded_key, unexpanded_value) in unexpanded_values {
1507 let key = match unexpanded_key {
1508 Some(unexpanded_key) => Some(
1509 expansion::basic_expand_assignment_word(shell, params, unexpanded_key)
1510 .await?,
1511 ),
1512 None => None,
1513 };
1514
1515 if key.is_some() {
1516 let value =
1517 expansion::basic_expand_assignment_word(shell, params, unexpanded_value)
1518 .await?;
1519 elements.push((key, value));
1520 } else {
1521 let values =
1523 expansion::full_expand_and_split_word(shell, params, unexpanded_value)
1524 .await?;
1525 for value in values {
1526 elements.push((None, value));
1527 }
1528 }
1529 }
1530 ShellValueLiteral::Array(ArrayLiteral(elements))
1531 }
1532 };
1533
1534 if shell.options().print_commands_and_arguments {
1535 let op = if assignment.append { "+=" } else { "=" };
1536 shell
1537 .trace_command(params, std::format!("{}{op}{new_value}", assignment.name))
1538 .await;
1539 }
1540
1541 if let Some(idx) = &array_index {
1543 let will_be_indexed_array = if let Some((_, existing_value)) =
1544 shell.env().get(variable_name)
1545 {
1546 matches!(
1547 existing_value.value(),
1548 ShellValue::IndexedArray(_) | ShellValue::Unset(ShellValueUnsetType::IndexedArray)
1549 )
1550 } else {
1551 true
1552 };
1553
1554 if will_be_indexed_array {
1555 array_index = Some(
1556 arithmetic::expand_and_eval(shell, params, idx.as_str(), false)
1557 .await?
1558 .to_string(),
1559 );
1560 }
1561 }
1562
1563 let export_variables_on_modification = shell.options().export_variables_on_modification;
1565
1566 if let Some((existing_value_scope, existing_value)) =
1568 shell.env_mut().get_mut(variable_name.as_str())
1569 {
1570 if required_scope.is_none() || Some(existing_value_scope) == required_scope {
1571 if let Some(array_index) = array_index {
1572 match new_value {
1573 ShellValueLiteral::Scalar(s) => {
1574 existing_value.assign_at_index(array_index, s, assignment.append)?;
1575 }
1576 ShellValueLiteral::Array(_) => {
1577 return error::unimp("replacing an array item with an array");
1578 }
1579 }
1580 } else {
1581 if !export
1582 && export_variables_on_modification
1583 && !matches!(new_value, ShellValueLiteral::Array(_))
1584 {
1585 export = true;
1586 }
1587
1588 existing_value.assign(new_value, assignment.append)?;
1589 }
1590
1591 if export {
1592 existing_value.export();
1593 }
1594
1595 return Ok(());
1597 }
1598 }
1599
1600 let new_value = if let Some(array_index) = array_index {
1602 match new_value {
1603 ShellValueLiteral::Scalar(s) => {
1604 ShellValue::indexed_array_from_literals(ArrayLiteral(vec![(Some(array_index), s)]))
1605 }
1606 ShellValueLiteral::Array(_) => {
1607 return error::unimp("cannot assign list to array member");
1608 }
1609 }
1610 } else {
1611 match new_value {
1612 ShellValueLiteral::Scalar(s) => {
1613 export = export || shell.options().export_variables_on_modification;
1614 ShellValue::String(s)
1615 }
1616 ShellValueLiteral::Array(values) => ShellValue::indexed_array_from_literals(values),
1617 }
1618 };
1619
1620 let mut new_var = ShellVariable::new(new_value);
1621
1622 if export {
1623 new_var.export();
1624 }
1625
1626 shell.env_mut().add(variable_name, new_var, creation_scope)
1627}
1628
1629#[expect(clippy::too_many_lines)]
1630pub(crate) async fn setup_redirect(
1631 shell: &mut Shell<impl extensions::ShellExtensions>,
1632 params: &'_ mut ExecutionParameters,
1633 redirect: &ast::IoRedirect,
1634) -> Result<(), error::Error> {
1635 match redirect {
1636 ast::IoRedirect::OutputAndError(f, append) => {
1637 let mut expanded_fields =
1638 expansion::full_expand_and_split_word(shell, params, f).await?;
1639 if expanded_fields.len() != 1 {
1640 return Err(error::ErrorKind::InvalidRedirection.into());
1641 }
1642
1643 let expanded_file_path = expanded_fields.remove(0);
1644 setup_redirect_output_and_error_to(shell, params, &expanded_file_path, *append)?;
1645 }
1646
1647 ast::IoRedirect::File(specified_fd_num, kind, target) => {
1648 match target {
1649 ast::IoFileRedirectTarget::Filename(f) => {
1650 let mut options = std::fs::File::options();
1651
1652 let mut expanded_fields =
1653 expansion::full_expand_and_split_word(shell, params, f).await?;
1654
1655 if expanded_fields.len() != 1 {
1656 return Err(error::ErrorKind::InvalidRedirection.into());
1657 }
1658
1659 let expanded_file_path: PathBuf =
1660 shell.absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1661
1662 let default_fd_if_unspecified = get_default_fd_for_redirect_kind(kind);
1663 match kind {
1664 ast::IoFileRedirectKind::Read => {
1665 options.read(true);
1666 }
1667 ast::IoFileRedirectKind::Write => {
1668 if shell
1669 .options()
1670 .disallow_overwriting_regular_files_via_output_redirection
1671 {
1672 if !expanded_file_path.is_file() {
1675 options.create(true);
1676 } else {
1677 options.create_new(true);
1678 }
1679 options.write(true);
1680 } else {
1681 options.create(true);
1682 options.write(true);
1683 options.truncate(true);
1684 }
1685 }
1686 ast::IoFileRedirectKind::Append => {
1687 options.create(true);
1688 options.append(true);
1689 }
1690 ast::IoFileRedirectKind::ReadAndWrite => {
1691 options.create(true);
1692 options.read(true);
1693 options.write(true);
1694 }
1695 ast::IoFileRedirectKind::Clobber => {
1696 options.create(true);
1697 options.write(true);
1698 options.truncate(true);
1699 }
1700 ast::IoFileRedirectKind::DuplicateInput => {
1701 options.read(true);
1702 }
1703 ast::IoFileRedirectKind::DuplicateOutput => {
1704 options.create(true);
1705 options.write(true);
1706 }
1707 }
1708
1709 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1710
1711 let opened_file = shell
1712 .open_file(&options, &expanded_file_path, params)
1713 .map_err(|err| {
1714 error::ErrorKind::RedirectionFailure(
1715 expanded_file_path.to_string_lossy().to_string(),
1716 err.to_string(),
1717 )
1718 })?;
1719
1720 params.open_files.set_fd(fd_num, opened_file);
1721 }
1722
1723 ast::IoFileRedirectTarget::Fd(fd) => {
1724 let default_fd_if_unspecified = match kind {
1725 ast::IoFileRedirectKind::DuplicateInput => 0,
1726 ast::IoFileRedirectKind::DuplicateOutput => 1,
1727 _ => {
1728 return error::unimp("unexpected redirect kind");
1729 }
1730 };
1731
1732 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1733
1734 if let Some(f) = params.try_fd(shell, *fd) {
1735 let target_file = f.try_clone()?;
1736
1737 params.open_files.set_fd(fd_num, target_file);
1738 } else {
1739 return Err(error::ErrorKind::BadFileDescriptor(*fd).into());
1740 }
1741 }
1742
1743 ast::IoFileRedirectTarget::Duplicate(word) => {
1744 let default_fd_if_unspecified = match kind {
1745 ast::IoFileRedirectKind::DuplicateInput => 0,
1746 ast::IoFileRedirectKind::DuplicateOutput => 1,
1747 _ => {
1748 return error::unimp("unexpected redirect kind");
1749 }
1750 };
1751
1752 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1753
1754 let mut expanded_fields =
1755 expansion::full_expand_and_split_word(shell, params, word).await?;
1756
1757 if expanded_fields.len() != 1 {
1758 return Err(error::ErrorKind::InvalidRedirection.into());
1759 }
1760
1761 let mut expanded = expanded_fields.remove(0);
1762
1763 let dash = if expanded.ends_with('-') {
1764 expanded.pop();
1765 true
1766 } else {
1767 false
1768 };
1769
1770 if expanded.is_empty() {
1771 } else if expanded.chars().all(|c: char| c.is_ascii_digit()) {
1773 let source_fd_num = expanded
1774 .parse::<ShellFd>()
1775 .map_err(|_| error::ErrorKind::InvalidRedirection)?;
1776
1777 let target_file = if let Some(f) = params.try_fd(shell, source_fd_num) {
1779 f.try_clone()?
1780 } else {
1781 return Err(error::ErrorKind::BadFileDescriptor(source_fd_num).into());
1782 };
1783
1784 params.open_files.set_fd(fd_num, target_file);
1785 } else if fd_num == 1 && !dash {
1786 setup_redirect_output_and_error_to(
1789 shell, params, &expanded, false, )?;
1791 } else {
1792 return Err(error::ErrorKind::InvalidRedirection.into());
1793 }
1794
1795 if dash {
1796 params.open_files.remove_fd(fd_num);
1798 }
1799 }
1800
1801 ast::IoFileRedirectTarget::ProcessSubstitution(substitution_kind, subshell_cmd) => {
1802 match kind {
1803 ast::IoFileRedirectKind::Read
1804 | ast::IoFileRedirectKind::Write
1805 | ast::IoFileRedirectKind::Append
1806 | ast::IoFileRedirectKind::ReadAndWrite
1807 | ast::IoFileRedirectKind::Clobber => {
1808 let (substitution_fd, substitution_file) = setup_process_substitution(
1809 shell,
1810 params,
1811 substitution_kind,
1812 subshell_cmd,
1813 )?;
1814
1815 let target_file = substitution_file.try_clone()?;
1816 params.open_files.set_fd(substitution_fd, substitution_file);
1817
1818 let fd_num = specified_fd_num
1819 .unwrap_or_else(|| get_default_fd_for_redirect_kind(kind));
1820
1821 params.open_files.set_fd(fd_num, target_file);
1822 }
1823 _ => return error::unimp("invalid process substitution"),
1824 }
1825 }
1826 }
1827 }
1828
1829 ast::IoRedirect::HereDocument(fd_num, io_here) => {
1830 let fd_num = fd_num.unwrap_or(0);
1832
1833 let io_here_doc = if io_here.requires_expansion {
1835 expansion::basic_expand_heredoc_word(shell, params, &io_here.doc).await?
1836 } else {
1837 io_here.doc.flatten()
1838 };
1839
1840 let f = setup_open_file_with_contents(io_here_doc.as_str())?;
1841
1842 params.open_files.set_fd(fd_num, f);
1843 }
1844
1845 ast::IoRedirect::HereString(fd_num, word) => {
1846 let fd_num = fd_num.unwrap_or(0);
1848
1849 let mut expanded_word = expansion::basic_expand_word(shell, params, word).await?;
1850 expanded_word.push('\n');
1851
1852 let f = setup_open_file_with_contents(expanded_word.as_str())?;
1853
1854 params.open_files.set_fd(fd_num, f);
1855 }
1856 }
1857
1858 Ok(())
1859}
1860
1861fn setup_redirect_output_and_error_to(
1870 shell: &Shell<impl extensions::ShellExtensions>,
1871 params: &mut ExecutionParameters,
1872 file_path: &str,
1873 append: bool,
1874) -> Result<(), error::Error> {
1875 let abs_file_path: PathBuf = shell.absolute_path(Path::new(file_path));
1876
1877 let mut file_options = std::fs::File::options();
1878 file_options
1879 .create(true)
1880 .write(true)
1881 .truncate(!append)
1882 .append(append);
1883
1884 let stdout_file = shell
1885 .open_file(&file_options, &abs_file_path, params)
1886 .map_err(|err| {
1887 error::ErrorKind::RedirectionFailure(
1888 abs_file_path.to_string_lossy().to_string(),
1889 err.to_string(),
1890 )
1891 })?;
1892
1893 let stderr_file = stdout_file.try_clone()?;
1894
1895 params.open_files.set_fd(OpenFiles::STDOUT_FD, stdout_file);
1896 params.open_files.set_fd(OpenFiles::STDERR_FD, stderr_file);
1897
1898 Ok(())
1899}
1900
1901const fn get_default_fd_for_redirect_kind(kind: &ast::IoFileRedirectKind) -> ShellFd {
1902 match kind {
1903 ast::IoFileRedirectKind::Read => 0,
1904 ast::IoFileRedirectKind::Write => 1,
1905 ast::IoFileRedirectKind::Append => 1,
1906 ast::IoFileRedirectKind::ReadAndWrite => 0,
1907 ast::IoFileRedirectKind::Clobber => 1,
1908 ast::IoFileRedirectKind::DuplicateInput => 0,
1909 ast::IoFileRedirectKind::DuplicateOutput => 1,
1910 }
1911}
1912
1913fn setup_process_substitution(
1914 shell: &Shell<impl extensions::ShellExtensions>,
1915 params: &ExecutionParameters,
1916 kind: &ast::ProcessSubstitutionKind,
1917 subshell_cmd: &ast::SubshellCommand,
1918) -> Result<(ShellFd, OpenFile), error::Error> {
1919 let mut subshell = shell.clone();
1922
1923 let mut child_params = params.clone();
1925 child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
1926
1927 let (reader, writer) = std::io::pipe()?;
1929 let (reader, writer) = (reader.into(), writer.into());
1930
1931 let target_file = match kind {
1932 ast::ProcessSubstitutionKind::Read => {
1933 child_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
1934 reader
1935 }
1936 ast::ProcessSubstitutionKind::Write => {
1937 child_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
1938 writer
1939 }
1940 };
1941
1942 let subshell_cmd = subshell_cmd.to_owned();
1945 tokio::spawn(async move {
1946 let _ = subshell_cmd
1948 .list
1949 .execute(&mut subshell, &child_params)
1950 .await;
1951 });
1952
1953 let mut candidate_fd_num = 63;
1956 while params.open_files.contains_fd(candidate_fd_num) {
1957 candidate_fd_num -= 1;
1958 if candidate_fd_num == 0 {
1959 return error::unimp("no available file descriptors");
1960 }
1961 }
1962
1963 Ok((candidate_fd_num, target_file))
1964}
1965
1966fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Error> {
1967 let (reader, mut writer) = std::io::pipe()?;
1968
1969 let bytes = contents.as_bytes();
1970
1971 #[cfg(any(target_os = "linux", target_os = "android"))]
1972 {
1973 use std::os::fd::AsFd as _;
1974
1975 let len = i32::try_from(bytes.len())
1976 .map_err(|_err| error::Error::from(error::ErrorKind::TooMuchData))?;
1977 nix::fcntl::fcntl(reader.as_fd(), nix::fcntl::FcntlArg::F_SETPIPE_SZ(len))?;
1978 }
1979
1980 writer.write_all(bytes)?;
1981 drop(writer);
1982
1983 Ok(reader.into())
1984}