From c4a5c7f843b000720e666cc9348fb3fd85277259 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 14 Mar 2026 09:46:03 +0100 Subject: [PATCH] block: qcow: Switch QcowFile constructors to BlockResult Switch the public QcowFile constructors (new, new_from_backing, new_from_header, from, from_backing, from_file_and_header) from qcow::Result to BlockResult. Internal calls to header functions that still return qcow::Result are wrapped with explicit error classification at each call site. Test assertions are updated to match on BlockErrorKind and use downcast to inspect the underlying qcow::Error variant. Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 81 +++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 0ed4cd858..8248c3f84 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -569,7 +569,7 @@ impl QcowFile { /// Creates a QcowFile from `file`. File must be a valid qcow2 image. /// /// Additionally, max nesting depth of this qcow2 image will be set to default value 10. - pub fn from(file: RawFile) -> Result { + pub fn from(file: RawFile) -> BlockResult { Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, true) } @@ -579,7 +579,7 @@ impl QcowFile { file: RawFile, max_nesting_depth: u32, sparse: bool, - ) -> Result { + ) -> BlockResult { let (inner, backing_file, sparse) = parse_qcow(file, max_nesting_depth, sparse)?; let metadata::QcowState { raw_file, @@ -607,8 +607,20 @@ impl QcowFile { } /// Creates a new QcowFile at the given path. - pub fn new(file: RawFile, version: u32, virtual_size: u64, sparse: bool) -> Result { - let header = QcowHeader::create_for_size_and_path(version, virtual_size, None)?; + pub fn new( + file: RawFile, + version: u32, + virtual_size: u64, + sparse: bool, + ) -> BlockResult { + let header = + QcowHeader::create_for_size_and_path(version, virtual_size, None).map_err(|e| { + let kind = match &e { + Error::BackingFileTooLong(_) => BlockErrorKind::InvalidFormat, + _ => BlockErrorKind::Io, + }; + BlockError::new(kind, e) + })?; QcowFile::new_from_header(file, &header, sparse) } @@ -619,12 +631,19 @@ impl QcowFile { backing_file_size: u64, backing_config: &BackingFileConfig, sparse: bool, - ) -> Result { + ) -> BlockResult { let mut header = QcowHeader::create_for_size_and_path( version, backing_file_size, Some(&backing_config.path), - )?; + ) + .map_err(|e| { + let kind = match &e { + Error::BackingFileTooLong(_) => BlockErrorKind::InvalidFormat, + _ => BlockErrorKind::Io, + }; + BlockError::new(kind, e) + })?; if let Some(backing_file) = &mut header.backing_file { backing_file.format = backing_config.format; } @@ -632,9 +651,16 @@ impl QcowFile { // backing_file is loaded by new_from_header -> Self::from() based on the header } - fn new_from_header(mut file: RawFile, header: &QcowHeader, sparse: bool) -> Result { - file.rewind().map_err(Error::SeekingFile)?; - header.write_to(&mut file)?; + fn new_from_header( + mut file: RawFile, + header: &QcowHeader, + sparse: bool, + ) -> BlockResult { + file.rewind() + .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; + header + .write_to(&mut file) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; let mut qcow = Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, sparse)?; @@ -646,9 +672,9 @@ impl QcowFile { let mut cluster_addr = 0; while cluster_addr < end_cluster_addr { - let mut unref_clusters = qcow - .set_cluster_refcount(cluster_addr, 1) - .map_err(Error::SettingRefcountRefcount)?; + let mut unref_clusters = qcow.set_cluster_refcount(cluster_addr, 1).map_err(|e| { + BlockError::new(BlockErrorKind::Io, Error::SettingRefcountRefcount(e)) + })?; qcow.unref_clusters.append(&mut unref_clusters); cluster_addr += cluster_size; } @@ -2180,6 +2206,7 @@ pub fn detect_image_type(file: &mut RawFile) -> Result { #[cfg(test)] mod unit_tests { + use std::error::Error as StdError; use std::fs::File; use std::path::Path; @@ -2531,7 +2558,11 @@ mod unit_tests { disk_file.rewind().unwrap(); // The maximum nesting depth is 0, which means backing file is not allowed. let res = QcowFile::from_with_nesting_depth(disk_file, 0, true); - assert!(matches!(res.unwrap_err(), Error::MaxNestingDepthExceeded)); + let err = res.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::Overflow)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); + assert!(matches!(qcow_err, Error::MaxNestingDepthExceeded)); } /// Create a qcow2 file with itself as its backing file. @@ -3839,7 +3870,7 @@ mod unit_tests { assert!(result.is_err()); let err = result.unwrap_err(); assert!( - matches!(err, Error::CorruptImage), + matches!(err.kind(), BlockErrorKind::CorruptImage), "Expected CorruptImage error, got: {err:?}" ); }); @@ -3853,8 +3884,11 @@ mod unit_tests { let result = QcowFile::from(disk_file); assert!(result.is_err()); let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("external")), + matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("external")), "Expected UnsupportedFeature error mentioning external, got: {err:?}" ); }); @@ -3868,8 +3902,11 @@ mod unit_tests { let result = QcowFile::from(disk_file); assert!(result.is_err()); let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("extended")), + matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("extended")), "Expected UnsupportedFeature error mentioning extended, got: {err:?}" ); }); @@ -3882,7 +3919,10 @@ mod unit_tests { with_basic_file(&header, |disk_file: RawFile| { let result = QcowFile::from(disk_file); assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), Error::UnsupportedFeature(_))); + assert!(matches!( + result.unwrap_err().kind(), + BlockErrorKind::UnsupportedFeature + )); }); } @@ -3894,8 +3934,11 @@ mod unit_tests { let result = QcowFile::from(disk_file); assert!(result.is_err()); let err = result.unwrap_err(); + assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature)); + let source = StdError::source(&err).unwrap(); + let qcow_err = source.downcast_ref::().unwrap(); assert!( - matches!(err, Error::UnsupportedFeature(ref v) if v.to_string().contains("unknown")), + matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("unknown")), "Expected UnsupportedFeature error mentioning unknown, got: {err:?}" ); }); @@ -4095,7 +4138,7 @@ mod unit_tests { assert!(result.is_err()); let err = result.unwrap_err(); assert!( - matches!(err, Error::CorruptImage), + matches!(err.kind(), BlockErrorKind::CorruptImage), "Expected CorruptImage error, got: {err:?}" ); });