From 6897c2a462dc255302554586bcb8cddfcdca435e Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Thu, 27 Nov 2025 09:39:30 +0100 Subject: [PATCH] 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 --- block/src/qcow/mod.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 251c1b772..46a938418 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -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 = 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::>>()?; + self.raw_file + .write_pointer_table(self.header.l1_table_offset, &l1_active, 0)?; self.l1_table.mark_clean(); true } else {