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