block: qcow: Set OFLAG_COPIED bit in L1 entries for spec compliance

The OFLAG_COPIED bit (bit 63) indicates a cluster's refcount is exactly
1 and doesn't need copy-on-write. This bit must be set in L1 entries
when their referenced L2 clusters have refcount=1.

Previously, L1 entries were always written as raw addresses without the
OFLAG_COPIED bit, violating the QCOW2 specification and causing qemu-img
check to report errors like

`ERROR OFLAG_COPIED L2 cluster: l1_index=X .... refcount=1`

The implementation queries each L2 cluster's refcount in sync_caches()
and sets OFLAG_COPIED appropriately when writing the L1 table. This
ensures QCOW2 images are specification compliant and maintain correct
COW semantics to avoid data corruption.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2025-11-27 09:39:30 +01:00
committed by Rob Bradford
parent 85556951a6
commit 6897c2a462

View File

@@ -207,6 +207,11 @@ fn l2_entry_make_std(cluster_addr: u64) -> u64 {
(cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG
}
// Make L1 entry with optional flags
fn l1_entry_make(cluster_addr: u64, refcount_is_one: bool) -> u64 {
(cluster_addr & L1_TABLE_OFFSET_MASK) | (refcount_is_one as u64 * CLUSTER_USED_FLAG)
}
/// Contains the information from the header of a qcow file.
#[derive(Clone, Debug)]
pub struct QcowHeader {
@@ -1571,11 +1576,25 @@ impl QcowFile {
// Push L1 table and refcount table last as all the clusters they point to are now
// guaranteed to be valid.
let mut sync_required = if self.l1_table.dirty() {
self.raw_file.write_pointer_table(
self.header.l1_table_offset,
self.l1_table.get_values(),
0,
)?;
// Build L1 table with OFLAG_COPIED bits set correctly based on L2 cluster refcounts
let l1_active: Vec<u64> = self
.l1_table
.get_values()
.iter()
.map(|&l2_addr| {
if l2_addr == 0 {
Ok(0)
} else {
let refcount = self
.refcounts
.get_cluster_refcount(&mut self.raw_file, l2_addr)
.map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?;
Ok(l1_entry_make(l2_addr, refcount == 1))
}
})
.collect::<io::Result<Vec<u64>>>()?;
self.raw_file
.write_pointer_table(self.header.l1_table_offset, &l1_active, 0)?;
self.l1_table.mark_clean();
true
} else {