block: qcow: Fix v3 header writing and add extension tests
The write_to() function is used by test code to create qcow2 files for testing. For v3 headers with extended header_size (>104), it needs to: 1. Write the mandatory compression_type field at bytes 104-111 2. Write the header extension end marker at the header_size offset 3. Seek to backing_file_offset before writing the backing file path Additionally, create_for_size_and_path() must set backing_file_offset to account for the 8 byte extension end marker in v3 files, so the backing file path doesn't overwrite the extension area. Add unit tests for read_header_extensions() covering backing format parsing (raw/qcow2), unknown extensions, and error cases (invalid formats, invalid UTF-8). These tests depend on the header writing fixes to create properly formatted v3 test files. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
parent
9eb2b9b0e5
commit
3fed706d6a
1 changed files with 129 additions and 3 deletions
|
|
@ -451,10 +451,13 @@ impl QcowHeader {
|
|||
Ok(QcowHeader {
|
||||
magic: QCOW_MAGIC,
|
||||
version,
|
||||
backing_file_offset: (if backing_file.is_none() {
|
||||
0
|
||||
} else {
|
||||
backing_file_offset: backing_file.map_or(0, |_| {
|
||||
header_size
|
||||
+ if version == 3 {
|
||||
QCOW_EMPTY_HEADER_EXTENSION_SIZE
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}) as u64,
|
||||
backing_file_size: backing_file.map_or(0, |x| x.len()) as u32,
|
||||
cluster_bits: DEFAULT_CLUSTER_BITS,
|
||||
|
|
@ -528,11 +531,20 @@ impl QcowHeader {
|
|||
write_u64_to_file(file, self.autoclear_features)?;
|
||||
write_u32_to_file(file, self.refcount_order)?;
|
||||
write_u32_to_file(file, self.header_size)?;
|
||||
|
||||
if self.header_size > V3_BARE_HEADER_SIZE {
|
||||
write_u64_to_file(file, 0)?; // no compression
|
||||
}
|
||||
|
||||
write_u32_to_file(file, 0)?; // header extension type: end of header extension area
|
||||
write_u32_to_file(file, 0)?; // length of header extension data: 0
|
||||
}
|
||||
|
||||
if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) {
|
||||
if self.backing_file_offset > 0 {
|
||||
file.seek(SeekFrom::Start(self.backing_file_offset))
|
||||
.map_err(Error::WritingHeader)?;
|
||||
}
|
||||
write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?;
|
||||
}
|
||||
|
||||
|
|
@ -2324,6 +2336,120 @@ mod unit_tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// Helper to create a test file with header extensions
|
||||
fn create_header_with_extension(ext_type: u32, ext_data: &[u8]) -> (RawFile, QcowHeader) {
|
||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
|
||||
.expect("Failed to create header.");
|
||||
|
||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header.write_to(&mut disk_file).unwrap();
|
||||
|
||||
// Write extension
|
||||
disk_file
|
||||
.seek(SeekFrom::Start(header.header_size as u64))
|
||||
.unwrap();
|
||||
disk_file.write_u32::<BigEndian>(ext_type).unwrap();
|
||||
disk_file
|
||||
.write_u32::<BigEndian>(ext_data.len() as u32)
|
||||
.unwrap();
|
||||
disk_file.write_all(ext_data).unwrap();
|
||||
|
||||
// Add padding to 8-byte boundary
|
||||
let padding = (8 - (ext_data.len() % 8)) % 8;
|
||||
if padding > 0 {
|
||||
disk_file.write_all(&vec![0u8; padding]).unwrap();
|
||||
}
|
||||
|
||||
disk_file.write_u32::<BigEndian>(HEADER_EXT_END).unwrap();
|
||||
|
||||
disk_file.rewind().unwrap();
|
||||
|
||||
(disk_file, header)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_unknown_extension() {
|
||||
let (mut disk_file, mut header) = create_header_with_extension(
|
||||
0x12345678, // unknown type
|
||||
"test".as_bytes(),
|
||||
);
|
||||
|
||||
// Extension parsing needs a backing file to set format on
|
||||
header.backing_file = Some(BackingFileConfig {
|
||||
path: "/test/backing".to_string(),
|
||||
format: None,
|
||||
});
|
||||
|
||||
QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap();
|
||||
assert_eq!(header.backing_file.as_ref().and_then(|bf| bf.format), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_raw_format() {
|
||||
let (mut disk_file, mut header) =
|
||||
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "raw".as_bytes());
|
||||
|
||||
header.backing_file = Some(BackingFileConfig {
|
||||
path: "/test/backing".to_string(),
|
||||
format: None,
|
||||
});
|
||||
|
||||
QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap();
|
||||
assert_eq!(
|
||||
header.backing_file.as_ref().and_then(|bf| bf.format),
|
||||
Some(ImageType::Raw)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_qcow2_format() {
|
||||
let (mut disk_file, mut header) =
|
||||
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "qcow2".as_bytes());
|
||||
|
||||
header.backing_file = Some(BackingFileConfig {
|
||||
path: "/test/backing".to_string(),
|
||||
format: None,
|
||||
});
|
||||
|
||||
QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap();
|
||||
assert_eq!(
|
||||
header.backing_file.as_ref().and_then(|bf| bf.format),
|
||||
Some(ImageType::Qcow2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_invalid_format() {
|
||||
let (mut disk_file, mut header) =
|
||||
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "vmdk".as_bytes());
|
||||
|
||||
header.backing_file = Some(BackingFileConfig {
|
||||
path: "/test/backing".to_string(),
|
||||
format: None,
|
||||
});
|
||||
|
||||
let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header);
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
Error::UnsupportedBackingFileFormat(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_invalid_utf8() {
|
||||
let (mut disk_file, mut header) = create_header_with_extension(
|
||||
HEADER_EXT_BACKING_FORMAT,
|
||||
&[0xFF, 0xFE, 0xFD], // invalid UTF-8
|
||||
);
|
||||
|
||||
let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header);
|
||||
// Should fail with InvalidBackingFileName error
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
Error::InvalidBackingFileName(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_backing_file() {
|
||||
// `backing_file` is `None`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue