@@ -174,6 +174,9 @@ pub enum CreateSnapshotError {
174
174
SerializeMicrovmState ( snapshot:: Error ) ,
175
175
/// Cannot perform {0} on the snapshot backing file: {1}
176
176
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 ) ,
177
180
#[ cfg( target_arch = "x86_64" ) ]
178
181
#[ rustfmt:: skip]
179
182
#[ 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(
231
234
. map_err ( |err| SnapshotBackingFile ( "sync_all" , err) )
232
235
}
233
236
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.
234
243
fn snapshot_memory_to_file (
235
244
vmm : & Vmm ,
236
245
mem_file_path : & Path ,
237
246
snapshot_type : SnapshotType ,
238
247
) -> Result < ( ) , CreateSnapshotError > {
239
248
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
+
240
253
let mut file = OpenOptions :: new ( )
241
254
. write ( true )
242
255
. create ( true )
243
- . truncate ( true )
244
256
. open ( mem_file_path)
245
257
. map_err ( |err| MemoryBackingFile ( "open" , err) ) ?;
246
258
247
- // Set the length of the file to the full size of the memory area.
259
+ // Determine what size our total memory area is .
248
260
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) ) ?;
251
285
252
286
match snapshot_type {
253
287
SnapshotType :: Diff => {
0 commit comments