Skip to main content

running_process_client/
paths.rs

1//! Shared path computation for daemon socket, PID file, database, and shadow directory.
2//!
3//! Both the server and client modules use these functions to agree on where
4//! the daemon listens and where auxiliary files are stored.
5
6use std::path::PathBuf;
7
8/// Returns the local socket name the daemon listens on.
9///
10/// - **Linux/macOS**: `$XDG_RUNTIME_DIR/running-process/daemon{-hash}.sock`
11///   (fallback: `/tmp/running-process-{uid}/daemon{-hash}.sock`)
12/// - **Windows**: `\\.\pipe\running-process-daemon-{username}{-hash}`
13///
14/// On Windows the returned string is a full named pipe path that should be
15/// passed to [`interprocess::local_socket::ToNsName::to_ns_name`] with
16/// [`GenericNamespaced`](interprocess::local_socket::GenericNamespaced).
17/// On Unix it is a filesystem path for
18/// [`interprocess::local_socket::ToFsName::to_fs_name`] with
19/// [`GenericFilePath`](interprocess::local_socket::GenericFilePath).
20pub fn socket_path(scope_hash: Option<&str>) -> String {
21    let suffix = match scope_hash {
22        Some(h) => format!("-{h}"),
23        None => String::new(),
24    };
25
26    #[cfg(windows)]
27    {
28        let username = std::env::var("USERNAME").unwrap_or_else(|_| "unknown".into());
29        format!(r"\\.\pipe\running-process-daemon-{username}{suffix}")
30    }
31
32    #[cfg(unix)]
33    {
34        let dir = runtime_dir_unix();
35        // Ensure the directory exists.
36        let _ = std::fs::create_dir_all(&dir);
37        format!("{}/daemon{suffix}.sock", dir.display())
38    }
39}
40
41/// Build an `interprocess` local socket [`Name`] from the path returned by
42/// [`socket_path`].
43///
44/// This must use the same name-type dispatch as the server so that client
45/// and server agree on the actual IPC endpoint.
46pub fn make_socket_name(path: &str) -> std::io::Result<interprocess::local_socket::Name<'_>> {
47    use interprocess::local_socket::prelude::*;
48
49    #[cfg(unix)]
50    {
51        use interprocess::local_socket::GenericFilePath;
52        path.to_fs_name::<GenericFilePath>()
53    }
54
55    #[cfg(windows)]
56    {
57        use interprocess::local_socket::GenericNamespaced;
58        path.to_ns_name::<GenericNamespaced>()
59    }
60}
61
62/// Returns the path to the daemon PID file.
63///
64/// - **Linux/macOS**: same directory as the socket, with `.pid` extension.
65/// - **Windows**: `%LOCALAPPDATA%\running-process\daemon{-hash}.pid`
66pub fn pid_file_path(scope_hash: Option<&str>) -> PathBuf {
67    let suffix = match scope_hash {
68        Some(h) => format!("-{h}"),
69        None => String::new(),
70    };
71
72    #[cfg(windows)]
73    {
74        let base = local_app_data_dir();
75        let _ = std::fs::create_dir_all(&base);
76        base.join(format!("daemon{suffix}.pid"))
77    }
78
79    #[cfg(unix)]
80    {
81        let dir = runtime_dir_unix();
82        let _ = std::fs::create_dir_all(&dir);
83        dir.join(format!("daemon{suffix}.pid"))
84    }
85}
86
87/// Returns the path to the daemon SQLite database.
88///
89/// - **Linux/macOS**: `$XDG_STATE_HOME/running-process/tracked-pids{-hash}.sqlite3`
90///   (fallback: `~/.local/state/running-process/tracked-pids{-hash}.sqlite3`)
91/// - **Windows**: `%LOCALAPPDATA%\running-process\tracked-pids{-hash}.sqlite3`
92pub fn db_path(scope_hash: Option<&str>) -> PathBuf {
93    let suffix = match scope_hash {
94        Some(h) => format!("-{h}"),
95        None => String::new(),
96    };
97
98    #[cfg(windows)]
99    {
100        let base = local_app_data_dir();
101        let _ = std::fs::create_dir_all(&base);
102        base.join(format!("tracked-pids{suffix}.sqlite3"))
103    }
104
105    #[cfg(unix)]
106    {
107        let dir = state_dir_unix();
108        let _ = std::fs::create_dir_all(&dir);
109        dir.join(format!("tracked-pids{suffix}.sqlite3"))
110    }
111}
112
113/// Returns the shadow directory used for ephemeral run data.
114///
115/// - **Windows**: `%LOCALAPPDATA%\running-process\run\`
116/// - **Linux**: `$XDG_RUNTIME_DIR/running-process/run/`
117/// - **macOS**: `$HOME/Library/Caches/running-process/run/`
118pub fn shadow_dir() -> PathBuf {
119    #[cfg(windows)]
120    {
121        let dir = local_app_data_dir().join("run");
122        let _ = std::fs::create_dir_all(&dir);
123        dir
124    }
125
126    #[cfg(target_os = "macos")]
127    {
128        let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp"));
129        let dir = home.join("Library/Caches/running-process/run");
130        let _ = std::fs::create_dir_all(&dir);
131        dir
132    }
133
134    #[cfg(all(unix, not(target_os = "macos")))]
135    {
136        let dir = runtime_dir_unix().join("run");
137        let _ = std::fs::create_dir_all(&dir);
138        dir
139    }
140}
141
142// ---------------------------------------------------------------------------
143// Platform helpers
144// ---------------------------------------------------------------------------
145
146#[cfg(windows)]
147fn local_app_data_dir() -> PathBuf {
148    dirs::data_local_dir()
149        .unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
150        .join("running-process")
151}
152
153#[cfg(unix)]
154fn runtime_dir_unix() -> PathBuf {
155    if let Some(d) = std::env::var_os("XDG_RUNTIME_DIR") {
156        PathBuf::from(d).join("running-process")
157    } else {
158        // Fallback: /tmp/running-process-{uid}
159        let uid = unsafe { libc::getuid() };
160        PathBuf::from(format!("/tmp/running-process-{uid}"))
161    }
162}
163
164#[cfg(unix)]
165fn state_dir_unix() -> PathBuf {
166    if let Some(d) = std::env::var_os("XDG_STATE_HOME") {
167        PathBuf::from(d).join("running-process")
168    } else if let Some(home) = dirs::home_dir() {
169        home.join(".local/state/running-process")
170    } else {
171        PathBuf::from("/tmp/running-process-state")
172    }
173}