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 <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2020-07-14 10:43:33 +01:00
parent ce79482ec4
commit 9c867edc35
9 changed files with 417 additions and 342 deletions

16
Cargo.lock generated
View File

@ -145,6 +145,19 @@ dependencies = [
"constant_time_eq", "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]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.3.4"
@ -1437,6 +1450,7 @@ dependencies = [
name = "vhost_user_block" name = "vhost_user_block"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"block_util",
"clap", "clap",
"epoll", "epoll",
"libc", "libc",
@ -1446,7 +1460,6 @@ dependencies = [
"vhost", "vhost",
"vhost_user_backend", "vhost_user_backend",
"virtio-bindings", "virtio-bindings",
"virtio-devices",
"vm-memory", "vm-memory",
"vmm-sys-util", "vmm-sys-util",
] ]
@ -1501,6 +1514,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
"block_util",
"byteorder", "byteorder",
"devices", "devices",
"epoll", "epoll",

View File

@ -66,5 +66,6 @@ members = [
"arch_gen", "arch_gen",
"net_gen", "net_gen",
"vm-allocator", "vm-allocator",
"option_parser" "option_parser",
"block_util"
] ]

14
block_util/Cargo.toml Normal file
View File

@ -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" }

376
block_util/src/lib.rs Normal file
View File

@ -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<String, Error> {
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<u8> {
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<RequestType, Error> {
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<u64, Error> {
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<Request, Error> {
// 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<T: Seek + Read + Write>(
&self,
disk: &mut T,
disk_nsectors: u64,
mem: &GuestMemoryMmap,
disk_id: &Vec<u8>,
) -> result::Result<u32, ExecuteError> {
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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", &sectors)?;
virtio_block_geometry.end()
}
}
unsafe impl ByteValued for VirtioBlockGeometry {}

View File

@ -5,6 +5,7 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
block_util = { path = "../block_util" }
clap = { version = "2.33.1", features=["wrap_help"] } clap = { version = "2.33.1", features=["wrap_help"] }
epoll = ">=4.0.1" epoll = ">=4.0.1"
libc = "0.2.72" libc = "0.2.72"
@ -14,6 +15,5 @@ qcow = { path = "../qcow" }
vhost_user_backend = { path = "../vhost_user_backend" } vhost_user_backend = { path = "../vhost_user_backend" }
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
virtio-bindings = "0.1.0" virtio-bindings = "0.1.0"
virtio-devices = { path = "../virtio-devices" }
vm-memory = "0.2.1" vm-memory = "0.2.1"
vmm-sys-util = ">=0.3.1" vmm-sys-util = ">=0.3.1"

View File

@ -8,11 +8,12 @@
// //
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause) // SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
extern crate block_util;
extern crate log; extern crate log;
extern crate vhost_rs; extern crate vhost_rs;
extern crate vhost_user_backend; extern crate vhost_user_backend;
extern crate virtio_devices;
use block_util::{build_disk_image_id, Request, VirtioBlockConfig};
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use log::*; use log::*;
use option_parser::{OptionParser, OptionParserError, Toggle}; use option_parser::{OptionParser, OptionParserError, Toggle};
@ -37,8 +38,6 @@ use vhost_rs::vhost_user::Listener;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring}; use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
use virtio_bindings::bindings::virtio_blk::*; use virtio_bindings::bindings::virtio_blk::*;
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; 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::ByteValued;
use vm_memory::{Bytes, GuestMemoryMmap}; use vm_memory::{Bytes, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;

View File

@ -12,6 +12,7 @@ mmio_support = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
arc-swap = ">=0.4.4" arc-swap = ">=0.4.4"
block_util = { path = "../block_util" }
byteorder = "1.3.4" byteorder = "1.3.4"
devices = { path = "../devices" } devices = { path = "../devices" }
epoll = ">=4.0.1" epoll = ">=4.0.1"

View File

@ -10,20 +10,19 @@
use super::Error as DeviceError; use super::Error as DeviceError;
use super::{ use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice, ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VirtioDeviceType, VirtioInterruptType, VirtioInterruptType,
}; };
use crate::VirtioInterrupt; use crate::VirtioInterrupt;
use anyhow::anyhow; use anyhow::anyhow;
use block_util::{build_disk_image_id, Request, RequestType, VirtioBlockConfig};
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
use std::num::Wrapping; use std::num::Wrapping;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::os::linux::fs::MetadataExt;
use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::PathBuf; use std::path::PathBuf;
use std::result; use std::result;
@ -33,8 +32,8 @@ use std::thread;
use virtio_bindings::bindings::virtio_blk::*; use virtio_bindings::bindings::virtio_blk::*;
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::{ use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryError,
GuestMemoryError, GuestMemoryMmap, GuestMemoryMmap,
}; };
use vm_migration::{ use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable, Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
@ -74,224 +73,9 @@ pub enum Error {
InvalidOffset, 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 {} pub trait DiskFile: Read + Seek + Write + Clone {}
impl<D: Read + Seek + Write + Clone> DiskFile for D {} impl<D: Read + Seek + Write + Clone> 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<RequestType, Error> {
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<u64, Error> {
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<String, Error> {
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<u8> {
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<Request, Error> {
// 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<T: Seek + Read + Write>(
&self,
disk: &mut T,
disk_nsectors: u64,
mem: &GuestMemoryMmap,
disk_id: &Vec<u8>,
) -> result::Result<u32, ExecuteError> {
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)] #[derive(Default, Clone)]
pub struct BlockCounters { pub struct BlockCounters {
read_bytes: Arc<AtomicU64>, read_bytes: Arc<AtomicU64>,
@ -547,120 +331,6 @@ impl<T: DiskFile> BlockEpollHandler<T> {
} }
} }
#[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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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", &sectors)?;
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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. /// Virtio device for exposing block level read/write operations on a host file.
pub struct Block<T: DiskFile> { pub struct Block<T: DiskFile> {
id: String, id: String,

View File

@ -6,8 +6,8 @@ use super::handler::*;
use super::vu_common_ctrl::*; use super::vu_common_ctrl::*;
use super::Error as DeviceError; use super::Error as DeviceError;
use super::{Error, Result}; use super::{Error, Result};
use crate::block::VirtioBlockConfig;
use crate::VirtioInterrupt; use crate::VirtioInterrupt;
use block_util::VirtioBlockConfig;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use std::cmp; use std::cmp;
use std::io::Write; use std::io::Write;