diff --git a/src/firecracker/src/main.rs b/src/firecracker/src/main.rs index 6b01f776729..4f1763985e3 100644 --- a/src/firecracker/src/main.rs +++ b/src/firecracker/src/main.rs @@ -529,17 +529,16 @@ fn warn_deprecated_parameters() {} enum SnapshotVersionError { /// Unable to open snapshot state file: {0} OpenSnapshot(io::Error), - /// Invalid data format version of snapshot file: {0} - SnapshotVersion(SnapshotError), + /// Invalid data format of snapshot file: {0} + LoadSnapshot(SnapshotError), } // Print data format of provided snapshot state file. fn print_snapshot_data_format(snapshot_path: &str) -> Result<(), SnapshotVersionError> { - let mut snapshot_reader = - File::open(snapshot_path).map_err(SnapshotVersionError::OpenSnapshot)?; + let mut snapshot_reader = File::open(snapshot_path).map_err(SnapshotVersionError::OpenSnapshot); - let data_format_version = Snapshot::get_format_version(&mut snapshot_reader) - .map_err(SnapshotVersionError::SnapshotVersion)?; + let snapshot = Snapshot::load(snapshot_reader).map_err(); + let data_format_version = snapshot.version(SnapshotVersionError::LoadSnapshot); println!("v{}", data_format_version); Ok(()) diff --git a/src/snapshot-editor/src/utils.rs b/src/snapshot-editor/src/utils.rs index bdbc6f48d6e..b2b5dc45470 100644 --- a/src/snapshot-editor/src/utils.rs +++ b/src/snapshot-editor/src/utils.rs @@ -30,7 +30,18 @@ pub fn open_vmstate(snapshot_path: &PathBuf) -> Result<(MicrovmState, Version), let mut snapshot_reader = File::open(snapshot_path).map_err(UtilsError::VmStateFileOpen)?; let metadata = std::fs::metadata(snapshot_path).map_err(UtilsError::VmStateFileMeta)?; let snapshot_len = u64_to_usize(metadata.len()); - Snapshot::load(&mut snapshot_reader, snapshot_len).map_err(UtilsError::VmStateLoad) + + let snapshot: Result, UtilsError> = Snapshot::load(&mut snapshot_reader, snapshot_len).map_err(UtilsError::VmStateLoad); + match snapshot { + Ok(snapshot) => { + let version = snapshot.version(); + Ok((snapshot.data, version.to_owned())) + } + Err(e) => { + return Err(e); + } + } + } // This method is used only in aarch64 code so far @@ -46,9 +57,10 @@ pub fn save_vmstate( .truncate(true) .open(output_path) .map_err(UtilsError::OutputFileOpen)?; - let mut snapshot = Snapshot::new(version); + let snapshot_hdr = SnapshotHdr::new(version); + let snapshot = Snapshot::new(snapshot_hdr, microvm_state); snapshot - .save(&mut output_file, µvm_state) + .save(&mut output_file) .map_err(UtilsError::VmStateSave)?; Ok(()) } diff --git a/src/vmm/src/persist.rs b/src/vmm/src/persist.rs index 4111d8d6c34..7cd3d2da82b 100644 --- a/src/vmm/src/persist.rs +++ b/src/vmm/src/persist.rs @@ -425,15 +425,12 @@ pub enum SnapshotStateFromFileError { fn snapshot_state_from_file( snapshot_path: &Path, ) -> Result { - let snapshot = Snapshot::new(SNAPSHOT_VERSION); let mut snapshot_reader = File::open(snapshot_path).map_err(SnapshotStateFromFileError::Open)?; - let metadata = std::fs::metadata(snapshot_path).map_err(SnapshotStateFromFileError::Meta)?; - let snapshot_len = u64_to_usize(metadata.len()); - let state: MicrovmState = snapshot - .load_with_version_check(&mut snapshot_reader, snapshot_len) + let state: Snapshot = Snapshot::load_with_version_check(&mut snapshot_reader, SNAPSHOT_VERSION) .map_err(SnapshotStateFromFileError::Load)?; - Ok(state) + + Ok(state.data) } /// Error type for [`guest_memory_from_file`]. @@ -686,17 +683,22 @@ mod tests { vm_state: vmm.vm.save_state().unwrap(), acpi_dev_state: vmm.acpi_device_manager.save(), }; + let vm_info = microvm_state.vm_info.clone(); + let device_states = microvm_state.device_states.clone(); let mut buf = vec![0; 10000]; - Snapshot::serialize(&mut buf.as_mut_slice(), µvm_state).unwrap(); - let restored_microvm_state: MicrovmState = - Snapshot::deserialize(&mut buf.as_slice()).unwrap(); + let snapshot = Snapshot::new(Version::new(1, 0, 42), microvm_state); + snapshot.save(&mut buf.as_mut_slice()).unwrap(); + + let restored_snapshot: Snapshot = + Snapshot::load(&mut buf.as_slice()); + let restored_microvm_state = restored_snapshot.data; - assert_eq!(restored_microvm_state.vm_info, microvm_state.vm_info); + assert_eq!(restored_microvm_state.vm_info, vm_info); assert_eq!( restored_microvm_state.device_states, - microvm_state.device_states + device_states ) } diff --git a/src/vmm/src/snapshot/mod.rs b/src/vmm/src/snapshot/mod.rs index 57ad3980215..8b660adc586 100644 --- a/src/vmm/src/snapshot/mod.rs +++ b/src/vmm/src/snapshot/mod.rs @@ -30,6 +30,7 @@ use std::io::{Read, Write}; use bincode::config; use bincode::config::{Configuration, Fixint, Limit, LittleEndian}; +use bincode::error::{DecodeError, EncodeError}; use semver::Version; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -73,182 +74,201 @@ pub enum SnapshotError { /// Firecracker snapshot header #[derive(Debug, Serialize, Deserialize)] struct SnapshotHdr { - /// magic value + /// Magic value magic: u64, /// Snapshot data version version: Version, } impl SnapshotHdr { + /// Create a new header for writing snapshots fn new(version: Version) -> Self { Self { magic: SNAPSHOT_MAGIC_ID, version, } } -} - -/// Firecracker snapshot type -/// -/// A type used to store and load Firecracker snapshots of a particular version -#[derive(Debug)] -pub struct Snapshot { - // The snapshot version we can handle - version: Version, -} -impl Snapshot { - /// Creates a new instance which can only be used to save a new snapshot. - pub fn new(version: Version) -> Snapshot { - Snapshot { version } + /// Load and deserialize just the header (magic + version) + fn load(reader: &mut R) -> Result { + let hdr: SnapshotHdr = deserialize(reader)?; + if hdr.magic != SNAPSHOT_MAGIC_ID { + Err(SnapshotError::InvalidMagic(hdr.magic)) + } else { + Ok(hdr) + } } - /// Fetches snapshot data version. - pub fn get_format_version(reader: &mut T) -> Result - where - T: Read + Debug, - { - let hdr: SnapshotHdr = Self::deserialize(reader)?; - Ok(hdr.version) + /// Serialize and write just the header + fn store(&self, writer: &mut W) -> Result<(), SnapshotError> { + serialize(writer, self)?; + Ok(()) } +} - /// Helper function to deserialize an object from a reader - pub fn deserialize(reader: &mut T) -> Result - where - T: Read, - O: DeserializeOwned + Debug, - { - bincode::serde::decode_from_std_read(reader, BINCODE_CONFIG) - .map_err(|err| SnapshotError::Serde(err.to_string())) - } +/// Helper function to serialize an object to a writer +pub fn serialize(writer: &mut T, data: &O) -> Result<(), SnapshotError> +where + T: Write, + O: Serialize + Debug, +{ + bincode::serde::encode_into_std_write(data, writer, BINCODE_CONFIG) + .map_err(|err| SnapshotError::Serde(err.to_string()))?; - /// Helper function to serialize an object to a writer - pub fn serialize(writer: &mut T, data: &O) -> Result<(), SnapshotError> - where - T: Write, - O: Serialize + Debug, - { - bincode::serde::encode_into_std_write(data, writer, BINCODE_CONFIG) - .map_err(|err| SnapshotError::Serde(err.to_string()))?; + Ok(()) +} - Ok(()) +// Implementations for deserializing snapshots +// Publicly exposed functions: +// - load_unchecked() +//- load() +impl Snapshot { + /// Load without CRC or version‐check, but verify magic via `SnapshotHdr::load`. + pub fn load_unchecked(reader: &mut R) -> Result { + // this calls `deserialize` + checks magic internally + let hdr: SnapshotHdr = SnapshotHdr::load(reader)?; + let data: Data = deserialize(reader)?; + Ok(Self { header: hdr, data }) } - /// Attempts to load an existing snapshot without performing CRC or version validation. - /// - /// This will check that the snapshot magic value is correct. - fn unchecked_load(reader: &mut T) -> Result<(O, Version), SnapshotError> - where - T: Read + Debug, - O: DeserializeOwned + Debug, - { - let hdr: SnapshotHdr = Self::deserialize(reader)?; - if hdr.magic != SNAPSHOT_MAGIC_ID { - return Err(SnapshotError::InvalidMagic(hdr.magic)); - } + /// Load with CRC64 validation + pub fn load(reader: &mut R) -> Result { + // 1) Wrap in CRC reader + let mut crc_reader = CRC64Reader::new(reader); - let data: O = Self::deserialize(reader)?; - Ok((data, hdr.version)) - } + // 2) Parse header + payload & magic‐check + let snapshot = Snapshot::load_unchecked(&mut crc_reader)?; - /// Load a snapshot from a reader and validate its CRC - pub fn load(reader: &mut T, snapshot_len: usize) -> Result<(O, Version), SnapshotError> - where - T: Read + Debug, - O: DeserializeOwned + Debug, - { - let mut crc_reader = CRC64Reader::new(reader); + // 3) Grab the computed CRC over everything read so far + let computed = crc_reader.checksum(); - // Fail-fast if the snapshot length is too small - let raw_snapshot_len = snapshot_len - .checked_sub(std::mem::size_of::()) - .ok_or(SnapshotError::InvalidSnapshotSize)?; - - // Read everything apart from the CRC. - let mut snapshot = vec![0u8; raw_snapshot_len]; - crc_reader - .read_exact(&mut snapshot) - .map_err(|ref err| SnapshotError::Io(err.raw_os_error().unwrap_or(libc::EINVAL)))?; - - // Since the reader updates the checksum as bytes ar being read from it, the order of these - // 2 statements is important, we first get the checksum computed on the read bytes - // then read the stored checksum. - let computed_checksum = crc_reader.checksum(); - let stored_checksum: u64 = Self::deserialize(&mut crc_reader)?; - if computed_checksum != stored_checksum { - return Err(SnapshotError::Crc64(computed_checksum)); + // 4) Deserialize the trailing u64 and compare + let stored: u64 = deserialize(&mut crc_reader)?; + if stored != computed { + return Err(SnapshotError::Crc64(computed)); } - let mut snapshot_slice: &[u8] = snapshot.as_mut_slice(); - Snapshot::unchecked_load::<_, O>(&mut snapshot_slice) + Ok(snapshot) } - /// Load a snapshot from a reader object and perform a snapshot version check - pub fn load_with_version_check( - &self, - reader: &mut T, - snapshot_len: usize, - ) -> Result - where - T: Read + Debug, - O: DeserializeOwned + Debug, - { - let (data, version) = Snapshot::load::<_, O>(reader, snapshot_len)?; - if version.major != self.version.major || version.minor > self.version.minor { - Err(SnapshotError::InvalidFormatVersion(version)) + /// Load with CRC64 validation, and check that snapshot against the specified version + pub fn load_with_verison_check( + reader: &mut R, + version: &Version, + ) -> Result { + Self = load(reader)?; + if Self.version.major != version.major || Self.version.minor > version.minor { + Err(SnapshotError::InvalidFormatVersion(Self.version)) } else { Ok(data) } } +} - /// Saves a snapshot and include a CRC64 checksum. - pub fn save(&self, writer: &mut T, object: &O) -> Result<(), SnapshotError> - where - T: Write + Debug, - O: Serialize + Debug, - { +// Implementations for serializing snapshots +// Publicly-exposed *methods*: +// - save(self,...) +// - save_with_crc(self,...) +impl Snapshot { + pub fn save(&self, mut writer: &mut W) -> Result { + // Write magic value and snapshot version + serialize(&mut writer, &SnapshotHdr::new(self.header.version.clone()))?; + // Write data + serialize(&mut writer, &self.data) + } + + pub fn save_with_crc(&self, writer: &mut W) -> Result { let mut crc_writer = CRC64Writer::new(writer); - self.save_without_crc(&mut crc_writer, object)?; + self.save(&mut crc_writer)?; // Now write CRC value let checksum = crc_writer.checksum(); - Self::serialize(&mut crc_writer, &checksum) + serialize(&mut crc_writer, &checksum) + } +} + +// General methods for snapshots (related to serialization, see above, since an +// instance is needed to serialize) +impl Snapshot { + /// Construct from a pre‐built header + payload + pub fn new(version: Version, data: Data) -> Self { + header = SnapshotHdr::new(version); + Snapshot { header, data } } - /// Save a snapshot with no CRC64 checksum included. - pub fn save_without_crc( - &self, - mut writer: &mut T, - object: &O, - ) -> Result<(), SnapshotError> - where - T: Write, - O: Serialize + Debug, - { - // Write magic value and snapshot version - Self::serialize(&mut writer, &SnapshotHdr::new(self.version.clone()))?; - // Write data - Self::serialize(&mut writer, object) + pub fn version(&self) -> Version { + self.header.version.clone() } } +/// Deserialize any `O: DeserializeOwned + Debug` via bincode + our config, +fn deserialize(reader: &mut T) -> Result +where + T: Read, + O: DeserializeOwned + Debug, +{ + bincode::serde::decode_from_std_read(reader, BINCODE_CONFIG).map_err(|err| match err { + // The reader hit an actual IO error. + DecodeError::Io { inner, .. } => SnapshotError::Io(inner.raw_os_error().unwrap_or(EIO)), + + // Not enough bytes in the input for what we expected. + DecodeError::UnexpectedEnd { .. } | DecodeError::LimitExceeded => { + SnapshotError::InvalidSnapshotSize + } + + // Anything else is a ser/de format issue. + other => SnapshotError::Serde(other.to_string()), + }) +} + +/// Serialize any `O: Serialize + Debug` into a Vec, write it, and return the byte‐count, +fn serialize(writer: &mut T, data: &O) -> Result +where + T: Write, + O: Serialize + Debug, +{ + // 1) Encode into an in-memory buffer + let mut buf = Vec::new(); + bincode::serde::encode_into_std_write(data, &mut buf, BINCODE_CONFIG).map_err( + |err| match err { + // Ran out of room while encoding + EncodeError::UnexpectedEnd => SnapshotError::Io(libc::EIO), + + // Underlying IO failure during encode (index tells how many bytes got written) + EncodeError::Io { inner, .. } => { + SnapshotError::Io(inner.raw_os_error().unwrap_or(libc::EIO)) + } + + // Any other encode error we surface as Serde + other => SnapshotError::Serde(other.to_string()), + }, + )?; + + // 2) Flush that buffer to the target writer + writer + .write_all(&buf) + .map_err(|io_err| SnapshotError::Io(io_err.raw_os_error().unwrap_or(libc::EIO)))?; + + Ok(buffer.len()) + // bincode::serialize_into(writer, data).map_err(|err| SnapshotError::Serde(err.to_string())) +} + #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_version_from_file() { - let snapshot = Snapshot::new(Version::new(1, 0, 42)); - // Enough memory for the header, 1 byte and the CRC let mut snapshot_data = vec![0u8; 100]; - snapshot - .save(&mut snapshot_data.as_mut_slice(), &42u8) - .unwrap(); + let snapshot = SnapshotHdr::new(Version::new(1, 0, 42)); + snapshot.store(&mut snapshot_data).unwrap(); assert_eq!( - Snapshot::get_format_version(&mut snapshot_data.as_slice()).unwrap(), + SnapshotHdr::load(&mut snapshot_data.as_slice()) + .unwrap() + .version, Version::new(1, 0, 42) ); } @@ -257,12 +277,9 @@ mod tests { fn test_bad_snapshot_size() { let snapshot_data = vec![0u8; 1]; - let snapshot = Snapshot::new(Version::new(1, 6, 1)); + let snapshot = SnapshotHdr::new(Version::new(1, 6, 1)); assert!(matches!( - snapshot.load_with_version_check::<_, u8>( - &mut snapshot_data.as_slice(), - snapshot_data.len() - ), + Snapshot::load::<_, u8>(&mut snapshot_data.as_slice(),), Err(SnapshotError::InvalidSnapshotSize) )); } @@ -280,9 +297,8 @@ mod tests { let mut reader = BadReader {}; - let snapshot = Snapshot::new(Version::new(42, 27, 18)); assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut reader, 1024), + Snapshot::load::<_, u8>(&mut reader), Err(SnapshotError::Io(_)) )); } @@ -291,8 +307,8 @@ mod tests { fn test_bad_magic() { let mut data = vec![0u8; 100]; - let snapshot = Snapshot::new(Version::new(24, 16, 1)); - snapshot.save(&mut data.as_mut_slice(), &42u8).unwrap(); + let snapshot = Snapshot::new(Version::new(24, 16, 1), &42u8); + snapshot.save(&mut data.as_mut_slice()).unwrap(); // Writing dummy values in the first bytes of the snapshot data (we are on little-endian // machines) should trigger an `Error::InvalidMagic` error. @@ -314,16 +330,17 @@ mod tests { fn test_bad_crc() { let mut data = vec![0u8; 100]; - let snapshot = Snapshot::new(Version::new(12, 1, 3)); - snapshot.save(&mut data.as_mut_slice(), &42u8).unwrap(); + let snapshot = Snapshot::new(Version::new(12, 1, 3), &42u8); + snapshot.save(&mut data.as_mut_slice()).unwrap(); // Tamper the bytes written, without touching the previously CRC. - snapshot - .save_without_crc(&mut data.as_mut_slice(), &43u8) + let snapshot2 = Snapshot::new(Version::new(12, 1, 3), &43u8); + snapshot2 + .save_without_crc(&mut data.as_mut_slice()) .unwrap(); assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + Snapshot::load::<_, u8>(&mut data.as_slice()), Err(SnapshotError::Crc64(_)) )); } @@ -337,9 +354,11 @@ mod tests { snapshot.save(&mut data.as_mut_slice(), &42u8).unwrap(); // Different major versions should not work - let snapshot = Snapshot::new(Version::new(2, 3, 12)); assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + Snapshot::load_with_version_check::<_, u8>( + &mut data.as_slice(), + Version::new(2, 3, 12) + ), Err(SnapshotError::InvalidFormatVersion(Version { major: 1, minor: 3, @@ -347,9 +366,8 @@ mod tests { .. })) )); - let snapshot = Snapshot::new(Version::new(0, 3, 12)); assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), Version::new(0, 3, 12)), Err(SnapshotError::InvalidFormatVersion(Version { major: 1, minor: 3, @@ -359,9 +377,8 @@ mod tests { )); // We can't support minor versions bigger than ours - let snapshot = Snapshot::new(Version::new(1, 2, 12)); assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), Version::new(1, 2, 12)), Err(SnapshotError::InvalidFormatVersion(Version { major: 1, minor: 3, @@ -372,21 +389,17 @@ mod tests { // But we can support minor versions smaller or equeal to ours. We also support // all patch versions within our supported major.minor version. - let snapshot = Snapshot::new(Version::new(1, 4, 12)); snapshot - .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) + .load_with_version_check::<_, u8>(&mut data.as_slice(), Version::new(1, 4, 12)) .unwrap(); - let snapshot = Snapshot::new(Version::new(1, 3, 0)); snapshot - .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) + .load_with_version_check::<_, u8>(&mut data.as_slice(), Version::new(1, 3, 0)) .unwrap(); - let snapshot = Snapshot::new(Version::new(1, 3, 12)); snapshot - .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) + .load_with_version_check::<_, u8>(&mut data.as_slice(), Version::new(1, 3, 12)) .unwrap(); - let snapshot = Snapshot::new(Version::new(1, 3, 1024)); snapshot - .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) + .load_with_version_check::<_, u8>(&mut data.as_slice(), Version::new(1, 3, 1024)) .unwrap(); } } diff --git a/src/vmm/src/vmm_config/boot_source.rs b/src/vmm/src/vmm_config/boot_source.rs index 37ba08be449..b7e95945ada 100644 --- a/src/vmm/src/vmm_config/boot_source.rs +++ b/src/vmm/src/vmm_config/boot_source.rs @@ -129,7 +129,7 @@ pub(crate) mod tests { }; let mut snapshot_data = vec![0u8; 1000]; - Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &boot_src_cfg).unwrap(); + Snapshot::serialize::<&BootSourceConfig>(&mut snapshot_data.as_mut_slice(), &boot_src_cfg).unwrap(); let restored_boot_cfg = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); assert_eq!(boot_src_cfg, restored_boot_cfg); } diff --git a/src/vmm/src/vstate/memory.rs b/src/vmm/src/vstate/memory.rs index 19367f7f997..6f5d0c915dd 100644 --- a/src/vmm/src/vstate/memory.rs +++ b/src/vmm/src/vstate/memory.rs @@ -433,7 +433,7 @@ mod tests { fn check_serde(guest_memory: &GuestMemoryMmap) { let mut snapshot_data = vec![0u8; 10000]; let original_state = guest_memory.describe(); - Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &original_state).unwrap(); + Snapshot::serialize::(&mut snapshot_data.as_mut_slice(), &original_state).unwrap(); let restored_state = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); assert_eq!(original_state, restored_state); }