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