tor_persist/load_store.rs
1//! Helper module for loading and storing via serde
2//!
3//! Utilities to load or store a serde-able object,
4//! in JSON format,
5//! to/from a disk file at a caller-specified filename.
6//!
7//! The caller is supposed to do any necessary locking.
8//!
9//! The entrypoints are methods on `[Target]`,
10//! which the caller is supposed to construct.
11
12use std::path::Path;
13
14use fs_mistrust::CheckedDir;
15use serde::{de::DeserializeOwned, Serialize};
16use tor_error::ErrorReport as _;
17use tracing::trace;
18
19use crate::err::ErrorSource;
20
21/// Common arguments to load/store operations
22#[derive(derive_more::Display)]
23#[display("{:?}/{:?}", dir.as_path(), rel_fname)]
24pub(crate) struct Target<'r> {
25 /// Directory
26 pub(crate) dir: &'r CheckedDir,
27
28 /// Filename relative to `dir`
29 ///
30 /// Might be a leafname; must be relative
31 /// Should include the `.json` extension.
32 pub(crate) rel_fname: &'r Path,
33}
34
35impl Target<'_> {
36 /// Load and deserialize a `D` from the file specified by `self`
37 ///
38 /// Returns `None` if the file doesn't exist.
39 pub(crate) fn load<D: DeserializeOwned>(&self) -> Result<Option<D>, ErrorSource> {
40 let string = match self.dir.read_to_string(self.rel_fname) {
41 Ok(string) => string,
42 Err(fs_mistrust::Error::NotFound(_)) => {
43 trace!("loading {self} (not found)");
44 return Ok(None);
45 }
46 Err(e) => {
47 trace!("loading {self}, error {}", e.report());
48 return Err(e.into());
49 }
50 };
51
52 let r = serde_json::from_str(&string)?;
53 trace!("loaded {self}");
54
55 Ok(Some(r))
56 }
57
58 /// Serialise and store an `S` to the file specified by `self`
59 ///
60 /// Concurrent readers (using `load`) will see either the old data,
61 /// or the new data,
62 /// not corruption or a mixture.
63 ///
64 /// Likewise, if something fails, the old data will remain.
65 /// (But, we do *not* use `fsync`.)
66 ///
67 /// It is a serious bug to make several concurrent calls to `store`
68 /// for the same file.
69 /// That might result in corrupted files.
70 ///
71 /// See [`fs_mistrust::CheckedDir::write_and_replace`]
72 /// for more details about the semantics.
73 pub(crate) fn store<S: Serialize>(&self, val: &S) -> Result<(), ErrorSource> {
74 trace!("storing {self}");
75 let output = serde_json::to_string_pretty(val)?;
76
77 self.dir.write_and_replace(self.rel_fname, output)?;
78
79 Ok(())
80 }
81
82 /// Delete the file specified by `self`
83 pub(crate) fn delete(&self) -> Result<(), ErrorSource> {
84 trace!("deleting {self}");
85 self.dir.remove_file(self.rel_fname)?;
86
87 Ok(())
88 }
89}