shell/
jobs.rs

1use crate::command_data::Run;
2use crate::parse::parse_line;
3use crate::platform::{OsSignal, Pid, Platform, STDIN_FILENO, Sys, TermSettings};
4use std::collections::HashMap;
5use std::ffi::{OsStr, OsString};
6use std::fmt::{Display, Formatter};
7use std::{env, fmt, io};
8
9/// Status of a Job.
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub enum JobStatus {
12    /// New job, not initialized.
13    New,
14    /// Running job.
15    Running,
16    /// Job is paused in background.
17    Stopped,
18    /// Job is done.
19    Done,
20}
21
22impl fmt::Display for JobStatus {
23    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24        match self {
25            JobStatus::New => write!(f, "New"),
26            JobStatus::Running => write!(f, "Running"),
27            JobStatus::Stopped => write!(f, "Stopped"),
28            JobStatus::Done => write!(f, "Done"),
29        }
30    }
31}
32
33/// Status of a process that is part of a job.
34#[derive(Copy, Clone, Debug)]
35pub enum PidStatus {
36    /// Process is running, contains the pid.
37    Running(Pid),
38    /// Process is done, contains the pid and status code.
39    Done(Pid, i32), // pid, status
40    /// Process had an error, no status code available.  Contains the pid.
41    Error(Pid),
42    /// Process was stopped due to a signal.  Contains the pid and signal that stopped it.
43    Signaled(Pid, OsSignal),
44}
45
46impl PidStatus {
47    pub fn pid(&self) -> Pid {
48        match self {
49            PidStatus::Running(pid) => *pid,
50            PidStatus::Done(pid, _) => *pid,
51            PidStatus::Error(pid) => *pid,
52            PidStatus::Signaled(pid, _) => *pid,
53        }
54    }
55}
56
57#[derive(Clone, Debug)]
58pub struct Job {
59    id: u32,
60    shell_pid: Pid,
61    pgid: Pid,
62    pids: Vec<PidStatus>,
63    names: Vec<String>,
64    status: JobStatus,
65    interactive: bool,
66    stealth: bool, // If true don't report when background job ends.
67}
68
69impl Job {
70    /// Create a new empty job with id.
71    fn new(id: u32, shell_pid: Pid, interactive: bool) -> Self {
72        Self {
73            id,
74            shell_pid,
75            pgid: shell_pid,
76            pids: vec![],
77            names: vec![],
78            status: JobStatus::New,
79            interactive,
80            stealth: false,
81        }
82    }
83
84    /// True if this job is empty (contains no processes).
85    pub fn is_empty(&self) -> bool {
86        self.pids.is_empty()
87    }
88
89    /// The process group id for the job.  Should be equal to the first pid.
90    pub fn pgid(&self) -> Pid {
91        self.pgid
92    }
93
94    /// Pids with status that make up a job.
95    pub fn pids(&self) -> &[PidStatus] {
96        &self.pids[..]
97    }
98
99    /// Status of this job.
100    pub fn status(&self) -> JobStatus {
101        self.status
102    }
103
104    /// Mark the job as stopped.
105    pub fn mark_stopped(&mut self) {
106        self.status = JobStatus::Stopped;
107    }
108
109    /// Mark the job as running.
110    pub fn mark_running(&mut self) {
111        self.status = JobStatus::Running;
112    }
113
114    /// Mark the job status to done.
115    pub fn mark_done(&mut self) {
116        self.status = JobStatus::Done;
117    }
118
119    /// Add pid to list of pids as Running with name.
120    /// If the pid list is currently empty record this pid as the pgid (process group) for the job.
121    pub fn add_process(&mut self, pid: Pid, name: impl Into<String>) {
122        if self.pids.is_empty() {
123            self.pgid = pid;
124        }
125        self.pids.push(PidStatus::Running(pid));
126        self.names.push(name.into());
127    }
128
129    /// Move the process pid to the done state with status.
130    /// Will panic if pid is not part of job.
131    pub fn process_done(&mut self, pid: Pid, status: i32) {
132        for proc in self.pids.iter_mut() {
133            if proc.pid() == pid {
134                *proc = PidStatus::Done(pid, status);
135                return;
136            }
137        }
138        panic!("process {pid} not part of job");
139    }
140
141    /// Move the process pid to the error state.
142    /// Will panic if pid is not part of job.
143    pub fn process_error(&mut self, pid: Pid) {
144        for proc in self.pids.iter_mut() {
145            if proc.pid() == pid {
146                *proc = PidStatus::Error(pid);
147                return;
148            }
149        }
150        panic!("process {pid} not part of job");
151    }
152
153    /// Move the process pid to the signaled state.
154    /// Will panic if pid is not part of job.
155    pub fn process_signaled(&mut self, pid: Pid, signal: OsSignal) {
156        for proc in self.pids.iter_mut() {
157            if proc.pid() == pid {
158                *proc = PidStatus::Signaled(pid, signal);
159                return;
160            }
161        }
162        panic!("process {pid} not part of job");
163    }
164
165    /// Reverse the list of pids in the job.
166    pub fn reverse(&mut self) {
167        self.pids.reverse();
168        self.names.reverse();
169    }
170
171    /// Set the stealth flag, if true then do NOT print out when a background job ends (be stealthy...).
172    pub fn set_stealth(&mut self, stealth: bool) {
173        self.stealth = stealth;
174    }
175
176    /// Is this a stealth job (do not report when complete if in the background)?
177    pub fn stealth(&self) -> bool {
178        self.stealth
179    }
180
181    /// Is this job running in an interactive shell?
182    pub fn interactive(&self) -> bool {
183        self.interactive
184    }
185
186    /// The PID of the shell running this job.
187    pub fn shell_pid(&self) -> Pid {
188        self.shell_pid
189    }
190}
191
192impl Display for Job {
193    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
194        writeln!(
195            f,
196            "[{}]\t{}\t{:?}\t{:?}",
197            self.id, self.status, self.pids, self.names
198        )
199    }
200}
201
202pub struct Jobs {
203    next_job: u32,
204    jobs: Vec<Job>,
205    shell_pid: Pid,
206    interactive: bool,
207    term_settings: Option<TermSettings>,
208    alias: HashMap<String, Run>,
209    local_vars: HashMap<OsString, OsString>,
210}
211
212impl Jobs {
213    pub fn new(interactive: bool) -> Self {
214        let shell_pid = Sys::getpid();
215        let term_settings = if interactive {
216            Sys::get_term_settings(STDIN_FILENO).ok()
217        } else {
218            None
219        };
220        Self {
221            next_job: 0,
222            jobs: vec![],
223            shell_pid,
224            interactive,
225            term_settings,
226            alias: HashMap::new(),
227            local_vars: HashMap::new(),
228        }
229    }
230
231    pub fn cap_term(&mut self) {
232        self.term_settings = Sys::get_term_settings(STDIN_FILENO).ok();
233    }
234
235    /// Create a new job with the next job number.
236    /// Note, this new job is NOT in the jobs list.
237    pub fn new_job(&mut self) -> Job {
238        if self.jobs.is_empty() {
239            self.next_job = 0;
240        }
241        // Job numbers/ids always start at 1.
242        self.next_job += 1;
243        Job::new(self.next_job, self.shell_pid, self.interactive)
244    }
245
246    /// Push job onto the list of jobs.
247    pub fn push_job(&mut self, job: Job) {
248        self.jobs.push(job);
249    }
250
251    /// Set not on a tty.
252    pub fn set_no_tty(&mut self) {
253        self.term_settings = None;
254    }
255
256    /// Sets whether we are interactive or not.
257    pub fn set_interactive(&mut self, interactive: bool) {
258        self.interactive = interactive;
259    }
260
261    /// Get the mutable job for job_id if it exists.
262    pub fn get_job_mut(&mut self, job_id: u32) -> Option<&mut Job> {
263        self.jobs.iter_mut().find(|job| job.id == job_id)
264    }
265
266    /// Check any pids in a job by calling wait and updating the books.
267    pub fn reap_procs(&mut self) {
268        for job in self.jobs.iter_mut() {
269            if let JobStatus::Running = job.status() {
270                let pids: Vec<Pid> = job.pids().iter().map(|s| s.pid()).collect();
271                for pid in &pids {
272                    Sys::try_wait_pid(*pid, job);
273                }
274                let mut done = true;
275                for pid_status in job.pids() {
276                    if let PidStatus::Running(_) = pid_status {
277                        done = false;
278                    }
279                }
280                if done {
281                    job.mark_done();
282                }
283            }
284        }
285        // Remove any Done jobs.
286        let jobs_len = self.jobs.len();
287        for i in (0..jobs_len).rev() {
288            if let JobStatus::Done = self.jobs[i].status() {
289                if !self.jobs[i].stealth() {
290                    eprintln!("{}", self.jobs[i]);
291                }
292                self.jobs.remove(i);
293            }
294        }
295    }
296
297    /// Move the job for job_num to te foreground.
298    pub fn foreground_job(&mut self, job_num: u32) {
299        let term_settings = self.term_settings.clone();
300        if let Some(job) = self.get_job_mut(job_num) {
301            if let Err(err) = Sys::foreground_job(job, &term_settings) {
302                eprintln!("Error making job {job_num} foreground in parent: {err}");
303            }
304        } else {
305            eprintln!("job number {job_num} is invalid");
306        }
307    }
308
309    /// Move the job for job_num to te background and running (start a stopped job in the background).
310    pub fn background_job(&mut self, job_num: u32) {
311        if let Some(job) = self.get_job_mut(job_num) {
312            if let Err(err) = Sys::background_job(job) {
313                eprintln!("Error making job {job_num} background in parent: {err}");
314            }
315        } else {
316            eprintln!("job number {job_num} is invalid");
317        }
318    }
319
320    /// Restore terminal settings and put the shell back into the foreground.
321    pub fn restore_terminal(&self) {
322        // This assumes file descriptor 0 (STDIN) is the terminal.
323        // Move the shell back into the foreground.
324        if let Some(term_settings) = &self.term_settings {
325            // Restore terminal settings.
326            if let Err(err) = Sys::restore_terminal(term_settings, self.shell_pid) {
327                eprintln!("Error resetting shell terminal settings: {}", err);
328            }
329        }
330    }
331
332    /// Gets an alias.
333    pub fn get_alias<S: AsRef<str>>(&self, name: S) -> Option<Run> {
334        self.alias.get(name.as_ref()).cloned()
335    }
336
337    /// Clears all the existing aliases.
338    pub fn clear_aliases(&mut self) {
339        self.alias.clear();
340    }
341
342    /// Removes the alias name, return the value if removed..
343    pub fn remove_alias<S: AsRef<str>>(&mut self, name: S) -> Option<Run> {
344        self.alias.remove(name.as_ref())
345    }
346
347    /// Add an alias.
348    pub fn add_alias(&mut self, name: String, value: String) -> Result<(), io::Error> {
349        let runj = parse_line(self, &value)?;
350        self.alias.insert(name, runj.into_run());
351        Ok(())
352    }
353
354    /// Add an already parsed alias.
355    pub fn add_alias_run(&mut self, name: String, value: Run) {
356        self.alias.insert(name, value);
357    }
358
359    /// Print a the alias for name if set.
360    pub fn print_alias(&self, name: String) {
361        if let Some(value) = self.alias.get(&name) {
362            println!("alias {name}='{value}'");
363        } else {
364            eprintln!("no alias set for: {name}");
365        }
366    }
367
368    /// Print all the defined aliases.
369    pub fn print_all_alias(&self) {
370        for (name, value) in &self.alias {
371            println!("alias {name}='{value}'");
372        }
373    }
374
375    /// Set a local var into key.
376    /// Will overwrite an existing value.
377    pub fn set_local_var(&mut self, key: OsString, val: OsString) {
378        self.local_vars.insert(key, val);
379    }
380
381    /// Get the local var key or None if does not exist.
382    pub fn get_local_var(&self, key: &OsStr) -> Option<&OsStr> {
383        self.local_vars.get(key).map(|s| s as &OsStr)
384    }
385
386    /// Remove the local var key.
387    /// If key exists returns the removed value.
388    pub fn remove_local_var(&mut self, key: &OsStr) -> Option<OsString> {
389        self.local_vars.remove(key)
390    }
391
392    /// First tries to fin key in the environment then checks local variable and returns None if
393    /// not found.
394    pub fn get_env_or_local_var(&self, key: &OsStr) -> Option<OsString> {
395        if let Some(val) = env::var_os(key) {
396            Some(val)
397        } else {
398            self.get_local_var(key).map(|val| val.to_os_string())
399        }
400    }
401}
402
403impl Display for Jobs {
404    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
405        for job in &self.jobs {
406            writeln!(f, "{job}")?;
407        }
408        Ok(())
409    }
410}