Skip to content

Commit f00eab8

Browse files
roypatCompuIves
andcommitted
Allow immediate merging of diff snapshots during creation
Allows the automatic merging of diff snapshots with a base snapshot by directly writing the diff snapshot on top of a base snapshot file of matching size. Signed-off-by: Patrick Roy <[email protected]> Co-authored-by: Ives van Hoorne <[email protected]>
1 parent 03418e3 commit f00eab8

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5959
Removed support for creating Firecracker snapshots targeting older versions
6060
of Firecracker. With this change, running 'firecracker --version' will not
6161
print the supported snapshot versions.
62+
- [#4301](https://github.com/firecracker-microvm/firecracker/pull/4301):
63+
Allow merging of diff snapshots into base snapshots by directly writing
64+
the diff snapshot on top of the base snapshot's memory file. This can be
65+
done by setting the `mem_file_path` to the path of the pre-existing full
66+
snapshot.
6267

6368
### Deprecated
6469

@@ -87,6 +92,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8792
Fixed a bug in the asynchronous virtio-block engine that rendered the device
8893
non-functional after a PATCH request was issued to Firecracker for updating
8994
the path to the host-side backing file of the device.
95+
- [#4301](https://github.com/firecracker-microvm/firecracker/pull/4301):
96+
Fixed a bug where if Firecracker was instructed to take a snapshot of a
97+
microvm which itself was restored from a snapshot, specifying `mem_file_path`
98+
to be the path of the memory file from which the microvm was restored would
99+
result in both the microvm and the snapshot being corrupted. It now instead
100+
performs a "write-back" of all memory that was updated since the snapshot
101+
was originally loaded.
90102

91103
## [1.5.0]
92104

src/vmm/src/persist.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ pub enum CreateSnapshotError {
174174
SerializeMicrovmState(snapshot::Error),
175175
/// Cannot perform {0} on the snapshot backing file: {1}
176176
SnapshotBackingFile(&'static str, io::Error),
177+
#[rustfmt::skip]
178+
#[doc = "Size mismatch when writing diff snapshot on top of base layer: base layer size is {0} but diff layer is size {1}."]
179+
SnapshotBackingFileLengthMismatch(u64, u64),
177180
#[cfg(target_arch = "x86_64")]
178181
#[rustfmt::skip]
179182
#[doc = "Too many devices attached: {0}. The maximum number allowed for the snapshot data version requested is {FC_V0_23_MAX_DEVICES:}."]
@@ -231,23 +234,54 @@ fn snapshot_state_to_file(
231234
.map_err(|err| SnapshotBackingFile("sync_all", err))
232235
}
233236

237+
/// Takes a snapshot of the virtual machine running inside the given [`Vmm`] and saves it to
238+
/// `mem_file_path`.
239+
///
240+
/// If `snapshot_type` is [`SnapshotType::Diff`], and `mem_file_path` exists and is a snapshot file
241+
/// of matching size, then the diff snapshot will be directly merged into the existing snapshot.
242+
/// Otherwise, existing files are simply overwritten.
234243
fn snapshot_memory_to_file(
235244
vmm: &Vmm,
236245
mem_file_path: &Path,
237246
snapshot_type: SnapshotType,
238247
) -> Result<(), CreateSnapshotError> {
239248
use self::CreateSnapshotError::*;
249+
250+
// Need to check this here, as we create the file in the line below
251+
let file_existed = mem_file_path.exists();
252+
240253
let mut file = OpenOptions::new()
241254
.write(true)
242255
.create(true)
243-
.truncate(true)
244256
.open(mem_file_path)
245257
.map_err(|err| MemoryBackingFile("open", err))?;
246258

247-
// Set the length of the file to the full size of the memory area.
259+
// Determine what size our total memory area is.
248260
let mem_size_mib = mem_size_mib(vmm.guest_memory());
249-
file.set_len(mem_size_mib * 1024 * 1024)
250-
.map_err(|err| MemoryBackingFile("set_length", err))?;
261+
let expected_size = mem_size_mib * 1024 * 1024;
262+
263+
if file_existed {
264+
let file_size = file
265+
.metadata()
266+
.map_err(|e| MemoryBackingFile("get_metadata", e))?
267+
.len();
268+
269+
// Here we only truncate the file if the size mismatches.
270+
// - For full snapshots, the entire file's contents will be overwritten anyway. We have to
271+
// avoid truncating here to deal with the edge case where it represents the snapshot file
272+
// from which this very microVM was loaded (as modifying the memory file would be
273+
// reflected in the mmap of the file, meaning a truncate operation would zero out guest
274+
// memory, and thus corrupt the VM).
275+
// - For diff snapshots, we want to merge the diff layer directly into the file.
276+
if file_size != expected_size {
277+
file.set_len(0)
278+
.map_err(|err| MemoryBackingFile("truncate", err))?;
279+
}
280+
}
281+
282+
// Set the length of the file to the full size of the memory area.
283+
file.set_len(expected_size)
284+
.map_err(|e| MemoryBackingFile("set_length", e))?;
251285

252286
match snapshot_type {
253287
SnapshotType::Diff => {

0 commit comments

Comments
 (0)