block: qcow: Implement extension parsing for QCOW v3

Add support for parsing QCOW v3 header extensions to read the
backing file format. The QCOW v3 spec allows optional header
extensions between the fixed header and the backing file name.

Implement read_header_extensions() to parse the extension area,
which starts at the header_size offset. At the moment it is
used to read the backing file format. Further extension
processing is open in folow up implementations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-01-12 15:09:14 +01:00
committed by Rob Bradford
parent 10394da0c4
commit 9eb2b9b0e5

View File

@@ -202,6 +202,11 @@ const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
const COMPRESSION_TYPE_ZLIB: u64 = 0; // zlib/deflate <https://www.ietf.org/rfc/rfc1951.txt>
const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd <http://github.com/facebook/zstd>
// Header extension types
const HEADER_EXT_END: u32 = 0x00000000;
// Backing file format name (raw, qcow2)
const HEADER_EXT_BACKING_FORMAT: u32 = 0xe2792aca;
// The format supports a "header extension area", that crosvm does not use.
const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
@@ -278,6 +283,46 @@ pub struct QcowHeader {
}
impl QcowHeader {
fn read_header_extensions(f: &mut RawFile, header: &mut QcowHeader) -> Result<()> {
// Extensions start directly after the header
f.seek(SeekFrom::Start(header.header_size as u64))
.map_err(Error::ReadingHeader)?;
loop {
let ext_type = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
if ext_type == HEADER_EXT_END {
break;
}
let ext_length = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
match ext_type {
HEADER_EXT_BACKING_FORMAT => {
let mut format_bytes = vec![0u8; ext_length as usize];
f.read_exact(&mut format_bytes)
.map_err(Error::ReadingHeader)?;
let format_str = String::from_utf8(format_bytes)
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
if let Some(backing_file) = &mut header.backing_file {
backing_file.format = Some(format_str.parse()?);
}
}
_ => {
// Skip unknown extension
f.seek(SeekFrom::Current(ext_length as i64))
.map_err(Error::ReadingHeader)?;
}
}
// Skip to the next 8 byte boundary
let padding = (8 - (ext_length % 8)) % 8;
f.seek(SeekFrom::Current(padding as i64))
.map_err(Error::ReadingHeader)?;
}
Ok(())
}
/// Creates a QcowHeader from a reference to a file.
pub fn new(f: &mut RawFile) -> Result<QcowHeader> {
f.rewind().map_err(Error::ReadingHeader)?;
@@ -363,6 +408,11 @@ impl QcowHeader {
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
header.backing_file = Some(BackingFileConfig { path, format: None });
}
if version == 3 && header.header_size > V3_BARE_HEADER_SIZE {
Self::read_header_extensions(f, &mut header)?;
}
Ok(header)
}