block: qcow: Classify errors in parse_qcow and BackingFile::new

Replace remaining automatic From conversions in parse_qcow and
BackingFile::new with explicit BlockError::new calls carrying the
appropriate BlockErrorKind at every error site.

Internal functions that still return qcow::Result (QcowHeader::new,
offset_is_cluster_boundary, clear_autoclear_features and others) are
wrapped with map_err at the boundary. These functions stay on
qcow::Result as they are internal to the qcow module and the
classification belongs at the call site rather than inside the
function itself.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-03-13 23:53:42 +01:00
committed by Rob Bradford
parent be9ef116aa
commit 524e620240
+64 -28
View File
@@ -205,7 +205,12 @@ impl BackingFile {
let backing_raw_file = OpenOptions::new() let backing_raw_file = OpenOptions::new()
.read(true) .read(true)
.open(&config.path) .open(&config.path)
.map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; .map_err(|e| {
BlockError::new(
BlockErrorKind::Io,
Error::BackingFileIo(config.path.clone(), e),
)
})?;
let mut raw_file = RawFile::new(backing_raw_file, direct_io); let mut raw_file = RawFile::new(backing_raw_file, direct_io);
@@ -217,12 +222,18 @@ impl BackingFile {
let (kind, virtual_size) = match backing_format { let (kind, virtual_size) = match backing_format {
ImageType::Raw => { ImageType::Raw => {
let size = raw_file let size = raw_file.seek(SeekFrom::End(0)).map_err(|e| {
.seek(SeekFrom::End(0)) BlockError::new(
.map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; BlockErrorKind::Io,
raw_file Error::BackingFileIo(config.path.clone(), e),
.rewind() )
.map_err(|e| Error::BackingFileIo(config.path.clone(), e))?; })?;
raw_file.rewind().map_err(|e| {
BlockError::new(
BlockErrorKind::Io,
Error::BackingFileIo(config.path.clone(), e),
)
})?;
(BackingKind::Raw(raw_file), size) (BackingKind::Raw(raw_file), size)
} }
ImageType::Qcow2 => { ImageType::Qcow2 => {
@@ -404,9 +415,12 @@ pub(crate) fn parse_qcow(
)?; )?;
// Validate refcount order to be 0..6 // Validate refcount order to be 0..6
let refcount_bits: u64 = 0x01u64 let refcount_bits: u64 = 0x01u64.checked_shl(header.refcount_order).ok_or_else(|| {
.checked_shl(header.refcount_order) BlockError::new(
.ok_or(Error::UnsupportedRefcountOrder)?; BlockErrorKind::UnsupportedFeature,
Error::UnsupportedRefcountOrder,
)
})?;
if refcount_bits > 64 { if refcount_bits > 64 {
return Err(BlockError::new( return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature, BlockErrorKind::UnsupportedFeature,
@@ -421,11 +435,17 @@ pub(crate) fn parse_qcow(
Error::NoRefcountClusters, Error::NoRefcountClusters,
)); ));
} }
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?; offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?; .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?;
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)
.map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?;
// refcount table must be a cluster boundary, and within the file's virtual or actual size. // refcount table must be a cluster boundary, and within the file's virtual or actual size.
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?; offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)
let file_size = file.metadata().map_err(Error::GettingFileSize)?.len(); .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?;
let file_size = file
.metadata()
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))?
.len();
if header.refcount_table_offset > max(file_size, header.size) { if header.refcount_table_offset > max(file_size, header.size) {
return Err(BlockError::new( return Err(BlockError::new(
BlockErrorKind::CorruptImage, BlockErrorKind::CorruptImage,
@@ -437,12 +457,14 @@ pub(crate) fn parse_qcow(
// this is an old file with broken refcounts, which requires a rebuild. // this is an old file with broken refcounts, which requires a rebuild.
let mut refcount_rebuild_required = true; let mut refcount_rebuild_required = true;
file.seek(SeekFrom::Start(header.refcount_table_offset)) file.seek(SeekFrom::Start(header.refcount_table_offset))
.map_err(Error::SeekingFile)?; .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
let first_refblock_addr = u64::read_be(&mut file).map_err(Error::ReadingHeader)?; let first_refblock_addr = u64::read_be(&mut file)
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?;
if first_refblock_addr != 0 { if first_refblock_addr != 0 {
file.seek(SeekFrom::Start(first_refblock_addr)) file.seek(SeekFrom::Start(first_refblock_addr))
.map_err(Error::SeekingFile)?; .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
let first_cluster_refcount = u16::read_be(&mut file).map_err(Error::ReadingHeader)?; let first_cluster_refcount = u16::read_be(&mut file)
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?;
if first_cluster_refcount != 0 { if first_cluster_refcount != 0 {
refcount_rebuild_required = false; refcount_rebuild_required = false;
} }
@@ -452,8 +474,8 @@ pub(crate) fn parse_qcow(
refcount_rebuild_required = true; refcount_rebuild_required = true;
} }
let mut raw_file = let mut raw_file = QcowRawFile::from(file, cluster_size, refcount_bits)
QcowRawFile::from(file, cluster_size, refcount_bits).ok_or(Error::InvalidClusterSize)?; .ok_or_else(|| BlockError::new(BlockErrorKind::InvalidFormat, Error::InvalidClusterSize))?;
let is_writable = raw_file.file().is_writable(); let is_writable = raw_file.file().is_writable();
if header.is_corrupt() { if header.is_corrupt() {
@@ -499,7 +521,7 @@ pub(crate) fn parse_qcow(
num_l2_clusters, num_l2_clusters,
Some(L1_TABLE_OFFSET_MASK), Some(L1_TABLE_OFFSET_MASK),
) )
.map_err(Error::ReadingHeader)?, .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?,
); );
let num_clusters = div_round_up_u64(header.size, cluster_size); let num_clusters = div_round_up_u64(header.size, cluster_size);
@@ -530,7 +552,7 @@ pub(crate) fn parse_qcow(
cluster_size, cluster_size,
refcount_bits, refcount_bits,
) )
.map_err(Error::ReadingRefCounts)?; .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingRefCounts(e)))?;
let l2_entries = cluster_size / size_of::<u64>() as u64; let l2_entries = cluster_size / size_of::<u64>() as u64;
@@ -539,23 +561,30 @@ pub(crate) fn parse_qcow(
header header
.l1_table_offset .l1_table_offset
.checked_add(l1_index * size_of::<u64>() as u64) .checked_add(l1_index * size_of::<u64>() as u64)
.ok_or(Error::InvalidL1TableOffset)?; .ok_or_else(|| {
BlockError::new(BlockErrorKind::CorruptImage, Error::InvalidL1TableOffset)
})?;
header header
.refcount_table_offset .refcount_table_offset
.checked_add(u64::from(header.refcount_table_clusters) * cluster_size) .checked_add(u64::from(header.refcount_table_clusters) * cluster_size)
.ok_or(Error::InvalidRefcountTableOffset)?; .ok_or_else(|| {
BlockError::new(
BlockErrorKind::CorruptImage,
Error::InvalidRefcountTableOffset,
)
})?;
// Find available (refcount == 0) clusters for the free list. // Find available (refcount == 0) clusters for the free list.
let file_size = raw_file let file_size = raw_file
.file_mut() .file_mut()
.metadata() .metadata()
.map_err(Error::GettingFileSize)? .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))?
.len(); .len();
let mut avail_clusters = Vec::new(); let mut avail_clusters = Vec::new();
for i in (0..file_size).step_by(cluster_size as usize) { for i in (0..file_size).step_by(cluster_size as usize) {
let refcount = refcounts let refcount = refcounts
.get_cluster_refcount(&mut raw_file, i) .get_cluster_refcount(&mut raw_file, i)
.map_err(Error::GettingRefcount)?; .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?;
if refcount == 0 { if refcount == 0 {
avail_clusters.push(i); avail_clusters.push(i);
} }
@@ -567,10 +596,17 @@ pub(crate) fn parse_qcow(
{ {
header header
.set_dirty_bit(raw_file.file_mut(), true) .set_dirty_bit(raw_file.file_mut(), true)
.map_err(|e| Error::WritingHeader(io::Error::other(e)))?; .map_err(|e| {
BlockError::new(
BlockErrorKind::Io,
Error::WritingHeader(io::Error::other(e)),
)
})?;
} }
header.clear_autoclear_features(raw_file.file_mut())?; header
.clear_autoclear_features(raw_file.file_mut())
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
} }
let inner = metadata::QcowState { let inner = metadata::QcowState {