1use nix::unistd::Pid;
6
7use crate::sys;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum JobStatus {
12 Running,
14 Stopped,
16 Done(i32),
18 Signaled(i32),
20}
21
22#[derive(Debug, Clone)]
24pub struct Job {
25 pub id: usize,
27 pub pid: Pid,
29 pub pgid: Pid,
31 pub status: JobStatus,
33 pub command: String,
35}
36
37#[derive(Debug, Default)]
39pub struct JobTable {
40 jobs: Vec<Job>,
41 next_id: usize,
42}
43
44impl JobTable {
45 pub fn new() -> Self {
47 Self {
48 jobs: Vec::new(),
49 next_id: 1,
50 }
51 }
52
53 pub fn add(&mut self, pid: Pid, pgid: Pid, command: String) -> usize {
55 let id = self.next_id;
56 self.next_id += 1;
57 self.jobs.push(Job {
58 id,
59 pid,
60 pgid,
61 status: JobStatus::Running,
62 command,
63 });
64 id
65 }
66
67 pub fn remove(&mut self, id: usize) -> Option<Job> {
69 if let Some(pos) = self.jobs.iter().position(|j| j.id == id) {
70 Some(self.jobs.remove(pos))
71 } else {
72 None
73 }
74 }
75
76 pub fn get(&self, id: usize) -> Option<&Job> {
78 self.jobs.iter().find(|j| j.id == id)
79 }
80
81 pub fn get_mut(&mut self, id: usize) -> Option<&mut Job> {
83 self.jobs.iter_mut().find(|j| j.id == id)
84 }
85
86 pub fn find_by_pid(&self, pid: Pid) -> Option<&Job> {
88 self.jobs.iter().find(|j| j.pid == pid)
89 }
90
91 pub fn iter(&self) -> impl Iterator<Item = &Job> {
93 self.jobs.iter()
94 }
95
96 pub fn len(&self) -> usize {
98 self.jobs.len()
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.jobs.is_empty()
104 }
105
106 pub fn wait_for(&mut self, id: usize) -> Option<JobStatus> {
110 let job = self.jobs.iter_mut().find(|j| j.id == id)?;
111
112 match sys::wait_pid(job.pid) {
113 Ok(sys::ChildStatus::Exited(code)) => {
114 job.status = JobStatus::Done(code);
115 }
116 Ok(sys::ChildStatus::Signaled(code)) => {
117 job.status = JobStatus::Signaled(code);
118 }
119 Ok(sys::ChildStatus::Stopped) => {
120 job.status = JobStatus::Stopped;
121 }
122 _ => {}
123 }
124
125 Some(job.status)
126 }
127
128 pub fn reap_finished(&mut self) -> Vec<usize> {
130 let mut finished = Vec::new();
131
132 for job in &mut self.jobs {
133 if job.status != JobStatus::Running {
134 continue;
135 }
136 match sys::try_wait_pid(job.pid) {
137 Ok(sys::ChildStatus::Exited(code)) => {
138 job.status = JobStatus::Done(code);
139 finished.push(job.id);
140 }
141 Ok(sys::ChildStatus::Signaled(code)) => {
142 job.status = JobStatus::Signaled(code);
143 finished.push(job.id);
144 }
145 Ok(sys::ChildStatus::Stopped) => {
146 job.status = JobStatus::Stopped;
147 finished.push(job.id);
148 }
149 _ => {}
150 }
151 }
152
153 finished
154 }
155
156 pub fn prune_done(&mut self) {
158 self.jobs
159 .retain(|j| !matches!(j.status, JobStatus::Done(_) | JobStatus::Signaled(_)));
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use nix::unistd::Pid;
167
168 #[test]
169 fn add_and_lookup() {
170 let mut table = JobTable::new();
171 let id = table.add(Pid::from_raw(1234), Pid::from_raw(1234), "sleep 10".into());
172 assert_eq!(id, 1);
173 assert_eq!(table.len(), 1);
174
175 let job = table.get(id).unwrap();
176 assert_eq!(job.pid, Pid::from_raw(1234));
177 assert_eq!(job.status, JobStatus::Running);
178 assert_eq!(job.command, "sleep 10");
179 }
180
181 #[test]
182 fn remove_job() {
183 let mut table = JobTable::new();
184 let id = table.add(Pid::from_raw(100), Pid::from_raw(100), "echo".into());
185 assert!(table.remove(id).is_some());
186 assert!(table.is_empty());
187 }
188
189 #[test]
190 fn find_by_pid() {
191 let mut table = JobTable::new();
192 table.add(Pid::from_raw(42), Pid::from_raw(42), "ls".into());
193 assert!(table.find_by_pid(Pid::from_raw(42)).is_some());
194 assert!(table.find_by_pid(Pid::from_raw(99)).is_none());
195 }
196
197 #[test]
198 fn prune_done() {
199 let mut table = JobTable::new();
200 let id1 = table.add(Pid::from_raw(1), Pid::from_raw(1), "a".into());
201 let _id2 = table.add(Pid::from_raw(2), Pid::from_raw(2), "b".into());
202
203 table.get_mut(id1).unwrap().status = JobStatus::Done(0);
204 table.prune_done();
205 assert_eq!(table.len(), 1);
206 assert!(table.get(id1).is_none());
207 }
208
209 #[test]
210 fn sequential_ids() {
211 let mut table = JobTable::new();
212 let id1 = table.add(Pid::from_raw(1), Pid::from_raw(1), "a".into());
213 let id2 = table.add(Pid::from_raw(2), Pid::from_raw(2), "b".into());
214 assert_eq!(id1, 1);
215 assert_eq!(id2, 2);
216 }
217}