linera_persistent/
file.rs1use std::{
7 io::{self, BufRead as _, Write as _},
8 path::Path,
9};
10
11use fs4::FileExt;
12
13use super::Persist;
14
15struct Lock(fs_err::File);
17
18#[derive(Debug, thiserror::Error)]
20#[non_exhaustive]
21pub enum Error {
22 #[error("I/O error: {0}")]
24 IoError(#[from] std::io::Error),
25 #[error("JSON error: {0}")]
27 JsonError(#[from] serde_json::Error),
28 #[error("failed to lock {}: {source}", path.display())]
30 Lock {
31 path: std::path::PathBuf,
33 #[source]
35 source: std::io::Error,
36 },
37 #[error("failed to clean up after an error: {cleanup}; the original error was: {original}")]
39 Cleanup {
40 #[source]
42 cleanup: Box<Error>,
43 original: Box<Error>,
45 },
46}
47
48trait CleanupExt {
51 type Ok;
53
54 fn or_cleanup<E: Into<Error>>(
56 self,
57 cleanup: impl FnOnce() -> Result<(), E>,
58 ) -> Result<Self::Ok, Error>;
59}
60
61impl<T> CleanupExt for Result<T, Error> {
62 type Ok = T;
63
64 fn or_cleanup<E: Into<Error>>(
65 self,
66 cleanup: impl FnOnce() -> Result<(), E>,
67 ) -> Result<T, Error> {
68 self.map_err(|original| match cleanup() {
69 Ok(()) => original,
70 Err(cleanup) => Error::Cleanup {
71 cleanup: Box::new(cleanup.into()),
72 original: Box::new(original),
73 },
74 })
75 }
76}
77
78impl Lock {
79 pub fn new(file: fs_err::File) -> std::io::Result<Self> {
82 file.file().try_lock_exclusive()?;
83 Ok(Lock(file))
84 }
85}
86
87impl Drop for Lock {
88 fn drop(&mut self) {
89 if let Err(error) = FileExt::unlock(self.0.file()) {
90 tracing::warn!("Failed to unlock wallet file: {error}");
91 }
92 }
93}
94
95pub struct File<T> {
100 _lock: Lock,
101 path: std::path::PathBuf,
102 value: T,
103}
104
105impl<T> std::ops::Deref for File<T> {
106 type Target = T;
107 fn deref(&self) -> &T {
108 &self.value
109 }
110}
111
112impl<T> std::ops::DerefMut for File<T> {
113 fn deref_mut(&mut self) -> &mut T {
114 &mut self.value
115 }
116}
117
118fn open_options() -> fs_err::OpenOptions {
123 let mut options = fs_err::OpenOptions::new();
124 #[cfg(target_family = "unix")]
125 fs_err::os::unix::fs::OpenOptionsExt::mode(&mut options, 0o600);
126 options.create(true).read(true).write(true);
127 options
128}
129
130impl<T: serde::Serialize + serde::de::DeserializeOwned> File<T> {
131 pub fn new(path: &Path, value: T) -> Result<Self, Error> {
133 let this = Self {
134 _lock: Lock::new(
135 fs_err::OpenOptions::new()
136 .read(true)
137 .write(true)
138 .create(true)
139 .open(path)?,
140 )
141 .map_err(|source| Error::Lock {
142 path: path.into(),
143 source,
144 })?,
145 path: path.into(),
146 value,
147 };
148 this.save()?;
149 Ok(this)
150 }
151
152 pub fn read(path: &Path) -> Result<Self, Error> {
154 Self::read_or_create(path, || {
155 Err(std::io::Error::new(
156 std::io::ErrorKind::NotFound,
157 format!("file is empty or does not exist: {}", path.display()),
158 )
159 .into())
160 })
161 }
162
163 pub fn read_or_create(
166 path: &Path,
167 value: impl FnOnce() -> Result<T, Error>,
168 ) -> Result<Self, Error> {
169 let lock = Lock::new(open_options().read(true).open(path)?)?;
170 let mut reader = io::BufReader::new(&lock.0);
171 let file_is_empty = reader.fill_buf()?.is_empty();
172
173 let me = Self {
174 value: if file_is_empty {
175 value()?
176 } else {
177 serde_json::from_reader(reader)?
178 },
179 path: path.into(),
180 _lock: lock,
181 };
182
183 me.save()?;
184
185 Ok(me)
186 }
187
188 pub fn save(&self) -> Result<(), Error> {
190 let mut temp_file_path = self.path.clone();
191 temp_file_path.set_extension("json.new");
192 let temp_file = open_options().open(&temp_file_path)?;
193 let mut temp_file_writer = std::io::BufWriter::new(temp_file);
194
195 let remove_temp_file = || fs_err::remove_file(&temp_file_path);
196
197 serde_json::to_writer_pretty(&mut temp_file_writer, &self.value)
198 .map_err(Error::from)
199 .or_cleanup(remove_temp_file)?;
200 temp_file_writer
201 .flush()
202 .map_err(Error::from)
203 .or_cleanup(remove_temp_file)?;
204 drop(temp_file_writer);
205 fs_err::rename(&temp_file_path, &self.path)?;
206 Ok(())
207 }
208}
209
210impl<T: serde::Serialize + serde::de::DeserializeOwned + Send> Persist for File<T> {
211 type Error = Error;
212
213 fn as_mut(&mut self) -> &mut T {
214 &mut self.value
215 }
216
217 fn persist(&mut self) -> impl std::future::Future<Output = Result<(), Error>> {
227 let result = self.save();
228 async { result }
229 }
230
231 fn into_value(self) -> T {
233 self.value
234 }
235}