From 9c867edc35632b8d8aa5eb0cbb60f04a0c8c7943 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 14 Jul 2020 10:43:33 +0100 Subject: [PATCH] vhost_user_block, virtio-devices, block_util: Extract common block code Extract the code that is used by vhost_user_block from the virtio-devices crate to remove the dependencies on unrequired functionality such as the virtio transports. Signed-off-by: Rob Bradford --- Cargo.lock | 16 +- Cargo.toml | 3 +- block_util/Cargo.toml | 14 + block_util/src/lib.rs | 376 +++++++++++++++++++++++++++ vhost_user_block/Cargo.toml | 2 +- vhost_user_block/src/lib.rs | 5 +- virtio-devices/Cargo.toml | 1 + virtio-devices/src/block.rs | 340 +----------------------- virtio-devices/src/vhost_user/blk.rs | 2 +- 9 files changed, 417 insertions(+), 342 deletions(-) create mode 100644 block_util/Cargo.toml create mode 100644 block_util/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1660e9221..0337123e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,19 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block_util" +version = "0.1.0" +dependencies = [ + "log 0.4.8", + "serde", + "serde_derive", + "serde_json", + "virtio-bindings", + "vm-memory", + "vm-virtio", +] + [[package]] name = "byteorder" version = "1.3.4" @@ -1437,6 +1450,7 @@ dependencies = [ name = "vhost_user_block" version = "0.1.0" dependencies = [ + "block_util", "clap", "epoll", "libc", @@ -1446,7 +1460,6 @@ dependencies = [ "vhost", "vhost_user_backend", "virtio-bindings", - "virtio-devices", "vm-memory", "vmm-sys-util", ] @@ -1501,6 +1514,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arc-swap", + "block_util", "byteorder", "devices", "epoll", diff --git a/Cargo.toml b/Cargo.toml index 7c2a05911..e9c84d9ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,5 +66,6 @@ members = [ "arch_gen", "net_gen", "vm-allocator", - "option_parser" + "option_parser", + "block_util" ] diff --git a/block_util/Cargo.toml b/block_util/Cargo.toml new file mode 100644 index 000000000..ff5cb3919 --- /dev/null +++ b/block_util/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "block_util" +version = "0.1.0" +authors = ["The Cloud Hypervisor Authors"] +edition = "2018" + +[dependencies] +log = "0.4.8" +serde = ">=1.0.27" +serde_derive = ">=1.0.27" +serde_json = ">=1.0.9" +virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]} +vm-memory = { version = "0.2.1", features = ["backend-mmap", "backend-atomic"] } +vm-virtio = { path = "../vm-virtio" } \ No newline at end of file diff --git a/block_util/src/lib.rs b/block_util/src/lib.rs new file mode 100644 index 000000000..6cff63619 --- /dev/null +++ b/block_util/src/lib.rs @@ -0,0 +1,376 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Portions Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-BSD-3-Clause file. +// +// Copyright © 2020 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; + +use serde::ser::{Serialize, SerializeStruct, Serializer}; +use std::cmp; +use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::os::linux::fs::MetadataExt; +use std::path::PathBuf; +use std::result; +use virtio_bindings::bindings::virtio_blk::*; +use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap}; +use vm_virtio::DescriptorChain; + +const SECTOR_SHIFT: u8 = 9; +pub const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT; + +#[derive(Debug)] +pub enum Error { + /// Guest gave us bad memory addresses. + GuestMemory(GuestMemoryError), + /// Guest gave us offsets that would have overflowed a usize. + CheckedOffset(GuestAddress, usize), + /// Guest gave us a write only descriptor that protocol says to read from. + UnexpectedWriteOnlyDescriptor, + /// Guest gave us a read only descriptor that protocol says to write to. + UnexpectedReadOnlyDescriptor, + /// Guest gave us too few descriptors in a descriptor chain. + DescriptorChainTooShort, + /// Guest gave us a descriptor that was too short to use. + DescriptorLengthTooSmall, + /// Getting a block's metadata fails for any reason. + GetFileMetadata, + /// The requested operation would cause a seek beyond disk end. + InvalidOffset, +} + +fn build_device_id(disk_path: &PathBuf) -> result::Result { + let blk_metadata = match disk_path.metadata() { + Err(_) => return Err(Error::GetFileMetadata), + Ok(m) => m, + }; + // This is how kvmtool does it. + let device_id = format!( + "{}{}{}", + blk_metadata.st_dev(), + blk_metadata.st_rdev(), + blk_metadata.st_ino() + ); + Ok(device_id) +} + +pub fn build_disk_image_id(disk_path: &PathBuf) -> Vec { + let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize]; + match build_device_id(disk_path) { + Err(_) => { + warn!("Could not generate device id. We'll use a default."); + } + Ok(m) => { + // The kernel only knows to read a maximum of VIRTIO_BLK_ID_BYTES. + // This will also zero out any leftover bytes. + let disk_id = m.as_bytes(); + let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize); + default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy]) + } + } + default_disk_image_id +} + +#[derive(Debug)] +pub enum ExecuteError { + BadRequest(Error), + Flush(io::Error), + Read(GuestMemoryError), + Seek(io::Error), + Write(GuestMemoryError), + Unsupported(u32), +} + +impl ExecuteError { + pub fn status(&self) -> u32 { + match *self { + ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR, + ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RequestType { + In, + Out, + Flush, + GetDeviceID, + Unsupported(u32), +} + +pub fn request_type( + mem: &GuestMemoryMmap, + desc_addr: GuestAddress, +) -> result::Result { + let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?; + match type_ { + VIRTIO_BLK_T_IN => Ok(RequestType::In), + VIRTIO_BLK_T_OUT => Ok(RequestType::Out), + VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush), + VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceID), + t => Ok(RequestType::Unsupported(t)), + } +} + +fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result { + const SECTOR_OFFSET: usize = 8; + let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) { + Some(v) => v, + None => return Err(Error::CheckedOffset(desc_addr, SECTOR_OFFSET)), + }; + + mem.read_obj(addr).map_err(Error::GuestMemory) +} + +pub struct Request { + pub request_type: RequestType, + sector: u64, + data_addr: GuestAddress, + pub data_len: u32, + pub status_addr: GuestAddress, + writeback: bool, +} + +impl Request { + pub fn parse( + avail_desc: &DescriptorChain, + mem: &GuestMemoryMmap, + ) -> result::Result { + // The head contains the request type which MUST be readable. + if avail_desc.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } + + let mut req = Request { + request_type: request_type(&mem, avail_desc.addr)?, + sector: sector(&mem, avail_desc.addr)?, + data_addr: GuestAddress(0), + data_len: 0, + status_addr: GuestAddress(0), + writeback: true, + }; + + let data_desc; + let status_desc; + let desc = avail_desc + .next_descriptor() + .ok_or(Error::DescriptorChainTooShort)?; + + if !desc.has_next() { + status_desc = desc; + // Only flush requests are allowed to skip the data descriptor. + if req.request_type != RequestType::Flush { + return Err(Error::DescriptorChainTooShort); + } + } else { + data_desc = desc; + status_desc = data_desc + .next_descriptor() + .ok_or(Error::DescriptorChainTooShort)?; + + if data_desc.is_write_only() && req.request_type == RequestType::Out { + return Err(Error::UnexpectedWriteOnlyDescriptor); + } + if !data_desc.is_write_only() && req.request_type == RequestType::In { + return Err(Error::UnexpectedReadOnlyDescriptor); + } + if !data_desc.is_write_only() && req.request_type == RequestType::GetDeviceID { + return Err(Error::UnexpectedReadOnlyDescriptor); + } + + req.data_addr = data_desc.addr; + req.data_len = data_desc.len; + } + + // The status MUST always be writable. + if !status_desc.is_write_only() { + return Err(Error::UnexpectedReadOnlyDescriptor); + } + + if status_desc.len < 1 { + return Err(Error::DescriptorLengthTooSmall); + } + + req.status_addr = status_desc.addr; + + Ok(req) + } + + #[allow(clippy::ptr_arg)] + pub fn execute( + &self, + disk: &mut T, + disk_nsectors: u64, + mem: &GuestMemoryMmap, + disk_id: &Vec, + ) -> result::Result { + let mut top: u64 = u64::from(self.data_len) / SECTOR_SIZE; + if u64::from(self.data_len) % SECTOR_SIZE != 0 { + top += 1; + } + top = top + .checked_add(self.sector) + .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; + if top > disk_nsectors { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + + disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT)) + .map_err(ExecuteError::Seek)?; + + match self.request_type { + RequestType::In => { + mem.read_exact_from(self.data_addr, disk, self.data_len as usize) + .map_err(ExecuteError::Read)?; + return Ok(self.data_len); + } + RequestType::Out => { + mem.write_all_to(self.data_addr, disk, self.data_len as usize) + .map_err(ExecuteError::Write)?; + if !self.writeback { + disk.flush().map_err(ExecuteError::Flush)?; + } + } + RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?, + RequestType::GetDeviceID => { + if (self.data_len as usize) < disk_id.len() { + return Err(ExecuteError::BadRequest(Error::InvalidOffset)); + } + mem.write_slice(&disk_id.as_slice(), self.data_addr) + .map_err(ExecuteError::Write)?; + } + RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), + }; + Ok(0) + } + + pub fn set_writeback(&mut self, writeback: bool) { + self.writeback = writeback + } +} + +#[derive(Copy, Clone, Debug, Default, Deserialize)] +#[repr(C, packed)] +pub struct VirtioBlockConfig { + pub capacity: u64, + pub size_max: u32, + pub seg_max: u32, + pub geometry: VirtioBlockGeometry, + pub blk_size: u32, + pub physical_block_exp: u8, + pub alignment_offset: u8, + pub min_io_size: u16, + pub opt_io_size: u32, + pub writeback: u8, + pub unused: u8, + pub num_queues: u16, + pub max_discard_sectors: u32, + pub max_discard_seg: u32, + pub discard_sector_alignment: u32, + pub max_write_zeroes_sectors: u32, + pub max_write_zeroes_seg: u32, + pub write_zeroes_may_unmap: u8, + pub unused1: [u8; 3], +} + +// We must explicitly implement Serialize since the structure is packed and +// it's unsafe to borrow from a packed structure. And by default, if we derive +// Serialize from serde, it will borrow the values from the structure. +// That's why this implementation copies each field separately before it +// serializes the entire structure field by field. +impl Serialize for VirtioBlockConfig { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let capacity = self.capacity; + let size_max = self.size_max; + let seg_max = self.seg_max; + let geometry = self.geometry; + let blk_size = self.blk_size; + let physical_block_exp = self.physical_block_exp; + let alignment_offset = self.alignment_offset; + let min_io_size = self.min_io_size; + let opt_io_size = self.opt_io_size; + let writeback = self.writeback; + let unused = self.unused; + let num_queues = self.num_queues; + let max_discard_sectors = self.max_discard_sectors; + let max_discard_seg = self.max_discard_seg; + let discard_sector_alignment = self.discard_sector_alignment; + let max_write_zeroes_sectors = self.max_write_zeroes_sectors; + let max_write_zeroes_seg = self.max_write_zeroes_seg; + let write_zeroes_may_unmap = self.write_zeroes_may_unmap; + let unused1 = self.unused1; + + let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?; + virtio_block_config.serialize_field("capacity", &capacity)?; + virtio_block_config.serialize_field("size_max", &size_max)?; + virtio_block_config.serialize_field("seg_max", &seg_max)?; + virtio_block_config.serialize_field("geometry", &geometry)?; + virtio_block_config.serialize_field("blk_size", &blk_size)?; + virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?; + virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?; + virtio_block_config.serialize_field("min_io_size", &min_io_size)?; + virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?; + virtio_block_config.serialize_field("writeback", &writeback)?; + virtio_block_config.serialize_field("unused", &unused)?; + virtio_block_config.serialize_field("num_queues", &num_queues)?; + virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?; + virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?; + virtio_block_config + .serialize_field("discard_sector_alignment", &discard_sector_alignment)?; + virtio_block_config + .serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?; + virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?; + virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?; + virtio_block_config.serialize_field("unused1", &unused1)?; + virtio_block_config.end() + } +} + +unsafe impl ByteValued for VirtioBlockConfig {} + +#[derive(Copy, Clone, Debug, Default, Deserialize)] +#[repr(C, packed)] +pub struct VirtioBlockGeometry { + pub cylinders: u16, + pub heads: u8, + pub sectors: u8, +} + +// We must explicitly implement Serialize since the structure is packed and +// it's unsafe to borrow from a packed structure. And by default, if we derive +// Serialize from serde, it will borrow the values from the structure. +// That's why this implementation copies each field separately before it +// serializes the entire structure field by field. +impl Serialize for VirtioBlockGeometry { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let cylinders = self.cylinders; + let heads = self.heads; + let sectors = self.sectors; + + let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?; + virtio_block_geometry.serialize_field("cylinders", &cylinders)?; + virtio_block_geometry.serialize_field("heads", &heads)?; + virtio_block_geometry.serialize_field("sectors", §ors)?; + virtio_block_geometry.end() + } +} + +unsafe impl ByteValued for VirtioBlockGeometry {} diff --git a/vhost_user_block/Cargo.toml b/vhost_user_block/Cargo.toml index ded507cd5..bcd414439 100644 --- a/vhost_user_block/Cargo.toml +++ b/vhost_user_block/Cargo.toml @@ -5,6 +5,7 @@ authors = ["The Cloud Hypervisor Authors"] edition = "2018" [dependencies] +block_util = { path = "../block_util" } clap = { version = "2.33.1", features=["wrap_help"] } epoll = ">=4.0.1" libc = "0.2.72" @@ -14,6 +15,5 @@ qcow = { path = "../qcow" } vhost_user_backend = { path = "../vhost_user_backend" } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } virtio-bindings = "0.1.0" -virtio-devices = { path = "../virtio-devices" } vm-memory = "0.2.1" vmm-sys-util = ">=0.3.1" diff --git a/vhost_user_block/src/lib.rs b/vhost_user_block/src/lib.rs index 7aa16b18c..7675698a0 100644 --- a/vhost_user_block/src/lib.rs +++ b/vhost_user_block/src/lib.rs @@ -8,11 +8,12 @@ // // SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause) +extern crate block_util; extern crate log; extern crate vhost_rs; extern crate vhost_user_backend; -extern crate virtio_devices; +use block_util::{build_disk_image_id, Request, VirtioBlockConfig}; use libc::EFD_NONBLOCK; use log::*; use option_parser::{OptionParser, OptionParserError, Toggle}; @@ -37,8 +38,6 @@ use vhost_rs::vhost_user::Listener; use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring}; use virtio_bindings::bindings::virtio_blk::*; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; -use virtio_devices::block::{build_disk_image_id, Request}; -use virtio_devices::VirtioBlockConfig; use vm_memory::ByteValued; use vm_memory::{Bytes, GuestMemoryMmap}; use vmm_sys_util::eventfd::EventFd; diff --git a/virtio-devices/Cargo.toml b/virtio-devices/Cargo.toml index bd65ede5d..2e0ec96f0 100644 --- a/virtio-devices/Cargo.toml +++ b/virtio-devices/Cargo.toml @@ -12,6 +12,7 @@ mmio_support = [] [dependencies] anyhow = "1.0" arc-swap = ">=0.4.4" +block_util = { path = "../block_util" } byteorder = "1.3.4" devices = { path = "../devices" } epoll = ">=4.0.1" diff --git a/virtio-devices/src/block.rs b/virtio-devices/src/block.rs index be3611d45..620b33ba3 100644 --- a/virtio-devices/src/block.rs +++ b/virtio-devices/src/block.rs @@ -10,20 +10,19 @@ use super::Error as DeviceError; use super::{ - ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice, - VirtioDeviceType, VirtioInterruptType, + ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType, + VirtioInterruptType, }; use crate::VirtioInterrupt; use anyhow::anyhow; +use block_util::{build_disk_image_id, Request, RequestType, VirtioBlockConfig}; use libc::EFD_NONBLOCK; -use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::cmp; use std::collections::HashMap; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::num::Wrapping; use std::ops::DerefMut; -use std::os::linux::fs::MetadataExt; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::path::PathBuf; use std::result; @@ -33,8 +32,8 @@ use std::thread; use virtio_bindings::bindings::virtio_blk::*; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_memory::{ - ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, - GuestMemoryError, GuestMemoryMmap, + ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryError, + GuestMemoryMmap, }; use vm_migration::{ Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable, @@ -74,224 +73,9 @@ pub enum Error { InvalidOffset, } -#[derive(Debug)] -pub enum ExecuteError { - BadRequest(Error), - Flush(io::Error), - Read(GuestMemoryError), - Seek(io::Error), - Write(GuestMemoryError), - Unsupported(u32), -} - -impl ExecuteError { - pub fn status(&self) -> u32 { - match *self { - ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR, - ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR, - ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR, - ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR, - ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR, - ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP, - } - } -} - pub trait DiskFile: Read + Seek + Write + Clone {} impl DiskFile for D {} -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum RequestType { - In, - Out, - Flush, - GetDeviceID, - Unsupported(u32), -} - -pub fn request_type( - mem: &GuestMemoryMmap, - desc_addr: GuestAddress, -) -> result::Result { - let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?; - match type_ { - VIRTIO_BLK_T_IN => Ok(RequestType::In), - VIRTIO_BLK_T_OUT => Ok(RequestType::Out), - VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush), - VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceID), - t => Ok(RequestType::Unsupported(t)), - } -} - -fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result { - const SECTOR_OFFSET: usize = 8; - let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) { - Some(v) => v, - None => return Err(Error::CheckedOffset(desc_addr, SECTOR_OFFSET)), - }; - - mem.read_obj(addr).map_err(Error::GuestMemory) -} - -fn build_device_id(disk_path: &PathBuf) -> result::Result { - let blk_metadata = match disk_path.metadata() { - Err(_) => return Err(Error::GetFileMetadata), - Ok(m) => m, - }; - // This is how kvmtool does it. - let device_id = format!( - "{}{}{}", - blk_metadata.st_dev(), - blk_metadata.st_rdev(), - blk_metadata.st_ino() - ); - Ok(device_id) -} - -pub fn build_disk_image_id(disk_path: &PathBuf) -> Vec { - let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize]; - match build_device_id(disk_path) { - Err(_) => { - warn!("Could not generate device id. We'll use a default."); - } - Ok(m) => { - // The kernel only knows to read a maximum of VIRTIO_BLK_ID_BYTES. - // This will also zero out any leftover bytes. - let disk_id = m.as_bytes(); - let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize); - default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy]) - } - } - default_disk_image_id -} - -pub struct Request { - request_type: RequestType, - sector: u64, - data_addr: GuestAddress, - data_len: u32, - pub status_addr: GuestAddress, - writeback: bool, -} - -impl Request { - pub fn parse( - avail_desc: &DescriptorChain, - mem: &GuestMemoryMmap, - ) -> result::Result { - // The head contains the request type which MUST be readable. - if avail_desc.is_write_only() { - return Err(Error::UnexpectedWriteOnlyDescriptor); - } - - let mut req = Request { - request_type: request_type(&mem, avail_desc.addr)?, - sector: sector(&mem, avail_desc.addr)?, - data_addr: GuestAddress(0), - data_len: 0, - status_addr: GuestAddress(0), - writeback: true, - }; - - let data_desc; - let status_desc; - let desc = avail_desc - .next_descriptor() - .ok_or(Error::DescriptorChainTooShort)?; - - if !desc.has_next() { - status_desc = desc; - // Only flush requests are allowed to skip the data descriptor. - if req.request_type != RequestType::Flush { - return Err(Error::DescriptorChainTooShort); - } - } else { - data_desc = desc; - status_desc = data_desc - .next_descriptor() - .ok_or(Error::DescriptorChainTooShort)?; - - if data_desc.is_write_only() && req.request_type == RequestType::Out { - return Err(Error::UnexpectedWriteOnlyDescriptor); - } - if !data_desc.is_write_only() && req.request_type == RequestType::In { - return Err(Error::UnexpectedReadOnlyDescriptor); - } - if !data_desc.is_write_only() && req.request_type == RequestType::GetDeviceID { - return Err(Error::UnexpectedReadOnlyDescriptor); - } - - req.data_addr = data_desc.addr; - req.data_len = data_desc.len; - } - - // The status MUST always be writable. - if !status_desc.is_write_only() { - return Err(Error::UnexpectedReadOnlyDescriptor); - } - - if status_desc.len < 1 { - return Err(Error::DescriptorLengthTooSmall); - } - - req.status_addr = status_desc.addr; - - Ok(req) - } - - #[allow(clippy::ptr_arg)] - pub fn execute( - &self, - disk: &mut T, - disk_nsectors: u64, - mem: &GuestMemoryMmap, - disk_id: &Vec, - ) -> result::Result { - let mut top: u64 = u64::from(self.data_len) / SECTOR_SIZE; - if u64::from(self.data_len) % SECTOR_SIZE != 0 { - top += 1; - } - top = top - .checked_add(self.sector) - .ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?; - if top > disk_nsectors { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - - disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT)) - .map_err(ExecuteError::Seek)?; - - match self.request_type { - RequestType::In => { - mem.read_exact_from(self.data_addr, disk, self.data_len as usize) - .map_err(ExecuteError::Read)?; - return Ok(self.data_len); - } - RequestType::Out => { - mem.write_all_to(self.data_addr, disk, self.data_len as usize) - .map_err(ExecuteError::Write)?; - if !self.writeback { - disk.flush().map_err(ExecuteError::Flush)?; - } - } - RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?, - RequestType::GetDeviceID => { - if (self.data_len as usize) < disk_id.len() { - return Err(ExecuteError::BadRequest(Error::InvalidOffset)); - } - mem.write_slice(&disk_id.as_slice(), self.data_addr) - .map_err(ExecuteError::Write)?; - } - RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)), - }; - Ok(0) - } - - pub fn set_writeback(&mut self, writeback: bool) { - self.writeback = writeback - } -} - #[derive(Default, Clone)] pub struct BlockCounters { read_bytes: Arc, @@ -547,120 +331,6 @@ impl BlockEpollHandler { } } -#[derive(Copy, Clone, Debug, Default, Deserialize)] -#[repr(C, packed)] -pub struct VirtioBlockGeometry { - pub cylinders: u16, - pub heads: u8, - pub sectors: u8, -} - -// We must explicitly implement Serialize since the structure is packed and -// it's unsafe to borrow from a packed structure. And by default, if we derive -// Serialize from serde, it will borrow the values from the structure. -// That's why this implementation copies each field separately before it -// serializes the entire structure field by field. -impl Serialize for VirtioBlockGeometry { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let cylinders = self.cylinders; - let heads = self.heads; - let sectors = self.sectors; - - let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?; - virtio_block_geometry.serialize_field("cylinders", &cylinders)?; - virtio_block_geometry.serialize_field("heads", &heads)?; - virtio_block_geometry.serialize_field("sectors", §ors)?; - virtio_block_geometry.end() - } -} - -unsafe impl ByteValued for VirtioBlockGeometry {} - -#[derive(Copy, Clone, Debug, Default, Deserialize)] -#[repr(C, packed)] -pub struct VirtioBlockConfig { - pub capacity: u64, - pub size_max: u32, - pub seg_max: u32, - pub geometry: VirtioBlockGeometry, - pub blk_size: u32, - pub physical_block_exp: u8, - pub alignment_offset: u8, - pub min_io_size: u16, - pub opt_io_size: u32, - pub writeback: u8, - unused: u8, - pub num_queues: u16, - pub max_discard_sectors: u32, - pub max_discard_seg: u32, - pub discard_sector_alignment: u32, - pub max_write_zeroes_sectors: u32, - pub max_write_zeroes_seg: u32, - pub write_zeroes_may_unmap: u8, - unused1: [u8; 3], -} - -// We must explicitly implement Serialize since the structure is packed and -// it's unsafe to borrow from a packed structure. And by default, if we derive -// Serialize from serde, it will borrow the values from the structure. -// That's why this implementation copies each field separately before it -// serializes the entire structure field by field. -impl Serialize for VirtioBlockConfig { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let capacity = self.capacity; - let size_max = self.size_max; - let seg_max = self.seg_max; - let geometry = self.geometry; - let blk_size = self.blk_size; - let physical_block_exp = self.physical_block_exp; - let alignment_offset = self.alignment_offset; - let min_io_size = self.min_io_size; - let opt_io_size = self.opt_io_size; - let writeback = self.writeback; - let unused = self.unused; - let num_queues = self.num_queues; - let max_discard_sectors = self.max_discard_sectors; - let max_discard_seg = self.max_discard_seg; - let discard_sector_alignment = self.discard_sector_alignment; - let max_write_zeroes_sectors = self.max_write_zeroes_sectors; - let max_write_zeroes_seg = self.max_write_zeroes_seg; - let write_zeroes_may_unmap = self.write_zeroes_may_unmap; - let unused1 = self.unused1; - - let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?; - virtio_block_config.serialize_field("capacity", &capacity)?; - virtio_block_config.serialize_field("size_max", &size_max)?; - virtio_block_config.serialize_field("seg_max", &seg_max)?; - virtio_block_config.serialize_field("geometry", &geometry)?; - virtio_block_config.serialize_field("blk_size", &blk_size)?; - virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?; - virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?; - virtio_block_config.serialize_field("min_io_size", &min_io_size)?; - virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?; - virtio_block_config.serialize_field("writeback", &writeback)?; - virtio_block_config.serialize_field("unused", &unused)?; - virtio_block_config.serialize_field("num_queues", &num_queues)?; - virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?; - virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?; - virtio_block_config - .serialize_field("discard_sector_alignment", &discard_sector_alignment)?; - virtio_block_config - .serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?; - virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?; - virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?; - virtio_block_config.serialize_field("unused1", &unused1)?; - virtio_block_config.end() - } -} - -unsafe impl ByteValued for VirtioBlockConfig {} - /// Virtio device for exposing block level read/write operations on a host file. pub struct Block { id: String, diff --git a/virtio-devices/src/vhost_user/blk.rs b/virtio-devices/src/vhost_user/blk.rs index a36e821a7..0285177cb 100644 --- a/virtio-devices/src/vhost_user/blk.rs +++ b/virtio-devices/src/vhost_user/blk.rs @@ -6,8 +6,8 @@ use super::handler::*; use super::vu_common_ctrl::*; use super::Error as DeviceError; use super::{Error, Result}; -use crate::block::VirtioBlockConfig; use crate::VirtioInterrupt; +use block_util::VirtioBlockConfig; use libc::EFD_NONBLOCK; use std::cmp; use std::io::Write;