use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
const HOOK_TIMEOUT: Duration = Duration::from_secs(30);
pub struct HookEvent<'a> {
pub event: &'a str,
pub device: &'a str,
pub util: f64,
pub message: &'a str,
}
pub fn run_hook(command: &str, ev: &HookEvent) {
let spawned = Command::new("sh")
.arg("-c")
.arg(command)
.env("IOWATCHD_EVENT", ev.event)
.env("IOWATCHD_DEVICE", ev.device)
.env("IOWATCHD_UTIL", format!("{:.0}", ev.util))
.env("IOWATCHD_MESSAGE", ev.message)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
let mut child = match spawned {
Ok(c) => c,
Err(e) => return eprintln!("warning: could not run notify_command: {e}"),
};
std::thread::spawn(move || {
let start = Instant::now();
loop {
match child.try_wait() {
Ok(Some(_)) | Err(_) => return,
Ok(None) if start.elapsed() > HOOK_TIMEOUT => {
let _ = child.kill();
let _ = child.wait();
return;
}
Ok(None) => std::thread::sleep(Duration::from_millis(200)),
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hook_receives_the_environment() {
let out = std::env::temp_dir().join(format!("iowatchd-hook-{}", std::process::id()));
let cmd = format!(
"echo \"$IOWATCHD_EVENT $IOWATCHD_DEVICE $IOWATCHD_UTIL $IOWATCHD_MESSAGE\" > {}",
out.display()
);
run_hook(
&cmd,
&HookEvent {
event: "alert",
device: "sda",
util: 97.4,
message: "hello world",
},
);
for _ in 0..50 {
if out.exists() {
std::thread::sleep(Duration::from_millis(100));
break;
}
std::thread::sleep(Duration::from_millis(100));
}
let text = std::fs::read_to_string(&out).unwrap_or_default();
let _ = std::fs::remove_file(&out);
assert_eq!(text.trim(), "alert sda 97 hello world");
}
}