brush_core/
interp.rs

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