diem_secure_storage/
on_disk.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8use crate::{CryptoKVStorage, Error, GetResponse, KVStorage};
9use serde::{de::DeserializeOwned, Serialize};
10use serde_json::Value;
11use std::{
12    collections::HashMap,
13    fs::File,
14    io::{Read, Write},
15    path::{Path, PathBuf},
16    time::{SystemTime, UNIX_EPOCH},
17};
18
19/// A key-value store persisted to a single JSON file.
20///
21/// The file is the source of truth: `get` reads it on every call and `set`
22/// rewrites it atomically (temp file + rename). Callers that need a fast
23/// per-read path cache one layer up (see
24/// `PersistentSafetyStorage::cached_safety_data`).
25///
26/// Tradeoffs inherited from Diem's non-Vault path:
27/// - No OS-level permission gating — relies on the file's Unix permissions.
28/// - Key material is held in plaintext in process memory — not an HSM.
29///
30/// Not thread-safe on its own; callers wrap it in `Arc<RwLock<_>>`.
31pub struct OnDiskStorage {
32    file_path: PathBuf,
33}
34
35impl OnDiskStorage {
36    pub fn new(file_path: PathBuf) -> Self {
37        if !file_path.exists() {
38            File::create(&file_path).expect("Unable to create storage");
39        }
40        Self { file_path }
41    }
42
43    pub fn file_path(&self) -> &PathBuf { &self.file_path }
44
45    fn read(&self) -> Result<HashMap<String, Value>, Error> {
46        let mut file = File::open(&self.file_path)?;
47        let mut contents = String::new();
48        file.read_to_string(&mut contents)?;
49        if contents.is_empty() {
50            return Ok(HashMap::new());
51        }
52        Ok(serde_json::from_str(&contents)?)
53    }
54
55    fn write(&self, data: &HashMap<String, Value>) -> Result<(), Error> {
56        let contents = serde_json::to_vec(data)?;
57        let dir = self.file_path.parent().unwrap_or_else(|| Path::new("."));
58        let mut temp = tempfile::Builder::new().tempfile_in(dir)?;
59        temp.write_all(&contents)?;
60        temp.persist(&self.file_path)
61            .map_err(|e| Error::from(e.error))?;
62        Ok(())
63    }
64}
65
66impl KVStorage for OnDiskStorage {
67    fn available(&self) -> Result<(), Error> { Ok(()) }
68
69    fn get<V: DeserializeOwned>(
70        &self, key: &str,
71    ) -> Result<GetResponse<V>, Error> {
72        let mut data = self.read()?;
73        data.remove(key)
74            .ok_or_else(|| Error::KeyNotSet(key.to_string()))
75            .and_then(|value| {
76                serde_json::from_value(value).map_err(|e| e.into())
77            })
78    }
79
80    fn set<V: Serialize>(&mut self, key: &str, value: V) -> Result<(), Error> {
81        let now = SystemTime::now()
82            .duration_since(UNIX_EPOCH)
83            .expect("System time is before UNIX_EPOCH")
84            .as_secs();
85        let mut data = self.read()?;
86        data.insert(
87            key.to_string(),
88            serde_json::to_value(&GetResponse::new(value, now))?,
89        );
90        self.write(&data)
91    }
92
93    #[cfg(any(test, feature = "testing"))]
94    fn reset_and_clear(&mut self) -> Result<(), Error> {
95        self.write(&HashMap::new())
96    }
97}
98
99impl CryptoKVStorage for OnDiskStorage {}