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#[derive(Debug, Default)]
25pub struct ExecutionResult {
26 pub exit_code: u8,
28 pub exit_shell: bool,
30 pub return_from_function_or_script: bool,
32 pub break_loop: Option<u8>,
34 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 pub fn new(exit_code: u8) -> Self {
71 Self {
72 exit_code,
73 ..Self::default()
74 }
75 }
76
77 pub fn success() -> Self {
79 Self::new(0)
80 }
81
82 pub const fn is_success(&self) -> bool {
84 self.exit_code == 0
85 }
86
87 pub fn stopped() -> Self {
89 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
97struct PipelineExecutionContext<'a> {
99 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#[derive(Clone, Default)]
111pub struct ExecutionParameters {
112 pub(crate) open_files: openfiles::OpenFiles,
114 pub process_group_policy: ProcessGroupPolicy,
116}
117
118impl ExecutionParameters {
119 pub fn stdin(&self) -> impl std::io::Read + 'static {
121 self.fd(0).unwrap()
122 }
123
124 pub fn stdout(&self) -> impl std::io::Write + 'static {
126 self.fd(1).unwrap()
127 }
128
129 pub fn stderr(&self) -> impl std::io::Write + 'static {
131 self.fd(2).unwrap()
132 }
133
134 #[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)]
155pub enum ProcessGroupPolicy {
157 #[default]
159 NewProcessGroup,
160 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 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 if result.exit_shell || result.return_from_function_or_script {
233 break;
234 }
235
236 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 let mut cloned_shell = shell.clone();
254 let cloned_params = params.clone();
255 let cloned_ao_list = ao_list.clone();
256
257 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 if result.exit_shell || result.return_from_function_or_script {
285 break;
286 }
287
288 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 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 let stopwatch = self
327 .timed
328 .is_some()
329 .then(timing::start_timing)
330 .transpose()?;
331
332 let spawn_results = spawn_pipeline_processes(self, shell, params).await?;
335
336 let mut result =
338 wait_for_pipeline_processes_and_update_status(self, spawn_results, shell).await?;
339
340 if self.bang {
342 result.exit_code = if result.exit_code == 0 { 1 } else { 0 };
343 }
344
345 shell.last_exit_status = result.exit_code;
347
348 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 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 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 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 !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 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 setup_pipeline_redirection(&mut params.open_files, pipeline_context)?;
514
515 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, ¶ms).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, ¶ms).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, ¶ms)
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 let mut subshell = shell.clone();
573 let subshell_result = s.execute(&mut subshell, params).await?;
574
575 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 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 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 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 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)] 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 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, ¶ms, 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 assignments.push(assignment);
961 } else {
962 if command_takes_assignments {
963 let expanded =
967 expand_assignment(context.shell, ¶ms, assignment).await?;
968 args.push(CommandArg::Assignment(expanded));
969 } else {
970 let mut next_args =
974 expansion::full_expand_and_split_word(context.shell, ¶ms, 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, ¶ms, 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 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 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 let Some(CommandArg::String(cmd_name)) = args.first().cloned() {
1029 execute_command(context, params, cmd_name, assignments, args).await
1030 } else {
1031 context.shell.last_exit_status = 0;
1033
1034 for assignment in assignments {
1036 apply_assignment(
1037 assignment,
1038 context.shell,
1039 ¶ms,
1040 false,
1041 None,
1042 EnvironmentScope::Global,
1043 )
1044 .await?;
1045 }
1046
1047 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 context.shell.env.push_scope(EnvironmentScope::Command);
1067 for assignment in &assignments {
1068 apply_assignment(
1070 assignment,
1071 context.shell,
1072 ¶ms,
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 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 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 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 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 let execution_result = commands::execute(
1135 cmd_context,
1136 &mut context.process_group_id,
1137 args,
1138 true, None,
1140 )
1141 .await;
1142
1143 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 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 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 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 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 return Ok(());
1336 }
1337 }
1338
1339 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 if let Some(preceding_output_reader) = context.output_pipes.pop() {
1375 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 context.pipeline_len > 1 && context.current_pipeline_index < context.pipeline_len - 1 {
1387 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 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 } 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 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 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 let fd_num = fd_num.unwrap_or(0);
1617
1618 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 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 let mut subshell = shell.clone();
1667
1668 let mut child_params = params.clone();
1670 child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
1671
1672 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 let subshell_cmd = subshell_cmd.to_owned();
1695 tokio::spawn(async move {
1696 let _ = subshell_cmd.0.execute(&mut subshell, &child_params).await;
1698 });
1699
1700 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}