Skip to main content

aiclient_api/daemon/
mod.rs

1pub mod control;
2
3use anyhow::{Context, Result};
4use std::path::Path;
5
6/// Read PID from file and check if process is alive
7pub fn read_pid() -> Result<Option<u32>> {
8    let pid_path = crate::util::xdg::pid_path();
9    if !pid_path.exists() {
10        return Ok(None);
11    }
12    let content = std::fs::read_to_string(&pid_path)?;
13    let pid: u32 = content.trim().parse()?;
14
15    let alive = unsafe { libc::kill(pid as i32, 0) == 0 };
16    if alive {
17        Ok(Some(pid))
18    } else {
19        let _ = std::fs::remove_file(&pid_path);
20        Ok(None)
21    }
22}
23
24/// Write PID to file
25pub fn write_pid(pid: u32) -> Result<()> {
26    let pid_path = crate::util::xdg::pid_path();
27    if let Some(parent) = pid_path.parent() {
28        std::fs::create_dir_all(parent)?;
29    }
30    std::fs::write(&pid_path, pid.to_string())?;
31    Ok(())
32}
33
34/// Remove PID file
35pub fn remove_pid() -> Result<()> {
36    let pid_path = crate::util::xdg::pid_path();
37    if pid_path.exists() {
38        std::fs::remove_file(&pid_path)?;
39    }
40    Ok(())
41}
42
43/// Stop the daemon: send SIGTERM, wait up to 10s, then SIGKILL
44pub fn stop_daemon() -> Result<()> {
45    match read_pid()? {
46        Some(pid) => {
47            eprintln!("Stopping daemon (pid {})...", pid);
48            unsafe {
49                libc::kill(pid as i32, libc::SIGTERM);
50            }
51
52            for _ in 0..100 {
53                std::thread::sleep(std::time::Duration::from_millis(100));
54                let alive = unsafe { libc::kill(pid as i32, 0) == 0 };
55                if !alive {
56                    remove_pid()?;
57                    eprintln!("Daemon stopped.");
58                    return Ok(());
59                }
60            }
61
62            eprintln!("Daemon didn't stop gracefully, sending SIGKILL...");
63            unsafe {
64                libc::kill(pid as i32, libc::SIGKILL);
65            }
66            std::thread::sleep(std::time::Duration::from_millis(500));
67            remove_pid()?;
68            eprintln!("Daemon killed.");
69            Ok(())
70        }
71        None => {
72            eprintln!("Daemon is not running.");
73            Ok(())
74        }
75    }
76}
77
78/// Daemonize the current process (fork to background)
79pub fn daemonize(log_file: &Path) -> Result<()> {
80    use daemonize::Daemonize;
81
82    let pid_path = crate::util::xdg::pid_path();
83    let runtime_dir = crate::util::xdg::runtime_dir();
84    std::fs::create_dir_all(&runtime_dir)?;
85
86    if let Some(parent) = log_file.parent() {
87        std::fs::create_dir_all(parent)?;
88    }
89
90    let stdout = std::fs::OpenOptions::new()
91        .create(true)
92        .append(true)
93        .open(log_file)?;
94    let stderr = stdout.try_clone()?;
95
96    let daemon = Daemonize::new()
97        .pid_file(&pid_path)
98        .working_directory(".")
99        .stdout(stdout)
100        .stderr(stderr);
101
102    daemon.start().context("Failed to daemonize")?;
103
104    Ok(())
105}