diff --git a/block/src/factory.rs b/block/src/factory.rs index d029952b7..03eeceb22 100644 --- a/block/src/factory.rs +++ b/block/src/factory.rs @@ -24,6 +24,7 @@ use crate::formats::qcow::QcowDisk; use crate::formats::raw::{RawBackend, RawDisk}; use crate::formats::vhd::VhdDisk; use crate::formats::vhdx::VhdxDisk; +use crate::formats::vmdk::VmdkDisk; use crate::{ ImageType, block_aio_is_supported, detect_image_type, open_disk_image, preallocate_disk, }; @@ -98,6 +99,7 @@ pub fn open_disk(options: &DiskOpenOptions<'_>) -> BlockResult { ImageType::Raw => open_raw(file, options)?, ImageType::Qcow2 => open_qcow2(file, options)?, ImageType::Vhdx => open_vhdx(file, options)?, + ImageType::FlatVmdk => open_flat_vmdk(file, options)?, ImageType::Unknown => { return Err( BlockError::from_kind(BlockErrorKind::UnsupportedFeature).with_path(options.path) @@ -215,6 +217,16 @@ fn open_qcow2( )) } +fn open_flat_vmdk( + file: fs::File, + options: &DiskOpenOptions<'_>, +) -> BlockResult> { + info!("Opening VMDK disk file with synchronous backend"); + Ok(Box::new( + VmdkDisk::new(file, options.path, options.direct).map_err(|e| e.with_path(options.path))?, + )) +} + #[cfg(test)] mod unit_tests { use std::path::Path; diff --git a/block/src/formats/vhd/footer.rs b/block/src/formats/vhd/footer.rs index cd033a1fc..42539faa2 100644 --- a/block/src/formats/vhd/footer.rs +++ b/block/src/formats/vhd/footer.rs @@ -119,7 +119,11 @@ impl VhdFooter { /// Determine image type through file parsing. pub fn is_fixed_vhd(f: &mut File) -> io::Result { - let footer = VhdFooter::new(f)?; + let footer = match VhdFooter::new(f) { + Ok(footer) => footer, + Err(e) if e.kind() == io::ErrorKind::InvalidInput => return Ok(false), + Err(e) => return Err(e), + }; // "conectix" => 0x636f6e6563746978 Ok(footer.cookie() == 0x636f6e6563746978 @@ -229,4 +233,11 @@ mod unit_tests { assert!(!(is_fixed_vhd(&mut file).unwrap())); }); } + + #[test] + fn test_is_fixed_vhd_short_file_is_not_vhd() { + let mut file: File = TempFile::new().unwrap().into_file(); + file.write_all(b"# Disk DescriptorFile\n").unwrap(); + assert!(!is_fixed_vhd(&mut file).unwrap()); + } } diff --git a/block/src/lib.rs b/block/src/lib.rs index 6940fe311..e3cd071e7 100644 --- a/block/src/lib.rs +++ b/block/src/lib.rs @@ -484,6 +484,7 @@ pub fn preallocate_disk>(file: &File, path: P) { #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum ImageType { + FlatVmdk, FixedVhd, Qcow2, Raw, @@ -495,6 +496,7 @@ pub enum ImageType { impl fmt::Display for ImageType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + ImageType::FlatVmdk => write!(f, "vmdk"), ImageType::FixedVhd => write!(f, "vhd"), ImageType::Qcow2 => write!(f, "qcow2"), ImageType::Raw => write!(f, "raw"), @@ -513,6 +515,7 @@ impl FromStr for ImageType { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { + "vmdk" => Ok(ImageType::FlatVmdk), "vhd" => Ok(ImageType::FixedVhd), "qcow2" => Ok(ImageType::Qcow2), "raw" => Ok(ImageType::Raw), @@ -538,10 +541,23 @@ pub fn open_disk_image(path: &Path, options: &OpenOptions) -> BlockResult /// Determine image type through file parsing. pub fn detect_image_type(f: &mut File) -> BlockResult { let aligned = AlignedFile::new(f.try_clone()?, true); + // A VMDK descriptor file is small few hundred bytes of text. Read + // best-effort so a short file yields a zero-padded partial block instead + // of an UnexpectedEof, and let the per-format probes below decide. let mut block = vec![0u8; aligned.alignment()]; - aligned - .read_exact_at(&mut block, 0) - .map_err(|e| BlockError::new(BlockErrorKind::Io, e).with_op(ErrorOp::DetectImageType))?; + let mut filled = 0; + while filled < block.len() { + match aligned.read_at(&mut block[filled..], filled as u64) { + Ok(0) => break, + Ok(n) => filled += n, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => { + return Err( + BlockError::new(BlockErrorKind::Io, e).with_op(ErrorOp::DetectImageType) + ); + } + } + } // Check 4 first bytes to get the header value and determine the image type let image_type = if u32::from_be_bytes(block[0..4].try_into().unwrap()) == QCOW_MAGIC { @@ -552,6 +568,11 @@ pub fn detect_image_type(f: &mut File) -> BlockResult { ImageType::FixedVhd } else if u64::from_le_bytes(block[0..8].try_into().unwrap()) == VHDX_SIGN { ImageType::Vhdx + } else if formats::vmdk::has_descriptor_header(&block) + && formats::vmdk::is_flat_vmdk(f) + .map_err(|e| BlockError::new(BlockErrorKind::Io, e).with_op(ErrorOp::DetectImageType))? + { + ImageType::FlatVmdk } else { ImageType::Raw }; @@ -682,6 +703,18 @@ mod unit_tests { use super::*; + #[test] + fn detect_short_file_is_not_eof_error() { + let tmp = TempFile::new().unwrap(); + let mut f = tmp.into_file(); + f.write_all(b"not-a-disk-magic-just-some-short-text\n") + .unwrap(); + f.sync_all().unwrap(); + + let image_type = detect_image_type(&mut f).unwrap(); + assert_eq!(image_type, ImageType::Raw); + } + #[test] fn test_probe_regular_file_returns_valid_alignment() { let temp_file = TempFile::new().unwrap(); diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 0826fa7ef..5ce0db03d 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1597,6 +1597,7 @@ mod common_parallel { ImageType::Qcow2 => ("qcow2", &[]), ImageType::FixedVhd => ("vpc", &["-o", "subformat=fixed"]), ImageType::Vhdx => ("vhdx", &[]), + ImageType::FlatVmdk => panic!("unsupported image_type {image_type}"), ImageType::Unknown => panic!("unsupported image_type {image_type}"), }; let image_type_str = image_type.to_string();