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

            
12
use std::path::Path;
13

            
14
use fs_mistrust::CheckedDir;
15
use serde::{Serialize, de::DeserializeOwned};
16
use tor_basic_utils::PathExt;
17
use tor_error::ErrorReport as _;
18
use tracing::trace;
19

            
20
use 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())]
25
pub(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

            
36
impl Target<'_> {
37
    /// Load and deserialize a `D` from the file specified by `self`
38
    ///
39
    /// Returns `None` if the file doesn't exist.
40
76
    pub(crate) fn load<D: DeserializeOwned>(&self) -> Result<Option<D>, ErrorSource> {
41
76
        let string = match self.dir.read_to_string(self.rel_fname) {
42
32
            Ok(string) => string,
43
            Err(fs_mistrust::Error::NotFound(_)) => {
44
42
                trace!("loading {self} (not found)");
45
42
                return Ok(None);
46
            }
47
2
            Err(e) => {
48
2
                trace!("loading {self}, error {}", e.report());
49
2
                return Err(e.into());
50
            }
51
        };
52

            
53
32
        let r = serde_json::from_str(&string)?;
54
28
        trace!("loaded {self}");
55

            
56
28
        Ok(Some(r))
57
76
    }
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
382
    pub(crate) fn store<S: Serialize>(&self, val: &S) -> Result<(), ErrorSource> {
75
382
        trace!("storing {self}");
76
382
        let output = serde_json::to_string_pretty(val)?;
77

            
78
382
        self.dir.write_and_replace(self.rel_fname, output)?;
79

            
80
382
        Ok(())
81
382
    }
82

            
83
    /// Delete the file specified by `self`
84
2
    pub(crate) fn delete(&self) -> Result<(), ErrorSource> {
85
2
        trace!("deleting {self}");
86
2
        self.dir.remove_file(self.rel_fname)?;
87

            
88
2
        Ok(())
89
2
    }
90
}