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 diem_temppath::TempPath;
10use diem_time_service::{TimeService, TimeServiceTrait};
11use serde::{de::DeserializeOwned, Serialize};
12use serde_json::Value;
13use std::{
14    collections::HashMap,
15    fs::{self, File},
16    io::{Read, Write},
17    path::PathBuf,
18};
19
20/// OnDiskStorage represents a key value store that is persisted to the local
21/// filesystem and is intended for single threads (or must be wrapped by a
22/// Arc<RwLock<>>). This provides no permission checks and simply offers a proof
23/// of concept to unblock building of applications without more complex data
24/// stores. Internally, it reads and writes all data to a file, which means that
25/// it must make copies of all key material which violates the Diem code base.
26/// It violates it because the anticipation is that data stores would securely
27/// handle key material. This should not be used in production.
28pub struct OnDiskStorage {
29    file_path: PathBuf,
30    temp_path: TempPath,
31    time_service: TimeService,
32}
33
34impl OnDiskStorage {
35    pub fn new(file_path: PathBuf) -> Self {
36        Self::new_with_time_service(file_path, TimeService::real())
37    }
38
39    fn new_with_time_service(
40        file_path: PathBuf, time_service: TimeService,
41    ) -> Self {
42        if !file_path.exists() {
43            File::create(&file_path).expect("Unable to create storage");
44        }
45
46        // The parent will be one when only a filename is supplied. Therefore
47        // use the current working directory provided by PathBuf::new().
48        let file_dir = file_path
49            .parent()
50            .map_or(PathBuf::new(), |p| p.to_path_buf());
51
52        Self {
53            file_path,
54            temp_path: TempPath::new_with_temp_dir(file_dir),
55            time_service,
56        }
57    }
58
59    fn read(&self) -> Result<HashMap<String, Value>, Error> {
60        let mut file = File::open(&self.file_path)?;
61        let mut contents = String::new();
62        file.read_to_string(&mut contents)?;
63        if contents.is_empty() {
64            return Ok(HashMap::new());
65        }
66        let data = serde_json::from_str(&contents)?;
67        Ok(data)
68    }
69
70    fn write(&self, data: &HashMap<String, Value>) -> Result<(), Error> {
71        let contents = serde_json::to_vec(data)?;
72        let mut file = File::create(self.temp_path.path())?;
73        file.write_all(&contents)?;
74        fs::rename(&self.temp_path, &self.file_path)?;
75        Ok(())
76    }
77
78    pub fn file_path(&self) -> &PathBuf { &self.file_path }
79}
80
81impl KVStorage for OnDiskStorage {
82    fn available(&self) -> Result<(), Error> { Ok(()) }
83
84    fn get<V: DeserializeOwned>(
85        &self, key: &str,
86    ) -> Result<GetResponse<V>, Error> {
87        let mut data = self.read()?;
88        data.remove(key)
89            .ok_or_else(|| Error::KeyNotSet(key.to_string()))
90            .and_then(|value| {
91                serde_json::from_value(value).map_err(|e| e.into())
92            })
93    }
94
95    fn set<V: Serialize>(&mut self, key: &str, value: V) -> Result<(), Error> {
96        let now = self.time_service.now_secs();
97        let mut data = self.read()?;
98        data.insert(
99            key.to_string(),
100            serde_json::to_value(&GetResponse::new(value, now))?,
101        );
102        self.write(&data)
103    }
104
105    #[cfg(any(test, feature = "testing"))]
106    fn reset_and_clear(&mut self) -> Result<(), Error> {
107        self.write(&HashMap::new())
108    }
109}
110
111impl CryptoKVStorage for OnDiskStorage {}