1use std::fs::{self, File};
4use std::io::{self, Write};
5use std::path::Path;
6
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9
10pub fn ensure_parent(path: &Path) -> io::Result<()> {
11 if let Some(parent) = path.parent() {
12 fs::create_dir_all(parent)?;
13 }
14 Ok(())
15}
16
17pub fn read_json<T>(path: &Path) -> io::Result<T>
18where
19 T: DeserializeOwned,
20{
21 let text = fs::read_to_string(path)?;
22 serde_json::from_str(&text).map_err(io::Error::other)
23}
24
25pub fn write_json_atomic<T>(path: &Path, value: &T) -> io::Result<()>
26where
27 T: Serialize,
28{
29 let bytes = serde_json::to_vec_pretty(value).map_err(io::Error::other)?;
30 write_bytes_atomic(path, &bytes)
31}
32
33pub fn write_json_private_atomic<T>(path: &Path, value: &T) -> io::Result<()>
34where
35 T: Serialize,
36{
37 let bytes = serde_json::to_vec_pretty(value).map_err(io::Error::other)?;
38 write_bytes_private_atomic(path, &bytes)
39}
40
41pub fn write_json_compact_atomic<T>(path: &Path, value: &T) -> io::Result<()>
42where
43 T: Serialize,
44{
45 let bytes = serde_json::to_vec(value).map_err(io::Error::other)?;
46 write_bytes_atomic(path, &bytes)
47}
48
49pub fn write_bytes_atomic(path: &Path, bytes: &[u8]) -> io::Result<()> {
50 ensure_parent(path)?;
51
52 let tmp_path = path.with_extension("tmp");
53 let mut file = File::create(&tmp_path)?;
54 file.write_all(bytes)?;
55 file.write_all(b"\n")?;
56 file.sync_all()?;
57 drop(file);
58
59 fs::rename(&tmp_path, path)?;
60 sync_parent_dir(path)?;
61 Ok(())
62}
63
64#[cfg(unix)]
65pub fn write_bytes_private_atomic(path: &Path, bytes: &[u8]) -> io::Result<()> {
66 use std::fs::OpenOptions;
67 use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
68
69 ensure_parent(path)?;
70 if let Some(parent) = path.parent() {
71 fs::set_permissions(parent, fs::Permissions::from_mode(0o700))?;
72 }
73
74 let tmp_path = path.with_extension("tmp");
75 let mut file = OpenOptions::new()
76 .create(true)
77 .truncate(true)
78 .write(true)
79 .mode(0o600)
80 .open(&tmp_path)?;
81 file.write_all(bytes)?;
82 file.write_all(b"\n")?;
83 file.sync_all()?;
84 drop(file);
85
86 fs::rename(&tmp_path, path)?;
87 fs::set_permissions(path, fs::Permissions::from_mode(0o600))?;
88 sync_parent_dir(path)?;
89 Ok(())
90}
91
92#[cfg(not(unix))]
93pub fn write_bytes_private_atomic(path: &Path, bytes: &[u8]) -> io::Result<()> {
94 write_bytes_atomic(path, bytes)
95}
96
97#[cfg(unix)]
98fn sync_parent_dir(path: &Path) -> io::Result<()> {
99 if let Some(parent) = path.parent() {
100 File::open(parent)?.sync_all()?;
101 }
102 Ok(())
103}
104
105#[cfg(not(unix))]
106fn sync_parent_dir(_path: &Path) -> io::Result<()> {
107 Ok(())
108}