use crate::error::CoreError;
pub fn kill_process(pid: u32, signal: i32) -> Result<(), CoreError> {
let pid_i32 = i32::try_from(pid).map_err(|_| CoreError::ProcessNotFound { pid })?;
if pid_i32 <= 0 {
return Err(CoreError::ProcessNotFound { pid });
}
let ret = unsafe { libc::kill(pid_i32, signal) };
if ret == 0 {
return Ok(());
}
let err = std::io::Error::last_os_error();
match err.raw_os_error() {
Some(code) if code == libc::ESRCH => Err(CoreError::ProcessNotFound { pid }),
Some(code) if code == libc::EPERM => Err(CoreError::Permission(format!(
"permission denied sending signal {signal} to pid {pid}"
))),
_ => Err(CoreError::Io(err)),
}
}
pub fn renice_process(pid: u32, nice_value: i32) -> Result<(), CoreError> {
let pid_i32 = i32::try_from(pid).map_err(|_| CoreError::ProcessNotFound { pid })?;
if pid_i32 <= 0 {
return Err(CoreError::ProcessNotFound { pid });
}
let ret = unsafe {
set_errno_raw(0);
libc::setpriority(libc::PRIO_PROCESS, pid_i32 as libc::id_t, nice_value)
};
if ret == 0 {
return Ok(());
}
let err = std::io::Error::last_os_error();
match err.raw_os_error() {
Some(0) => Ok(()), Some(code) if code == libc::ESRCH => Err(CoreError::ProcessNotFound { pid }),
Some(code) if code == libc::EPERM => Err(CoreError::Permission(format!(
"permission denied changing priority of pid {pid} to {nice_value}"
))),
_ => Err(CoreError::Io(err)),
}
}
unsafe fn set_errno_raw(value: i32) {
#[cfg(target_os = "macos")]
{
unsafe {
*libc::__error() = value;
}
}
#[cfg(target_os = "linux")]
{
unsafe {
*libc::__errno_location() = value;
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
let _ = value;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn log(msg: &str) {
let mut f = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("/tmp/muxtop-test-actions.log")
.unwrap();
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis();
writeln!(f, "[{ts}] {msg}").unwrap();
}
#[test]
fn test_kill_zero_signal_self() {
let pid = std::process::id();
log(&format!("test_kill_zero_signal_self: pid={pid}, signal=0"));
let result = kill_process(pid, 0);
log(&format!("test_kill_zero_signal_self: result={result:?}"));
assert!(result.is_ok(), "kill(self, 0) should succeed");
}
#[test]
fn test_kill_invalid_pid() {
let bad_pid: u32 = i32::MAX as u32 - 1; log(&format!(
"test_kill_invalid_pid: pid={bad_pid}, signal=SIGTERM({})",
libc::SIGTERM
));
let result = kill_process(bad_pid, libc::SIGTERM);
log(&format!("test_kill_invalid_pid: result={result:?}"));
assert!(
result.is_err(),
"kill(large_pid, SIGTERM) must return an error"
);
match result.unwrap_err() {
CoreError::ProcessNotFound { .. } | CoreError::Permission(_) | CoreError::Io(_) => {}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn test_kill_u32_max_rejected() {
log("test_kill_u32_max_rejected: verifying u32::MAX is rejected");
let result = kill_process(u32::MAX, 0);
log(&format!("test_kill_u32_max_rejected: result={result:?}"));
assert!(
matches!(result, Err(CoreError::ProcessNotFound { .. })),
"u32::MAX must be rejected as ProcessNotFound, got: {result:?}"
);
}
#[test]
fn test_kill_pid_zero_rejected() {
log("test_kill_pid_zero_rejected: verifying pid=0 is rejected");
let result = kill_process(0, 0);
log(&format!("test_kill_pid_zero_rejected: result={result:?}"));
assert!(
matches!(result, Err(CoreError::ProcessNotFound { .. })),
"pid 0 must be rejected, got: {result:?}"
);
}
#[test]
fn test_renice_self() {
let pid = std::process::id();
log(&format!("test_renice_self: pid={pid}, nice=10"));
let result = renice_process(pid, 10);
log(&format!("test_renice_self: result={result:?}"));
assert!(
result.is_ok(),
"renice(self, 10) should succeed: {result:?}"
);
}
#[test]
fn test_renice_invalid_pid() {
let bad_pid: u32 = i32::MAX as u32 - 1;
log(&format!("test_renice_invalid_pid: pid={bad_pid}, nice=10"));
let result = renice_process(bad_pid, 10);
log(&format!("test_renice_invalid_pid: result={result:?}"));
assert!(
result.is_err(),
"renice(large_pid, 10) must return an error"
);
}
#[test]
fn test_kill_renice_error_types() {
let bad_pid: u32 = i32::MAX as u32 - 1;
log(&format!(
"test_kill_renice_error_types: kill bad_pid={bad_pid}"
));
let r = kill_process(bad_pid, libc::SIGTERM);
log(&format!("test_kill_renice_error_types: kill result={r:?}"));
if let Err(e) = r {
let is_expected = matches!(
e,
CoreError::ProcessNotFound { .. } | CoreError::Permission(_) | CoreError::Io(_)
);
assert!(is_expected, "unexpected error variant: {e:?}");
}
log(&format!(
"test_kill_renice_error_types: renice bad_pid={bad_pid}"
));
let r2 = renice_process(bad_pid, 0);
log(&format!(
"test_kill_renice_error_types: renice result={r2:?}"
));
if let Err(e) = r2 {
let is_expected = matches!(
e,
CoreError::ProcessNotFound { .. } | CoreError::Permission(_) | CoreError::Io(_)
);
assert!(is_expected, "unexpected error variant: {e:?}");
}
}
}