diff --git a/src/firecracker/src/main.rs b/src/firecracker/src/main.rs index 3e6ad35d6a9..739214999a4 100644 --- a/src/firecracker/src/main.rs +++ b/src/firecracker/src/main.rs @@ -28,7 +28,7 @@ use vmm::persist::SNAPSHOT_VERSION; use vmm::resources::VmResources; use vmm::seccomp::BpfThreadMap; use vmm::signal_handler::register_signal_handlers; -use vmm::snapshot::{Snapshot, SnapshotError}; +use vmm::snapshot::{SnapshotError, get_format_version}; use vmm::vmm_config::instance_info::{InstanceInfo, VmState}; use vmm::vmm_config::metrics::{MetricsConfig, MetricsConfigError, init_metrics}; use vmm::{EventManager, FcExitCode, HTTP_MAX_PAYLOAD_SIZE}; @@ -546,8 +546,8 @@ fn print_snapshot_data_format(snapshot_path: &str) -> Result<(), SnapshotVersion 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 data_format_version = + get_format_version(&mut snapshot_reader).map_err(SnapshotVersionError::SnapshotVersion)?; println!("v{}", data_format_version); Ok(()) diff --git a/src/snapshot-editor/src/edit_vmstate.rs b/src/snapshot-editor/src/edit_vmstate.rs index ba5b8370096..e538156814a 100644 --- a/src/snapshot-editor/src/edit_vmstate.rs +++ b/src/snapshot-editor/src/edit_vmstate.rs @@ -51,9 +51,9 @@ fn edit( output_path: &PathBuf, f: impl Fn(MicrovmState) -> Result, ) -> Result<(), EditVmStateError> { - let (microvm_state, version) = open_vmstate(vmstate_path)?; - let microvm_state = f(microvm_state)?; - save_vmstate(microvm_state, output_path, version)?; + let snapshot = open_vmstate(vmstate_path)?; + let microvm_state = f(snapshot.data)?; + save_vmstate(microvm_state, output_path)?; Ok(()) } @@ -78,7 +78,7 @@ fn remove_regs( } vcpu_state.regs = new_regs; for (reg, removed) in remove_regs.iter().zip(removed.iter()) { - print!("Regsiter {reg:#x}: "); + print!("Register {reg:#x}: "); match removed { true => println!("removed"), false => println!("not present"), diff --git a/src/snapshot-editor/src/info.rs b/src/snapshot-editor/src/info.rs index 659c86e6b50..97d06a3e5f9 100644 --- a/src/snapshot-editor/src/info.rs +++ b/src/snapshot-editor/src/info.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use clap::Subcommand; -use semver::Version; use vmm::persist::MicrovmState; +use vmm::snapshot::Snapshot; use crate::utils::*; @@ -50,27 +50,27 @@ pub fn info_vmstate_command(command: InfoVmStateSubCommand) -> Result<(), InfoVm fn info( vmstate_path: &PathBuf, - f: impl Fn(&MicrovmState, Version) -> Result<(), InfoVmStateError>, + f: impl Fn(&Snapshot) -> Result<(), InfoVmStateError>, ) -> Result<(), InfoVmStateError> { - let (vmstate, version) = open_vmstate(vmstate_path)?; - f(&vmstate, version)?; + let snapshot = open_vmstate(vmstate_path)?; + f(&snapshot)?; Ok(()) } -fn info_version(_: &MicrovmState, version: Version) -> Result<(), InfoVmStateError> { - println!("v{version}"); +fn info_version(snapshot: &Snapshot) -> Result<(), InfoVmStateError> { + println!("v{}", snapshot.version()); Ok(()) } -fn info_vcpu_states(state: &MicrovmState, _: Version) -> Result<(), InfoVmStateError> { - for (i, state) in state.vcpu_states.iter().enumerate() { +fn info_vcpu_states(snapshot: &Snapshot) -> Result<(), InfoVmStateError> { + for (i, state) in snapshot.data.vcpu_states.iter().enumerate() { println!("vcpu {i}:"); println!("{state:#?}"); } Ok(()) } -fn info_vmstate(vmstate: &MicrovmState, _version: Version) -> Result<(), InfoVmStateError> { - println!("{vmstate:#?}"); +fn info_vmstate(snapshot: &Snapshot) -> Result<(), InfoVmStateError> { + println!("{:#?}", snapshot.data); Ok(()) } diff --git a/src/snapshot-editor/src/utils.rs b/src/snapshot-editor/src/utils.rs index bdbc6f48d6e..1d3d053d26a 100644 --- a/src/snapshot-editor/src/utils.rs +++ b/src/snapshot-editor/src/utils.rs @@ -4,10 +4,8 @@ use std::fs::{File, OpenOptions}; use std::path::PathBuf; -use semver::Version; use vmm::persist::MicrovmState; use vmm::snapshot::Snapshot; -use vmm::utils::u64_to_usize; // Some errors are only used in aarch64 code #[allow(unused)] @@ -15,8 +13,6 @@ use vmm::utils::u64_to_usize; pub enum UtilsError { /// Can not open snapshot file: {0} VmStateFileOpen(std::io::Error), - /// Can not retrieve metadata for snapshot file: {0} - VmStateFileMeta(std::io::Error), /// Can not load snapshot: {0} VmStateLoad(vmm::snapshot::SnapshotError), /// Can not open output file: {0} @@ -26,29 +22,23 @@ pub enum UtilsError { } #[allow(unused)] -pub fn open_vmstate(snapshot_path: &PathBuf) -> Result<(MicrovmState, Version), UtilsError> { +pub fn open_vmstate(snapshot_path: &PathBuf) -> Result, UtilsError> { 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) + Snapshot::load(&mut snapshot_reader).map_err(UtilsError::VmStateLoad) } // This method is used only in aarch64 code so far #[allow(unused)] -pub fn save_vmstate( - microvm_state: MicrovmState, - output_path: &PathBuf, - version: Version, -) -> Result<(), UtilsError> { +pub fn save_vmstate(microvm_state: MicrovmState, output_path: &PathBuf) -> Result<(), UtilsError> { let mut output_file = OpenOptions::new() .create(true) .write(true) .truncate(true) .open(output_path) .map_err(UtilsError::OutputFileOpen)?; - let mut snapshot = Snapshot::new(version); + let mut snapshot = Snapshot::new(microvm_state); snapshot - .save(&mut output_file, µvm_state) + .save(&mut output_file) .map_err(UtilsError::VmStateSave)?; Ok(()) } diff --git a/src/vmm/src/arch/aarch64/regs.rs b/src/vmm/src/arch/aarch64/regs.rs index 163a9914b6a..e596922466e 100644 --- a/src/vmm/src/arch/aarch64/regs.rs +++ b/src/vmm/src/arch/aarch64/regs.rs @@ -546,8 +546,8 @@ mod tests { let mut buf = vec![0; 10000]; - Snapshot::serialize(&mut buf.as_mut_slice(), &v).unwrap(); - let restored: Aarch64RegisterVec = Snapshot::deserialize(&mut buf.as_slice()).unwrap(); + Snapshot::new(&v).save(&mut buf.as_mut_slice()).unwrap(); + let restored: Aarch64RegisterVec = Snapshot::load(&mut buf.as_slice()).unwrap().data; for (old, new) in v.iter().zip(restored.iter()) { assert_eq!(old, new); @@ -572,11 +572,11 @@ mod tests { let mut buf = vec![0; 10000]; - Snapshot::serialize(&mut buf.as_mut_slice(), &v).unwrap(); + Snapshot::new(&v).save(&mut buf.as_mut_slice()).unwrap(); // Total size of registers according IDs are 16 + 16 = 32, // but actual data size is 8 + 16 = 24. - Snapshot::deserialize::<_, Aarch64RegisterVec>(&mut buf.as_slice()).unwrap_err(); + Snapshot::::load(&mut buf.as_slice()).unwrap_err(); } #[test] @@ -595,10 +595,10 @@ mod tests { let mut buf = vec![0; 10000]; - Snapshot::serialize(&mut buf.as_mut_slice(), &v).unwrap(); + Snapshot::new(v).save(&mut buf.as_mut_slice()).unwrap(); // 4096 bit wide registers are not supported. - Snapshot::deserialize::<_, Aarch64RegisterVec>(&mut buf.as_slice()).unwrap_err(); + Snapshot::::load(&mut buf.as_slice()).unwrap_err(); } #[test] diff --git a/src/vmm/src/arch/x86_64/vm.rs b/src/vmm/src/arch/x86_64/vm.rs index e194296928d..eae3730135a 100644 --- a/src/vmm/src/arch/x86_64/vm.rs +++ b/src/vmm/src/arch/x86_64/vm.rs @@ -318,8 +318,10 @@ mod tests { let (_, mut vm) = setup_vm_with_memory(0x1000); vm.setup_irqchip().unwrap(); let state = vm.save_state().unwrap(); - Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &state).unwrap(); - let restored_state: VmState = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); + Snapshot::new(state) + .save(&mut snapshot_data.as_mut_slice()) + .unwrap(); + let restored_state: VmState = Snapshot::load(&mut snapshot_data.as_slice()).unwrap().data; vm.restore_state(&restored_state).unwrap(); } diff --git a/src/vmm/src/device_manager/pci_mngr.rs b/src/vmm/src/device_manager/pci_mngr.rs index 578d521162b..adfde6252ad 100644 --- a/src/vmm/src/device_manager/pci_mngr.rs +++ b/src/vmm/src/device_manager/pci_mngr.rs @@ -645,7 +645,9 @@ mod tests { let entropy_config = EntropyDeviceConfig::default(); insert_entropy_device(&mut vmm, &mut cmdline, &mut event_manager, entropy_config); - Snapshot::serialize(&mut buf.as_mut_slice(), &vmm.device_manager.save()).unwrap(); + Snapshot::new(vmm.device_manager.save()) + .save(&mut buf.as_mut_slice()) + .unwrap(); } tmp_sock_file.remove().unwrap(); @@ -657,7 +659,7 @@ mod tests { // object and calling default_vmm() is the easiest way to create one. let vmm = default_vmm(); let device_manager_state: device_manager::DevicesState = - Snapshot::deserialize(&mut buf.as_slice()).unwrap(); + Snapshot::load(&mut buf.as_slice()).unwrap().data; let vm_resources = &mut VmResources::default(); let restore_args = PciDevicesConstructorArgs { vm: vmm.vm.clone(), diff --git a/src/vmm/src/device_manager/persist.rs b/src/vmm/src/device_manager/persist.rs index 74e71f3a6bf..eb6bb06e8d6 100644 --- a/src/vmm/src/device_manager/persist.rs +++ b/src/vmm/src/device_manager/persist.rs @@ -676,7 +676,9 @@ mod tests { let entropy_config = EntropyDeviceConfig::default(); insert_entropy_device(&mut vmm, &mut cmdline, &mut event_manager, entropy_config); - Snapshot::serialize(&mut buf.as_mut_slice(), &vmm.device_manager.save()).unwrap(); + Snapshot::new(vmm.device_manager.save()) + .save(&mut buf.as_mut_slice()) + .unwrap(); } tmp_sock_file.remove().unwrap(); @@ -684,7 +686,7 @@ mod tests { let mut event_manager = EventManager::new().expect("Unable to create EventManager"); let vmm = default_vmm(); let device_manager_state: device_manager::DevicesState = - Snapshot::deserialize(&mut buf.as_slice()).unwrap(); + Snapshot::load(&mut buf.as_slice()).unwrap().data; let vm_resources = &mut VmResources::default(); let restore_args = MMIODevManagerConstructorArgs { mem: vmm.vm.guest_memory(), diff --git a/src/vmm/src/devices/virtio/balloon/persist.rs b/src/vmm/src/devices/virtio/balloon/persist.rs index 15ae1e26b9e..41e92c839c3 100644 --- a/src/vmm/src/devices/virtio/balloon/persist.rs +++ b/src/vmm/src/devices/virtio/balloon/persist.rs @@ -187,7 +187,9 @@ mod tests { // Create and save the balloon device. let balloon = Balloon::new(0x42, false, 2, false).unwrap(); - Snapshot::serialize(&mut mem.as_mut_slice(), &balloon.save()).unwrap(); + Snapshot::new(balloon.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); // Deserialize and restore the balloon device. let restored_balloon = Balloon::restore( @@ -195,7 +197,7 @@ mod tests { mem: guest_mem, restored_from_file: true, }, - &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), + &Snapshot::load(&mut mem.as_slice()).unwrap().data, ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/block/virtio/persist.rs b/src/vmm/src/devices/virtio/block/virtio/persist.rs index 1c7a1bce106..4b6584fb5d1 100644 --- a/src/vmm/src/devices/virtio/block/virtio/persist.rs +++ b/src/vmm/src/devices/virtio/block/virtio/persist.rs @@ -171,7 +171,9 @@ mod tests { // Save the block device. let mut mem = vec![0; 4096]; - Snapshot::serialize(&mut mem.as_mut_slice(), &block.save()).unwrap(); + Snapshot::new(block.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); } #[test] @@ -214,12 +216,14 @@ mod tests { // Save the block device. let mut mem = vec![0; 4096]; - Snapshot::serialize(&mut mem.as_mut_slice(), &block.save()).unwrap(); + Snapshot::new(block.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); // Restore the block device. let restored_block = VirtioBlock::restore( BlockConstructorArgs { mem: guest_mem }, - &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), + &Snapshot::load(&mut mem.as_slice()).unwrap().data, ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/net/persist.rs b/src/vmm/src/devices/virtio/net/persist.rs index 6ef8ad842ac..711dc539b38 100644 --- a/src/vmm/src/devices/virtio/net/persist.rs +++ b/src/vmm/src/devices/virtio/net/persist.rs @@ -153,7 +153,9 @@ mod tests { // Create and save the net device. { - Snapshot::serialize(&mut mem.as_mut_slice(), &net.save()).unwrap(); + Snapshot::new(net.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); // Save some fields that we want to check later. id = net.id.clone(); @@ -173,7 +175,7 @@ mod tests { mem: guest_mem, mmds: mmds_ds, }, - &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), + &Snapshot::load(&mut mem.as_slice()).unwrap().data, ) { Ok(restored_net) => { // Test that virtio specific fields are the same. diff --git a/src/vmm/src/devices/virtio/persist.rs b/src/vmm/src/devices/virtio/persist.rs index 776c7179048..17a39c6882f 100644 --- a/src/vmm/src/devices/virtio/persist.rs +++ b/src/vmm/src/devices/virtio/persist.rs @@ -358,14 +358,16 @@ mod tests { let mut bytes = vec![0; 4096]; - Snapshot::serialize(&mut bytes.as_mut_slice(), &queue.save()).unwrap(); + Snapshot::new(queue.save()) + .save(&mut bytes.as_mut_slice()) + .unwrap(); let ca = QueueConstructorArgs { mem, is_activated: true, }; let restored_queue = - Queue::restore(ca, &Snapshot::deserialize(&mut bytes.as_slice()).unwrap()).unwrap(); + Queue::restore(ca, &Snapshot::load(&mut bytes.as_slice()).unwrap().data).unwrap(); assert_eq!(restored_queue, queue); } @@ -376,9 +378,9 @@ mod tests { let mut mem = vec![0; 4096]; let state = VirtioDeviceState::from_device(&dummy); - Snapshot::serialize(&mut mem.as_mut_slice(), &state).unwrap(); + Snapshot::new(&state).save(&mut mem.as_mut_slice()).unwrap(); - let restored_state: VirtioDeviceState = Snapshot::deserialize(&mut mem.as_slice()).unwrap(); + let restored_state: VirtioDeviceState = Snapshot::load(&mut mem.as_slice()).unwrap().data; assert_eq!(restored_state, state); } @@ -405,7 +407,9 @@ mod tests { ) { let mut buf = vec![0; 4096]; - Snapshot::serialize(&mut buf.as_mut_slice(), &mmio_transport.save()).unwrap(); + Snapshot::new(mmio_transport.save()) + .save(&mut buf.as_mut_slice()) + .unwrap(); let restore_args = MmioTransportConstructorArgs { mem, @@ -415,7 +419,7 @@ mod tests { }; let restored_mmio_transport = MmioTransport::restore( restore_args, - &Snapshot::deserialize(&mut buf.as_slice()).unwrap(), + &Snapshot::load(&mut buf.as_slice()).unwrap().data, ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/rng/persist.rs b/src/vmm/src/devices/virtio/rng/persist.rs index d266e259418..cb9289ecb85 100644 --- a/src/vmm/src/devices/virtio/rng/persist.rs +++ b/src/vmm/src/devices/virtio/rng/persist.rs @@ -85,12 +85,14 @@ mod tests { let mut mem = vec![0u8; 4096]; let entropy = Entropy::new(RateLimiter::default()).unwrap(); - Snapshot::serialize(&mut mem.as_mut_slice(), &entropy.save()).unwrap(); + Snapshot::new(entropy.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); let guest_mem = create_virtio_mem(); let restored = Entropy::restore( EntropyConstructorArgs { mem: guest_mem }, - &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), + &Snapshot::load(&mut mem.as_slice()).unwrap().data, ) .unwrap(); diff --git a/src/vmm/src/devices/virtio/vsock/persist.rs b/src/vmm/src/devices/virtio/vsock/persist.rs index 6775707da3e..14d7984ce54 100644 --- a/src/vmm/src/devices/virtio/vsock/persist.rs +++ b/src/vmm/src/devices/virtio/vsock/persist.rs @@ -178,9 +178,9 @@ pub(crate) mod tests { frontend: ctx.device.save(), }; - Snapshot::serialize(&mut mem.as_mut_slice(), &state).unwrap(); + Snapshot::new(&state).save(&mut mem.as_mut_slice()).unwrap(); - let restored_state: VsockState = Snapshot::deserialize(&mut mem.as_slice()).unwrap(); + let restored_state: VsockState = Snapshot::load(&mut mem.as_slice()).unwrap().data; let mut restored_device = Vsock::restore( VsockConstructorArgs { mem: ctx.mem.clone(), diff --git a/src/vmm/src/mmds/persist.rs b/src/vmm/src/mmds/persist.rs index 10b09c4f7ae..f9074cc9394 100644 --- a/src/vmm/src/mmds/persist.rs +++ b/src/vmm/src/mmds/persist.rs @@ -62,11 +62,13 @@ mod tests { let mut mem = vec![0; 4096]; - Snapshot::serialize(&mut mem.as_mut_slice(), &ns.save()).unwrap(); + Snapshot::new(ns.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); let restored_ns = MmdsNetworkStack::restore( Arc::new(Mutex::new(Mmds::default())), - &Snapshot::deserialize(&mut mem.as_slice()).unwrap(), + &Snapshot::load(&mut mem.as_slice()).unwrap().data, ) .unwrap(); diff --git a/src/vmm/src/persist.rs b/src/vmm/src/persist.rs index b78d69fcdec..503f183ea6c 100644 --- a/src/vmm/src/persist.rs +++ b/src/vmm/src/persist.rs @@ -184,8 +184,8 @@ fn snapshot_state_to_file( .open(snapshot_path) .map_err(|err| SnapshotBackingFile("open", err))?; - let snapshot = Snapshot::new(SNAPSHOT_VERSION); - snapshot.save(&mut snapshot_file, microvm_state)?; + let snapshot = Snapshot::new(microvm_state); + snapshot.save(&mut snapshot_file)?; snapshot_file .flush() .map_err(|err| SnapshotBackingFile("flush", err))?; @@ -405,9 +405,7 @@ pub fn restore_from_snapshot( #[derive(Debug, thiserror::Error, displaydoc::Display)] pub enum SnapshotStateFromFileError { /// Failed to open snapshot file: {0} - Open(std::io::Error), - /// Failed to read snapshot file metadata: {0} - Meta(std::io::Error), + Open(#[from] std::io::Error), /// Failed to load snapshot state from file: {0} Load(#[from] crate::snapshot::SnapshotError), /// Unknown Network Device. @@ -417,15 +415,10 @@ 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) - .map_err(SnapshotStateFromFileError::Load)?; - Ok(state) + let mut snapshot_reader = File::open(snapshot_path)?; + let snapshot = Snapshot::load(&mut snapshot_reader)?; + + Ok(snapshot.data) } /// Error type for [`guest_memory_from_file`]. @@ -679,10 +672,12 @@ mod tests { }; let mut buf = vec![0; 10000]; - Snapshot::serialize(&mut buf.as_mut_slice(), µvm_state).unwrap(); + Snapshot::new(µvm_state) + .save(&mut buf.as_mut_slice()) + .unwrap(); let restored_microvm_state: MicrovmState = - Snapshot::deserialize(&mut buf.as_slice()).unwrap(); + Snapshot::load(&mut buf.as_slice()).unwrap().data; assert_eq!(restored_microvm_state.vm_info, microvm_state.vm_info); assert_eq!( diff --git a/src/vmm/src/rate_limiter/persist.rs b/src/vmm/src/rate_limiter/persist.rs index fea5e9a8742..23dff6d4bde 100644 --- a/src/vmm/src/rate_limiter/persist.rs +++ b/src/vmm/src/rate_limiter/persist.rs @@ -116,10 +116,12 @@ mod tests { // Test serialization. let mut mem = vec![0; 4096]; - Snapshot::serialize(&mut mem.as_mut_slice(), &tb.save()).unwrap(); + Snapshot::new(tb.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); let restored_tb = - TokenBucket::restore((), &Snapshot::deserialize(&mut mem.as_slice()).unwrap()).unwrap(); + TokenBucket::restore((), &Snapshot::load(&mut mem.as_slice()).unwrap().data).unwrap(); assert!(tb.partial_eq(&restored_tb)); } @@ -192,9 +194,11 @@ mod tests { // Test serialization. let mut mem = vec![0; 4096]; - Snapshot::serialize(&mut mem.as_mut_slice(), &rate_limiter.save()).unwrap(); + Snapshot::new(rate_limiter.save()) + .save(&mut mem.as_mut_slice()) + .unwrap(); let restored_rate_limiter = - RateLimiter::restore((), &Snapshot::deserialize(&mut mem.as_slice()).unwrap()).unwrap(); + RateLimiter::restore((), &Snapshot::load(&mut mem.as_slice()).unwrap().data).unwrap(); assert!( rate_limiter diff --git a/src/vmm/src/snapshot/crc.rs b/src/vmm/src/snapshot/crc.rs index 8bc84c6c581..df97da26b13 100644 --- a/src/vmm/src/snapshot/crc.rs +++ b/src/vmm/src/snapshot/crc.rs @@ -28,7 +28,8 @@ use crc64::crc64; /// ``` #[derive(Debug)] pub struct CRC64Reader { - reader: T, + /// The underlying raw reader. Using this directly will bypass CRC computation! + pub reader: T, crc64: u64, } @@ -77,7 +78,8 @@ where /// ``` #[derive(Debug)] pub struct CRC64Writer { - writer: T, + /// The underlying raw writer. Using this directly will bypass CRC computation! + pub writer: T, crc64: u64, } diff --git a/src/vmm/src/snapshot/mod.rs b/src/vmm/src/snapshot/mod.rs index 57ad3980215..da214129c57 100644 --- a/src/vmm/src/snapshot/mod.rs +++ b/src/vmm/src/snapshot/mod.rs @@ -30,10 +30,12 @@ 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}; +use crate::persist::SNAPSHOT_VERSION; use crate::snapshot::crc::{CRC64Reader, CRC64Writer}; pub use crate::snapshot::persist::Persist; use crate::utils::mib_to_bytes; @@ -54,7 +56,7 @@ const BINCODE_CONFIG: Configuration(data: &S, write: &mut W) -> Result<(), SnapshotError> { + bincode::serde::encode_into_std_write(data, write, BINCODE_CONFIG) + .map_err(SnapshotError::Encode) + .map(|_| ()) } /// Firecracker snapshot header @@ -80,155 +86,86 @@ struct SnapshotHdr { } impl SnapshotHdr { - fn new(version: Version) -> Self { - Self { - magic: SNAPSHOT_MAGIC_ID, - version, + fn load(reader: &mut R) -> Result { + let hdr: SnapshotHdr = bincode::serde::decode_from_std_read(reader, BINCODE_CONFIG)?; + + if hdr.magic != SNAPSHOT_MAGIC_ID { + return Err(SnapshotError::InvalidMagic(hdr.magic)); } + + if hdr.version.major != SNAPSHOT_VERSION.major || hdr.version.minor > SNAPSHOT_VERSION.minor + { + return Err(SnapshotError::InvalidFormatVersion(hdr.version)); + } + + Ok(hdr) } } +/// Assumes the raw bytes stream read from the given [`Read`] instance is a snapshot file, +/// and returns the version of it. +pub fn get_format_version(reader: &mut R) -> Result { + let hdr: SnapshotHdr = bincode::serde::decode_from_std_read(reader, BINCODE_CONFIG)?; + Ok(hdr.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, +#[derive(Debug, Serialize)] +pub struct Snapshot { + header: SnapshotHdr, + /// The data stored int his [`Snapshot`] + pub data: Data, } -impl Snapshot { - /// Creates a new instance which can only be used to save a new snapshot. - pub fn new(version: Version) -> Snapshot { - Snapshot { version } - } - - /// 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) - } - - /// 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())) +impl Snapshot { + /// Constructs a new snapshot with the given `data`. + pub fn new(data: Data) -> Self { + Self { + header: SnapshotHdr { + magic: SNAPSHOT_MAGIC_ID, + version: SNAPSHOT_VERSION.clone(), + }, + data, + } } - /// 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(()) + /// Gets the version of this snapshot + pub fn version(&self) -> &Version { + &self.header.version } +} - /// 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)); - } - - let data: O = Self::deserialize(reader)?; - Ok((data, hdr.version)) +impl Snapshot { + fn load_unchecked(reader: &mut R) -> Result { + let header = SnapshotHdr::load(reader)?; + let data = bincode::serde::decode_from_std_read(reader, BINCODE_CONFIG)?; + Ok(Self { header, data }) } - /// 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, - { + /// Loads a snapshot from the given [`Read`] instance, performing all validations + /// (CRC, snapshot magic value, snapshot version). + pub fn load(reader: &mut R) -> Result { let mut crc_reader = CRC64Reader::new(reader); - - // 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 snapshot = Self::load_unchecked(&mut crc_reader)?; let computed_checksum = crc_reader.checksum(); - let stored_checksum: u64 = Self::deserialize(&mut crc_reader)?; + let stored_checksum: u64 = + bincode::serde::decode_from_std_read(&mut crc_reader.reader, BINCODE_CONFIG)?; if computed_checksum != stored_checksum { return Err(SnapshotError::Crc64(computed_checksum)); } - - let mut snapshot_slice: &[u8] = snapshot.as_mut_slice(); - Snapshot::unchecked_load::<_, O>(&mut snapshot_slice) - } - - /// 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)) - } else { - Ok(data) - } + Ok(snapshot) } +} - /// 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, - { +impl Snapshot { + /// Saves `self` to the given [`Write`] instance, computing the CRC of the written data, + /// and then writing the CRC into the `Write` instance, too. + pub fn save(&self, writer: &mut W) -> Result<(), SnapshotError> { let mut crc_writer = CRC64Writer::new(writer); - self.save_without_crc(&mut crc_writer, object)?; - - // Now write CRC value - let checksum = crc_writer.checksum(); - Self::serialize(&mut crc_writer, &checksum) - } - - /// 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) + serialize(self, &mut crc_writer)?; + serialize(&crc_writer.checksum(), crc_writer.writer) } } @@ -238,35 +175,19 @@ mod tests { #[test] fn test_parse_version_from_file() { - let snapshot = Snapshot::new(Version::new(1, 0, 42)); + let snapshot = Snapshot::new(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(); + snapshot.save(&mut snapshot_data.as_mut_slice()).unwrap(); assert_eq!( - Snapshot::get_format_version(&mut snapshot_data.as_slice()).unwrap(), - Version::new(1, 0, 42) + get_format_version(&mut snapshot_data.as_slice()).unwrap(), + SNAPSHOT_VERSION ); } - #[test] - fn test_bad_snapshot_size() { - let snapshot_data = vec![0u8; 1]; - - let snapshot = Snapshot::new(Version::new(1, 6, 1)); - assert!(matches!( - snapshot.load_with_version_check::<_, u8>( - &mut snapshot_data.as_slice(), - snapshot_data.len() - ), - Err(SnapshotError::InvalidSnapshotSize) - )); - } - #[test] fn test_bad_reader() { #[derive(Debug)] @@ -280,19 +201,17 @@ 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), - Err(SnapshotError::Io(_)) - )); + assert!( + matches!(Snapshot::<()>::load(&mut reader), Err(SnapshotError::Decode(DecodeError::Io {inner, ..})) if inner.kind() == std::io::ErrorKind::InvalidInput) + ); } #[test] 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(()); + 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. @@ -305,7 +224,7 @@ mod tests { data[6] = 0x44; data[7] = 0x45; assert!(matches!( - Snapshot::unchecked_load::<_, u8>(&mut data.as_slice()), + SnapshotHdr::load(&mut data.as_slice()), Err(SnapshotError::InvalidMagic(0x4544_4342_0403_0201u64)) )); } @@ -314,16 +233,13 @@ 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(); - - // Tamper the bytes written, without touching the previously CRC. - snapshot - .save_without_crc(&mut data.as_mut_slice(), &43u8) - .unwrap(); + let snapshot = Snapshot::new(()); + // Write the snapshot without CRC, so that when loading with CRC check, we'll read + // zeros for the CRC and fail. + serialize(&snapshot, &mut data.as_mut_slice()).unwrap(); assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), + Snapshot::<()>::load(&mut data.as_slice()), Err(SnapshotError::Crc64(_)) )); } @@ -332,61 +248,51 @@ mod tests { fn test_bad_version() { let mut data = vec![0u8; 100]; - // We write a snapshot with version "v1.3.12" - let snapshot = Snapshot::new(Version::new(1, 3, 12)); - snapshot.save(&mut data.as_mut_slice(), &42u8).unwrap(); + // Different major version: shouldn't work + let mut snapshot = Snapshot::new(()); + snapshot.header.version.major = SNAPSHOT_VERSION.major + 1; + snapshot.save(&mut data.as_mut_slice()).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()), - Err(SnapshotError::InvalidFormatVersion(Version { - major: 1, - minor: 3, - patch: 12, - .. - })) - )); - let snapshot = Snapshot::new(Version::new(0, 3, 12)); - assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), - Err(SnapshotError::InvalidFormatVersion(Version { - major: 1, - minor: 3, - patch: 12, - .. - })) + Snapshot::<()>::load(&mut data.as_slice()), + Err(SnapshotError::InvalidFormatVersion(v)) if v.major == SNAPSHOT_VERSION.major + 1 )); - // We can't support minor versions bigger than ours - let snapshot = Snapshot::new(Version::new(1, 2, 12)); + // minor > SNAPSHOT_VERSION.minor: shouldn't work + let mut snapshot = Snapshot::new(()); + snapshot.header.version.minor = SNAPSHOT_VERSION.minor + 1; + snapshot.save(&mut data.as_mut_slice()).unwrap(); assert!(matches!( - snapshot.load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()), - Err(SnapshotError::InvalidFormatVersion(Version { - major: 1, - minor: 3, - patch: 12, - .. - })) + Snapshot::<()>::load(&mut data.as_slice()), + Err(SnapshotError::InvalidFormatVersion(v)) if v.minor == SNAPSHOT_VERSION.minor + 1 )); - // But we can support minor versions smaller or equeal to ours. We also support + // But we can support minor versions smaller or equal 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()) - .unwrap(); - let snapshot = Snapshot::new(Version::new(1, 3, 0)); - snapshot - .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) - .unwrap(); - let snapshot = Snapshot::new(Version::new(1, 3, 12)); - snapshot - .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) - .unwrap(); - let snapshot = Snapshot::new(Version::new(1, 3, 1024)); - snapshot - .load_with_version_check::<_, u8>(&mut data.as_slice(), data.len()) - .unwrap(); + let snapshot = Snapshot::new(()); + snapshot.save(&mut data.as_mut_slice()).unwrap(); + Snapshot::<()>::load(&mut data.as_slice()).unwrap(); + + if SNAPSHOT_VERSION.minor != 0 { + let mut snapshot = Snapshot::new(()); + snapshot.header.version.minor = SNAPSHOT_VERSION.minor - 1; + snapshot.save(&mut data.as_mut_slice()).unwrap(); + Snapshot::<()>::load(&mut data.as_slice()).unwrap(); + } + + let mut snapshot = Snapshot::new(()); + snapshot.header.version.patch = 0; + snapshot.save(&mut data.as_mut_slice()).unwrap(); + Snapshot::<()>::load(&mut data.as_slice()).unwrap(); + + let mut snapshot = Snapshot::new(()); + snapshot.header.version.patch = SNAPSHOT_VERSION.patch + 1; + snapshot.save(&mut data.as_mut_slice()).unwrap(); + Snapshot::<()>::load(&mut data.as_slice()).unwrap(); + + let mut snapshot = Snapshot::new(()); + snapshot.header.version.patch = 1024; + snapshot.save(&mut data.as_mut_slice()).unwrap(); + Snapshot::<()>::load(&mut data.as_slice()).unwrap(); } } diff --git a/src/vmm/src/vmm_config/boot_source.rs b/src/vmm/src/vmm_config/boot_source.rs index dc21523af3c..ba87c2e872e 100644 --- a/src/vmm/src/vmm_config/boot_source.rs +++ b/src/vmm/src/vmm_config/boot_source.rs @@ -129,8 +129,10 @@ pub(crate) mod tests { }; let mut snapshot_data = vec![0u8; 1000]; - Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &boot_src_cfg).unwrap(); - let restored_boot_cfg = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); + Snapshot::new(&boot_src_cfg) + .save(&mut snapshot_data.as_mut_slice()) + .unwrap(); + let restored_boot_cfg = Snapshot::load(&mut snapshot_data.as_slice()).unwrap().data; 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..2f46e13fb7d 100644 --- a/src/vmm/src/vstate/memory.rs +++ b/src/vmm/src/vstate/memory.rs @@ -433,8 +433,10 @@ 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(); - let restored_state = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); + Snapshot::new(&original_state) + .save(&mut snapshot_data.as_mut_slice()) + .unwrap(); + let restored_state = Snapshot::load(&mut snapshot_data.as_slice()).unwrap().data; assert_eq!(original_state, restored_state); } diff --git a/src/vmm/src/vstate/resources.rs b/src/vmm/src/vstate/resources.rs index 545b211699f..e2b801b8d22 100644 --- a/src/vmm/src/vstate/resources.rs +++ b/src/vmm/src/vstate/resources.rs @@ -280,8 +280,10 @@ mod tests { fn clone_allocator(allocator: &ResourceAllocator) -> ResourceAllocator { let mut buf = vec![0u8; 1024]; - Snapshot::serialize(&mut buf.as_mut_slice(), &allocator.save()).unwrap(); - let restored_state: ResourceAllocator = Snapshot::deserialize(&mut buf.as_slice()).unwrap(); + Snapshot::new(allocator.save()) + .save(&mut buf.as_mut_slice()) + .unwrap(); + let restored_state: ResourceAllocator = Snapshot::load(&mut buf.as_slice()).unwrap().data; ResourceAllocator::restore((), &restored_state).unwrap() } diff --git a/src/vmm/src/vstate/vm.rs b/src/vmm/src/vstate/vm.rs index 8c4049f9e0c..f1222a30337 100644 --- a/src/vmm/src/vstate/vm.rs +++ b/src/vmm/src/vstate/vm.rs @@ -1044,9 +1044,11 @@ pub(crate) mod tests { }; let state = vm.save_state().unwrap(); - Snapshot::serialize(&mut snapshot_data.as_mut_slice(), &state).unwrap(); + Snapshot::new(state) + .save(&mut snapshot_data.as_mut_slice()) + .unwrap(); - let restored_state: VmState = Snapshot::deserialize(&mut snapshot_data.as_slice()).unwrap(); + let restored_state: VmState = Snapshot::load(&mut snapshot_data.as_slice()).unwrap().data; vm.restore_state(&restored_state).unwrap(); let mut resource_allocator = vm.resource_allocator(); diff --git a/src/vmm/tests/integration_tests.rs b/src/vmm/tests/integration_tests.rs index 4dd993d7c90..4abbedc4530 100644 --- a/src/vmm/tests/integration_tests.rs +++ b/src/vmm/tests/integration_tests.rs @@ -235,11 +235,8 @@ fn verify_create_snapshot(is_diff: bool, pci_enabled: bool) -> (TempFile, TempFi vmm.lock().unwrap().stop(FcExitCode::Ok); // Check that we can deserialize the microVM state from `snapshot_file`. - let snapshot_path = snapshot_file.as_path().to_path_buf(); - let snapshot_file_metadata = std::fs::metadata(snapshot_path).unwrap(); - let snapshot_len = snapshot_file_metadata.len().try_into().unwrap(); - let (restored_microvm_state, _) = - Snapshot::load::<_, MicrovmState>(&mut snapshot_file.as_file(), snapshot_len).unwrap(); + let restored_microvm_state: MicrovmState = + Snapshot::load(&mut snapshot_file.as_file()).unwrap().data; assert_eq!(restored_microvm_state.vm_info, vm_info); @@ -344,11 +341,8 @@ fn get_microvm_state_from_snapshot(pci_enabled: bool) -> MicrovmState { let (snapshot_file, _) = verify_create_snapshot(true, pci_enabled); // Deserialize the microVM state. - let snapshot_file_metadata = snapshot_file.as_file().metadata().unwrap(); - let snapshot_len = snapshot_file_metadata.len() as usize; snapshot_file.as_file().seek(SeekFrom::Start(0)).unwrap(); - let (state, _) = Snapshot::load(&mut snapshot_file.as_file(), snapshot_len).unwrap(); - state + Snapshot::load(&mut snapshot_file.as_file()).unwrap().data } fn verify_load_snap_disallowed_after_boot_resources(res: VmmAction, res_name: &str) { diff --git a/tests/integration_tests/functional/test_snapshot_basic.py b/tests/integration_tests/functional/test_snapshot_basic.py index c4eac866028..233ec9e6a8b 100644 --- a/tests/integration_tests/functional/test_snapshot_basic.py +++ b/tests/integration_tests/functional/test_snapshot_basic.py @@ -236,11 +236,7 @@ def test_load_snapshot_failure_handling(uvm_plain): jailed_vmstate = vm.create_jailed_resource(snapshot_vmstate) # Load the snapshot - expected_msg = ( - "Load snapshot error: Failed to restore from snapshot: Failed to get snapshot " - "state from file: Failed to load snapshot state from file: Snapshot file is smaller " - "than CRC length." - ) + expected_msg = 'An error occured during bincode decoding: Io { inner: Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }' with pytest.raises(RuntimeError, match=expected_msg): vm.api.snapshot_load.put(mem_file_path=jailed_mem, snapshot_path=jailed_vmstate)