1use std::io;
7use std::path::Path;
8
9pub fn secure_file_perms(path: &Path) -> io::Result<()> {
11 crate::platform::set_mode(path, 0o600)
12}
13
14pub fn secure_dir_perms(path: &Path) -> io::Result<()> {
16 crate::platform::set_mode(path, 0o700)
17}
18
19pub fn ensure_file_private(path: &Path) -> io::Result<Option<u32>> {
24 crate::platform::ensure_private(path, 0o600)
25}
26
27pub fn write_private(path: &Path, contents: &[u8]) -> io::Result<()> {
41 crate::platform::write_with_mode(path, contents, 0o600)
42}
43
44pub fn open_private_append(path: &Path) -> io::Result<std::fs::File> {
63 crate::platform::open_append_with_mode(path, 0o600)
64}
65
66pub fn create_private_dir_all(path: &Path) -> io::Result<()> {
76 crate::platform::create_dir_all_with_mode(path, 0o700)
77}
78
79#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[cfg(unix)]
89 fn mode_of(path: &Path) -> u32 {
90 use std::os::unix::fs::PermissionsExt;
91 std::fs::metadata(path).unwrap().permissions().mode() & 0o777
92 }
93
94 #[cfg(unix)]
95 fn set_mode(path: &Path, mode: u32) {
96 use std::os::unix::fs::PermissionsExt;
97 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).unwrap();
98 }
99
100 #[cfg(windows)]
106 fn env_lock() -> std::sync::MutexGuard<'static, ()> {
107 crate::platform::ENV_LOCK.lock().expect("env lock")
108 }
109
110 #[test]
116 fn write_private_creates_an_owner_only_file_with_the_content() {
117 #[cfg(windows)]
118 let _env = env_lock();
119 let dir = tempfile::tempdir().unwrap();
120 let path = dir.path().join("secret");
121
122 write_private(&path, b"the key").unwrap();
123 assert_eq!(std::fs::read(&path).unwrap(), b"the key");
124 #[cfg(unix)]
125 assert_eq!(mode_of(&path), 0o600);
126 }
127
128 #[test]
132 fn write_private_retightens_an_existing_permissive_file() {
133 #[cfg(windows)]
134 let _env = env_lock();
135 let dir = tempfile::tempdir().unwrap();
136 let path = dir.path().join("secret");
137 std::fs::write(&path, b"old").unwrap();
138 #[cfg(unix)]
139 set_mode(&path, 0o644);
140
141 write_private(&path, b"new").unwrap();
142 assert_eq!(std::fs::read(&path).unwrap(), b"new");
143 #[cfg(unix)]
144 assert_eq!(mode_of(&path), 0o600);
145 }
146
147 #[test]
149 fn write_private_propagates_an_unwritable_path() {
150 let dir = tempfile::tempdir().unwrap();
151 let path = dir.path().join("no-such-dir").join("secret");
152 assert!(write_private(&path, b"x").is_err());
153 }
154
155 #[test]
159 fn open_private_append_creates_an_owner_only_file() {
160 #[cfg(windows)]
161 let _env = env_lock();
162 let dir = tempfile::tempdir().unwrap();
163 let path = dir.path().join("run.lvr");
164
165 {
166 use std::io::Write;
167 let mut f = open_private_append(&path).unwrap();
168 f.write_all(b"first").unwrap();
169 }
170
171 assert_eq!(std::fs::read(&path).unwrap(), b"first");
172 #[cfg(unix)]
173 assert_eq!(mode_of(&path), 0o600);
174 }
175
176 #[test]
179 fn open_private_append_adds_to_an_existing_file() {
180 #[cfg(windows)]
181 let _env = env_lock();
182 let dir = tempfile::tempdir().unwrap();
183 let path = dir.path().join("log");
184
185 for chunk in [b"one", b"two"] {
186 use std::io::Write;
187 let mut f = open_private_append(&path).unwrap();
188 f.write_all(chunk).unwrap();
189 }
190
191 assert_eq!(std::fs::read(&path).unwrap(), b"onetwo");
192 }
193
194 #[test]
198 fn open_private_append_retightens_an_existing_permissive_file() {
199 #[cfg(windows)]
200 let _env = env_lock();
201 let dir = tempfile::tempdir().unwrap();
202 let path = dir.path().join("run.lvr");
203 std::fs::write(&path, b"old").unwrap();
204 #[cfg(unix)]
205 set_mode(&path, 0o644);
206
207 drop(open_private_append(&path).unwrap());
208
209 #[cfg(unix)]
210 assert_eq!(mode_of(&path), 0o600);
211 }
212
213 #[test]
214 fn open_private_append_propagates_an_unwritable_path() {
215 let dir = tempfile::tempdir().unwrap();
216 let path = dir.path().join("no-such-dir").join("log");
217 assert!(open_private_append(&path).is_err());
218 }
219
220 #[test]
221 fn create_private_dir_all_makes_owner_only_directories() {
222 #[cfg(windows)]
223 let _env = env_lock();
224 let dir = tempfile::tempdir().unwrap();
225 let nested = dir.path().join("stages").join("0");
226
227 create_private_dir_all(&nested).unwrap();
228
229 assert!(nested.is_dir());
230 #[cfg(unix)]
231 assert_eq!(mode_of(&nested), 0o700);
232 }
233
234 #[test]
240 fn create_private_dir_all_propagates_a_path_it_cannot_create() {
241 #[cfg(windows)]
242 let _env = env_lock();
243 let dir = tempfile::tempdir().unwrap();
244 let blocker = dir.path().join("not-a-dir");
246 std::fs::write(&blocker, b"x").unwrap();
247
248 assert!(create_private_dir_all(&blocker.join("child")).is_err());
249 assert!(create_private_dir_all(&blocker).is_err());
252 }
253
254 #[test]
255 fn create_private_dir_all_is_idempotent() {
256 #[cfg(windows)]
257 let _env = env_lock();
258 let dir = tempfile::tempdir().unwrap();
259 let nested = dir.path().join("stages").join("0");
260
261 create_private_dir_all(&nested).unwrap();
262 create_private_dir_all(&nested).unwrap();
263
264 assert!(nested.is_dir());
265 }
266
267 #[test]
268 fn secure_file_perms_restricts_on_unix_and_succeeds_everywhere() {
269 #[cfg(windows)]
270 let _env = env_lock();
271 let dir = tempfile::tempdir().unwrap();
272 let path = dir.path().join("secret");
273 std::fs::write(&path, b"x").unwrap();
274 #[cfg(unix)]
275 set_mode(&path, 0o644);
276
277 secure_file_perms(&path).unwrap();
278
279 #[cfg(unix)]
280 assert_eq!(mode_of(&path), 0o600);
281 }
282
283 #[test]
284 fn secure_dir_perms_restricts_on_unix_and_succeeds_everywhere() {
285 #[cfg(windows)]
286 let _env = env_lock();
287 let dir = tempfile::tempdir().unwrap();
288 let sub = dir.path().join("d");
289 std::fs::create_dir(&sub).unwrap();
290 #[cfg(unix)]
291 set_mode(&sub, 0o755);
292
293 secure_dir_perms(&sub).unwrap();
294
295 #[cfg(unix)]
296 assert_eq!(mode_of(&sub), 0o700);
297 }
298
299 #[test]
300 fn secure_file_perms_missing_path_behavior() {
301 #[cfg(windows)]
302 let _env = env_lock();
303 let dir = tempfile::tempdir().unwrap();
309 let missing = dir.path().join("nope");
310 let result = secure_file_perms(&missing);
311 #[cfg(any(unix, windows))]
312 assert!(result.is_err());
313 #[cfg(not(any(unix, windows)))]
314 assert!(result.is_ok());
315 }
316
317 #[test]
318 fn ensure_file_private_tightens_permissive_file_on_unix() {
319 #[cfg(windows)]
320 let _env = env_lock();
321 let dir = tempfile::tempdir().unwrap();
322 let path = dir.path().join("cfg");
323 std::fs::write(&path, b"x").unwrap();
324 #[cfg(unix)]
325 set_mode(&path, 0o644);
326
327 let previous = ensure_file_private(&path).unwrap();
328
329 #[cfg(unix)]
330 {
331 assert_eq!(previous, Some(0o100644));
332 assert_eq!(mode_of(&path), 0o600);
333 }
334 #[cfg(not(unix))]
336 assert_eq!(previous, None);
337 }
338
339 #[test]
340 fn ensure_file_private_leaves_private_file_untouched() {
341 #[cfg(windows)]
342 let _env = env_lock();
343 let dir = tempfile::tempdir().unwrap();
344 let path = dir.path().join("cfg");
345 std::fs::write(&path, b"x").unwrap();
346 #[cfg(unix)]
347 set_mode(&path, 0o600);
348
349 assert_eq!(ensure_file_private(&path).unwrap(), None);
350
351 #[cfg(unix)]
352 assert_eq!(mode_of(&path), 0o600);
353 }
354
355 #[test]
356 fn ensure_file_private_is_noop_for_missing_path() {
357 let dir = tempfile::tempdir().unwrap();
358 let missing = dir.path().join("nope");
359 assert_eq!(ensure_file_private(&missing).unwrap(), None);
360 }
361}