krait/daemon/
lifecycle.rs1use std::path::{Path, PathBuf};
2
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum LifecycleError {
7 #[error("Daemon already running (pid {pid})")]
8 AlreadyRunning { pid: u32 },
9 #[error("IO error: {0}")]
10 Io(#[from] std::io::Error),
11}
12
13#[must_use]
15pub fn pid_path(socket_path: &Path) -> PathBuf {
16 socket_path.with_extension("pid")
17}
18
19pub fn acquire_pid_file(pid_path: &Path) -> Result<(), LifecycleError> {
25 if let Ok(contents) = std::fs::read_to_string(pid_path) {
26 if let Ok(pid) = contents.trim().parse::<u32>() {
27 if is_process_alive(pid) {
28 return Err(LifecycleError::AlreadyRunning { pid });
29 }
30 }
31 let _ = std::fs::remove_file(pid_path);
33 }
34
35 let pid = std::process::id();
36 std::fs::write(pid_path, pid.to_string())?;
37 Ok(())
38}
39
40pub fn cleanup(socket_path: &Path, pid_path: &Path) {
42 let _ = std::fs::remove_file(socket_path);
43 let _ = std::fs::remove_file(pid_path);
44}
45
46fn is_process_alive(pid: u32) -> bool {
47 unsafe { libc::kill(pid.cast_signed(), 0) == 0 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn pid_path_from_socket_path() {
57 let sock = PathBuf::from("/tmp/krait-abc123.sock");
58 assert_eq!(pid_path(&sock), PathBuf::from("/tmp/krait-abc123.pid"));
59 }
60
61 #[test]
62 fn acquire_pid_file_creates_file() {
63 let dir = tempfile::tempdir().unwrap();
64 let path = dir.path().join("test.pid");
65
66 acquire_pid_file(&path).unwrap();
67
68 let contents = std::fs::read_to_string(&path).unwrap();
69 assert_eq!(contents, std::process::id().to_string());
70 }
71
72 #[test]
73 fn acquire_pid_file_rejects_live_process() {
74 let dir = tempfile::tempdir().unwrap();
75 let path = dir.path().join("test.pid");
76
77 std::fs::write(&path, std::process::id().to_string()).unwrap();
79
80 let result = acquire_pid_file(&path);
81 assert!(result.is_err());
82 assert!(result.unwrap_err().to_string().contains("already running"));
83 }
84
85 #[test]
86 fn acquire_pid_file_cleans_stale() {
87 let dir = tempfile::tempdir().unwrap();
88 let path = dir.path().join("test.pid");
89
90 std::fs::write(&path, "999999999").unwrap();
92
93 acquire_pid_file(&path).unwrap();
94
95 let contents = std::fs::read_to_string(&path).unwrap();
96 assert_eq!(contents, std::process::id().to_string());
97 }
98
99 #[test]
100 fn cleanup_removes_files() {
101 let dir = tempfile::tempdir().unwrap();
102 let sock = dir.path().join("test.sock");
103 let pid = dir.path().join("test.pid");
104
105 std::fs::write(&sock, "").unwrap();
106 std::fs::write(&pid, "").unwrap();
107
108 cleanup(&sock, &pid);
109
110 assert!(!sock.exists());
111 assert!(!pid.exists());
112 }
113}