block: qcow: Switch resize and grow_l1_table to BlockResult

Switch resize and grow_l1_table from qcow::Result to BlockResult.
All I/O error sites use explicit BlockError::new with the Io kind.
The write_to call in grow_l1_table rewraps WritingHeader as
ResizeIo to preserve the existing error semantics.

The two resize tests that check for ShrinkNotSupported and
ResizeWithBackingFile are updated to match on BlockErrorKind with
downcast to inspect the underlying variant.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-03-14 00:00:14 +01:00
committed by Rob Bradford
parent 524e620240
commit f6ec817b9b

View File

@@ -868,7 +868,7 @@ impl QcowFile {
/// if needed. Shrinking is not supported, as it could lead to data
/// loss. Not supported when a backing file is present in that case
/// an error is returned.
pub fn resize(&mut self, new_size: u64) -> Result<()> {
pub fn resize(&mut self, new_size: u64) -> BlockResult<()> {
let current_size = self.virtual_size();
if new_size == current_size {
@@ -876,11 +876,17 @@ impl QcowFile {
}
if new_size < current_size {
return Err(Error::ShrinkNotSupported);
return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
Error::ShrinkNotSupported,
));
}
if self.backing_file.is_some() {
return Err(Error::ResizeWithBackingFile);
return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
Error::ResizeWithBackingFile,
));
}
// Grow the L1 table if needed
@@ -898,18 +904,20 @@ impl QcowFile {
self.raw_file
.file_mut()
.rewind()
.map_err(Error::SeekingFile)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
self.header
.write_to(self.raw_file.file_mut())
.map_err(|e| match e {
Error::WritingHeader(io_err) => Error::ResizeIo(io_err),
other => other,
Error::WritingHeader(io_err) => {
BlockError::new(BlockErrorKind::Io, Error::ResizeIo(io_err))
}
other => BlockError::new(BlockErrorKind::Io, other),
})?;
self.raw_file
.file_mut()
.sync_all()
.map_err(Error::SyncingHeader)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?;
Ok(())
}
@@ -919,7 +927,7 @@ impl QcowFile {
/// This allocates a new L1 table at file end (guaranteeing contiguity),
/// copies existing entries, updates refcounts, and atomically switches
/// to the new table.
fn grow_l1_table(&mut self, new_l1_size: u32) -> Result<()> {
fn grow_l1_table(&mut self, new_l1_size: u32) -> BlockResult<()> {
let old_l1_size = self.header.l1_size;
let old_l1_offset = self.header.l1_table_offset;
let cluster_size = self.raw_file.cluster_size();
@@ -932,7 +940,7 @@ impl QcowFile {
.raw_file
.file_mut()
.seek(SeekFrom::End(0))
.map_err(Error::ResizeIo)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?;
let new_l1_offset = self.raw_file.cluster_address(file_size + cluster_size - 1);
// Extend file to fit all L1 clusters
@@ -940,12 +948,12 @@ impl QcowFile {
self.raw_file
.file_mut()
.set_len(new_file_end)
.map_err(Error::SettingFileSize)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SettingFileSize(e)))?;
// Set refcounts for the contiguous range
for i in 0..new_l1_clusters {
self.set_cluster_refcount(new_l1_offset + i * cluster_size, 1)
.map_err(Error::ResizeIo)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?;
}
let mut new_l1_data = vec![0u64; new_l1_size as usize];
@@ -957,7 +965,7 @@ impl QcowFile {
let refcount = self
.refcounts
.get_cluster_refcount(&mut self.raw_file, *l2_addr)
.map_err(Error::GettingRefcount)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?;
*l2_addr = l1_entry_make(*l2_addr, refcount == 1);
}
}
@@ -965,12 +973,12 @@ impl QcowFile {
// Write the new L1 table to the file.
self.raw_file
.write_pointer_table_direct(new_l1_offset, new_l1_data.iter())
.map_err(Error::ResizeIo)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?;
self.raw_file
.file_mut()
.sync_all()
.map_err(Error::SyncingHeader)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?;
self.header.l1_size = new_l1_size;
self.header.l1_table_offset = new_l1_offset;
@@ -978,13 +986,15 @@ impl QcowFile {
self.raw_file
.file_mut()
.rewind()
.map_err(Error::SeekingFile)?;
self.header.write_to(self.raw_file.file_mut())?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
self.header
.write_to(self.raw_file.file_mut())
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
self.raw_file
.file_mut()
.sync_all()
.map_err(Error::SyncingHeader)?;
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?;
// Free old L1 table clusters
let old_l1_bytes = old_l1_size as u64 * size_of::<u64>() as u64;
@@ -3141,7 +3151,12 @@ mod unit_tests {
let result = q.resize(smaller_size);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::ShrinkNotSupported));
let err = result.unwrap_err();
assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature));
assert!(matches!(
err.downcast_ref::<Error>(),
Some(Error::ShrinkNotSupported)
));
assert_eq!(q.virtual_size(), original_size);
});
@@ -3172,7 +3187,12 @@ mod unit_tests {
let result = overlay.resize(backing_size * 2);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::ResizeWithBackingFile));
let err = result.unwrap_err();
assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature));
assert!(matches!(
err.downcast_ref::<Error>(),
Some(Error::ResizeWithBackingFile)
));
assert_eq!(overlay.virtual_size(), backing_size);
}