QCOW2 Snapshot System¶
QCOW2 supports internal snapshots that capture the state of the virtual disk at a point in time. Each snapshot maintains its own L1 table, enabling efficient storage through copy-on-write.
Snapshot Table Location¶
The snapshot table is stored at snapshots_offset (header bytes 64-71).
The number of snapshots is in nb_snapshots (header bytes 60-63).
Snapshot Table Format¶
The snapshot table is a contiguous area containing variable-length entries:
+------------------+
| Snapshot 1 | (variable size, 8-byte aligned)
+------------------+
| Padding | (to 8-byte boundary)
+------------------+
| Snapshot 2 |
+------------------+
| Padding |
+------------------+
| ... |
+------------------+
Snapshot Header Structure¶
typedef struct QCowSnapshotHeader {
uint64_t l1_table_offset; // Snapshot's L1 table (cluster-aligned)
uint32_t l1_size; // Number of L1 entries
uint16_t id_str_size; // Unique ID string length
uint16_t name_size; // Snapshot name length
uint32_t date_sec; // Creation time (seconds since epoch)
uint32_t date_nsec; // Nanoseconds component
uint64_t vm_clock_nsec; // VM clock at snapshot time
uint32_t vm_state_size; // VM state size (32-bit, legacy)
uint32_t extra_data_size; // Size of extra data following header
// Extra data follows (if extra_data_size > 0)
// ID string follows (id_str_size bytes)
// Name string follows (name_size bytes)
// Padding to 8-byte boundary
} qemu_PACKED QCowSnapshotHeader;
Size: 48 bytes (base) + extra_data + id_str + name + padding
Extended Snapshot Data¶
Version 3 images include extra data after the base header:
typedef struct QCowSnapshotExtraData {
uint64_t vm_state_size_large; // 64-bit VM state size
uint64_t disk_size; // Virtual disk size at snapshot
uint64_t icount; // Instruction count (record/replay)
} qemu_PACKED QCowSnapshotExtraData;
Size: 24 bytes minimum for version 3
Version 3 images must have extra_data_size >= 16 (at least bytes 0-15).
In-Memory Snapshot Representation¶
typedef struct QCowSnapshot {
uint64_t l1_table_offset;
uint32_t l1_size;
char *id_str; // Unique identifier
char *name; // Human-readable name
uint64_t disk_size;
uint64_t vm_state_size;
uint32_t date_sec;
uint32_t date_nsec;
uint64_t vm_clock_nsec;
uint64_t icount;
uint32_t extra_data_size;
void *unknown_extra_data; // For forward compatibility
} QCowSnapshot;
Snapshot L1 Table¶
Each snapshot has its own L1 table, independent of the "active" L1 table. This is the key to copy-on-write:
Active State:
L1 (active) --> L2 tables --> Data clusters
After Snapshot:
L1 (active) --> L2 tables --> Data clusters
^ ^
L1 (snapshot) ------+ |
|
(Both L1s may point to same L2/data until modified)
When data is written after a snapshot: 1. Check if cluster is shared (refcount > 1) 2. If shared, allocate new cluster (COW) 3. Copy data, write new data 4. Update active L1/L2 to point to new cluster 5. Decrement refcount on old cluster
Snapshot Operations¶
Creating a Snapshot¶
1. Flush all pending writes
2. Allocate space for snapshot L1 table
3. Copy current L1 table to snapshot location
4. Increment refcounts for all referenced clusters
5. Clear COPIED flags on shared clusters
6. Allocate new snapshot table entry
7. Write snapshot header with metadata
8. Update header (nb_snapshots, snapshots_offset)
9. Free old snapshot table if reallocated
Restoring a Snapshot (goto)¶
1. Validate snapshot L1 table offset/size
2. Grow current L1 table if needed
3. Increment refcounts for snapshot's clusters
4. Decrement refcounts for current L1's clusters
5. Copy snapshot L1 to current L1
6. Update COPIED flags based on new refcounts
7. Clear DIRTY flag if set
Deleting a Snapshot¶
1. Load snapshot's L1 table
2. Decrement refcounts for all referenced clusters
3. Free clusters with refcount reaching 0
4. Set COPIED flags where refcount becomes 1
5. Free snapshot's L1 table
6. Remove entry from snapshot table
7. Write updated snapshot table
8. Update header (nb_snapshots)
Listing Snapshots¶
For each snapshot:
- ID: unique identifier
- Name: human-readable name
- Date: creation timestamp
- VM state size: saved RAM size
- Disk size: virtual disk size at snapshot time
VM State Storage¶
Snapshots can include VM state (memory, device state) for hibernation:
- VM state is stored as regular data in the image
- Located via L1 table entries beyond virtual disk size
vm_state_size/vm_state_size_largeindicates size- Address:
l1_vm_state_index << (cluster_bits + l2_bits)
The active image's VM state area is typically discarded after snapshot creation to avoid unnecessary copy-on-write.
Snapshot Table Limits¶
#define QCOW_MAX_SNAPSHOTS 65536
#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS) // 64 MB
#define QCOW_MAX_SNAPSHOT_EXTRA_DATA 1024
Refcount Updates for Snapshots¶
The function qcow2_update_snapshot_refcount() handles bulk updates:
int qcow2_update_snapshot_refcount(
BlockDriverState *bs,
int64_t l1_table_offset, // Snapshot's L1 table
int l1_size, // Number of L1 entries
int addend // +1 for create, -1 for delete
) {
// For each L1 entry:
// Load L2 table
// For each L2 entry:
// If compressed: update refcount for compressed extent
// If normal: update refcount for cluster
// Update COPIED flag if needed
}
Snapshot Consistency¶
Atomic updates are critical for snapshot table modifications:
- Allocate new snapshot table at new location
- Write all snapshot entries
- Update header (nb_snapshots, snapshots_offset) atomically
- Only after header update succeeds, free old table
This ensures crash recovery always finds a valid snapshot table.
Forward Compatibility¶
The extra_data_size field enables future extensions:
- Unknown extra data is preserved on read
- Written back unchanged on update
- Allows older qemu to handle newer snapshot formats
Common Issues¶
- Snapshot chain depth: Too many snapshots degrades read performance
- Space consumption: Deleted snapshots may not free space until refcounts drop
- VM state size: Large VM state can significantly increase snapshot size
- Consistency: Snapshots taken during writes may have inconsistent state