1use std::fmt::Debug;
6use std::fmt::Formatter;
7use std::fs::File;
8use std::io::BufReader;
9use std::io::Read;
10use std::io::Write;
11use std::path::Path;
12use std::path::PathBuf;
13
14use anyhow::Context;
15use anyhow::Result;
16use crypto::CryptKey;
17
18mod any_snapshot;
19
20pub use any_snapshot::AnySnapshot;
21
22const DEFAULT_ENCRYPTED_CHUNK_SIZE_BYTES: usize = 1024 * 4;
24
25#[derive(Clone, serde::Serialize, serde::Deserialize)]
34pub struct SnapshotWriter {
35 dir: PathBuf,
36 key: Option<CryptKey>,
38}
39
40impl Debug for SnapshotWriter {
41 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("SnapshotWriter")
43 .field("dir", &format!("{:?}", self.dir))
44 .field("key", if self.key.is_some() { &"Some" } else { &"None" })
45 .finish()
46 }
47}
48
49impl SnapshotWriter {
50 pub fn new(root: PathBuf, encrypt: bool) -> Result<Self> {
57 std::fs::create_dir(&root)
58 .with_context(|| format!("failed to create snapshot root dir: {}", root.display()))?;
59
60 if encrypt {
61 let key = crypto::generate_random_key();
62 let mut writer = crypto::CryptWriter::new_from_key(
66 File::create(root.join("enc_metadata")).context("failed to create enc_metadata")?,
67 1024,
68 &key,
69 )
70 .context("failed to create enc_metadata writer")?;
71 writer.flush().context("flush of enc_metadata failed")?;
72 return Ok(Self {
73 dir: root,
74 key: Some(key),
75 });
76 }
77
78 Ok(Self {
79 dir: root,
80 key: None,
81 })
82 }
83
84 pub fn raw_fragment(&self, name: &str) -> Result<Box<dyn Write>> {
86 self.raw_fragment_with_chunk_size(name, DEFAULT_ENCRYPTED_CHUNK_SIZE_BYTES)
87 }
88
89 pub fn raw_fragment_with_chunk_size(
91 &self,
92 name: &str,
93 chunk_size_bytes: usize,
94 ) -> Result<Box<dyn Write>> {
95 let path = self.dir.join(name);
96 let file = File::options()
97 .write(true)
98 .create_new(true)
99 .open(&path)
100 .with_context(|| {
101 format!(
102 "failed to create snapshot fragment {name:?} at {}",
103 path.display()
104 )
105 })?;
106
107 if let Some(key) = self.key.as_ref() {
108 return Ok(Box::new(crypto::CryptWriter::new_from_key(
109 file,
110 chunk_size_bytes,
111 key,
112 )?));
113 }
114
115 Ok(Box::new(file))
116 }
117
118 pub fn write_fragment<T: serde::Serialize>(&self, name: &str, v: &T) -> Result<()> {
120 let mut w = std::io::BufWriter::new(self.raw_fragment(name)?);
121 ciborium::into_writer(v, &mut w)?;
122 w.flush()?;
123 Ok(())
124 }
125
126 pub fn add_namespace(&self, name: &str) -> Result<Self> {
129 let dir = self.dir.join(name);
130 std::fs::create_dir(&dir).with_context(|| {
131 format!(
132 "failed to create nested snapshot writer {name:?} at {}",
133 dir.display()
134 )
135 })?;
136 Ok(Self {
137 dir,
138 key: self.key.clone(),
139 })
140 }
141}
142
143#[derive(Clone, serde::Serialize, serde::Deserialize)]
145pub struct SnapshotReader {
146 dir: PathBuf,
147 key: Option<CryptKey>,
149}
150
151impl Debug for SnapshotReader {
152 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153 f.debug_struct("SnapshotReader")
154 .field("dir", &format!("{:?}", self.dir))
155 .field("key", if self.key.is_some() { &"Some" } else { &"None" })
156 .finish()
157 }
158}
159
160impl SnapshotReader {
161 pub fn new(root: &Path, require_encrypted: bool) -> Result<Self> {
163 let enc_metadata_path = root.join("enc_metadata");
164 if Path::exists(&enc_metadata_path) {
165 let key = Some(
166 crypto::CryptReader::extract_key(
167 File::open(&enc_metadata_path).context("failed to open encryption metadata")?,
168 )
169 .context("failed to load snapshot key")?,
170 );
171 return Ok(Self {
172 dir: root.to_path_buf(),
173 key,
174 });
175 } else if require_encrypted {
176 return Err(anyhow::anyhow!("snapshot was not encrypted"));
177 }
178
179 Ok(Self {
180 dir: root.to_path_buf(),
181 key: None,
182 })
183 }
184
185 pub fn raw_fragment(&self, name: &str) -> Result<Box<dyn Read>> {
187 let path = self.dir.join(name);
188 let file = File::open(&path).with_context(|| {
189 format!(
190 "failed to open snapshot fragment {name:?} at {}",
191 path.display()
192 )
193 })?;
194 if let Some(key) = self.key.as_ref() {
195 return Ok(Box::new(crypto::CryptReader::from_file_and_key(file, key)?));
196 }
197
198 Ok(Box::new(file))
199 }
200
201 pub fn read_fragment<T: serde::de::DeserializeOwned>(&self, name: &str) -> Result<T> {
203 Ok(ciborium::from_reader(BufReader::new(
204 self.raw_fragment(name)?,
205 ))?)
206 }
207
208 pub fn list_fragments(&self) -> Result<Vec<String>> {
210 let mut result = Vec::new();
211 for entry in std::fs::read_dir(&self.dir)? {
212 let entry = entry?;
213 if entry.file_type()?.is_file() {
214 result.push(entry.file_name().to_string_lossy().into_owned());
215 }
216 }
217 Ok(result)
218 }
219
220 pub fn namespace(&self, name: &str) -> Result<Self> {
222 let dir = self.dir.join(name);
223 Ok(Self {
224 dir,
225 key: self.key.clone(),
226 })
227 }
228
229 pub fn list_namespaces(&self) -> Result<Vec<String>> {
231 let mut result = Vec::new();
232 for entry in std::fs::read_dir(&self.dir)? {
233 let entry = entry?;
234 if entry.path().is_dir() {
235 if let Some(file_name) = entry.path().file_name() {
236 result.push(file_name.to_string_lossy().into_owned());
237 }
238 }
239 }
240 Ok(result)
241 }
242}