1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Signal {
9 Term,
11 Kill,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ProcessStatus {
18 Running,
20 NotFound,
22 NoPermission,
24}
25
26pub trait ProcessController: Send + Sync {
31 fn check_process(&self, pid: u32) -> Result<ProcessStatus, std::io::Error>;
33
34 fn send_signal(&self, pid: u32, signal: Signal) -> Result<(), std::io::Error>;
36}
37
38pub struct UnixProcessController;
40
41impl ProcessController for UnixProcessController {
42 fn check_process(&self, pid: u32) -> Result<ProcessStatus, std::io::Error> {
43 let pid_t: libc::pid_t = pid.try_into().map_err(|_| {
44 std::io::Error::new(std::io::ErrorKind::InvalidInput, "PID out of range")
45 })?;
46
47 let result = unsafe { libc::kill(pid_t, 0) };
50 if result == 0 {
51 return Ok(ProcessStatus::Running);
52 }
53
54 let err = std::io::Error::last_os_error();
55 match err.raw_os_error() {
56 Some(libc::ESRCH) => Ok(ProcessStatus::NotFound),
57 Some(libc::EPERM) => Ok(ProcessStatus::NoPermission),
58 _ => Err(err),
59 }
60 }
61
62 fn send_signal(&self, pid: u32, signal: Signal) -> Result<(), std::io::Error> {
63 let pid_t: libc::pid_t = pid.try_into().map_err(|_| {
64 std::io::Error::new(std::io::ErrorKind::InvalidInput, "PID out of range")
65 })?;
66
67 let sig = match signal {
68 Signal::Term => libc::SIGTERM,
69 Signal::Kill => libc::SIGKILL,
70 };
71
72 let result = unsafe { libc::kill(pid_t, sig) };
74 if result == 0 {
75 Ok(())
76 } else {
77 Err(std::io::Error::last_os_error())
78 }
79 }
80}
81
82#[cfg(test)]
83pub mod mock {
84 use super::*;
85 use std::collections::HashMap;
86 use std::sync::Mutex;
87
88 pub struct MockProcessController {
90 process_states: Mutex<HashMap<u32, ProcessStatus>>,
91 signals_sent: Mutex<Vec<(u32, Signal)>>,
92 check_error: Mutex<Option<std::io::Error>>,
93 signal_error: Mutex<Option<std::io::Error>>,
94 }
95
96 impl Default for MockProcessController {
97 fn default() -> Self {
98 Self::new()
99 }
100 }
101
102 impl MockProcessController {
103 pub fn new() -> Self {
104 Self {
105 process_states: Mutex::new(HashMap::new()),
106 signals_sent: Mutex::new(Vec::new()),
107 check_error: Mutex::new(None),
108 signal_error: Mutex::new(None),
109 }
110 }
111
112 pub fn with_process(self, pid: u32, status: ProcessStatus) -> Self {
113 self.process_states.lock().unwrap().insert(pid, status);
114 self
115 }
116
117 pub fn with_check_error(self, error: std::io::Error) -> Self {
118 *self.check_error.lock().unwrap() = Some(error);
119 self
120 }
121
122 pub fn with_signal_error(self, error: std::io::Error) -> Self {
123 *self.signal_error.lock().unwrap() = Some(error);
124 self
125 }
126
127 pub fn signals_sent(&self) -> Vec<(u32, Signal)> {
128 self.signals_sent.lock().unwrap().clone()
129 }
130 }
131
132 impl ProcessController for MockProcessController {
133 fn check_process(&self, pid: u32) -> Result<ProcessStatus, std::io::Error> {
134 if let Some(err) = self.check_error.lock().unwrap().take() {
135 return Err(err);
136 }
137 Ok(self
138 .process_states
139 .lock()
140 .unwrap()
141 .get(&pid)
142 .copied()
143 .unwrap_or(ProcessStatus::NotFound))
144 }
145
146 fn send_signal(&self, pid: u32, signal: Signal) -> Result<(), std::io::Error> {
147 if let Some(err) = self.signal_error.lock().unwrap().take() {
148 return Err(err);
149 }
150 self.signals_sent.lock().unwrap().push((pid, signal));
151 Ok(())
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use mock::MockProcessController;
160
161 #[test]
162 fn test_signal_variants() {
163 assert_ne!(Signal::Term, Signal::Kill);
164 }
165
166 #[test]
167 fn test_process_status_variants() {
168 assert_ne!(ProcessStatus::Running, ProcessStatus::NotFound);
169 assert_ne!(ProcessStatus::Running, ProcessStatus::NoPermission);
170 assert_ne!(ProcessStatus::NotFound, ProcessStatus::NoPermission);
171 }
172
173 #[test]
174 fn test_mock_check_process_not_found() {
175 let mock = MockProcessController::new();
176 assert_eq!(mock.check_process(1234).unwrap(), ProcessStatus::NotFound);
177 }
178
179 #[test]
180 fn test_mock_check_process_running() {
181 let mock = MockProcessController::new().with_process(1234, ProcessStatus::Running);
182 assert_eq!(mock.check_process(1234).unwrap(), ProcessStatus::Running);
183 }
184
185 #[test]
186 fn test_mock_send_signal() {
187 let mock = MockProcessController::new().with_process(1234, ProcessStatus::Running);
188 mock.send_signal(1234, Signal::Term).unwrap();
189 assert_eq!(mock.signals_sent(), vec![(1234, Signal::Term)]);
190 }
191
192 #[test]
193 fn test_mock_send_multiple_signals() {
194 let mock = MockProcessController::new().with_process(1234, ProcessStatus::Running);
195 mock.send_signal(1234, Signal::Term).unwrap();
196 mock.send_signal(1234, Signal::Kill).unwrap();
197 assert_eq!(
198 mock.signals_sent(),
199 vec![(1234, Signal::Term), (1234, Signal::Kill)]
200 );
201 }
202
203 #[test]
204 fn test_mock_check_error() {
205 let mock =
206 MockProcessController::new().with_check_error(std::io::Error::other("test error"));
207 assert!(mock.check_process(1234).is_err());
208 }
209
210 #[test]
211 fn test_mock_signal_error() {
212 let mock = MockProcessController::new()
213 .with_process(1234, ProcessStatus::Running)
214 .with_signal_error(std::io::Error::other("test error"));
215 assert!(mock.send_signal(1234, Signal::Term).is_err());
216 }
217}