1use std::path::{Path, PathBuf};
13
14use anyhow::{bail, Context, Result};
15
16pub const MAX_SOCKET_PATH_LEN: usize = 104;
19
20pub fn runtime_dir() -> Result<PathBuf> {
22 let base = dirs::data_dir().context("could not determine the user data directory")?;
23 Ok(base.join("omni-dev"))
24}
25
26pub fn socket_path() -> Result<PathBuf> {
28 Ok(runtime_dir()?.join("daemon.sock"))
29}
30
31pub fn token_path() -> Result<PathBuf> {
34 Ok(runtime_dir()?.join("bridge.token"))
35}
36
37pub fn token_path_for_socket(socket: &Path) -> PathBuf {
41 socket.parent().map_or_else(
42 || PathBuf::from("bridge.token"),
43 |dir| dir.join("bridge.token"),
44 )
45}
46
47pub fn ensure_dir_0700(dir: &Path) -> Result<()> {
50 std::fs::create_dir_all(dir)
51 .with_context(|| format!("failed to create directory {}", dir.display()))?;
52 #[cfg(unix)]
53 {
54 use std::os::unix::fs::PermissionsExt;
55 std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))
56 .with_context(|| format!("failed to set 0700 on {}", dir.display()))?;
57 }
58 Ok(())
59}
60
61pub fn set_file_0600(path: &Path) -> Result<()> {
63 #[cfg(unix)]
64 {
65 use std::os::unix::fs::PermissionsExt;
66 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
67 .with_context(|| format!("failed to set 0600 on {}", path.display()))?;
68 }
69 #[cfg(not(unix))]
70 {
71 let _ = path;
72 }
73 Ok(())
74}
75
76pub fn check_socket_path_len(path: &Path) -> Result<()> {
79 let len = path.as_os_str().len();
80 if len >= MAX_SOCKET_PATH_LEN {
81 bail!(
82 "socket path is {len} bytes, exceeding the {MAX_SOCKET_PATH_LEN}-byte limit: {} — \
83 pass a shorter --socket path",
84 path.display()
85 );
86 }
87 Ok(())
88}
89
90#[cfg(test)]
91#[allow(clippy::unwrap_used, clippy::expect_used)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn default_paths_sit_under_the_runtime_dir() {
97 let rt = runtime_dir().unwrap();
98 assert!(socket_path().unwrap().starts_with(&rt));
99 assert!(token_path().unwrap().starts_with(&rt));
100 }
101
102 #[test]
103 fn token_sits_beside_its_socket() {
104 assert_eq!(
105 token_path_for_socket(Path::new("/tmp/x/daemon.sock")),
106 Path::new("/tmp/x/bridge.token")
107 );
108 assert_eq!(
110 token_path_for_socket(Path::new("daemon.sock")),
111 Path::new("bridge.token")
112 );
113 }
114
115 #[test]
116 fn socket_path_length_guard() {
117 check_socket_path_len(Path::new("/tmp/short.sock")).unwrap();
118 let too_long = PathBuf::from(format!("/{}", "a".repeat(MAX_SOCKET_PATH_LEN)));
119 assert!(check_socket_path_len(&too_long).is_err());
120 }
121
122 #[test]
123 fn ensure_dir_and_file_perms() {
124 let dir = tempfile::tempdir().unwrap();
125 let sub = dir.path().join("run");
126 ensure_dir_0700(&sub).unwrap();
127 assert!(sub.is_dir());
128 let file = sub.join("k");
129 std::fs::write(&file, "x").unwrap();
130 set_file_0600(&file).unwrap();
131 #[cfg(unix)]
132 {
133 use std::os::unix::fs::PermissionsExt;
134 assert_eq!(
135 std::fs::metadata(&sub).unwrap().permissions().mode() & 0o777,
136 0o700
137 );
138 assert_eq!(
139 std::fs::metadata(&file).unwrap().permissions().mode() & 0o777,
140 0o600
141 );
142 }
143 }
144}