1use crate::error::CoreError;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Signal {
12 Term,
14 Kill,
16}
17
18impl Signal {
19 fn as_libc(self) -> i32 {
20 match self {
21 Signal::Term => libc::SIGTERM,
22 Signal::Kill => libc::SIGKILL,
23 }
24 }
25}
26
27pub fn kill_process(pid: u32, signal: Signal) -> Result<(), CoreError> {
39 let pid_i32 = i32::try_from(pid).map_err(|_| CoreError::ProcessNotFound { pid })?;
42 if pid_i32 <= 0 {
43 return Err(CoreError::ProcessNotFound { pid });
44 }
45
46 let ret = unsafe { libc::kill(pid_i32, signal.as_libc()) };
47 if ret == 0 {
48 return Ok(());
49 }
50
51 let err = std::io::Error::last_os_error();
52 match err.raw_os_error() {
53 Some(code) if code == libc::ESRCH => Err(CoreError::ProcessNotFound { pid }),
54 Some(code) if code == libc::EPERM => Err(CoreError::Permission(format!(
55 "permission denied sending signal {signal:?} to pid {pid}"
56 ))),
57 _ => Err(CoreError::Io(err)),
58 }
59}
60
61pub fn renice_process(pid: u32, nice_value: i32) -> Result<(), CoreError> {
72 let pid_i32 = i32::try_from(pid).map_err(|_| CoreError::ProcessNotFound { pid })?;
75 if pid_i32 <= 0 {
76 return Err(CoreError::ProcessNotFound { pid });
77 }
78
79 let ret = unsafe {
82 set_errno_raw(0);
83 libc::setpriority(libc::PRIO_PROCESS, pid_i32 as libc::id_t, nice_value)
84 };
85
86 if ret == 0 {
87 return Ok(());
88 }
89
90 let err = std::io::Error::last_os_error();
92 match err.raw_os_error() {
93 Some(0) => Ok(()), Some(code) if code == libc::ESRCH => Err(CoreError::ProcessNotFound { pid }),
95 Some(code) if code == libc::EPERM => Err(CoreError::Permission(format!(
96 "permission denied changing priority of pid {pid} to {nice_value}"
97 ))),
98 _ => Err(CoreError::Io(err)),
99 }
100}
101
102pub fn get_process_priority(pid: u32) -> Result<i32, CoreError> {
112 let pid_i32 = i32::try_from(pid).map_err(|_| CoreError::ProcessNotFound { pid })?;
113 if pid_i32 <= 0 {
114 return Err(CoreError::ProcessNotFound { pid });
115 }
116
117 let ret = unsafe {
118 set_errno_raw(0);
119 libc::getpriority(libc::PRIO_PROCESS, pid_i32 as libc::id_t)
120 };
121
122 let err = std::io::Error::last_os_error();
123 match err.raw_os_error() {
124 Some(0) => Ok(ret), Some(code) if code == libc::ESRCH => Err(CoreError::ProcessNotFound { pid }),
126 Some(code) if code == libc::EPERM => Err(CoreError::Permission(format!(
127 "permission denied reading priority of pid {pid}"
128 ))),
129 _ => Err(CoreError::Io(err)),
130 }
131}
132
133unsafe fn set_errno_raw(value: i32) {
138 #[cfg(target_os = "macos")]
139 {
140 unsafe {
141 *libc::__error() = value;
142 }
143 }
144 #[cfg(target_os = "linux")]
145 {
146 unsafe {
147 *libc::__errno_location() = value;
148 }
149 }
150 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
151 {
152 let _ = value;
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
163 fn test_kill_sigterm_nonexistent_pid() {
164 let bad_pid: u32 = i32::MAX as u32 - 1;
165 let result = kill_process(bad_pid, Signal::Term);
166 assert!(result.is_err(), "kill(nonexistent, SIGTERM) must fail");
167 }
168
169 #[test]
171 fn test_kill_sigkill_nonexistent_pid() {
172 let bad_pid: u32 = i32::MAX as u32 - 1;
173 let result = kill_process(bad_pid, Signal::Kill);
174 assert!(result.is_err(), "kill(nonexistent, SIGKILL) must fail");
175 }
176
177 #[test]
179 fn test_kill_invalid_pid() {
180 let bad_pid: u32 = i32::MAX as u32 - 1; let result = kill_process(bad_pid, Signal::Term);
184 assert!(
185 result.is_err(),
186 "kill(large_pid, SIGTERM) must return an error"
187 );
188 match result.unwrap_err() {
189 CoreError::ProcessNotFound { .. } | CoreError::Permission(_) | CoreError::Io(_) => {}
190 other => panic!("unexpected error variant: {other:?}"),
191 }
192 }
193
194 #[test]
196 fn test_kill_u32_max_rejected() {
197 let result = kill_process(u32::MAX, Signal::Term);
198 assert!(
199 matches!(result, Err(CoreError::ProcessNotFound { .. })),
200 "u32::MAX must be rejected as ProcessNotFound, got: {result:?}"
201 );
202 }
203
204 #[test]
206 fn test_kill_pid_zero_rejected() {
207 let result = kill_process(0, Signal::Term);
208 assert!(
209 matches!(result, Err(CoreError::ProcessNotFound { .. })),
210 "pid 0 must be rejected, got: {result:?}"
211 );
212 }
213
214 #[test]
217 fn test_renice_self() {
218 let pid = std::process::id();
219 let result = renice_process(pid, 10);
220 assert!(
221 result.is_ok(),
222 "renice(self, 10) should succeed: {result:?}"
223 );
224 }
225
226 #[test]
228 fn test_renice_invalid_pid() {
229 let bad_pid: u32 = i32::MAX as u32 - 1;
230 let result = renice_process(bad_pid, 10);
231 assert!(
232 result.is_err(),
233 "renice(large_pid, 10) must return an error"
234 );
235 }
236
237 #[test]
239 fn test_kill_renice_error_types() {
240 let bad_pid: u32 = i32::MAX as u32 - 1;
241
242 let r = kill_process(bad_pid, Signal::Term);
243 if let Err(e) = r {
244 let is_expected = matches!(
245 e,
246 CoreError::ProcessNotFound { .. } | CoreError::Permission(_) | CoreError::Io(_)
247 );
248 assert!(is_expected, "unexpected error variant: {e:?}");
249 }
250
251 let r2 = renice_process(bad_pid, 0);
252 if let Err(e) = r2 {
253 let is_expected = matches!(
254 e,
255 CoreError::ProcessNotFound { .. } | CoreError::Permission(_) | CoreError::Io(_)
256 );
257 assert!(is_expected, "unexpected error variant: {e:?}");
258 }
259 }
260
261 #[test]
263 fn test_get_priority_self() {
264 let pid = std::process::id();
265 let result = get_process_priority(pid);
266 assert!(
267 result.is_ok(),
268 "get_process_priority(self) should succeed: {result:?}"
269 );
270 let nice = result.unwrap();
271 assert!(
272 (-20..=19).contains(&nice),
273 "nice value {nice} out of POSIX range"
274 );
275 }
276
277 #[test]
279 fn test_get_priority_pid_zero_rejected() {
280 let result = get_process_priority(0);
281 assert!(
282 matches!(result, Err(CoreError::ProcessNotFound { .. })),
283 "pid 0 must be rejected, got: {result:?}"
284 );
285 }
286}