shell/
run.rs

1use crate::builtins::run_builtin;
2use crate::command_data::{CommandWithArgs, Run};
3use crate::jobs::{Job, Jobs};
4use crate::parse::parse_line;
5use crate::platform::{FileDesc, Platform, Sys};
6use crate::signals::{install_sigint_handler, mask_signals};
7use std::{env, io};
8
9pub fn setup_shell_tty(shell_terminal: FileDesc) {
10    Sys::terminal_foreground(shell_terminal);
11
12    mask_signals();
13
14    if let Err(err) = Sys::set_self_pgroup() {
15        eprintln!("Couldn't put the shell in its own process group: {}\n", err)
16    }
17    /* Grab control of the terminal.  */
18    if let Err(err) = Sys::grab_terminal(shell_terminal) {
19        eprintln!("Couldn't grab control of terminal: {err}");
20        return;
21    }
22
23    if !install_sigint_handler() {
24        std::process::exit(1)
25    }
26}
27
28/// Finish a job run.
29///
30/// If not a background job will wait for proc to end and return the exit status
31/// or -66 if the wait fails.  If it is a background job then return the pid of
32/// the last proc in the job.
33/// Also sets the LAST_STATUS env var to the exit status (if not background) or
34/// 0 if a background job.
35fn finish_run(background: bool, mut job: Job, jobs: &mut Jobs) -> i32 {
36    job.mark_running();
37    let status = if !background {
38        if let Some(status) = Sys::wait_job(&mut job) {
39            unsafe {
40                env::set_var("LAST_STATUS", format!("{}", status));
41            }
42            status
43        } else {
44            unsafe {
45                env::set_var("LAST_STATUS", format!("{}", -66));
46            }
47            jobs.push_job(job);
48            -66
49        }
50    } else {
51        unsafe {
52            env::set_var("LAST_STATUS", format!("{}", 0));
53        }
54        let pid = if let Some(p) = job.pids().last() {
55            p.pid().try_into().unwrap_or(0)
56        } else {
57            0
58        };
59        jobs.push_job(job);
60        pid
61    };
62    jobs.restore_terminal();
63    status
64}
65
66fn run_command(
67    command: &CommandWithArgs,
68    jobs: &mut Jobs,
69    background: bool,
70    stealth: bool,
71) -> Result<i32, io::Error> {
72    Ok(if let Some(command_name) = command.command(jobs) {
73        let command_name = command_name?;
74        if let Some(init_alias_run) = jobs.remove_alias(command_name.to_string_lossy()) {
75            // need to remove the alias so we don't recurse for alias with a command the same name
76            let mut alias_run = init_alias_run.clone();
77            for arg in command.args_iter() {
78                alias_run.push_arg_end(arg.clone());
79            }
80            if let Some(stdios) = command.stdios() {
81                alias_run.extend_redirs_end(stdios)
82            }
83            let r = run_job(&alias_run, jobs, background);
84            jobs.add_alias_run(command_name.to_string_lossy().to_string(), init_alias_run);
85            r?
86        } else {
87            let mut args = command.args_iter();
88            match run_builtin(&command_name, &mut args, jobs) {
89                Some(status) => status,
90                None => {
91                    let mut job = jobs.new_job();
92                    job.set_stealth(stealth);
93                    match Sys::fork_exec(command, &mut job, jobs) {
94                        Ok(()) => finish_run(background, job, jobs),
95                        Err(err) => {
96                            // Make sure we restore the terminal...
97                            jobs.restore_terminal();
98                            return Err(err);
99                        }
100                    }
101                }
102            }
103        }
104    } else {
105        0
106    })
107}
108
109pub fn run_job(run: &Run, jobs: &mut Jobs, force_background: bool) -> Result<i32, io::Error> {
110    let status = match run {
111        Run::Command(command) => run_command(command, jobs, force_background, force_background)?,
112        Run::BackgroundCommand(command) => run_command(command, jobs, true, false)?,
113        Run::Pipe(pipe) => {
114            let mut job = jobs.new_job();
115            job.set_stealth(force_background);
116            match run_pipe(&pipe[..], &mut job, jobs) {
117                Ok(background) => finish_run(force_background || background, job, jobs),
118                Err(err) => {
119                    // Make sure we restore the terminal...
120                    jobs.restore_terminal();
121                    return Err(err);
122                }
123            }
124        }
125        Run::Sequence(seq) => {
126            let mut status = 0;
127            for r in seq {
128                status = run_job(r, jobs, force_background)?;
129            }
130            status
131        }
132        Run::And(seq) => {
133            // XXXX should background == true be an error?
134            let mut status = 0;
135            for r in seq {
136                status = run_job(r, jobs, force_background)?;
137                if status != 0 {
138                    break;
139                }
140            }
141            status
142        }
143        Run::Or(seq) => {
144            // XXXX should background == true be an error?
145            let mut status = 0;
146            for r in seq {
147                status = run_job(r, jobs, force_background)?;
148                if status == 0 {
149                    break;
150                }
151            }
152            status
153        }
154        Run::Subshell(sub_run) => {
155            let mut job = jobs.new_job();
156            job.set_stealth(force_background);
157            match Sys::fork_run(sub_run, &mut job, jobs) {
158                Ok(()) => finish_run(false, job, jobs),
159                Err(err) => {
160                    // Make sure we restore the terminal...
161                    jobs.restore_terminal();
162                    return Err(err);
163                }
164            }
165        }
166        Run::Empty => 0,
167    };
168    Ok(status)
169}
170
171pub fn run_one_command(command: &str, jobs: &mut Jobs) -> Result<i32, io::Error> {
172    // Try to make sense out of whatever crap we get (looking at you fzf-tmux)
173    // and make it work.
174    let commands = parse_line(jobs, command)?;
175
176    run_job(commands.commands(), jobs, false)
177}
178
179fn pipe_command(
180    command: &CommandWithArgs,
181    next_in: Option<FileDesc>,
182    next_out: Option<FileDesc>,
183    job: &mut Job,
184    jobs: &mut Jobs,
185) -> Result<(), io::Error> {
186    let mut command = command.clone();
187    if let Some(command_name) = command.command(jobs) {
188        let command_name = command_name?;
189        if let Some(mut alias_run) = jobs.get_alias(command_name.to_string_lossy()) {
190            alias_run.push_stdin_front(next_in);
191            alias_run.push_stdout_front(next_out);
192            for arg in command.args_iter() {
193                alias_run.push_arg_end(arg.clone());
194            }
195            if let Some(stdios) = command.stdios() {
196                alias_run.extend_redirs_end(stdios)
197            }
198            match alias_run {
199                Run::Command(command) | Run::BackgroundCommand(command) => {
200                    Sys::fork_exec(&command, job, jobs)?;
201                }
202                _ => {
203                    Sys::fork_run(&alias_run, job, jobs)?;
204                }
205            }
206        } else {
207            command.push_stdin_front(next_in);
208            command.push_stdout_front(next_out);
209            Sys::fork_exec(&command, job, jobs)?;
210        }
211    }
212    Ok(())
213}
214
215fn run_pipe(new_job: &[Run], job: &mut Job, jobs: &mut Jobs) -> Result<bool, io::Error> {
216    let progs = new_job.len();
217    let (p_in, p_out) = Sys::anon_pipe()?;
218    let mut next_in = Some(p_in);
219    let mut next_out = None;
220    let mut upcoming_out = p_out;
221    let mut background = false;
222    for (i, program) in new_job.iter().rev().enumerate() {
223        match program {
224            Run::Command(command) => {
225                pipe_command(command, next_in, next_out, job, jobs)?;
226            }
227            Run::BackgroundCommand(command) => {
228                pipe_command(command, next_in, next_out, job, jobs)?;
229                if i == 0 {
230                    background = true;
231                }
232            }
233            Run::Subshell(_) => {
234                let mut program = program.clone();
235                program.push_stdin_front(next_in);
236                program.push_stdout_front(next_out);
237                if let Run::Subshell(sub_run) = &mut program {
238                    match Sys::fork_run(&*sub_run, job, jobs) {
239                        Ok(()) => {
240                            jobs.restore_terminal();
241                        }
242                        Err(err) => {
243                            // Make sure we restore the terminal...
244                            jobs.restore_terminal();
245                            return Err(err);
246                        }
247                    }
248                }
249            }
250            Run::Pipe(_) | Run::Sequence(_) | Run::And(_) | Run::Or(_) => {
251                // Don't think this is expressible with the parser and maybe should be an error?
252                let mut program = program.clone();
253                program.push_stdin_front(next_in);
254                program.push_stdout_front(next_out);
255                run_job(&program, jobs, true)?;
256            }
257            Run::Empty => {}
258        }
259        next_out = Some(upcoming_out);
260        if i < (progs - 1) {
261            let (p_in, p_out) = Sys::anon_pipe()?;
262            upcoming_out = p_out;
263            next_in = Some(p_in);
264        } else {
265            next_in = None;
266        }
267    }
268    if job.is_empty() {
269        Err(io::Error::other("no processes started"))
270    } else {
271        job.reverse();
272        Ok(background)
273    }
274}