use std::path::PathBuf;
pub fn darq_home() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".darq")
}
pub fn socket_path() -> PathBuf {
darq_home().join("daemon.sock")
}
pub fn pid_path() -> PathBuf {
darq_home().join("daemon.pid")
}
pub fn is_daemon_running() -> bool {
let pid_file = pid_path();
let sock_file = socket_path();
if !pid_file.exists() || !sock_file.exists() {
return false;
}
let pid_str = match std::fs::read_to_string(&pid_file) {
Ok(s) => s.trim().to_string(),
Err(_) => return false,
};
let pid: u32 = match pid_str.parse() {
Ok(p) => p,
Err(_) => return false,
};
#[cfg(unix)]
{
let result = unsafe { libc::kill(pid as i32, 0) };
result == 0
}
#[cfg(not(unix))]
{
let _ = pid;
sock_file.exists()
}
}
pub fn write_pid_file() -> std::io::Result<()> {
let pid_file = pid_path();
if let Some(parent) = pid_file.parent() {
std::fs::create_dir_all(parent)?;
}
let pid = std::process::id();
std::fs::write(&pid_file, pid.to_string())
}
pub fn remove_pid_file() {
let _ = std::fs::remove_file(pid_path());
}
pub fn remove_socket_file() {
let _ = std::fs::remove_file(socket_path());
}
pub fn cleanup() {
remove_pid_file();
remove_socket_file();
}
pub fn ensure_home_dir() -> std::io::Result<()> {
std::fs::create_dir_all(darq_home())
}
pub fn daemon_pid() -> Option<u32> {
let pid_file = pid_path();
let pid_str = std::fs::read_to_string(&pid_file).ok()?;
pid_str.trim().parse().ok()
}
pub async fn spawn_daemon(config_path: &str) -> anyhow::Result<()> {
ensure_home_dir()?;
if socket_path().exists() && !is_daemon_running() {
tracing::warn!("stale socket found, cleaning up");
cleanup();
}
let exe = std::env::current_exe()?;
let log_path = darq_home().join("daemon.log");
let log_file = std::fs::File::create(&log_path)?;
let mut cmd = std::process::Command::new(exe);
cmd.arg("--config")
.arg(config_path)
.arg("daemon")
.arg("start")
.arg("--background")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(log_file);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
}
cmd.spawn()?;
let sock = socket_path();
for _ in 0..50 {
if sock.exists() {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
return Ok(());
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
anyhow::bail!("daemon did not start within 5 seconds")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paths_are_under_home() {
let home = darq_home();
assert!(socket_path().starts_with(&home));
assert!(pid_path().starts_with(&home));
}
#[test]
fn test_socket_filename() {
assert_eq!(socket_path().file_name().unwrap(), "daemon.sock");
}
#[test]
fn test_pid_filename() {
assert_eq!(pid_path().file_name().unwrap(), "daemon.pid");
}
#[test]
fn test_write_and_remove_pid() {
ensure_home_dir().unwrap();
write_pid_file().unwrap();
assert!(pid_path().exists());
assert_eq!(daemon_pid(), Some(std::process::id()));
remove_pid_file();
assert!(!pid_path().exists());
assert_eq!(daemon_pid(), None);
}
}