llm_kernel/secrets/
atomic.rs1use std::io::Write;
2use std::path::Path;
3
4use crate::error::{KernelError, Result};
5
6pub fn write_atomic(path: &str, data: &[u8], mode: u32) -> Result<()> {
10 let target = Path::new(path);
11 let parent = target
12 .parent()
13 .ok_or_else(|| KernelError::Vault(format!("path has no parent directory: {path}")))?;
14 std::fs::create_dir_all(parent)?;
15
16 let mut tmp = tempfile::NamedTempFile::new_in(parent)?;
17 tmp.write_all(data)?;
18
19 #[cfg(unix)]
20 {
21 use std::os::unix::fs::PermissionsExt;
22 tmp.as_file_mut()
23 .set_permissions(std::fs::Permissions::from_mode(mode))?;
24 }
25
26 tmp.persist(target)
27 .map_err(|e| KernelError::Vault(format!("atomic persist failed: {}", e)))?;
28 Ok(())
29}
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34 use std::fs;
35
36 #[test]
37 fn test_write_atomic_creates_file() {
38 let dir = tempfile::tempdir().unwrap();
39 let path = dir.path().join("test.txt");
40 let path_str = path.to_string_lossy().to_string();
41
42 write_atomic(&path_str, b"hello", 0o644).unwrap();
43
44 let content = fs::read_to_string(&path).unwrap();
45 assert_eq!(content, "hello");
46 }
47
48 #[test]
49 fn test_write_atomic_overwrites() {
50 let dir = tempfile::tempdir().unwrap();
51 let path = dir.path().join("test.txt");
52 let path_str = path.to_string_lossy().to_string();
53
54 write_atomic(&path_str, b"first", 0o644).unwrap();
55 write_atomic(&path_str, b"second", 0o644).unwrap();
56
57 let content = fs::read_to_string(&path).unwrap();
58 assert_eq!(content, "second");
59 }
60}