Rust · 7241 bytes Raw Blame History
1 use crate::command::find_in_path;
2 use crate::redirect::apply_redirects;
3 use crate::{ExecutionError, PipelineError};
4 use rush_expand::Context;
5 use rush_interactive::ErrorHints;
6 use rush_parser::ast::{Pipeline, SimpleCommand};
7 use std::process::{Command, Stdio};
8
9 #[cfg(unix)]
10 use nix::unistd::{setpgid, Pid};
11
12 /// Execute a simple command in the background
13 ///
14 /// Returns (pid, pgid, command_string) for adding to JobList
15 #[cfg(unix)]
16 pub fn execute_simple_background(
17 simple_cmd: &SimpleCommand,
18 context: &mut Context,
19 ) -> Result<(Pid, Pid, String), ExecutionError> {
20 // Expand words
21 let expanded = rush_expand::expand_words(&simple_cmd.words, context)
22 .map_err(|e| ExecutionError::IoError(std::io::Error::new(
23 std::io::ErrorKind::Other,
24 e.to_string(),
25 )))?;
26
27 if expanded.is_empty() {
28 return Err(ExecutionError::EmptyCommand);
29 }
30
31 let command_name = &expanded[0];
32 let args = &expanded[1..];
33
34 // Find the command in PATH
35 let program_path = find_in_path(command_name)
36 .ok_or_else(|| ExecutionError::CommandNotFound(ErrorHints::command_not_found(command_name)))?;
37
38 // Build the command string for display
39 let command_string = format!("{} {}", command_name, args.join(" "));
40
41 // Build the command
42 let mut cmd = Command::new(program_path);
43 cmd.args(args);
44
45 // Apply redirections
46 apply_redirects(&mut cmd, &simple_cmd.redirects, context)
47 .map_err(|e| ExecutionError::IoError(std::io::Error::new(
48 std::io::ErrorKind::Other,
49 e.to_string(),
50 )))?;
51
52 // Spawn the command
53 let child = cmd.spawn()?;
54 let pid = Pid::from_raw(child.id() as i32);
55
56 // Put the child in its own process group
57 // This is important for job control
58 let pgid = pid; // Use the PID as the PGID (process becomes group leader)
59 if let Err(e) = setpgid(pid, pgid) {
60 eprintln!("Warning: failed to set process group: {}", e);
61 }
62
63 Ok((pid, pgid, command_string))
64 }
65
66 /// Execute a pipeline in the background
67 ///
68 /// Returns (pids, pgid, command_string) for adding to JobList
69 #[cfg(unix)]
70 pub fn execute_pipeline_background(
71 pipeline: &Pipeline,
72 context: &mut Context,
73 ) -> Result<(Vec<Pid>, Pid, String), PipelineError> {
74 if pipeline.commands.is_empty() {
75 return Err(PipelineError::EmptyPipeline);
76 }
77
78 // Build command string for display
79 let mut command_parts = Vec::new();
80 for element in &pipeline.commands {
81 let simple_cmd = match element {
82 rush_parser::ast::PipelineElement::Simple(cmd) => cmd,
83 rush_parser::ast::PipelineElement::Subshell(_) => {
84 return Err(PipelineError::ExecutionError(ExecutionError::CommandNotFound(
85 "Subshells in pipelines not yet fully supported".to_string()
86 )));
87 }
88 rush_parser::ast::PipelineElement::ExtendedTest(_) => {
89 return Err(PipelineError::ExecutionError(ExecutionError::CommandNotFound(
90 "Extended tests in pipelines not yet supported".to_string()
91 )));
92 }
93 };
94 let expanded = rush_expand::expand_words(&simple_cmd.words, context)
95 .map_err(|e| PipelineError::ExpansionError(e.to_string()))?;
96 if !expanded.is_empty() {
97 command_parts.push(expanded.join(" "));
98 }
99 }
100 let command_string = command_parts.join(" | ");
101
102 // Build and spawn all commands in the pipeline
103 let mut pids = Vec::new();
104 let mut prev_stdout = None;
105 let mut pgid: Option<Pid> = None;
106
107 for (i, element) in pipeline.commands.iter().enumerate() {
108 let is_first = i == 0;
109 let is_last = i == pipeline.commands.len() - 1;
110
111 // Get the simple command from the element
112 let simple_cmd = match element {
113 rush_parser::ast::PipelineElement::Simple(cmd) => cmd,
114 rush_parser::ast::PipelineElement::Subshell(_) => {
115 return Err(PipelineError::ExecutionError(ExecutionError::CommandNotFound(
116 "Subshells in pipelines not yet fully supported".to_string()
117 )));
118 }
119 rush_parser::ast::PipelineElement::ExtendedTest(_) => {
120 return Err(PipelineError::ExecutionError(ExecutionError::CommandNotFound(
121 "Extended tests in pipelines not yet supported".to_string()
122 )));
123 }
124 };
125
126 // Expand words
127 let expanded = rush_expand::expand_words(&simple_cmd.words, context)
128 .map_err(|e| PipelineError::ExpansionError(e.to_string()))?;
129
130 if expanded.is_empty() {
131 continue;
132 }
133
134 let command_name = &expanded[0];
135 let args = &expanded[1..];
136
137 // Find the command in PATH
138 let program_path = find_in_path(command_name)
139 .ok_or_else(|| ExecutionError::CommandNotFound(ErrorHints::command_not_found(command_name)))?;
140
141 // Build the command
142 let mut cmd = Command::new(program_path);
143 cmd.args(args);
144
145 // Set up stdin
146 if is_first {
147 cmd.stdin(Stdio::inherit());
148 } else if let Some(prev_out) = prev_stdout.take() {
149 cmd.stdin(prev_out);
150 }
151
152 // Set up stdout
153 if is_last {
154 cmd.stdout(Stdio::inherit());
155 } else {
156 cmd.stdout(Stdio::piped());
157 }
158
159 cmd.stderr(Stdio::inherit());
160
161 // Apply redirections
162 apply_redirects(&mut cmd, &simple_cmd.redirects, context)?;
163
164 // Spawn the command
165 let mut child = cmd.spawn()?;
166 let pid = Pid::from_raw(child.id() as i32);
167
168 // Set up process group
169 // First process in pipeline becomes the group leader
170 if pgid.is_none() {
171 pgid = Some(pid);
172 if let Err(e) = setpgid(pid, pid) {
173 eprintln!("Warning: failed to set process group leader: {}", e);
174 }
175 } else {
176 // Other processes join the group
177 if let Err(e) = setpgid(pid, pgid.unwrap()) {
178 eprintln!("Warning: failed to join process group: {}", e);
179 }
180 }
181
182 pids.push(pid);
183
184 // Save stdout for next command
185 if !is_last {
186 prev_stdout = child.stdout.take().map(Stdio::from);
187 }
188
189 // Don't wait - we're running in background
190 std::mem::forget(child); // Prevent child from being killed when dropped
191 }
192
193 Ok((pids, pgid.unwrap(), command_string))
194 }
195
196 #[cfg(not(unix))]
197 pub fn execute_simple_background(
198 _simple_cmd: &SimpleCommand,
199 _context: &mut Context,
200 ) -> Result<(u32, u32, String), ExecutionError> {
201 Err(ExecutionError::IoError(std::io::Error::new(
202 std::io::ErrorKind::Unsupported,
203 "Background execution not supported on this platform",
204 )))
205 }
206
207 #[cfg(not(unix))]
208 pub fn execute_pipeline_background(
209 _pipeline: &Pipeline,
210 _context: &mut Context,
211 ) -> Result<(Vec<u32>, u32, String), PipelineError> {
212 Err(PipelineError::IoError(std::io::Error::new(
213 std::io::ErrorKind::Unsupported,
214 "Background execution not supported on this platform",
215 )))
216 }
217