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::{de::DeserializeOwned, Serialize};
16
use tor_error::ErrorReport as _;
17
use tracing::trace;
18

            
19
use crate::err::ErrorSource;
20

            
21
/// Common arguments to load/store operations
22
#[derive(derive_more::Display)]
23
#[display("{:?}/{:?}", dir.as_path(), rel_fname)]
24
pub(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

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

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

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

            
77
242
        self.dir.write_and_replace(self.rel_fname, output)?;
78

            
79
242
        Ok(())
80
242
    }
81

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

            
87
2
        Ok(())
88
2
    }
89
}