From 4545fe113eecf07ed60ba65fb9d950e12227bfe1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 28 Jan 2026 20:13:20 +0100 Subject: [PATCH] block: qcow: Clear autoclear features on writable open QCOW2 v3 autoclear_features field contains bits for features whose metadata becomes invalid when the image is modified by software that doesn't understand them. Defined bits: - Bit 0: Bitmaps extension - Bit 1: Raw external data Cloud-hypervisor doesn't support bitmaps or external data files, so all autoclear bits are cleared on writable open. This signals other tools that these features' data may be stale. Readonly opens preserve autoclear bits unchanged. Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index efd4c5b70..6d5a9c2d4 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -194,6 +194,7 @@ const DEFAULT_REFCOUNT_ORDER: u32 = 4; const V2_BARE_HEADER_SIZE: u32 = 72; const V3_BARE_HEADER_SIZE: u32 = 104; +const AUTOCLEAR_FEATURES_OFFSET: u64 = 88; // bits 0-8 and 56-63 are reserved. const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; @@ -719,6 +720,24 @@ impl QcowHeader { IncompatFeatures::from_bits_truncate(self.incompatible_features) .contains(IncompatFeatures::CORRUPT) } + + /// Clear all autoclear feature bits for QCOW2 v3 images. + /// + /// These bits indicate features that can be safely disabled when modified + /// by software that doesn't understand them. + pub fn clear_autoclear_features( + &mut self, + file: &mut F, + ) -> Result<()> { + if self.version == 3 && self.autoclear_features != 0 { + self.autoclear_features = 0; + file.seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET)) + .map_err(Error::WritingHeader)?; + u64::write_be(file, 0).map_err(Error::WritingHeader)?; + file.fsync().map_err(Error::WritingHeader)?; + } + Ok(()) + } } fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 { @@ -1023,11 +1042,15 @@ impl QcowFile { qcow.find_avail_clusters()?; - if !IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features) - .contains(IncompatFeatures::DIRTY) - && is_writable - { - qcow.header.set_dirty_bit(qcow.raw_file.file_mut(), true)?; + if is_writable { + if !IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features) + .contains(IncompatFeatures::DIRTY) + { + qcow.header.set_dirty_bit(qcow.raw_file.file_mut(), true)?; + } + + qcow.header + .clear_autoclear_features(qcow.raw_file.file_mut())?; } Ok(qcow)