mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: qcow: Extend corrupt bit unit tests
Add tests for corrupt bit behavior during I/O operations. - Unaligned L2 table address triggers corrupt bit on read - Unaligned cluster address triggers corrupt bit on read and write - Normal operations do not set the corrupt bit - V2 images work correctly without feature bits Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
323b8230a3
commit
2da05d4258
@@ -4190,4 +4190,207 @@ mod unit_tests {
|
|||||||
"Dirty bit should also be set"
|
"Dirty bit should also be set"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to check if corrupt bit is set on disk by re-reading the header
|
||||||
|
fn is_corrupt_on_disk(disk_file: &mut RawFile) -> bool {
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
QcowHeader::new(disk_file).unwrap().is_corrupt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to clear the corrupt bit on disk while preserving other bits
|
||||||
|
fn clear_corrupt_bit_on_disk(disk_file: &mut RawFile) {
|
||||||
|
disk_file
|
||||||
|
.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
|
||||||
|
.unwrap();
|
||||||
|
let features = u64::read_be(disk_file).unwrap();
|
||||||
|
let mut flags = IncompatFeatures::from_bits_retain(features);
|
||||||
|
flags.remove(IncompatFeatures::CORRUPT);
|
||||||
|
disk_file
|
||||||
|
.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
|
||||||
|
.unwrap();
|
||||||
|
u64::write_be(disk_file, flags.bits()).unwrap();
|
||||||
|
assert!(
|
||||||
|
!is_corrupt_on_disk(disk_file),
|
||||||
|
"Corrupt bit should be cleared"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to corrupt L1 entry by making L2 table address unaligned.
|
||||||
|
///
|
||||||
|
/// Returns true if corruption was applied, i.e. the L1 entry was allocated.
|
||||||
|
fn corrupt_l1_entry(disk_file: &mut RawFile) -> bool {
|
||||||
|
let l1_table_offset = 0x0004_0000u64;
|
||||||
|
disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap();
|
||||||
|
let l1_entry = u64::read_be(disk_file).unwrap();
|
||||||
|
if l1_entry != 0 {
|
||||||
|
let unaligned = l1_entry | 0x200; // Make unaligned
|
||||||
|
disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap();
|
||||||
|
u64::write_be(disk_file, unaligned).unwrap();
|
||||||
|
disk_file.sync_all().unwrap();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to corrupt L2 entry by making cluster address unaligned.
|
||||||
|
///
|
||||||
|
/// Returns true if corruption was applied, i.e. an allocated non-compressed
|
||||||
|
/// L2 entry was found.
|
||||||
|
fn corrupt_l2_entry(disk_file: &mut RawFile) -> bool {
|
||||||
|
let l1_table_offset = 0x0004_0000u64;
|
||||||
|
disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap();
|
||||||
|
let l1_entry = u64::read_be(disk_file).unwrap();
|
||||||
|
if l1_entry == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let l2_table_addr = l1_entry & L1_TABLE_OFFSET_MASK;
|
||||||
|
disk_file.seek(SeekFrom::Start(l2_table_addr)).unwrap();
|
||||||
|
let l2_entry = u64::read_be(disk_file).unwrap();
|
||||||
|
if l2_entry != 0 && !l2_entry_is_compressed(l2_entry) {
|
||||||
|
let unaligned = l2_entry | 0x200;
|
||||||
|
disk_file.seek(SeekFrom::Start(l2_table_addr)).unwrap();
|
||||||
|
u64::write_be(disk_file, unaligned).unwrap();
|
||||||
|
disk_file.sync_all().unwrap();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that read on corrupted disk sets the corrupt bit
|
||||||
|
fn assert_corruption_on_read(disk_file: &mut RawFile) {
|
||||||
|
clear_corrupt_bit_on_disk(disk_file);
|
||||||
|
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
let mut buf = [0u8; 16];
|
||||||
|
let result = qcow.read(&mut buf);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result.map_err(|e| e.raw_os_error()),
|
||||||
|
Err(Some(libc::EIO)),
|
||||||
|
"read should fail with EIO on corrupted image"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
is_corrupt_on_disk(disk_file),
|
||||||
|
"Corrupt bit should be set after read"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that write on corrupted disk sets the corrupt bit
|
||||||
|
fn assert_corruption_on_write(disk_file: &mut RawFile) {
|
||||||
|
clear_corrupt_bit_on_disk(disk_file);
|
||||||
|
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
let result = qcow.write_all(b"overwrite");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result.map_err(|e| e.raw_os_error()),
|
||||||
|
Err(Some(libc::EIO)),
|
||||||
|
"write should fail with EIO on corrupted image"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
is_corrupt_on_disk(disk_file),
|
||||||
|
"Corrupt bit should be set after write"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn corrupt_bit_on_unaligned_l2_address() {
|
||||||
|
let header = valid_header_v3();
|
||||||
|
with_basic_file(&header, |mut disk_file: RawFile| {
|
||||||
|
{
|
||||||
|
let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
qcow.write_all(b"test data").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
corrupt_l1_entry(&mut disk_file),
|
||||||
|
"Failed to corrupt L1 entry - was data written?"
|
||||||
|
);
|
||||||
|
assert_corruption_on_read(&mut disk_file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn corrupt_bit_on_unaligned_cluster_address_read() {
|
||||||
|
let header = valid_header_v3();
|
||||||
|
with_basic_file(&header, |mut disk_file: RawFile| {
|
||||||
|
{
|
||||||
|
let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
qcow.write_all(b"test data to allocate cluster").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
corrupt_l2_entry(&mut disk_file),
|
||||||
|
"Failed to corrupt L2 entry - was cluster allocated?"
|
||||||
|
);
|
||||||
|
assert_corruption_on_read(&mut disk_file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn corrupt_bit_on_unaligned_cluster_address_write() {
|
||||||
|
let header = valid_header_v3();
|
||||||
|
with_basic_file(&header, |mut disk_file: RawFile| {
|
||||||
|
{
|
||||||
|
let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
qcow.write_all(b"test data to allocate cluster").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
corrupt_l2_entry(&mut disk_file),
|
||||||
|
"Failed to corrupt L2 entry - was cluster allocated?"
|
||||||
|
);
|
||||||
|
assert_corruption_on_write(&mut disk_file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn corrupt_bit_not_set_on_normal_operations() {
|
||||||
|
let header = valid_header_v3();
|
||||||
|
with_basic_file(&header, |mut disk_file: RawFile| {
|
||||||
|
{
|
||||||
|
let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
|
||||||
|
qcow.write_all(b"test data 1234567890").unwrap();
|
||||||
|
qcow.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
|
||||||
|
let mut buf = [0u8; 20];
|
||||||
|
qcow.read_exact(&mut buf).unwrap();
|
||||||
|
assert_eq!(&buf, b"test data 1234567890");
|
||||||
|
|
||||||
|
qcow.seek(SeekFrom::Start(0x10000)).unwrap();
|
||||||
|
qcow.write_all(b"more data").unwrap();
|
||||||
|
|
||||||
|
qcow.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!is_corrupt_on_disk(&mut disk_file),
|
||||||
|
"Corrupt bit should NOT be set after normal operations"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn corrupt_bit_v2_image_not_affected() {
|
||||||
|
let header = valid_header_v2();
|
||||||
|
with_basic_file(&header, |mut disk_file: RawFile| {
|
||||||
|
{
|
||||||
|
let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
qcow.write_all(b"test data").unwrap();
|
||||||
|
qcow.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
let mut buf = [0u8; 9];
|
||||||
|
qcow.read_exact(&mut buf).unwrap();
|
||||||
|
assert_eq!(&buf, b"test data");
|
||||||
|
}
|
||||||
|
|
||||||
|
disk_file.rewind().unwrap();
|
||||||
|
let qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
|
||||||
|
assert_eq!(qcow.header.version, 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user