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/// Directory name this product owns beneath each host location.
9const PRODUCT: &str = "running-process";
10
11/// Returns the local socket name the daemon listens on.
12///
13/// - **Linux/macOS**: `$XDG_RUNTIME_DIR/running-process/daemon{-hash}.sock`
14///   (fallback: `/tmp/running-process-{uid}/daemon{-hash}.sock`)
15/// - **Windows**: `\\.\pipe\running-process-daemon-{username}{-hash}`
16///
17/// The returned display path must be passed through
18/// [`crate::broker::server::singleton_bind::wrap_socket_name`] before use.
19/// That shared boundary prevents Windows pipe paths from acquiring the
20/// namespace prefix more than once.
21pub fn socket_path(scope_hash: Option<&str>) -> String {
22    // This is a product-owned, dedicated leaf, so the caller is allowed to
23    // repair legacy permissions before the platform bind verifies it. A host
24    // whose sockets live in a kernel namespace rather than a directory has
25    // nothing there to repair.
26    if crate::platform::ipc::endpoint_is_filesystem_backed() {
27        let _ = crate::broker::secure_dir::ensure_private_dir(&runtime_dir());
28    }
29    socket_path_view(scope_hash)
30}
31
32/// Read-only variant of [`socket_path`]: derives the same endpoint string
33/// without creating any directory. Used by read-only inspectors (#391).
34pub fn socket_path_view(scope_hash: Option<&str>) -> String {
35    let suffix = match scope_hash {
36        Some(h) => format!("-{h}"),
37        None => String::new(),
38    };
39
40    if crate::platform::ipc::endpoint_is_filesystem_backed() {
41        return format!("{}/daemon{suffix}.sock", runtime_dir().display());
42    }
43    let username = crate::env_vars::USERNAME
44        .text()
45        .unwrap_or_else(|| "unknown".into());
46    format!(r"\\.\pipe\running-process-daemon-{username}{suffix}")
47}
48
49/// Build an opaque local IPC endpoint from the path returned by [`socket_path`].
50///
51/// This must use the same name-type dispatch as the server so that client
52/// and server agree on the actual IPC endpoint.
53pub fn make_socket_endpoint(path: &str) -> std::io::Result<crate::platform::ipc::Endpoint> {
54    crate::platform::ipc::Endpoint::new(path)
55}
56
57/// Returns the path to the daemon PID file.
58///
59/// - **Linux/macOS**: same directory as the socket, with `.pid` extension.
60/// - **Windows**: `%LOCALAPPDATA%\running-process\daemon{-hash}.pid`
61pub fn pid_file_path(scope_hash: Option<&str>) -> PathBuf {
62    let path = pid_file_path_view(scope_hash);
63    if let Some(parent) = path.parent() {
64        let _ = std::fs::create_dir_all(parent);
65    }
66    path
67}
68
69/// Read-only variant of [`pid_file_path`]: derives the same path without
70/// creating any directory. Used by read-only inspectors (#391).
71pub fn pid_file_path_view(scope_hash: Option<&str>) -> PathBuf {
72    let suffix = match scope_hash {
73        Some(h) => format!("-{h}"),
74        None => String::new(),
75    };
76
77    runtime_dir().join(format!("daemon{suffix}.pid"))
78}
79
80/// Returns the path to the daemon SQLite database.
81///
82/// - **Linux/macOS**: `$XDG_STATE_HOME/running-process/tracked-pids{-hash}.sqlite3`
83///   (fallback: `~/.local/state/running-process/tracked-pids{-hash}.sqlite3`)
84/// - **Windows**: `%LOCALAPPDATA%\running-process\tracked-pids{-hash}.sqlite3`
85pub fn db_path(scope_hash: Option<&str>) -> PathBuf {
86    let path = db_path_view(scope_hash);
87    if let Some(parent) = path.parent() {
88        let _ = std::fs::create_dir_all(parent);
89    }
90    path
91}
92
93/// Read-only variant of [`db_path`]: derives the same path without creating
94/// any directory. Used by read-only inspectors (#391).
95pub fn db_path_view(scope_hash: Option<&str>) -> PathBuf {
96    let suffix = match scope_hash {
97        Some(h) => format!("-{h}"),
98        None => String::new(),
99    };
100    data_dir().join(format!("tracked-pids{suffix}.sqlite3"))
101}
102
103/// Returns the shadow directory used for ephemeral run data.
104///
105/// - **Windows**: `%LOCALAPPDATA%\running-process\run\`
106/// - **Linux**: `$XDG_RUNTIME_DIR/running-process/run/`
107/// - **macOS**: `$HOME/Library/Caches/running-process/run/`
108pub fn shadow_dir() -> PathBuf {
109    let dir = shadow_dir_view();
110    let _ = std::fs::create_dir_all(&dir);
111    dir
112}
113
114/// Read-only variant of [`shadow_dir`]: derives the same path without
115/// creating any directory. Used by read-only inspectors (#391).
116pub fn shadow_dir_view() -> PathBuf {
117    crate::platform::fs::user_run_data_root(PRODUCT).join("run")
118}
119
120/// Returns the daemon data directory (where the SQLite tracking database
121/// lives) WITHOUT creating it. Read-only callers (doctor, status probes)
122/// use this; [`db_path`] keeps its create-on-derive behavior.
123pub fn data_dir() -> PathBuf {
124    crate::platform::fs::user_state_dir(PRODUCT)
125}
126
127/// Where this host keeps our ephemeral runtime artifacts.
128fn runtime_dir() -> PathBuf {
129    crate::platform::fs::user_runtime_dir(PRODUCT)
130}