Skip to content

Commit 3fed706

Browse files
weltlingrbradford
authored andcommitted
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>
1 parent 9eb2b9b commit 3fed706

File tree

1 file changed

+129
-3
lines changed

1 file changed

+129
-3
lines changed

block/src/qcow/mod.rs

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,13 @@ impl QcowHeader {
451451
Ok(QcowHeader {
452452
magic: QCOW_MAGIC,
453453
version,
454-
backing_file_offset: (if backing_file.is_none() {
455-
0
456-
} else {
454+
backing_file_offset: backing_file.map_or(0, |_| {
457455
header_size
456+
+ if version == 3 {
457+
QCOW_EMPTY_HEADER_EXTENSION_SIZE
458+
} else {
459+
0
460+
}
458461
}) as u64,
459462
backing_file_size: backing_file.map_or(0, |x| x.len()) as u32,
460463
cluster_bits: DEFAULT_CLUSTER_BITS,
@@ -528,11 +531,20 @@ impl QcowHeader {
528531
write_u64_to_file(file, self.autoclear_features)?;
529532
write_u32_to_file(file, self.refcount_order)?;
530533
write_u32_to_file(file, self.header_size)?;
534+
535+
if self.header_size > V3_BARE_HEADER_SIZE {
536+
write_u64_to_file(file, 0)?; // no compression
537+
}
538+
531539
write_u32_to_file(file, 0)?; // header extension type: end of header extension area
532540
write_u32_to_file(file, 0)?; // length of header extension data: 0
533541
}
534542

535543
if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) {
544+
if self.backing_file_offset > 0 {
545+
file.seek(SeekFrom::Start(self.backing_file_offset))
546+
.map_err(Error::WritingHeader)?;
547+
}
536548
write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?;
537549
}
538550

@@ -2324,6 +2336,120 @@ mod unit_tests {
23242336
);
23252337
}
23262338

2339+
/// Helper to create a test file with header extensions
2340+
fn create_header_with_extension(ext_type: u32, ext_data: &[u8]) -> (RawFile, QcowHeader) {
2341+
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
2342+
.expect("Failed to create header.");
2343+
2344+
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
2345+
header.write_to(&mut disk_file).unwrap();
2346+
2347+
// Write extension
2348+
disk_file
2349+
.seek(SeekFrom::Start(header.header_size as u64))
2350+
.unwrap();
2351+
disk_file.write_u32::<BigEndian>(ext_type).unwrap();
2352+
disk_file
2353+
.write_u32::<BigEndian>(ext_data.len() as u32)
2354+
.unwrap();
2355+
disk_file.write_all(ext_data).unwrap();
2356+
2357+
// Add padding to 8-byte boundary
2358+
let padding = (8 - (ext_data.len() % 8)) % 8;
2359+
if padding > 0 {
2360+
disk_file.write_all(&vec![0u8; padding]).unwrap();
2361+
}
2362+
2363+
disk_file.write_u32::<BigEndian>(HEADER_EXT_END).unwrap();
2364+
2365+
disk_file.rewind().unwrap();
2366+
2367+
(disk_file, header)
2368+
}
2369+
2370+
#[test]
2371+
fn read_header_extensions_unknown_extension() {
2372+
let (mut disk_file, mut header) = create_header_with_extension(
2373+
0x12345678, // unknown type
2374+
"test".as_bytes(),
2375+
);
2376+
2377+
// Extension parsing needs a backing file to set format on
2378+
header.backing_file = Some(BackingFileConfig {
2379+
path: "/test/backing".to_string(),
2380+
format: None,
2381+
});
2382+
2383+
QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap();
2384+
assert_eq!(header.backing_file.as_ref().and_then(|bf| bf.format), None);
2385+
}
2386+
2387+
#[test]
2388+
fn read_header_extensions_raw_format() {
2389+
let (mut disk_file, mut header) =
2390+
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "raw".as_bytes());
2391+
2392+
header.backing_file = Some(BackingFileConfig {
2393+
path: "/test/backing".to_string(),
2394+
format: None,
2395+
});
2396+
2397+
QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap();
2398+
assert_eq!(
2399+
header.backing_file.as_ref().and_then(|bf| bf.format),
2400+
Some(ImageType::Raw)
2401+
);
2402+
}
2403+
2404+
#[test]
2405+
fn read_header_extensions_qcow2_format() {
2406+
let (mut disk_file, mut header) =
2407+
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "qcow2".as_bytes());
2408+
2409+
header.backing_file = Some(BackingFileConfig {
2410+
path: "/test/backing".to_string(),
2411+
format: None,
2412+
});
2413+
2414+
QcowHeader::read_header_extensions(&mut disk_file, &mut header).unwrap();
2415+
assert_eq!(
2416+
header.backing_file.as_ref().and_then(|bf| bf.format),
2417+
Some(ImageType::Qcow2)
2418+
);
2419+
}
2420+
2421+
#[test]
2422+
fn read_header_extensions_invalid_format() {
2423+
let (mut disk_file, mut header) =
2424+
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "vmdk".as_bytes());
2425+
2426+
header.backing_file = Some(BackingFileConfig {
2427+
path: "/test/backing".to_string(),
2428+
format: None,
2429+
});
2430+
2431+
let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header);
2432+
assert!(matches!(
2433+
result.unwrap_err(),
2434+
Error::UnsupportedBackingFileFormat(_)
2435+
));
2436+
}
2437+
2438+
#[test]
2439+
fn read_header_extensions_invalid_utf8() {
2440+
let (mut disk_file, mut header) = create_header_with_extension(
2441+
HEADER_EXT_BACKING_FORMAT,
2442+
&[0xFF, 0xFE, 0xFD], // invalid UTF-8
2443+
);
2444+
2445+
let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header);
2446+
// Should fail with InvalidBackingFileName error
2447+
assert!(matches!(
2448+
result.unwrap_err(),
2449+
Error::InvalidBackingFileName(_)
2450+
));
2451+
}
2452+
23272453
#[test]
23282454
fn no_backing_file() {
23292455
// `backing_file` is `None`

0 commit comments

Comments
 (0)