Skip to main content

agent_procs/
paths.rs

1//! Socket, PID file, and log directory path resolution.
2//!
3//! Sockets and PID files live under `/tmp/agent-procs-<uid>/` to stay
4//! within the macOS 103-byte Unix socket path limit.  Persistent state
5//! (logs, state files) goes to `$XDG_STATE_HOME/agent-procs/sessions/<session>/`.
6
7use std::env;
8use std::path::PathBuf;
9
10/// macOS limits Unix socket paths to 103 bytes; Linux allows 107.
11const MAX_SOCKET_PATH_LEN: usize = 103;
12
13/// Base directory for sockets and PID files: /tmp/agent-procs-<uid>/
14/// Short fixed path to avoid macOS 103-byte socket path limit.
15pub fn socket_base_dir() -> PathBuf {
16    let uid = nix::unistd::getuid();
17    PathBuf::from(format!("/tmp/agent-procs-{}", uid))
18}
19
20pub fn socket_path(session: &str) -> PathBuf {
21    let path = socket_base_dir().join(format!("{}.sock", session));
22    if path.as_os_str().len() > MAX_SOCKET_PATH_LEN {
23        tracing::warn!(
24            path = %path.display(),
25            max = MAX_SOCKET_PATH_LEN,
26            actual = path.as_os_str().len(),
27            "socket path exceeds maximum length; use a shorter session name"
28        );
29    }
30    path
31}
32
33pub fn pid_path(session: &str) -> PathBuf {
34    socket_base_dir().join(format!("{}.pid", session))
35}
36
37/// State directory for persistent data (logs, state.json).
38/// Uses $`XDG_STATE_HOME`, defaults to ~/.local/state/.
39pub fn state_dir(session: &str) -> PathBuf {
40    let base = match env::var("XDG_STATE_HOME") {
41        Ok(dir) => PathBuf::from(dir),
42        Err(_) => {
43            let home = env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
44            PathBuf::from(home).join(".local/state")
45        }
46    };
47    base.join("agent-procs/sessions").join(session)
48}
49
50pub fn log_dir(session: &str) -> PathBuf {
51    state_dir(session).join("logs")
52}
53pub fn state_file(session: &str) -> PathBuf {
54    state_dir(session).join("state.json")
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_socket_base_dir_contains_uid() {
63        let uid = nix::unistd::getuid();
64        let dir = socket_base_dir();
65        let dir_str = dir.to_string_lossy();
66        assert!(
67            dir_str.contains(&uid.to_string()),
68            "socket_base_dir '{}' should contain uid '{}'",
69            dir_str,
70            uid
71        );
72    }
73
74    #[test]
75    fn test_socket_path_format() {
76        let path = socket_path("mysession");
77        let path_str = path.to_string_lossy();
78        assert!(
79            path_str.ends_with(".sock"),
80            "socket path '{}' should end with .sock",
81            path_str
82        );
83        assert!(path_str.contains("mysession"));
84    }
85
86    #[test]
87    fn test_pid_path_format() {
88        let path = pid_path("mysession");
89        let path_str = path.to_string_lossy();
90        assert!(
91            path_str.ends_with(".pid"),
92            "pid path '{}' should end with .pid",
93            path_str
94        );
95        assert!(path_str.contains("mysession"));
96    }
97
98    #[test]
99    fn test_log_dir_under_state_dir() {
100        let sdir = state_dir("test-session");
101        let ldir = log_dir("test-session");
102        assert!(
103            ldir.starts_with(&sdir),
104            "log_dir '{}' should be under state_dir '{}'",
105            ldir.display(),
106            sdir.display()
107        );
108    }
109}