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 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 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 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 mark_job_member_stopped(sh, pid, gid, true);
163 } else {
164 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 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}