open_sesame/util/
paths.rs1use crate::util::{Error, Result};
8use std::fs;
9use std::os::unix::fs::PermissionsExt;
10use std::path::PathBuf;
11
12const SECURE_DIR_MODE: u32 = 0o700;
14
15pub fn cache_dir() -> Result<PathBuf> {
25 let base = dirs::cache_dir()
26 .or_else(|| {
27 dirs::home_dir().map(|h| h.join(".cache"))
29 })
30 .ok_or_else(|| {
31 Error::Other(
32 "Cannot determine cache directory: HOME environment variable not set".to_string(),
33 )
34 })?;
35
36 let cache_path = base.join("open-sesame");
37 ensure_secure_dir(&cache_path)?;
38 Ok(cache_path)
39}
40
41pub fn config_dir() -> Result<PathBuf> {
46 let base = dirs::config_dir()
47 .or_else(|| dirs::home_dir().map(|h| h.join(".config")))
48 .ok_or_else(|| {
49 Error::Other(
50 "Cannot determine config directory: HOME environment variable not set".to_string(),
51 )
52 })?;
53
54 Ok(base.join("open-sesame"))
55}
56
57pub fn cosmic_shortcuts_path() -> Result<PathBuf> {
61 let base = dirs::config_dir()
62 .or_else(|| dirs::home_dir().map(|h| h.join(".config")))
63 .ok_or_else(|| {
64 Error::Other(
65 "Cannot determine config directory: HOME environment variable not set".to_string(),
66 )
67 })?;
68
69 Ok(base.join("cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom"))
70}
71
72pub fn lock_file() -> Result<PathBuf> {
76 Ok(cache_dir()?.join("instance.lock"))
77}
78
79pub fn mru_file() -> Result<PathBuf> {
83 Ok(cache_dir()?.join("mru"))
84}
85
86pub fn log_file() -> Result<PathBuf> {
90 Ok(cache_dir()?.join("debug.log"))
91}
92
93fn ensure_secure_dir(path: &PathBuf) -> Result<()> {
98 if path.exists() {
99 if !path.is_dir() {
101 return Err(Error::Other(format!(
102 "{} exists but is not a directory",
103 path.display()
104 )));
105 }
106
107 let metadata = fs::metadata(path).map_err(|e| {
109 Error::Other(format!(
110 "Failed to read metadata for {}: {}",
111 path.display(),
112 e
113 ))
114 })?;
115
116 let current_mode = metadata.permissions().mode() & 0o777;
117 if current_mode != SECURE_DIR_MODE {
118 tracing::warn!(
119 "Fixing permissions on {} from {:o} to {:o}",
120 path.display(),
121 current_mode,
122 SECURE_DIR_MODE
123 );
124 fs::set_permissions(path, fs::Permissions::from_mode(SECURE_DIR_MODE)).map_err(
125 |e| {
126 Error::Other(format!(
127 "Failed to set permissions on {}: {}",
128 path.display(),
129 e
130 ))
131 },
132 )?;
133 }
134 } else {
135 fs::create_dir_all(path).map_err(|e| {
137 Error::Other(format!(
138 "Failed to create directory {}: {}",
139 path.display(),
140 e
141 ))
142 })?;
143
144 fs::set_permissions(path, fs::Permissions::from_mode(SECURE_DIR_MODE)).map_err(|e| {
145 Error::Other(format!(
146 "Failed to set permissions on {}: {}",
147 path.display(),
148 e
149 ))
150 })?;
151
152 tracing::debug!(
153 "Created secure directory: {} (mode {:o})",
154 path.display(),
155 SECURE_DIR_MODE
156 );
157 }
158
159 Ok(())
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_cache_dir_structure() {
168 if std::env::var("HOME").is_err() {
170 return;
171 }
172
173 let cache = cache_dir().expect("Should get cache dir");
174 assert!(cache.ends_with("open-sesame"));
175 assert!(cache.to_string_lossy().contains(".cache"));
176 }
177
178 #[test]
179 fn test_lock_file_path() {
180 if std::env::var("HOME").is_err() {
181 return;
182 }
183
184 let lock = lock_file().expect("Should get lock file path");
185 assert!(lock.ends_with("instance.lock"));
186 }
187
188 #[test]
189 fn test_mru_file_path() {
190 if std::env::var("HOME").is_err() {
191 return;
192 }
193
194 let mru = mru_file().expect("Should get MRU file path");
195 assert!(mru.ends_with("mru"));
196 }
197}