Skip to main content

debugger/common/
paths.rs

1//! Cross-platform socket and configuration paths
2//!
3//! Unix/macOS: Uses Unix domain sockets at $XDG_RUNTIME_DIR or /tmp
4//! Windows: Uses named pipes at \\.\pipe\debugger-cli-<username>
5
6use std::io;
7use std::path::PathBuf;
8
9/// Name used for the IPC socket/pipe
10const SOCKET_NAME: &str = "debugger-cli";
11
12/// Get the socket/pipe path for IPC communication
13///
14/// Platform-specific:
15/// - Unix: `$XDG_RUNTIME_DIR/debugger-cli/daemon.sock` or `/tmp/debugger-cli-<uid>/daemon.sock`
16/// - Windows: Named pipe path (handled by interprocess crate)
17#[cfg(unix)]
18pub fn socket_path() -> PathBuf {
19    // Try XDG_RUNTIME_DIR first (preferred on Linux)
20    if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") {
21        return PathBuf::from(runtime_dir)
22            .join(SOCKET_NAME)
23            .join("daemon.sock");
24    }
25
26    // Fallback to /tmp with uid for security
27    let uid = unsafe { libc::getuid() };
28    PathBuf::from(format!("/tmp/{}-{}", SOCKET_NAME, uid)).join("daemon.sock")
29}
30
31#[cfg(windows)]
32pub fn socket_path() -> PathBuf {
33    // On Windows, we return a path that will be converted to a named pipe
34    // The interprocess crate handles the \\.\pipe\ prefix
35    let username = std::env::var("USERNAME").unwrap_or_else(|_| "default".to_string());
36    PathBuf::from(format!("{}-{}", SOCKET_NAME, username))
37}
38
39/// Get the socket name for interprocess LocalSocketName
40///
41/// Returns a string suitable for use with interprocess crate's local socket API
42#[cfg(unix)]
43pub fn socket_name() -> String {
44    socket_path().to_string_lossy().into_owned()
45}
46
47#[cfg(windows)]
48pub fn socket_name() -> String {
49    let username = std::env::var("USERNAME").unwrap_or_else(|_| "default".to_string());
50    format!("{}-{}", SOCKET_NAME, username)
51}
52
53/// Ensure the socket directory exists with proper permissions
54///
55/// On Unix, creates the directory with mode 0700 for security
56#[cfg(unix)]
57pub fn ensure_socket_dir() -> io::Result<PathBuf> {
58    let socket = socket_path();
59    let dir = socket.parent().ok_or_else(|| {
60        io::Error::new(io::ErrorKind::InvalidInput, "Invalid socket path")
61    })?;
62
63    if !dir.exists() {
64        std::fs::create_dir_all(dir)?;
65        // Set directory permissions to 0700 (owner only)
66        use std::os::unix::fs::PermissionsExt;
67        std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))?;
68    }
69
70    Ok(dir.to_path_buf())
71}
72
73#[cfg(windows)]
74pub fn ensure_socket_dir() -> io::Result<PathBuf> {
75    // Named pipes don't need a directory on Windows
76    Ok(PathBuf::new())
77}
78
79/// Remove the socket file if it exists (for cleanup)
80#[cfg(unix)]
81pub fn remove_socket() -> io::Result<()> {
82    let path = socket_path();
83    if path.exists() {
84        std::fs::remove_file(&path)?;
85    }
86    Ok(())
87}
88
89#[cfg(windows)]
90pub fn remove_socket() -> io::Result<()> {
91    // Named pipes are automatically cleaned up on Windows
92    Ok(())
93}
94
95/// Get the configuration directory path
96///
97/// Uses the directories crate for platform-appropriate locations:
98/// - Linux: `~/.config/debugger-cli/`
99/// - macOS: `~/Library/Application Support/debugger-cli/`
100/// - Windows: `%APPDATA%\debugger-cli\`
101pub fn config_dir() -> Option<PathBuf> {
102    directories::ProjectDirs::from("", "", SOCKET_NAME)
103        .map(|dirs| dirs.config_dir().to_path_buf())
104}
105
106/// Get the path to the configuration file
107pub fn config_path() -> Option<PathBuf> {
108    config_dir().map(|dir| dir.join("config.toml"))
109}
110
111/// Get the path to the log directory
112pub fn log_dir() -> Option<PathBuf> {
113    directories::ProjectDirs::from("", "", SOCKET_NAME)
114        .map(|dirs| dirs.data_dir().join("logs"))
115}
116
117/// Ensure the configuration directory exists
118pub fn ensure_config_dir() -> io::Result<Option<PathBuf>> {
119    if let Some(dir) = config_dir() {
120        if !dir.exists() {
121            std::fs::create_dir_all(&dir)?;
122        }
123        Ok(Some(dir))
124    } else {
125        Ok(None)
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_socket_path_is_valid() {
135        let path = socket_path();
136        assert!(!path.as_os_str().is_empty());
137    }
138
139    #[test]
140    fn test_config_dir_is_valid() {
141        let dir = config_dir();
142        assert!(dir.is_some());
143    }
144}