cicada/
jobc.rs

1use std::io::Write;
2
3use nix::sys::signal::Signal;
4use nix::sys::wait::waitpid;
5use nix::sys::wait::WaitPidFlag as WF;
6use nix::sys::wait::WaitStatus as WS;
7use nix::unistd::Pid;
8
9use crate::shell;
10use crate::signals;
11use crate::types::{self, CommandResult};
12
13pub fn get_job_line(job: &types::Job, trim: bool) -> String {
14    let mut cmd = job.cmd.clone();
15    if trim && cmd.len() > 50 {
16        cmd.truncate(50);
17        cmd.push_str(" ...");
18    }
19    let _cmd = if job.is_bg && job.status == "Running" {
20        format!("{} &", cmd)
21    } else {
22        cmd
23    };
24    format!("[{}] {}  {}   {}", job.id, job.gid, job.status, _cmd)
25}
26
27pub fn print_job(job: &types::Job) {
28    let line = get_job_line(job, true);
29    println_stderr!("{}", line);
30}
31
32pub fn mark_job_as_done(sh: &mut shell::Shell, gid: i32, pid: i32, reason: &str) {
33    if let Some(mut job) = sh.remove_pid_from_job(gid, pid) {
34        job.status = reason.to_string();
35        if job.is_bg {
36            println_stderr!("");
37            print_job(&job);
38        }
39    }
40}
41
42pub fn mark_job_as_stopped(sh: &mut shell::Shell, gid: i32, report: bool) {
43    sh.mark_job_as_stopped(gid);
44    if !report {
45        return;
46    }
47
48    // add an extra line to separate output of fg commands if any.
49    if let Some(job) = sh.get_job_by_gid(gid) {
50        println_stderr!("");
51        print_job(job);
52    }
53}
54
55pub fn mark_job_member_stopped(sh: &mut shell::Shell, pid: i32, gid: i32, report: bool) {
56    let _gid = if gid == 0 {
57        unsafe { libc::getpgid(pid) }
58    } else {
59        gid
60    };
61
62    if let Some(job) = sh.mark_job_member_stopped(pid, gid) {
63        if job.all_members_stopped() {
64            mark_job_as_stopped(sh, gid, report);
65        }
66    }
67}
68
69pub fn mark_job_member_continued(sh: &mut shell::Shell, pid: i32, gid: i32) {
70    let _gid = if gid == 0 {
71        unsafe { libc::getpgid(pid) }
72    } else {
73        gid
74    };
75
76    if let Some(job) = sh.mark_job_member_continued(pid, gid) {
77        if job.all_members_running() {
78            mark_job_as_running(sh, gid, true);
79        }
80    }
81}
82
83pub fn mark_job_as_running(sh: &mut shell::Shell, gid: i32, bg: bool) {
84    sh.mark_job_as_running(gid, bg);
85}
86
87#[allow(unreachable_patterns)]
88pub fn waitpidx(wpid: i32, block: bool) -> types::WaitStatus {
89    let options = if block {
90        Some(WF::WUNTRACED | WF::WCONTINUED)
91    } else {
92        Some(WF::WUNTRACED | WF::WCONTINUED | WF::WNOHANG)
93    };
94    match waitpid(Pid::from_raw(wpid), options) {
95        Ok(WS::Exited(pid, status)) => {
96            let pid = i32::from(pid);
97            types::WaitStatus::from_exited(pid, status)
98        }
99        Ok(WS::Stopped(pid, sig)) => {
100            let pid = i32::from(pid);
101            types::WaitStatus::from_stopped(pid, sig as i32)
102        }
103        Ok(WS::Continued(pid)) => {
104            let pid = i32::from(pid);
105            types::WaitStatus::from_continuted(pid)
106        }
107        Ok(WS::Signaled(pid, sig, _core_dumped)) => {
108            let pid = i32::from(pid);
109            types::WaitStatus::from_signaled(pid, sig as i32)
110        }
111        Ok(WS::StillAlive) => types::WaitStatus::empty(),
112        Ok(_others) => {
113            // this is for PtraceEvent and PtraceSyscall on Linux,
114            // unreachable on other platforms.
115            types::WaitStatus::from_others()
116        }
117        Err(e) => types::WaitStatus::from_error(e as i32),
118    }
119}
120
121pub fn wait_fg_job(sh: &mut shell::Shell, gid: i32, pids: &[i32]) -> CommandResult {
122    let mut cmd_result = CommandResult::new();
123    let mut count_waited = 0;
124    let count_child = pids.len();
125    if count_child == 0 {
126        return cmd_result;
127    }
128    let pid_last = pids.last().unwrap();
129
130    loop {
131        let ws = waitpidx(-1, true);
132        // here when we calling waitpidx(), all signals should have
133        // been masked. There should no errors (ECHILD/EINTR etc) happen.
134        if ws.is_error() {
135            let err = ws.get_errno();
136            if err == nix::Error::ECHILD {
137                break;
138            }
139
140            log!("jobc unexpected waitpid error: {}", err);
141            cmd_result = CommandResult::from_status(gid, err as i32);
142            break;
143        }
144
145        let pid = ws.get_pid();
146        let is_a_fg_child = pids.contains(&pid);
147        if is_a_fg_child && !ws.is_continued() {
148            count_waited += 1;
149        }
150
151        if ws.is_exited() {
152            if is_a_fg_child {
153                mark_job_as_done(sh, gid, pid, "Done");
154            } else {
155                let status = ws.get_status();
156                signals::insert_reap_map(pid, status);
157            }
158        } else if ws.is_stopped() {
159            if is_a_fg_child {
160                // for stop signal of fg job (current job)
161                // i.e. Ctrl-Z is pressed on the fg job
162                mark_job_member_stopped(sh, pid, gid, true);
163            } else {
164                // for stop signal of bg jobs
165                signals::insert_stopped_map(pid);
166                mark_job_member_stopped(sh, pid, 0, false);
167            }
168        } else if ws.is_continued() {
169            if !is_a_fg_child {
170                signals::insert_cont_map(pid);
171            }
172            continue;
173        } else if ws.is_signaled() {
174            if is_a_fg_child {
175                mark_job_as_done(sh, gid, pid, "Killed");
176            } else {
177                signals::killed_map_insert(pid, ws.get_signal());
178            }
179        }
180
181        if is_a_fg_child && pid == *pid_last {
182            let status = ws.get_status();
183            cmd_result.status = status;
184        }
185
186        if count_waited >= count_child {
187            break;
188        }
189    }
190    cmd_result
191}
192
193pub fn try_wait_bg_jobs(sh: &mut shell::Shell, report: bool, sig_handler_enabled: bool) {
194    if sh.jobs.is_empty() {
195        return;
196    }
197
198    if !sig_handler_enabled {
199        // we need to wait pids in case CICADA_ENABLE_SIG_HANDLER=0
200        signals::handle_sigchld(Signal::SIGCHLD as i32);
201    }
202
203    let jobs = sh.jobs.clone();
204    for (_i, job) in jobs.iter() {
205        for pid in job.pids.iter() {
206            if let Some(_status) = signals::pop_reap_map(*pid) {
207                mark_job_as_done(sh, job.gid, *pid, "Done");
208                continue;
209            }
210
211            if let Some(sig) = signals::killed_map_pop(*pid) {
212                let reason = if sig == Signal::SIGQUIT as i32 {
213                    format!("Quit: {}", sig)
214                } else if sig == Signal::SIGINT as i32 {
215                    format!("Interrupt: {}", sig)
216                } else if sig == Signal::SIGKILL as i32 {
217                    format!("Killed: {}", sig)
218                } else if sig == Signal::SIGTERM as i32 {
219                    format!("Terminated: {}", sig)
220                } else {
221                    format!("Killed: {}", sig)
222                };
223                mark_job_as_done(sh, job.gid, *pid, &reason);
224                continue;
225            }
226
227            if signals::pop_stopped_map(*pid) {
228                mark_job_member_stopped(sh, *pid, job.gid, report);
229            } else if signals::pop_cont_map(*pid) {
230                mark_job_member_continued(sh, *pid, job.gid);
231            }
232        }
233    }
234}