virtio: Enable qcow support for virtio-block

With this enabled, one can pass a QCOW format disk
image with '--disk' switch.

Signed-off-by: Chao Peng <chao.p.peng@linux.intel.com>
This commit is contained in:
Chao Peng 2019-05-10 07:27:56 +00:00 committed by Rob Bradford
parent 919226f31e
commit 6ecdd98634
6 changed files with 109 additions and 29 deletions

View File

@ -315,7 +315,7 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct QcowFile { pub struct QcowFile {
raw_file: QcowRawFile, raw_file: QcowRawFile,
header: QcowHeader, header: QcowHeader,

View File

@ -134,3 +134,13 @@ impl QcowRawFile {
address & self.cluster_mask address & self.cluster_mask
} }
} }
impl Clone for QcowRawFile {
fn clone(&self) -> Self {
QcowRawFile {
file: self.file.try_clone().expect("QcowRawFile cloning failed"),
cluster_size: self.cluster_size,
cluster_mask: self.cluster_mask,
}
}
}

View File

@ -48,7 +48,7 @@ impl Display for Error {
} }
/// Represents the refcount entries for an open qcow file. /// Represents the refcount entries for an open qcow file.
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct RefCount { pub struct RefCount {
ref_table: VecCache<u64>, ref_table: VecCache<u64>,
refcount_table_offset: u64, refcount_table_offset: u64,

View File

@ -15,7 +15,7 @@ pub trait Cacheable {
fn dirty(&self) -> bool; fn dirty(&self) -> bool;
} }
#[derive(Debug)] #[derive(Clone, Debug)]
/// Represents a vector that implements the `Cacheable` trait so it can be held in a cache. /// Represents a vector that implements the `Cacheable` trait so it can be held in a cache.
pub struct VecCache<T: 'static + Copy + Default> { pub struct VecCache<T: 'static + Copy + Default> {
vec: Box<[T]>, vec: Box<[T]>,
@ -83,7 +83,7 @@ impl<T: 'static + Copy + Default> IndexMut<usize> for VecCache<T> {
} }
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct CacheMap<T: Cacheable> { pub struct CacheMap<T: Cacheable> {
capacity: usize, capacity: usize,
map: HashMap<usize, T>, map: HashMap<usize, T>,

93
vm-virtio/src/block.rs Normal file → Executable file
View File

@ -15,6 +15,7 @@ use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
use std::os::linux::fs::MetadataExt; use std::os::linux::fs::MetadataExt;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::result; use std::result;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -86,6 +87,49 @@ impl ExecuteError {
} }
} }
pub trait DiskFile: Read + Seek + Write + Clone {}
impl<D: Read + Seek + Write + Clone> DiskFile for D {}
pub struct RawFile {
file: File,
}
impl RawFile {
pub fn new(file: File) -> Self {
RawFile { file }
}
}
impl Read for RawFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.file.read(buf)
}
}
impl Seek for RawFile {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.file.seek(pos)
}
}
impl Write for RawFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.flush()
}
}
impl Clone for RawFile {
fn clone(&self) -> Self {
RawFile {
file: self.file.try_clone().expect("RawFile cloning failed"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
enum RequestType { enum RequestType {
In, In,
@ -119,8 +163,8 @@ fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64,
mem.read_obj(addr).map_err(Error::GuestMemory) mem.read_obj(addr).map_err(Error::GuestMemory)
} }
fn build_device_id(disk_image: &File) -> result::Result<String, Error> { fn build_device_id(disk_path: &PathBuf) -> result::Result<String, Error> {
let blk_metadata = match disk_image.metadata() { let blk_metadata = match disk_path.metadata() {
Err(_) => return Err(Error::GetFileMetadata), Err(_) => return Err(Error::GetFileMetadata),
Ok(m) => m, Ok(m) => m,
}; };
@ -135,9 +179,9 @@ fn build_device_id(disk_image: &File) -> result::Result<String, Error> {
Ok(device_id) Ok(device_id)
} }
fn build_disk_image_id(disk_image: &File) -> Vec<u8> { fn build_disk_image_id(disk_path: &PathBuf) -> Vec<u8> {
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize]; let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
match build_device_id(disk_image) { match build_device_id(disk_path) {
Err(_) => { Err(_) => {
warn!("Could not generate device id. We'll use a default."); warn!("Could not generate device id. We'll use a default.");
} }
@ -275,17 +319,17 @@ impl Request {
} }
} }
struct BlockEpollHandler { struct BlockEpollHandler<T: DiskFile> {
queues: Vec<Queue>, queues: Vec<Queue>,
mem: GuestMemoryMmap, mem: GuestMemoryMmap,
disk_image: File, disk_image: T,
disk_nsectors: u64, disk_nsectors: u64,
interrupt_status: Arc<AtomicUsize>, interrupt_status: Arc<AtomicUsize>,
interrupt_evt: EventFd, interrupt_evt: EventFd,
disk_image_id: Vec<u8>, disk_image_id: Vec<u8>,
} }
impl BlockEpollHandler { impl<T: DiskFile> BlockEpollHandler<T> {
fn process_queue(&mut self, queue_index: usize) -> bool { fn process_queue(&mut self, queue_index: usize) -> bool {
let queue = &mut self.queues[queue_index]; let queue = &mut self.queues[queue_index];
@ -340,14 +384,18 @@ impl BlockEpollHandler {
} }
#[allow(dead_code)] #[allow(dead_code)]
fn update_disk_image(&mut self, disk_image: File) -> result::Result<(), DeviceError> { fn update_disk_image(
&mut self,
disk_image: T,
disk_path: &PathBuf,
) -> result::Result<(), DeviceError> {
self.disk_image = disk_image; self.disk_image = disk_image;
self.disk_nsectors = self self.disk_nsectors = self
.disk_image .disk_image
.seek(SeekFrom::End(0)) .seek(SeekFrom::End(0))
.map_err(DeviceError::IoError)? .map_err(DeviceError::IoError)?
/ SECTOR_SIZE; / SECTOR_SIZE;
self.disk_image_id = build_disk_image_id(&self.disk_image); self.disk_image_id = build_disk_image_id(disk_path);
Ok(()) Ok(())
} }
@ -409,9 +457,10 @@ impl BlockEpollHandler {
} }
/// Virtio device for exposing block level read/write operations on a host file. /// Virtio device for exposing block level read/write operations on a host file.
pub struct Block { pub struct Block<T: DiskFile> {
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
disk_image: Option<File>, disk_image: Option<T>,
disk_path: PathBuf,
disk_nsectors: u64, disk_nsectors: u64,
avail_features: u64, avail_features: u64,
acked_features: u64, acked_features: u64,
@ -432,11 +481,15 @@ pub fn build_config_space(disk_size: u64) -> Vec<u8> {
config config
} }
impl Block { impl<T: DiskFile> Block<T> {
/// Create a new virtio block device that operates on the given file. /// Create a new virtio block device that operates on the given file.
/// ///
/// The given file must be seekable and sizable. /// The given file must be seekable and sizable.
pub fn new(mut disk_image: File, is_disk_read_only: bool) -> io::Result<Block> { pub fn new(
mut disk_image: T,
disk_path: PathBuf,
is_disk_read_only: bool,
) -> io::Result<Block<T>> {
let disk_size = disk_image.seek(SeekFrom::End(0))? as u64; let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
if disk_size % SECTOR_SIZE != 0 { if disk_size % SECTOR_SIZE != 0 {
warn!( warn!(
@ -455,6 +508,7 @@ impl Block {
Ok(Block { Ok(Block {
kill_evt: None, kill_evt: None,
disk_image: Some(disk_image), disk_image: Some(disk_image),
disk_path,
disk_nsectors: disk_size / SECTOR_SIZE, disk_nsectors: disk_size / SECTOR_SIZE,
avail_features, avail_features,
acked_features: 0u64, acked_features: 0u64,
@ -465,7 +519,7 @@ impl Block {
} }
} }
impl Drop for Block { impl<T: DiskFile> Drop for Block<T> {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() { if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it. // Ignore the result because there is nothing we can do about it.
@ -474,7 +528,7 @@ impl Drop for Block {
} }
} }
impl VirtioDevice for Block { impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
fn device_type(&self) -> u32 { fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_BLOCK as u32 VirtioDeviceType::TYPE_BLOCK as u32
} }
@ -568,13 +622,8 @@ impl VirtioDevice for Block {
}; };
self.kill_evt = Some(self_kill_evt); self.kill_evt = Some(self_kill_evt);
if self.disk_image.is_some() { if let Some(disk_image) = self.disk_image.clone() {
let disk_image = self.disk_image.as_ref().unwrap().try_clone().map_err(|e| { let disk_image_id = build_disk_image_id(&self.disk_path);
error!("failed to clone disk image: {}", e);
ActivateError::BadActivate
})?;
let disk_image_id = build_disk_image_id(&disk_image);
// Save the interrupt EventFD as we need to return it on reset // Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread. // but clone it to pass into the thread.

View File

@ -28,6 +28,7 @@ use linux_loader::cmdline;
use linux_loader::loader::KernelLoader; use linux_loader::loader::KernelLoader;
use net_util::{MacAddr, Tap}; use net_util::{MacAddr, Tap};
use pci::{PciConfigIo, PciDevice, PciInterruptPin, PciRoot}; use pci::{PciConfigIo, PciDevice, PciInterruptPin, PciRoot};
use qcow::{self, ImageType, QcowFile};
use std::ffi::CString; use std::ffi::CString;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{self, stdout}; use std::io::{self, stdout};
@ -170,6 +171,12 @@ pub enum Error {
/// Cannot open tap interface /// Cannot open tap interface
OpenTap(net_util::TapError), OpenTap(net_util::TapError),
/// Failed parsing disk image format
DetectImageType(qcow::Error),
/// Cannot open qcow disk path
QcowDeviceCreate(qcow::Error),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@ -367,11 +374,25 @@ impl DeviceManager {
.map_err(Error::Disk)?; .map_err(Error::Disk)?;
// Add virtio-blk // Add virtio-blk
let virtio_block_device = let image_type = qcow::detect_image_type(&raw_img).map_err(Error::DetectImageType)?;
vm_virtio::Block::new(raw_img, false).map_err(Error::CreateVirtioBlock)?; let disk_path = vm_cfg.disk_path.to_path_buf();
let block = match image_type {
ImageType::Raw => {
let raw_img = vm_virtio::RawFile::new(raw_img);
let dev = vm_virtio::Block::new(raw_img, disk_path, false)
.map_err(Error::CreateVirtioBlock)?;
Box::new(dev) as Box<vm_virtio::VirtioDevice>
}
ImageType::Qcow2 => {
let qcow_img = QcowFile::from(raw_img).map_err(Error::QcowDeviceCreate)?;
let dev = vm_virtio::Block::new(qcow_img, disk_path, false)
.map_err(Error::CreateVirtioBlock)?;
Box::new(dev) as Box<vm_virtio::VirtioDevice>
}
};
DeviceManager::add_virtio_pci_device( DeviceManager::add_virtio_pci_device(
Box::new(virtio_block_device), block,
memory.clone(), memory.clone(),
allocator, allocator,
vm_fd, vm_fd,