mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: qcow: Switch parse_qcow and BackingFile::new to BlockResult
Switch parse_qcow and BackingFile::new from qcow::Result to BlockResult. Every early return site now produces an explicit BlockError with the appropriate kind. Remaining internal calls to functions still on qcow::Result rely on the From scaffolding and will be converted in subsequent commits. Two helpers are added to BlockError. with_kind replaces the classification on an existing error, used in QcowDiskSync::new to avoid double wrapping when the caller needs a different kind. into_source consumes the error and returns the boxed source, used at the recursive BackingFile open to extract the qcow::Error for BackingFileOpen without letting qcow::Error hold a BlockError. The qcow_sync boundary is simplified to a single closure that operates on the BlockError already returned by parse_qcow. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
c4a5c7f843
commit
be9ef116aa
@@ -167,6 +167,12 @@ impl BlockError {
|
||||
self
|
||||
}
|
||||
|
||||
/// Replace the error classification (builder-style).
|
||||
pub fn with_kind(mut self, kind: BlockErrorKind) -> Self {
|
||||
self.kind = kind;
|
||||
self
|
||||
}
|
||||
|
||||
/// Shorthand: attach an operation name.
|
||||
pub fn with_op(mut self, op: ErrorOp) -> Self {
|
||||
self.ctx.get_or_insert_with(ErrorContext::default).op = Some(op);
|
||||
@@ -204,6 +210,11 @@ impl BlockError {
|
||||
pub fn downcast_ref<T: StdError + 'static>(&self) -> Option<&T> {
|
||||
self.source.as_ref()?.downcast_ref::<T>()
|
||||
}
|
||||
|
||||
/// Consume the error and return the boxed source, if any.
|
||||
pub fn into_source(self) -> Option<Box<dyn StdError + Send + Sync + 'static>> {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BlockError {
|
||||
|
||||
@@ -189,14 +189,17 @@ impl BackingFile {
|
||||
direct_io: bool,
|
||||
max_nesting_depth: u32,
|
||||
sparse: bool,
|
||||
) -> Result<Option<Self>> {
|
||||
) -> BlockResult<Option<Self>> {
|
||||
let Some(config) = backing_file_config else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Check nesting depth - applies to any backing file
|
||||
if max_nesting_depth == 0 {
|
||||
return Err(Error::MaxNestingDepthExceeded);
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::Overflow,
|
||||
Error::MaxNestingDepthExceeded,
|
||||
));
|
||||
}
|
||||
|
||||
let backing_raw_file = OpenOptions::new()
|
||||
@@ -224,8 +227,17 @@ impl BackingFile {
|
||||
}
|
||||
ImageType::Qcow2 => {
|
||||
let (inner, nested_backing, _sparse) =
|
||||
parse_qcow(raw_file, max_nesting_depth - 1, sparse)
|
||||
.map_err(|e| Error::BackingFileOpen(config.path.clone(), Box::new(e)))?;
|
||||
parse_qcow(raw_file, max_nesting_depth - 1, sparse).map_err(|e| {
|
||||
let kind = e.kind();
|
||||
let source = e
|
||||
.into_source()
|
||||
.and_then(|s| s.downcast::<Error>().ok())
|
||||
.map(|qcow_err| Error::BackingFileOpen(config.path.clone(), qcow_err));
|
||||
match source {
|
||||
Some(err) => BlockError::new(kind, err),
|
||||
None => BlockError::from_kind(kind),
|
||||
}
|
||||
})?;
|
||||
let size = inner.header.size;
|
||||
(
|
||||
BackingKind::Qcow {
|
||||
@@ -335,28 +347,51 @@ pub(crate) fn parse_qcow(
|
||||
mut file: RawFile,
|
||||
max_nesting_depth: u32,
|
||||
sparse: bool,
|
||||
) -> Result<(metadata::QcowState, Option<BackingFile>, bool)> {
|
||||
let mut header = QcowHeader::new(&mut file)?;
|
||||
) -> BlockResult<(metadata::QcowState, Option<BackingFile>, bool)> {
|
||||
let mut header = QcowHeader::new(&mut file).map_err(|e| {
|
||||
let kind = match &e {
|
||||
Error::InvalidMagic
|
||||
| Error::BackingFileTooLong(_)
|
||||
| Error::InvalidBackingFileName(_) => BlockErrorKind::InvalidFormat,
|
||||
Error::UnsupportedFeature(_) | Error::UnsupportedCompressionType => {
|
||||
BlockErrorKind::UnsupportedFeature
|
||||
}
|
||||
_ => BlockErrorKind::Io,
|
||||
};
|
||||
BlockError::new(kind, e)
|
||||
})?;
|
||||
|
||||
// Only v2 and v3 files are supported.
|
||||
if header.version != 2 && header.version != 3 {
|
||||
return Err(Error::UnsupportedVersion(header.version));
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::UnsupportedFeature,
|
||||
Error::UnsupportedVersion(header.version),
|
||||
));
|
||||
}
|
||||
|
||||
// Make sure that the L1 table fits in RAM.
|
||||
if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::InvalidL1TableSize(header.l1_size));
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::InvalidFormat,
|
||||
Error::InvalidL1TableSize(header.l1_size),
|
||||
));
|
||||
}
|
||||
|
||||
let cluster_bits: u32 = header.cluster_bits;
|
||||
if !(MIN_CLUSTER_BITS..=MAX_CLUSTER_BITS).contains(&cluster_bits) {
|
||||
return Err(Error::InvalidClusterSize);
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::InvalidFormat,
|
||||
Error::InvalidClusterSize,
|
||||
));
|
||||
}
|
||||
let cluster_size = 0x01u64 << cluster_bits;
|
||||
|
||||
// Limit the total size of the disk.
|
||||
if header.size > MAX_QCOW_FILE_SIZE {
|
||||
return Err(Error::FileTooBig(header.size));
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::InvalidFormat,
|
||||
Error::FileTooBig(header.size),
|
||||
));
|
||||
}
|
||||
|
||||
let direct_io = file.is_direct();
|
||||
@@ -373,12 +408,18 @@ pub(crate) fn parse_qcow(
|
||||
.checked_shl(header.refcount_order)
|
||||
.ok_or(Error::UnsupportedRefcountOrder)?;
|
||||
if refcount_bits > 64 {
|
||||
return Err(Error::UnsupportedRefcountOrder);
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::UnsupportedFeature,
|
||||
Error::UnsupportedRefcountOrder,
|
||||
));
|
||||
}
|
||||
|
||||
// Need at least one refcount cluster
|
||||
if header.refcount_table_clusters == 0 {
|
||||
return Err(Error::NoRefcountClusters);
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::InvalidFormat,
|
||||
Error::NoRefcountClusters,
|
||||
));
|
||||
}
|
||||
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
|
||||
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
|
||||
@@ -386,7 +427,10 @@ pub(crate) fn parse_qcow(
|
||||
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
|
||||
let file_size = file.metadata().map_err(Error::GettingFileSize)?.len();
|
||||
if header.refcount_table_offset > max(file_size, header.size) {
|
||||
return Err(Error::RefcountTableOffEnd);
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::CorruptImage,
|
||||
Error::RefcountTableOffEnd,
|
||||
));
|
||||
}
|
||||
|
||||
// The first cluster should always have a non-zero refcount, so if it is 0,
|
||||
@@ -414,7 +458,10 @@ pub(crate) fn parse_qcow(
|
||||
|
||||
if header.is_corrupt() {
|
||||
if is_writable {
|
||||
return Err(Error::CorruptImage);
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::CorruptImage,
|
||||
Error::CorruptImage,
|
||||
));
|
||||
}
|
||||
let path = read_link(format!("/proc/self/fd/{}", raw_file.file().as_raw_fd()))
|
||||
.map_or_else(|_| "<unknown>".to_string(), |p| p.display().to_string());
|
||||
@@ -440,7 +487,10 @@ pub(crate) fn parse_qcow(
|
||||
let l1_clusters = div_round_up_u64(num_l2_clusters, entries_per_cluster);
|
||||
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
|
||||
if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::TooManyL1Entries(num_l2_clusters));
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::CorruptImage,
|
||||
Error::TooManyL1Entries(num_l2_clusters),
|
||||
));
|
||||
}
|
||||
let l1_table = VecCache::from_vec(
|
||||
raw_file
|
||||
@@ -460,10 +510,16 @@ pub(crate) fn parse_qcow(
|
||||
);
|
||||
// Check that the given header doesn't have a suspiciously sized refcount table.
|
||||
if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters {
|
||||
return Err(Error::RefcountTableTooLarge);
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::CorruptImage,
|
||||
Error::RefcountTableTooLarge,
|
||||
));
|
||||
}
|
||||
if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||
return Err(Error::TooManyRefcounts(refcount_clusters));
|
||||
return Err(BlockError::new(
|
||||
BlockErrorKind::InvalidFormat,
|
||||
Error::TooManyRefcounts(refcount_clusters),
|
||||
));
|
||||
}
|
||||
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
||||
let mut refcounts = RefCount::new(
|
||||
|
||||
@@ -197,25 +197,14 @@ impl QcowDiskSync {
|
||||
) -> BlockResult<Self> {
|
||||
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
|
||||
let (inner, backing_file, sparse) =
|
||||
parse_qcow(RawFile::new(file, direct_io), max_nesting_depth, sparse)
|
||||
.map_err(|e| match e {
|
||||
QcowError::MaxNestingDepthExceeded if !backing_files => {
|
||||
QcowError::BackingFilesDisabled
|
||||
}
|
||||
other => other,
|
||||
})
|
||||
.map_err(|e| {
|
||||
let kind = match &e {
|
||||
QcowError::InvalidMagic | QcowError::UnsupportedVersion(_) => {
|
||||
BlockErrorKind::InvalidFormat
|
||||
}
|
||||
QcowError::UnsupportedFeature(_) | QcowError::BackingFilesDisabled => {
|
||||
BlockErrorKind::UnsupportedFeature
|
||||
}
|
||||
_ => BlockErrorKind::Io,
|
||||
};
|
||||
BlockError::new(kind, e).with_op(ErrorOp::Open)
|
||||
})?;
|
||||
parse_qcow(RawFile::new(file, direct_io), max_nesting_depth, sparse).map_err(|e| {
|
||||
let e = if !backing_files && matches!(e.kind(), BlockErrorKind::Overflow) {
|
||||
e.with_kind(BlockErrorKind::UnsupportedFeature)
|
||||
} else {
|
||||
e
|
||||
};
|
||||
e.with_op(ErrorOp::Open)
|
||||
})?;
|
||||
let data_raw_file = inner.raw_file.clone();
|
||||
Ok(QcowDiskSync {
|
||||
metadata: Arc::new(QcowMetadata::new(inner)),
|
||||
|
||||
Reference in New Issue
Block a user