Skip to main content

oven_cli/cli/
off.rs

1use anyhow::{Context, Result};
2
3use super::GlobalOpts;
4
5#[allow(clippy::unused_async)]
6pub async fn run(_global: &GlobalOpts) -> Result<()> {
7    let project_dir = std::env::current_dir().context("getting current directory")?;
8    let pid_path = project_dir.join(".oven").join("oven.pid");
9
10    let pid_str = std::fs::read_to_string(&pid_path)
11        .context("no detached process found (missing .oven/oven.pid)")?;
12    let pid = pid_str.trim().parse::<u32>().context("invalid PID in .oven/oven.pid")?;
13
14    // Send SIGTERM via the kill command (avoids unsafe libc calls)
15    let status = std::process::Command::new("kill")
16        .arg("-TERM")
17        .arg(pid.to_string())
18        .status()
19        .context("sending SIGTERM")?;
20
21    if !status.success() {
22        // Process might already be dead, which is fine
23        tracing::warn!(pid, "kill returned non-zero (process may already be stopped)");
24    }
25
26    // Wait briefly for the process to exit
27    for _ in 0..50 {
28        let check = std::process::Command::new("kill").arg("-0").arg(pid.to_string()).status();
29        match check {
30            Ok(s) if !s.success() => break, // process gone
31            _ => std::thread::sleep(std::time::Duration::from_millis(100)),
32        }
33    }
34
35    std::fs::remove_file(&pid_path).ok();
36    println!("stopped (pid {pid})");
37    Ok(())
38}
39
40#[cfg(test)]
41mod tests {
42    #[test]
43    fn pid_parse_valid() {
44        let pid: u32 = "12345\n".trim().parse().unwrap();
45        assert_eq!(pid, 12345);
46    }
47
48    #[test]
49    fn pid_parse_invalid() {
50        let result = "not_a_pid".parse::<u32>();
51        assert!(result.is_err());
52    }
53
54    #[test]
55    fn missing_pid_file_gives_helpful_error() {
56        let dir = tempfile::tempdir().unwrap();
57        let path = dir.path().join(".oven").join("oven.pid");
58        let result = std::fs::read_to_string(&path);
59        assert!(result.is_err());
60    }
61}