diff --git a/Cargo.lock b/Cargo.lock index 4f6becbfe..5715bd7fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "net_gen 0.1.0", "net_util 0.1.0", + "qcow 0.1.0", "ssh2 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "vhost_rs 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index 83d522d13..782f057ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ libc = "0.2.65" log = { version = "0.4.8", features = ["std"] } net_gen = { path = "net_gen" } net_util = { path = "net_util" } +qcow = { path = "qcow" } vhost_user_backend = { path = "vhost_user_backend"} virtio-bindings = "0.1.0" vmm = { path = "vmm" } diff --git a/src/bin/vhost_user_blk.rs b/src/bin/vhost_user_blk.rs new file mode 100644 index 000000000..96fa13005 --- /dev/null +++ b/src/bin/vhost_user_blk.rs @@ -0,0 +1,300 @@ +// Copyright 2019 Red Hat, Inc. All Rights Reserved. +// +// Portions Copyright 2019 Intel Corporation. All Rights Reserved. +// +// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Portions Copyright 2017 The Chromium OS Authors. All rights reserved. +// +// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause) + +#[macro_use(crate_version, crate_authors)] +extern crate clap; +extern crate log; +extern crate vhost_rs; +extern crate vhost_user_backend; +extern crate vm_virtio; + +use clap::{App, Arg}; +use epoll; +use log::*; +use std::fs::File; +use std::fs::OpenOptions; +use std::io::Read; +use std::io::{Seek, SeekFrom, Write}; +use std::mem; +use std::path::PathBuf; +use std::process; +use std::slice; +use std::sync::{Arc, RwLock}; +use std::vec::Vec; + +use qcow::{self, ImageType, QcowFile}; + +use vhost_rs::vhost_user::message::*; +use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker}; + +use virtio_bindings::bindings::virtio_blk::*; +use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap}; +use vm_virtio::block::{build_disk_image_id, Request}; + +const QUEUE_SIZE: usize = 1024; +const NUM_QUEUES: usize = 1; +const SECTOR_SHIFT: u8 = 9; +const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT; +const BLK_SIZE: u32 = 512; + +trait DiskFile: Read + Seek + Write + Send + Sync {} +impl DiskFile for D {} + +pub type Result = std::result::Result; +pub type VhostUserBackendResult = std::result::Result; + +#[derive(Debug)] +pub enum Error { + /// Failed to detect image type. + DetectImageType, + /// Bad memory address. + GuestMemory(GuestMemoryError), + /// Can't open image file. + OpenImage, + /// Failed to parse image parameter. + ParseImageParam, + /// Failed to parse sock parameter. + ParseSockParam, +} + +struct VhostUserBlkBackend { + mem: Option, + vring_worker: Option>, + disk_image: Box, + disk_image_id: Vec, + disk_nsectors: u64, + config: virtio_blk_config, +} + +impl VhostUserBlkBackend { + pub fn new(image_path: String) -> Result { + let raw_img: File = OpenOptions::new() + .read(true) + .write(true) + .open(&image_path) + .unwrap(); + + let image_id = build_disk_image_id(&PathBuf::from(&image_path)); + let image_type = qcow::detect_image_type(&raw_img).unwrap(); + let mut image = match image_type { + ImageType::Raw => Box::new(vm_virtio::RawFile::new(raw_img)) as Box, + ImageType::Qcow2 => Box::new(QcowFile::from(raw_img).unwrap()) as Box, + }; + + let nsectors = (image.seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE; + let mut config = virtio_blk_config::default(); + + config.capacity = nsectors; + config.blk_size = BLK_SIZE; + config.size_max = 65535; + config.seg_max = 128 - 2; + config.min_io_size = 1; + config.opt_io_size = 1; + config.num_queues = 1; + + Ok(VhostUserBlkBackend { + mem: None, + vring_worker: None, + disk_image: image, + disk_image_id: image_id, + disk_nsectors: nsectors, + config, + }) + } + + pub fn process_queue(&mut self, vring: &mut Vring) -> bool { + let mut used_any = false; + let mem = match self.mem.as_ref() { + Some(m) => m, + None => return false, + }; + + while let Some(head) = vring.mut_queue().iter(mem).next() { + debug!("got an element in the queue"); + let len; + match Request::parse(&head, mem) { + Ok(request) => { + debug!("element is a valid request"); + let status = match request.execute( + &mut self.disk_image, + self.disk_nsectors, + mem, + &self.disk_image_id, + ) { + Ok(l) => { + len = l; + VIRTIO_BLK_S_OK + } + Err(e) => { + len = 1; + e.status() + } + }; + mem.write_obj(status, request.status_addr).unwrap(); + } + Err(err) => { + error!("failed to parse available descriptor chain: {:?}", err); + len = 0; + } + } + vring.mut_queue().add_used(mem, head.index, len); + used_any = true; + } + + used_any + } +} + +impl VhostUserBackend for VhostUserBlkBackend { + fn num_queues(&self) -> usize { + NUM_QUEUES + } + + fn max_queue_size(&self) -> usize { + QUEUE_SIZE + } + + fn features(&self) -> u64 { + 1 << VIRTIO_BLK_F_MQ + | 1 << VIRTIO_F_VERSION_1 + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + VhostUserProtocolFeatures::CONFIG + } + + fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> { + self.mem = Some(mem); + Ok(()) + } + + fn handle_event( + &mut self, + device_event: u16, + evset: epoll::Events, + vrings: &[Arc>], + ) -> VhostUserBackendResult { + if evset != epoll::Events::EPOLLIN { + warn!("invalid events operation"); + return Ok(false); + } + + debug!("event received: {:?}", device_event); + + let mut vring = vrings[0].write().unwrap(); + if self.process_queue(&mut vring) { + debug!("signalling queue"); + vring.signal_used_queue().unwrap(); + } + + Ok(false) + } + + fn get_config(&self, _offset: u32, _size: u32) -> Vec { + // self.config is a statically allocated virtio_blk_config + let buf = unsafe { + slice::from_raw_parts( + &self.config as *const virtio_blk_config as *const _, + mem::size_of::(), + ) + }; + + buf.to_vec() + } +} + +pub struct VhostUserBlkBackendConfig<'a> { + pub image: &'a str, + pub sock: &'a str, +} + +impl<'a> VhostUserBlkBackendConfig<'a> { + pub fn parse(backend: &'a str) -> Result { + let params_list: Vec<&str> = backend.split(',').collect(); + + let mut image: &str = ""; + let mut sock: &str = ""; + + for param in params_list.iter() { + if param.starts_with("image=") { + image = ¶m[6..]; + } else if param.starts_with("sock=") { + sock = ¶m[5..]; + } + } + + if image.is_empty() { + return Err(Error::ParseImageParam); + } + if sock.is_empty() { + return Err(Error::ParseSockParam); + } + + Ok(VhostUserBlkBackendConfig { image, sock }) + } +} + +fn main() { + let cmd_arguments = App::new("vhost-user-blk backend") + .version(crate_version!()) + .author(crate_authors!()) + .about("Launch a vhost-user-blk backend.") + .arg( + Arg::with_name("backend") + .long("backend") + .help( + "Backend parameters \"image=,\ + sock=\"", + ) + .takes_value(true) + .min_values(1), + ) + .get_matches(); + + let vhost_user_blk_backend = cmd_arguments.value_of("backend").unwrap(); + + let backend_config = match VhostUserBlkBackendConfig::parse(vhost_user_blk_backend) { + Ok(config) => config, + Err(e) => { + println!("Failed parsing parameters {:?}", e); + process::exit(1); + } + }; + + let blk_backend = Arc::new(RwLock::new( + VhostUserBlkBackend::new(backend_config.image.to_string()).unwrap(), + )); + + debug!("blk_backend is created!\n"); + + let name = "vhost-user-blk-backend"; + let mut blk_daemon = VhostUserDaemon::new( + name.to_string(), + backend_config.sock.to_string(), + blk_backend.clone(), + ) + .unwrap(); + debug!("blk_daemon is created!\n"); + + let vring_worker = blk_daemon.get_vring_worker(); + + blk_backend.write().unwrap().vring_worker = Some(vring_worker); + + if let Err(e) = blk_daemon.start() { + println!( + "failed to start daemon for vhost-user-blk with error: {:?}\n", + e + ); + process::exit(1); + } + + blk_daemon.wait().unwrap(); +}