Skip to main content

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