• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2025 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::fmt::Debug;
6 use std::fmt::Formatter;
7 use std::fs::File;
8 use std::io::Read;
9 use std::io::Write;
10 use std::path::Path;
11 use std::path::PathBuf;
12 
13 use anyhow::Context;
14 use anyhow::Result;
15 use crypto::CryptKey;
16 
17 mod any_snapshot;
18 
19 pub use any_snapshot::AnySnapshot;
20 
21 // Use 4kB encrypted chunks by default (if encryption is used).
22 const DEFAULT_ENCRYPTED_CHUNK_SIZE_BYTES: usize = 1024 * 4;
23 
24 /// Writer of serialized VM snapshots.
25 ///
26 /// Each fragment is an opaque byte blob. Namespaces can be used to avoid fragment naming
27 /// collisions between devices.
28 ///
29 /// In the current implementation, fragments are files and namespaces are directories, but the API
30 /// is kept abstract so that we can potentially support something like a single file archive
31 /// output.
32 #[derive(Clone, serde::Serialize, serde::Deserialize)]
33 pub struct SnapshotWriter {
34     dir: PathBuf,
35     /// If encryption is used, the plaintext key will be stored here.
36     key: Option<CryptKey>,
37 }
38 
39 impl Debug for SnapshotWriter {
fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result40     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41         f.debug_struct("SnapshotWriter")
42             .field("dir", &format!("{:?}", self.dir))
43             .field("key", if self.key.is_some() { &"Some" } else { &"None" })
44             .finish()
45     }
46 }
47 
48 impl SnapshotWriter {
49     /// Creates a new `SnapshotWriter` that will writes its data to a dir at `root`. The path must
50     /// not exist yet. If encryption is desired, set encrypt (Note: only supported downstream on
51     /// Windows).
52     // TODO(b/268094487): If the snapshot fails, we leave incomplete snapshot files at the
53     // requested path. Consider building up the snapshot dir somewhere else and moving it into
54     // place at the end.
new(root: PathBuf, encrypt: bool) -> Result<Self>55     pub fn new(root: PathBuf, encrypt: bool) -> Result<Self> {
56         std::fs::create_dir(&root)
57             .with_context(|| format!("failed to create snapshot root dir: {}", root.display()))?;
58 
59         if encrypt {
60             let key = crypto::generate_random_key();
61             // Creating an empty CryptWriter will still write header information
62             // to the file, and that header information is what we need. This
63             // ensures we use a single key for *all* snapshot files.
64             let mut writer = crypto::CryptWriter::new_from_key(
65                 File::create(root.join("enc_metadata")).context("failed to create enc_metadata")?,
66                 1024,
67                 &key,
68             )
69             .context("failed to create enc_metadata writer")?;
70             writer.flush().context("flush of enc_metadata failed")?;
71             return Ok(Self {
72                 dir: root,
73                 key: Some(key),
74             });
75         }
76 
77         Ok(Self {
78             dir: root,
79             key: None,
80         })
81     }
82 
83     /// Creates a snapshot fragment and get access to the `Write` impl representing it.
raw_fragment(&self, name: &str) -> Result<Box<dyn Write>>84     pub fn raw_fragment(&self, name: &str) -> Result<Box<dyn Write>> {
85         self.raw_fragment_with_chunk_size(name, DEFAULT_ENCRYPTED_CHUNK_SIZE_BYTES)
86     }
87 
88     /// When encryption is used, allows direct control of the encrypted chunk size.
raw_fragment_with_chunk_size( &self, name: &str, chunk_size_bytes: usize, ) -> Result<Box<dyn Write>>89     pub fn raw_fragment_with_chunk_size(
90         &self,
91         name: &str,
92         chunk_size_bytes: usize,
93     ) -> Result<Box<dyn Write>> {
94         let path = self.dir.join(name);
95         let file = File::options()
96             .write(true)
97             .create_new(true)
98             .open(&path)
99             .with_context(|| {
100                 format!(
101                     "failed to create snapshot fragment {name:?} at {}",
102                     path.display()
103                 )
104             })?;
105 
106         if let Some(key) = self.key.as_ref() {
107             return Ok(Box::new(crypto::CryptWriter::new_from_key(
108                 file,
109                 chunk_size_bytes,
110                 key,
111             )?));
112         }
113 
114         Ok(Box::new(file))
115     }
116 
117     /// Creates a snapshot fragment from a serialized representation of `v`.
write_fragment<T: serde::Serialize>(&self, name: &str, v: &T) -> Result<()>118     pub fn write_fragment<T: serde::Serialize>(&self, name: &str, v: &T) -> Result<()> {
119         let mut w = std::io::BufWriter::new(self.raw_fragment(name)?);
120         serde_json::to_writer(&mut w, v)?;
121         w.flush()?;
122         Ok(())
123     }
124 
125     /// Creates new namespace and returns a `SnapshotWriter` that writes to it. Namespaces can be
126     /// nested.
add_namespace(&self, name: &str) -> Result<Self>127     pub fn add_namespace(&self, name: &str) -> Result<Self> {
128         let dir = self.dir.join(name);
129         std::fs::create_dir(&dir).with_context(|| {
130             format!(
131                 "failed to create nested snapshot writer {name:?} at {}",
132                 dir.display()
133             )
134         })?;
135         Ok(Self {
136             dir,
137             key: self.key.clone(),
138         })
139     }
140 }
141 
142 /// Reads snapshots created by `SnapshotWriter`.
143 #[derive(Clone, serde::Serialize, serde::Deserialize)]
144 pub struct SnapshotReader {
145     dir: PathBuf,
146     /// If encryption is used, the plaintext key will be stored here.
147     key: Option<CryptKey>,
148 }
149 
150 impl Debug for SnapshotReader {
fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result151     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
152         f.debug_struct("SnapshotReader")
153             .field("dir", &format!("{:?}", self.dir))
154             .field("key", if self.key.is_some() { &"Some" } else { &"None" })
155             .finish()
156     }
157 }
158 
159 impl SnapshotReader {
160     /// Reads a snapshot at `root`. Set require_encrypted to require an encrypted snapshot.
new(root: &Path, require_encrypted: bool) -> Result<Self>161     pub fn new(root: &Path, require_encrypted: bool) -> Result<Self> {
162         let enc_metadata_path = root.join("enc_metadata");
163         if Path::exists(&enc_metadata_path) {
164             let key = Some(
165                 crypto::CryptReader::extract_key(
166                     File::open(&enc_metadata_path).context("failed to open encryption metadata")?,
167                 )
168                 .context("failed to load snapshot key")?,
169             );
170             return Ok(Self {
171                 dir: root.to_path_buf(),
172                 key,
173             });
174         } else if require_encrypted {
175             return Err(anyhow::anyhow!("snapshot was not encrypted"));
176         }
177 
178         Ok(Self {
179             dir: root.to_path_buf(),
180             key: None,
181         })
182     }
183 
184     /// Gets access to a `Read` impl that represents a fragment.
raw_fragment(&self, name: &str) -> Result<Box<dyn Read>>185     pub fn raw_fragment(&self, name: &str) -> Result<Box<dyn Read>> {
186         let path = self.dir.join(name);
187         let file = File::open(&path).with_context(|| {
188             format!(
189                 "failed to open snapshot fragment {name:?} at {}",
190                 path.display()
191             )
192         })?;
193         if let Some(key) = self.key.as_ref() {
194             return Ok(Box::new(crypto::CryptReader::from_file_and_key(file, key)?));
195         }
196 
197         Ok(Box::new(file))
198     }
199 
200     /// Reads a fragment.
read_fragment<T: serde::de::DeserializeOwned>(&self, name: &str) -> Result<T>201     pub fn read_fragment<T: serde::de::DeserializeOwned>(&self, name: &str) -> Result<T> {
202         serde_json::from_reader(std::io::BufReader::new(self.raw_fragment(name)?))
203             .with_context(|| format!("failed to parse json from snapshot fragment named {}", name))
204     }
205 
206     /// Reads the names of all fragments in this namespace.
list_fragments(&self) -> Result<Vec<String>>207     pub fn list_fragments(&self) -> Result<Vec<String>> {
208         let mut result = Vec::new();
209         for entry in std::fs::read_dir(&self.dir)? {
210             let entry = entry?;
211             if entry.file_type()?.is_file() {
212                 result.push(entry.file_name().to_string_lossy().into_owned());
213             }
214         }
215         Ok(result)
216     }
217 
218     /// Open a namespace.
namespace(&self, name: &str) -> Result<Self>219     pub fn namespace(&self, name: &str) -> Result<Self> {
220         let dir = self.dir.join(name);
221         Ok(Self {
222             dir,
223             key: self.key.clone(),
224         })
225     }
226 
227     /// Reads the names of all child namespaces
list_namespaces(&self) -> Result<Vec<String>>228     pub fn list_namespaces(&self) -> Result<Vec<String>> {
229         let mut result = Vec::new();
230         for entry in std::fs::read_dir(&self.dir)? {
231             let entry = entry?;
232             if entry.path().is_dir() {
233                 if let Some(file_name) = entry.path().file_name() {
234                     result.push(file_name.to_string_lossy().into_owned());
235                 }
236             }
237         }
238         Ok(result)
239     }
240 }
241