block: merge qcow, vhdx and block_util into block crate

This commit merges crates `qcow`, `vhdx` and `block_util` into the
crate `block`, which can allow `qcow` to use functions from `block_util`
without introducing a circular crate dependency.

This commit is based on crosvm implementation:
f2eecc4152

Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
This commit is contained in:
Yu Li
2023-07-12 10:24:28 +08:00
committed by Rob Bradford
parent 47fbe9af1e
commit 447cad3861
38 changed files with 117 additions and 201 deletions

145
block/src/async_io.rs Normal file
View File

@@ -0,0 +1,145 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use libc::{ioctl, S_IFBLK, S_IFMT};
use std::fs::File;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
#[derive(Error, Debug)]
pub enum DiskFileError {
/// Failed getting disk file size.
#[error("Failed getting disk file size: {0}")]
Size(#[source] std::io::Error),
/// Failed creating a new AsyncIo.
#[error("Failed creating a new AsyncIo: {0}")]
NewAsyncIo(#[source] std::io::Error),
}
#[derive(Debug)]
pub struct DiskTopology {
pub logical_block_size: u64,
pub physical_block_size: u64,
pub minimum_io_size: u64,
pub optimal_io_size: u64,
}
impl Default for DiskTopology {
fn default() -> Self {
Self {
logical_block_size: 512,
physical_block_size: 512,
minimum_io_size: 512,
optimal_io_size: 0,
}
}
}
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
enum BlockSize {
LogicalBlock,
PhysicalBlock,
MinimumIo,
OptimalIo,
}
impl DiskTopology {
fn is_block_device(f: &mut File) -> std::io::Result<bool> {
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
// SAFETY: FFI call with a valid fd and buffer
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
// SAFETY: stat is valid at this point
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
Ok(is_block)
}
// libc::ioctl() takes different types on different architectures
fn query_block_size(f: &mut File, block_size_type: BlockSize) -> std::io::Result<u64> {
let mut block_size = 0;
// SAFETY: FFI call with correct arguments
let ret = unsafe {
ioctl(
f.as_raw_fd(),
match block_size_type {
BlockSize::LogicalBlock => BLKSSZGET(),
BlockSize::PhysicalBlock => BLKPBSZGET(),
BlockSize::MinimumIo => BLKIOMIN(),
BlockSize::OptimalIo => BLKIOOPT(),
} as _,
&mut block_size,
)
};
if ret != 0 {
return Err(std::io::Error::last_os_error());
};
Ok(block_size)
}
pub fn probe(f: &mut File) -> std::io::Result<Self> {
if !Self::is_block_device(f)? {
return Ok(DiskTopology::default());
}
Ok(DiskTopology {
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
})
}
}
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
pub trait DiskFile: Send {
fn size(&mut self) -> DiskFileResult<u64>;
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
fn topology(&mut self) -> DiskTopology {
DiskTopology::default()
}
}
#[derive(Error, Debug)]
pub enum AsyncIoError {
/// Failed vectored reading from file.
#[error("Failed vectored reading from file: {0}")]
ReadVectored(#[source] std::io::Error),
/// Failed vectored writing to file.
#[error("Failed vectored writing to file: {0}")]
WriteVectored(#[source] std::io::Error),
/// Failed synchronizing file.
#[error("Failed synchronizing file: {0}")]
Fsync(#[source] std::io::Error),
}
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
pub trait AsyncIo: Send {
fn notifier(&self) -> &EventFd;
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()>;
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()>;
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
}

87
block/src/fixed_vhd.rs Normal file
View File

@@ -0,0 +1,87 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhd::VhdFooter;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, RawFd};
#[derive(Debug)]
pub struct FixedVhd {
file: File,
size: u64,
position: u64,
}
impl FixedVhd {
pub fn new(mut file: File) -> std::io::Result<Self> {
let footer = VhdFooter::new(&mut file)?;
Ok(Self {
file,
size: footer.current_size(),
position: 0,
})
}
pub fn size(&self) -> u64 {
self.size
}
}
impl AsRawFd for FixedVhd {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl Read for FixedVhd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self.file.read(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
}
}
impl Write for FixedVhd {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self.file.write(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.sync_all()
}
}
impl Seek for FixedVhd {
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
match self.file.seek(newpos) {
Ok(pos) => {
self.position = pos;
Ok(pos)
}
Err(e) => Err(e),
}
}
}
impl Clone for FixedVhd {
fn clone(&self) -> Self {
Self {
file: self.file.try_clone().expect("FixedVhd cloning failed"),
size: self.size,
position: self.position,
}
}
}

View File

@@ -0,0 +1,102 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_async::RawFileAsync;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct FixedVhdDiskAsync(FixedVhd);
impl FixedVhdDiskAsync {
pub fn new(file: File) -> std::io::Result<Self> {
Ok(Self(FixedVhd::new(file)?))
}
}
impl DiskFile for FixedVhdDiskAsync {
fn size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.size())
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, self.0.size())
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
}
pub struct FixedVhdAsync {
raw_file_async: RawFileAsync,
size: u64,
}
impl FixedVhdAsync {
pub fn new(fd: RawFd, ring_depth: u32, size: u64) -> std::io::Result<Self> {
let raw_file_async = RawFileAsync::new(fd, ring_depth)?;
Ok(FixedVhdAsync {
raw_file_async,
size,
})
}
}
impl AsyncIo for FixedVhdAsync {
fn notifier(&self) -> &EventFd {
self.raw_file_async.notifier()
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_async.read_vectored(offset, iovecs, user_data)
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_async
.write_vectored(offset, iovecs, user_data)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.raw_file_async.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_async.next_completed_request()
}
}

View File

@@ -0,0 +1,99 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_sync::RawFileSync;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct FixedVhdDiskSync(FixedVhd);
impl FixedVhdDiskSync {
pub fn new(file: File) -> std::io::Result<Self> {
Ok(Self(FixedVhd::new(file)?))
}
}
impl DiskFile for FixedVhdDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.size())
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdSync::new(self.0.as_raw_fd(), self.0.size())
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
}
pub struct FixedVhdSync {
raw_file_sync: RawFileSync,
size: u64,
}
impl FixedVhdSync {
pub fn new(fd: RawFd, size: u64) -> std::io::Result<Self> {
Ok(FixedVhdSync {
raw_file_sync: RawFileSync::new(fd),
size,
})
}
}
impl AsyncIo for FixedVhdSync {
fn notifier(&self) -> &EventFd {
self.raw_file_sync.notifier()
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_sync.read_vectored(offset, iovecs, user_data)
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_sync.write_vectored(offset, iovecs, user_data)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.raw_file_sync.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_sync.next_completed_request()
}
}

741
block/src/lib.rs Normal file
View File

@@ -0,0 +1,741 @@
// 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;
pub mod async_io;
pub mod fixed_vhd;
#[cfg(feature = "io_uring")]
/// Enabled with the `"io_uring"` feature
pub mod fixed_vhd_async;
pub mod fixed_vhd_sync;
pub mod qcow;
pub mod qcow_sync;
#[cfg(feature = "io_uring")]
/// Async primitives based on `io-uring`
///
/// Enabled with the `"io_uring"` feature
pub mod raw_async;
pub mod raw_sync;
pub mod vhd;
pub mod vhdx;
pub mod vhdx_sync;
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
#[cfg(feature = "io_uring")]
use io_uring::{opcode, IoUring, Probe};
use smallvec::SmallVec;
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::cmp;
use std::collections::VecDeque;
use std::convert::TryInto;
use std::fs::File;
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::os::linux::fs::MetadataExt;
use std::path::Path;
use std::result;
use std::sync::Arc;
use std::sync::MutexGuard;
use std::time::Instant;
use thiserror::Error;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use virtio_bindings::virtio_blk::*;
use virtio_queue::DescriptorChain;
use vm_memory::{
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
GuestMemoryError, GuestMemoryLoadGuard,
};
use vm_virtio::{AccessPlatform, Translatable};
use vmm_sys_util::eventfd::EventFd;
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
const SECTOR_SHIFT: u8 = 9;
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
#[derive(Error, Debug)]
pub enum Error {
#[error("Guest gave us bad memory addresses")]
GuestMemory(GuestMemoryError),
#[error("Guest gave us offsets that would have overflowed a usize")]
CheckedOffset(GuestAddress, usize),
#[error("Guest gave us a write only descriptor that protocol says to read from")]
UnexpectedWriteOnlyDescriptor,
#[error("Guest gave us a read only descriptor that protocol says to write to")]
UnexpectedReadOnlyDescriptor,
#[error("Guest gave us too few descriptors in a descriptor chain")]
DescriptorChainTooShort,
#[error("Guest gave us a descriptor that was too short to use")]
DescriptorLengthTooSmall,
#[error("Getting a block's metadata fails for any reason")]
GetFileMetadata,
#[error("The requested operation would cause a seek beyond disk end")]
InvalidOffset,
#[error("The requested operation does not support multiple descriptors")]
TooManyDescriptors,
}
fn build_device_id(disk_path: &Path) -> 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: &Path) -> 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(Error, Debug)]
pub enum ExecuteError {
#[error("Bad request: {0}")]
BadRequest(Error),
#[error("Falied to flush: {0}")]
Flush(io::Error),
#[error("Failed to read: {0}")]
Read(GuestMemoryError),
#[error("Failed to seek: {0}")]
Seek(io::Error),
#[error("Failed to write: {0}")]
Write(GuestMemoryError),
#[error("Unsupported request: {0}")]
Unsupported(u32),
#[error("Failed to submit io uring: {0}")]
SubmitIoUring(io::Error),
#[error("Failed to get guest address: {0}")]
GetHostAddress(GuestMemoryError),
#[error("Failed to async read: {0}")]
AsyncRead(AsyncIoError),
#[error("Failed to async write: {0}")]
AsyncWrite(AsyncIoError),
#[error("failed to async flush: {0}")]
AsyncFlush(AsyncIoError),
#[error("Failed allocating a temporary buffer: {0}")]
TemporaryBufferAllocation(io::Error),
}
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,
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
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)
}
#[derive(Debug)]
pub struct AlignedOperation {
origin_ptr: u64,
aligned_ptr: u64,
size: usize,
layout: Layout,
}
#[derive(Debug)]
pub struct Request {
pub request_type: RequestType,
pub sector: u64,
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
pub status_addr: GuestAddress,
pub writeback: bool,
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
pub start: Instant,
}
impl Request {
pub fn parse(
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
access_platform: Option<&Arc<dyn AccessPlatform>>,
) -> result::Result<Request, Error> {
let hdr_desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.map_err(|e| {
error!("Missing head descriptor");
e
})?;
// The head contains the request type which MUST be readable.
if hdr_desc.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
let hdr_desc_addr = hdr_desc
.addr()
.translate_gva(access_platform, hdr_desc.len() as usize);
let mut req = Request {
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
data_descriptors: SmallVec::with_capacity(1),
status_addr: GuestAddress(0),
writeback: true,
aligned_operations: SmallVec::with_capacity(1),
start: Instant::now(),
};
let status_desc;
let mut desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.map_err(|e| {
error!("Only head descriptor present: request = {:?}", req);
e
})?;
if !desc.has_next() {
status_desc = desc;
// Only flush requests are allowed to skip the data descriptor.
if req.request_type != RequestType::Flush {
error!("Need a data descriptor: request = {:?}", req);
return Err(Error::DescriptorChainTooShort);
}
} else {
req.data_descriptors.reserve_exact(1);
while desc.has_next() {
if desc.is_write_only() && req.request_type == RequestType::Out {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
if !desc.is_write_only() && req.request_type == RequestType::In {
return Err(Error::UnexpectedReadOnlyDescriptor);
}
if !desc.is_write_only() && req.request_type == RequestType::GetDeviceId {
return Err(Error::UnexpectedReadOnlyDescriptor);
}
req.data_descriptors.push((
desc.addr()
.translate_gva(access_platform, desc.len() as usize),
desc.len(),
));
desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.map_err(|e| {
error!("DescriptorChain corrupted: request = {:?}", req);
e
})?;
}
status_desc = desc;
}
// 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()
.translate_gva(access_platform, status_desc.len() as usize);
Ok(req)
}
pub fn execute<T: Seek + Read + Write>(
&self,
disk: &mut T,
disk_nsectors: u64,
mem: &GuestMemoryMmap,
disk_id: &[u8],
) -> result::Result<u32, ExecuteError> {
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
.map_err(ExecuteError::Seek)?;
let mut len = 0;
for (data_addr, data_len) in &self.data_descriptors {
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
if u64::from(*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));
}
match self.request_type {
RequestType::In => {
mem.read_exact_from(*data_addr, disk, *data_len as usize)
.map_err(ExecuteError::Read)?;
len += data_len;
}
RequestType::Out => {
mem.write_all_to(*data_addr, disk, *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 (*data_len as usize) < disk_id.len() {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
mem.write_slice(disk_id, *data_addr)
.map_err(ExecuteError::Write)?;
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
};
}
Ok(len)
}
pub fn execute_async(
&mut self,
mem: &GuestMemoryMmap,
disk_nsectors: u64,
disk_image: &mut dyn AsyncIo,
disk_id: &[u8],
user_data: u64,
) -> result::Result<bool, ExecuteError> {
let sector = self.sector;
let request_type = self.request_type;
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
let mut iovecs: SmallVec<[libc::iovec; 1]> =
SmallVec::with_capacity(self.data_descriptors.len());
for (data_addr, data_len) in &self.data_descriptors {
if *data_len == 0 {
continue;
}
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
if u64::from(*data_len) % SECTOR_SIZE != 0 {
top += 1;
}
top = top
.checked_add(sector)
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
if top > disk_nsectors {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
let origin_ptr = mem
.get_slice(*data_addr, *data_len as usize)
.map_err(ExecuteError::GetHostAddress)?
.as_ptr();
// Verify the buffer alignment.
// In case it's not properly aligned, an intermediate buffer is
// created with the correct alignment, and a copy from/to the
// origin buffer is performed, depending on the type of operation.
let iov_base = if (origin_ptr as u64) % SECTOR_SIZE != 0 {
let layout =
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
// SAFETY: layout has non-zero size
let aligned_ptr = unsafe { alloc_zeroed(layout) };
if aligned_ptr.is_null() {
return Err(ExecuteError::TemporaryBufferAllocation(
io::Error::last_os_error(),
));
}
// We need to perform the copy beforehand in case we're writing
// data out.
if request_type == RequestType::Out {
// SAFETY: destination buffer has been allocated with
// the proper size.
unsafe {
std::ptr::copy(origin_ptr as *const u8, aligned_ptr, *data_len as usize)
};
}
// Store both origin and aligned pointers for complete_async()
// to process them.
self.aligned_operations.push(AlignedOperation {
origin_ptr: origin_ptr as u64,
aligned_ptr: aligned_ptr as u64,
size: *data_len as usize,
layout,
});
aligned_ptr as *mut libc::c_void
} else {
origin_ptr as *mut libc::c_void
};
let iovec = libc::iovec {
iov_base,
iov_len: *data_len as libc::size_t,
};
iovecs.push(iovec);
}
// Queue operations expected to be submitted.
match request_type {
RequestType::In => {
for (data_addr, data_len) in &self.data_descriptors {
mem.get_slice(*data_addr, *data_len as usize)
.map_err(ExecuteError::GetHostAddress)?
.bitmap()
.mark_dirty(0, *data_len as usize);
}
disk_image
.read_vectored(offset, &iovecs, user_data)
.map_err(ExecuteError::AsyncRead)?;
}
RequestType::Out => {
disk_image
.write_vectored(offset, &iovecs, user_data)
.map_err(ExecuteError::AsyncWrite)?;
}
RequestType::Flush => {
disk_image
.fsync(Some(user_data))
.map_err(ExecuteError::AsyncFlush)?;
}
RequestType::GetDeviceId => {
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
(self.data_descriptors[0].0, self.data_descriptors[0].1)
} else {
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
};
if (data_len as usize) < disk_id.len() {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
mem.write_slice(disk_id, data_addr)
.map_err(ExecuteError::Write)?;
return Ok(false);
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
}
Ok(true)
}
pub fn complete_async(&mut self) -> result::Result<(), Error> {
for aligned_operation in self.aligned_operations.drain(..) {
// We need to perform the copy after the data has been read inside
// the aligned buffer in case we're reading data in.
if self.request_type == RequestType::In {
// SAFETY: origin buffer has been allocated with the
// proper size.
unsafe {
std::ptr::copy(
aligned_operation.aligned_ptr as *const u8,
aligned_operation.origin_ptr as *mut u8,
aligned_operation.size,
)
};
}
// Free the temporary aligned buffer.
// SAFETY: aligned_ptr was allocated by alloc_zeroed with the same
// layout
unsafe {
dealloc(
aligned_operation.aligned_ptr as *mut u8,
aligned_operation.layout,
)
};
}
Ok(())
}
pub fn set_writeback(&mut self, writeback: bool) {
self.writeback = writeback
}
}
#[derive(Copy, Clone, Debug, Default, Versionize)]
#[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],
}
#[derive(Copy, Clone, Debug, Default, Versionize)]
#[repr(C, packed)]
pub struct VirtioBlockGeometry {
pub cylinders: u16,
pub heads: u8,
pub sectors: u8,
}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for VirtioBlockConfig {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for VirtioBlockGeometry {}
/// Check if io_uring for block device can be used on the current system, as
/// it correctly supports the expected io_uring features.
pub fn block_io_uring_is_supported() -> bool {
#[cfg(not(feature = "io_uring"))]
{
info!("io_uring is disabled by crate features");
false
}
#[cfg(feature = "io_uring")]
{
let error_msg = "io_uring not supported:";
// Check we can create an io_uring instance, which effectively verifies
// that io_uring_setup() syscall is supported.
let io_uring = match IoUring::new(1) {
Ok(io_uring) => io_uring,
Err(e) => {
info!("{} failed to create io_uring instance: {}", error_msg, e);
return false;
}
};
let submitter = io_uring.submitter();
let mut probe = Probe::new();
// Check we can register a probe to validate supported operations.
match submitter.register_probe(&mut probe) {
Ok(_) => {}
Err(e) => {
info!("{} failed to register a probe: {}", error_msg, e);
return false;
}
}
// Check IORING_OP_FSYNC is supported
if !probe.is_supported(opcode::Fsync::CODE) {
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
return false;
}
// Check IORING_OP_READV is supported
if !probe.is_supported(opcode::Readv::CODE) {
info!("{} IORING_OP_READV operation not supported", error_msg);
return false;
}
// Check IORING_OP_WRITEV is supported
if !probe.is_supported(opcode::Writev::CODE) {
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
return false;
}
true
}
}
pub trait AsyncAdaptor<F>
where
F: Read + Write + Seek,
{
fn read_vectored_sync(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSliceMut
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
for iovec in iovecs.iter() {
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
}
let result = {
let mut file = self.file();
// Move the cursor to the right offset
file.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::ReadVectored)?;
// Read vectored
file.read_vectored(slices.as_mut_slice())
.map_err(AsyncIoError::ReadVectored)?
};
completion_list.push_back((user_data, result as i32));
eventfd.write(1).unwrap();
Ok(())
}
fn write_vectored_sync(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSlice
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
for iovec in iovecs.iter() {
// SAFETY: on Linux IoSlice wraps around libc::iovec
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
}
let result = {
let mut file = self.file();
// Move the cursor to the right offset
file.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::WriteVectored)?;
// Write vectored
file.write_vectored(slices.as_slice())
.map_err(AsyncIoError::WriteVectored)?
};
completion_list.push_back((user_data, result as i32));
eventfd.write(1).unwrap();
Ok(())
}
fn fsync_sync(
&mut self,
user_data: Option<u64>,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
) -> AsyncIoResult<()> {
let result: i32 = {
let mut file = self.file();
// Flush
file.flush().map_err(AsyncIoError::Fsync)?;
0
};
if let Some(user_data) = user_data {
completion_list.push_back((user_data, result));
eventfd.write(1).unwrap();
}
Ok(())
}
fn file(&mut self) -> MutexGuard<F>;
}
pub enum ImageType {
FixedVhd,
Qcow2,
Raw,
Vhdx,
}
const QCOW_MAGIC: u32 = 0x5146_49fb;
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
/// Determine image type through file parsing.
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
// We must create a buffer aligned on 512 bytes with a size being a
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
#[repr(align(512))]
struct Sector {
data: [u8; 512],
}
let mut s = Sector { data: [0; 512] };
f.read_exact(&mut s.data)?;
// Check 4 first bytes to get the header value and determine the image type
let image_type = if u32::from_be_bytes(s.data[0..4].try_into().unwrap()) == QCOW_MAGIC {
ImageType::Qcow2
} else if vhd::is_fixed_vhd(f)? {
ImageType::FixedVhd
} else if u64::from_le_bytes(s.data[0..8].try_into().unwrap()) == VHDX_SIGN {
ImageType::Vhdx
} else {
ImageType::Raw
};
Ok(image_type)
}

2601
block/src/qcow/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
// Copyright 2018 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.
use super::RawFile;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, BufWriter, Seek, SeekFrom};
use std::mem::size_of;
use vmm_sys_util::write_zeroes::WriteZeroes;
/// A qcow file. Allows reading/writing clusters and appending clusters.
#[derive(Debug)]
pub struct QcowRawFile {
file: RawFile,
cluster_size: u64,
cluster_mask: u64,
}
impl QcowRawFile {
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
/// a power of two.
pub fn from(file: RawFile, cluster_size: u64) -> Option<Self> {
if cluster_size.count_ones() != 1 {
return None;
}
Some(QcowRawFile {
file,
cluster_size,
cluster_mask: cluster_size - 1,
})
}
/// Reads `count` 64 bit offsets and returns them as a vector.
/// `mask` optionally ands out some of the bits on the file.
pub fn read_pointer_table(
&mut self,
offset: u64,
count: u64,
mask: Option<u64>,
) -> io::Result<Vec<u64>> {
let mut table = vec![0; count as usize];
self.file.seek(SeekFrom::Start(offset))?;
self.file.read_u64_into::<BigEndian>(&mut table)?;
if let Some(m) = mask {
for ptr in &mut table {
*ptr &= m;
}
}
Ok(table)
}
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
/// `mask` optionally ands out some of the bits on the file.
pub fn read_pointer_cluster(&mut self, offset: u64, mask: Option<u64>) -> io::Result<Vec<u64>> {
let count = self.cluster_size / size_of::<u64>() as u64;
self.read_pointer_table(offset, count, mask)
}
/// Writes `table` of u64 pointers to `offset` in the file.
/// `non_zero_flags` will be ORed with all non-zero values in `table`.
/// writing.
pub fn write_pointer_table(
&mut self,
offset: u64,
table: &[u64],
non_zero_flags: u64,
) -> io::Result<()> {
self.file.seek(SeekFrom::Start(offset))?;
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
for addr in table {
let val = if *addr == 0 {
0
} else {
*addr | non_zero_flags
};
buffer.write_u64::<BigEndian>(val)?;
}
Ok(())
}
/// Read a refcount block from the file and returns a Vec containing the block.
/// Always returns a cluster's worth of data.
pub fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u16>> {
let count = self.cluster_size / size_of::<u16>() as u64;
let mut table = vec![0; count as usize];
self.file.seek(SeekFrom::Start(offset))?;
self.file.read_u16_into::<BigEndian>(&mut table)?;
Ok(table)
}
/// Writes a refcount block to the file.
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
self.file.seek(SeekFrom::Start(offset))?;
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
for count in table {
buffer.write_u16::<BigEndian>(*count)?;
}
Ok(())
}
/// Allocates a new cluster at the end of the current file, return the address.
pub fn add_cluster_end(&mut self, max_valid_cluster_offset: u64) -> io::Result<Option<u64>> {
// Determine where the new end of the file should be and set_len, which
// translates to truncate(2).
let file_end: u64 = self.file.seek(SeekFrom::End(0))?;
let new_cluster_address: u64 = (file_end + self.cluster_size - 1) & !self.cluster_mask;
if new_cluster_address > max_valid_cluster_offset {
return Ok(None);
}
self.file.set_len(new_cluster_address + self.cluster_size)?;
Ok(Some(new_cluster_address))
}
/// Returns a mutable reference to the underlying file.
pub fn file_mut(&mut self) -> &mut RawFile {
&mut self.file
}
/// Returns the size of the file's clusters.
pub fn cluster_size(&self) -> u64 {
self.cluster_size
}
/// Returns the offset of `address` within a cluster.
pub fn cluster_offset(&self, address: u64) -> u64 {
address & self.cluster_mask
}
/// Zeros out a cluster in the file.
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
let cluster_size = self.cluster_size as usize;
self.file.seek(SeekFrom::Start(address))?;
self.file.write_zeroes(cluster_size)?;
Ok(())
}
}
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,
}
}
}

354
block/src/qcow/raw_file.rs Normal file
View File

@@ -0,0 +1,354 @@
// 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
use libc::c_void;
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::convert::TryInto;
use std::fs::{File, Metadata};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, RawFd};
use std::slice;
use vmm_sys_util::{seek_hole::SeekHole, write_zeroes::PunchHole, write_zeroes::WriteZeroesAt};
#[derive(Debug)]
pub struct RawFile {
file: File,
alignment: usize,
position: u64,
}
const BLK_ALIGNMENTS: [usize; 2] = [512, 4096];
fn is_valid_alignment(fd: RawFd, alignment: usize) -> bool {
let layout = Layout::from_size_align(alignment, alignment).unwrap();
// SAFETY: layout has non-zero size
let ptr = unsafe { alloc_zeroed(layout) };
assert!(!ptr.is_null());
// SAFETY: FFI call
let ret = unsafe {
::libc::pread(
fd,
ptr as *mut c_void,
alignment,
alignment.try_into().unwrap(),
)
};
// SAFETY: ptr was allocated by alloc_zeroed with layout
unsafe { dealloc(ptr, layout) };
ret >= 0
}
impl RawFile {
pub fn new(file: File, direct_io: bool) -> Self {
// Assume no alignment restrictions if we aren't using O_DIRECT.
let mut alignment = 0;
if direct_io {
for align in &BLK_ALIGNMENTS {
if is_valid_alignment(file.as_raw_fd(), *align) {
alignment = *align;
break;
}
}
}
RawFile {
file,
alignment,
position: 0,
}
}
fn round_up(&self, offset: u64) -> u64 {
let align: u64 = self.alignment.try_into().unwrap();
((offset + align - 1) / align) * align
}
fn round_down(&self, offset: u64) -> u64 {
let align: u64 = self.alignment.try_into().unwrap();
(offset / align) * align
}
fn is_aligned(&self, buf: &[u8]) -> bool {
if self.alignment == 0 {
return true;
}
let align64: u64 = self.alignment.try_into().unwrap();
(self.position % align64 == 0)
&& ((buf.as_ptr() as usize) % self.alignment == 0)
&& (buf.len() % self.alignment == 0)
}
pub fn set_len(&self, size: u64) -> std::io::Result<()> {
self.file.set_len(size)
}
pub fn metadata(&self) -> std::io::Result<Metadata> {
self.file.metadata()
}
pub fn try_clone(&self) -> std::io::Result<RawFile> {
Ok(RawFile {
file: self.file.try_clone().expect("RawFile cloning failed"),
alignment: self.alignment,
position: self.position,
})
}
pub fn sync_all(&self) -> std::io::Result<()> {
self.file.sync_all()
}
pub fn sync_data(&self) -> std::io::Result<()> {
self.file.sync_data()
}
}
impl Read for RawFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.is_aligned(buf) {
match self.file.read(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
} else {
let rounded_pos: u64 = self.round_down(self.position);
let file_offset: usize = self
.position
.checked_sub(rounded_pos)
.unwrap()
.try_into()
.unwrap();
let buf_len: usize = buf.len();
let rounded_len: usize = self
.round_up(
file_offset
.checked_add(buf_len)
.unwrap()
.try_into()
.unwrap(),
)
.try_into()
.unwrap();
let layout = Layout::from_size_align(rounded_len, self.alignment).unwrap();
// SAFETY: layout has non-zero size
let tmp_ptr = unsafe { alloc_zeroed(layout) };
if tmp_ptr.is_null() {
return Err(io::Error::last_os_error());
}
// SAFETY: tmp_ptr is valid and at least rounded_len long
let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
// This can eventually replaced with read_at once its interface
// has been stabilized.
// SAFETY: FFI call. All parameters are valid.
let ret = unsafe {
::libc::pread64(
self.file.as_raw_fd(),
tmp_buf.as_mut_ptr() as *mut c_void,
tmp_buf.len(),
rounded_pos.try_into().unwrap(),
)
};
if ret < 0 {
// SAFETY: tmp_ptr was allocated by alloc_zeroed with layout
unsafe { dealloc(tmp_ptr, layout) };
return Err(io::Error::last_os_error());
}
let read: usize = ret.try_into().unwrap();
if read < file_offset {
// SAFETY: tmp_ptr was allocated by alloc_zeroed with layout
unsafe { dealloc(tmp_ptr, layout) };
return Ok(0);
}
let mut to_copy = read - file_offset;
if to_copy > buf_len {
to_copy = buf_len;
}
buf.copy_from_slice(&tmp_buf[file_offset..(file_offset + buf_len)]);
// SAFETY: tmp_ptr was allocated by alloc_zeroed with layout
unsafe { dealloc(tmp_ptr, layout) };
self.seek(SeekFrom::Current(to_copy.try_into().unwrap()))
.unwrap();
Ok(to_copy)
}
}
}
impl Write for RawFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.is_aligned(buf) {
match self.file.write(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
} else {
let rounded_pos: u64 = self.round_down(self.position);
let file_offset: usize = self
.position
.checked_sub(rounded_pos)
.unwrap()
.try_into()
.unwrap();
let buf_len: usize = buf.len();
let rounded_len: usize = self
.round_up(
file_offset
.checked_add(buf_len)
.unwrap()
.try_into()
.unwrap(),
)
.try_into()
.unwrap();
let layout = Layout::from_size_align(rounded_len, self.alignment).unwrap();
// SAFETY: layout has non-zero size
let tmp_ptr = unsafe { alloc_zeroed(layout) };
if tmp_ptr.is_null() {
return Err(io::Error::last_os_error());
}
// SAFETY: tmp_ptr is at least rounded_len long
let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
// This can eventually replaced with read_at once its interface
// has been stabilized.
// SAFETY: FFI call
let ret = unsafe {
::libc::pread64(
self.file.as_raw_fd(),
tmp_buf.as_mut_ptr() as *mut c_void,
tmp_buf.len(),
rounded_pos.try_into().unwrap(),
)
};
if ret < 0 {
// SAFETY: tmp_ptr was allocated by alloc_zeroed with layout
unsafe { dealloc(tmp_ptr, layout) };
return Err(io::Error::last_os_error());
};
tmp_buf[file_offset..(file_offset + buf_len)].copy_from_slice(buf);
// This can eventually replaced with write_at once its interface
// has been stabilized.
// SAFETY: FFI call
let ret = unsafe {
::libc::pwrite64(
self.file.as_raw_fd(),
tmp_buf.as_ptr() as *const c_void,
tmp_buf.len(),
rounded_pos.try_into().unwrap(),
)
};
// SAFETY: tmp_ptr was allocated by alloc_zeroed with layout
unsafe { dealloc(tmp_ptr, layout) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
let written: usize = ret.try_into().unwrap();
if written < file_offset {
Ok(0)
} else {
let mut to_seek = written - file_offset;
if to_seek > buf_len {
to_seek = buf_len;
}
self.seek(SeekFrom::Current(to_seek.try_into().unwrap()))
.unwrap();
Ok(to_seek)
}
}
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.sync_all()
}
}
impl Seek for RawFile {
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
match self.file.seek(newpos) {
Ok(pos) => {
self.position = pos;
Ok(pos)
}
Err(e) => Err(e),
}
}
}
impl WriteZeroesAt for RawFile {
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> std::io::Result<usize> {
self.file.write_zeroes_at(offset, length)
}
}
impl PunchHole for RawFile {
fn punch_hole(&mut self, offset: u64, length: u64) -> std::io::Result<()> {
self.file.punch_hole(offset, length)
}
}
impl SeekHole for RawFile {
fn seek_hole(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
match self.file.seek_hole(offset) {
Ok(pos) => {
if let Some(p) = pos {
self.position = p;
}
Ok(pos)
}
Err(e) => Err(e),
}
}
fn seek_data(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
match self.file.seek_data(offset) {
Ok(pos) => {
if let Some(p) = pos {
self.position = p;
}
Ok(pos)
}
Err(e) => Err(e),
}
}
}
impl Clone for RawFile {
fn clone(&self) -> Self {
RawFile {
file: self.file.try_clone().expect("RawFile cloning failed"),
alignment: self.alignment,
position: self.position,
}
}
}

252
block/src/qcow/refcount.rs Normal file
View File

@@ -0,0 +1,252 @@
// Copyright 2018 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.
use std::fmt::{self, Display};
use std::io;
use libc::EINVAL;
use crate::qcow::{
qcow_raw_file::QcowRawFile,
vec_cache::{CacheMap, Cacheable, VecCache},
};
#[derive(Debug)]
pub enum Error {
/// `EvictingCache` - Error writing a refblock from the cache to disk.
EvictingRefCounts(io::Error),
/// `InvalidIndex` - Address requested isn't within the range of the disk.
InvalidIndex,
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
NeedCluster(u64),
/// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function again.
NeedNewCluster,
/// `ReadingRefCounts` - Error reading the file in to the refcount cache.
ReadingRefCounts(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
EvictingRefCounts(e) => {
write!(f, "failed to write a refblock from the cache to disk: {e}")
}
InvalidIndex => write!(f, "address requested is not within the range of the disk"),
NeedCluster(addr) => write!(f, "cluster with addr={addr} needs to be read"),
NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
ReadingRefCounts(e) => {
write!(f, "failed to read the file into the refcount cache: {e}")
}
}
}
}
/// Represents the refcount entries for an open qcow file.
#[derive(Clone, Debug)]
pub struct RefCount {
ref_table: VecCache<u64>,
refcount_table_offset: u64,
refblock_cache: CacheMap<VecCache<u16>>,
refcount_block_entries: u64, // number of refcounts in a cluster.
cluster_size: u64,
max_valid_cluster_offset: u64,
}
impl RefCount {
/// Creates a `RefCount` from `file`, reading the refcount table from `refcount_table_offset`.
/// `refcount_table_entries` specifies the number of refcount blocks used by this image.
/// `refcount_block_entries` indicates the number of refcounts in each refcount block.
/// Each refcount table entry points to a refcount block.
pub fn new(
raw_file: &mut QcowRawFile,
refcount_table_offset: u64,
refcount_table_entries: u64,
refcount_block_entries: u64,
cluster_size: u64,
) -> io::Result<RefCount> {
let ref_table = VecCache::from_vec(raw_file.read_pointer_table(
refcount_table_offset,
refcount_table_entries,
None,
)?);
let max_valid_cluster_index = (ref_table.len() as u64) * refcount_block_entries - 1;
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
Ok(RefCount {
ref_table,
refcount_table_offset,
refblock_cache: CacheMap::new(50),
refcount_block_entries,
cluster_size,
max_valid_cluster_offset,
})
}
/// Returns the number of refcounts per block.
pub fn refcounts_per_block(&self) -> u64 {
self.refcount_block_entries
}
/// Returns the maximum valid cluster offset in the raw file for this refcount table.
pub fn max_valid_cluster_offset(&self) -> u64 {
self.max_valid_cluster_offset
}
/// Returns `NeedNewCluster` if a new cluster needs to be allocated for refcounts. If an
/// existing cluster needs to be read, `NeedCluster(addr)` is returned. The Caller should
/// allocate a cluster or read the required one and call this function again with the cluster.
/// On success, an optional address of a dropped cluster is returned. The dropped cluster can
/// be reused for other purposes.
pub fn set_cluster_refcount(
&mut self,
raw_file: &mut QcowRawFile,
cluster_address: u64,
refcount: u16,
mut new_cluster: Option<(u64, VecCache<u16>)>,
) -> Result<Option<u64>> {
let (table_index, block_index) = self.get_refcount_index(cluster_address);
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
// Fill the cache if this block isn't yet there.
if !self.refblock_cache.contains_key(table_index) {
// Need a new cluster
if let Some((addr, table)) = new_cluster.take() {
self.ref_table[table_index] = addr;
let ref_table = &self.ref_table;
self.refblock_cache
.insert(table_index, table, |index, evicted| {
raw_file.write_refcount_block(ref_table[index], evicted.get_values())
})
.map_err(Error::EvictingRefCounts)?;
} else {
if block_addr_disk == 0 {
return Err(Error::NeedNewCluster);
}
return Err(Error::NeedCluster(block_addr_disk));
}
}
// Unwrap is safe here as the entry was filled directly above.
let dropped_cluster = if !self.refblock_cache.get(table_index).unwrap().dirty() {
// Free the previously used block and use a new one. Writing modified counts to new
// blocks keeps the on-disk state consistent even if it's out of date.
if let Some((addr, _)) = new_cluster.take() {
self.ref_table[table_index] = addr;
Some(block_addr_disk)
} else {
return Err(Error::NeedNewCluster);
}
} else {
None
};
self.refblock_cache.get_mut(table_index).unwrap()[block_index] = refcount;
Ok(dropped_cluster)
}
/// Flush the dirty refcount blocks. This must be done before flushing the table that points to
/// the blocks.
pub fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> {
// Write out all dirty L2 tables.
for (table_index, block) in self.refblock_cache.iter_mut().filter(|(_k, v)| v.dirty()) {
let addr = self.ref_table[*table_index];
if addr != 0 {
raw_file.write_refcount_block(addr, block.get_values())?;
} else {
return Err(std::io::Error::from_raw_os_error(EINVAL));
}
block.mark_clean();
}
Ok(())
}
/// Flush the refcount table that keeps the address of the refcounts blocks.
/// Returns true if the table changed since the previous `flush_table()` call.
pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
if self.ref_table.dirty() {
raw_file.write_pointer_table(
self.refcount_table_offset,
self.ref_table.get_values(),
0,
)?;
self.ref_table.mark_clean();
Ok(true)
} else {
Ok(false)
}
}
/// Gets the refcount for a cluster with the given address.
pub fn get_cluster_refcount(
&mut self,
raw_file: &mut QcowRawFile,
address: u64,
) -> Result<u16> {
let (table_index, block_index) = self.get_refcount_index(address);
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
if block_addr_disk == 0 {
return Ok(0);
}
if !self.refblock_cache.contains_key(table_index) {
let table = VecCache::from_vec(
raw_file
.read_refcount_block(block_addr_disk)
.map_err(Error::ReadingRefCounts)?,
);
let ref_table = &self.ref_table;
self.refblock_cache
.insert(table_index, table, |index, evicted| {
raw_file.write_refcount_block(ref_table[index], evicted.get_values())
})
.map_err(Error::EvictingRefCounts)?;
}
Ok(self.refblock_cache.get(table_index).unwrap()[block_index])
}
/// Returns the refcount table for this file. This is only useful for debugging.
pub fn ref_table(&self) -> &[u64] {
self.ref_table.get_values()
}
/// Returns the refcounts stored in the given block.
pub fn refcount_block(
&mut self,
raw_file: &mut QcowRawFile,
table_index: usize,
) -> Result<Option<&[u16]>> {
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
if block_addr_disk == 0 {
return Ok(None);
}
if !self.refblock_cache.contains_key(table_index) {
let table = VecCache::from_vec(
raw_file
.read_refcount_block(block_addr_disk)
.map_err(Error::ReadingRefCounts)?,
);
// TODO(dgreid) - closure needs to return an error.
let ref_table = &self.ref_table;
self.refblock_cache
.insert(table_index, table, |index, evicted| {
raw_file.write_refcount_block(ref_table[index], evicted.get_values())
})
.map_err(Error::EvictingRefCounts)?;
}
// The index must exist as it was just inserted if it didn't already.
Ok(Some(
self.refblock_cache.get(table_index).unwrap().get_values(),
))
}
// Gets the address of the refcount block and the index into the block for the given address.
fn get_refcount_index(&self, address: u64) -> (usize, usize) {
let block_index = (address / self.cluster_size) % self.refcount_block_entries;
let refcount_table_index = (address / self.cluster_size) / self.refcount_block_entries;
(refcount_table_index as usize, block_index as usize)
}
}

185
block/src/qcow/vec_cache.rs Normal file
View File

@@ -0,0 +1,185 @@
// Copyright 2018 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.
use std::collections::hash_map::IterMut;
use std::collections::HashMap;
use std::io;
use std::ops::{Index, IndexMut};
use std::slice::SliceIndex;
/// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so
/// it can be checked if they need to be committed to disk.
pub trait Cacheable {
/// Used to check if the item needs to be written out or if it can be discarded.
fn dirty(&self) -> bool;
}
#[derive(Clone, Debug)]
/// Represents a vector that implements the `Cacheable` trait so it can be held in a cache.
pub struct VecCache<T: 'static + Copy + Default> {
vec: Box<[T]>,
dirty: bool,
}
impl<T: 'static + Copy + Default> VecCache<T> {
/// Creates a `VecCache` that can hold `count` elements.
pub fn new(count: usize) -> VecCache<T> {
VecCache {
vec: vec![Default::default(); count].into_boxed_slice(),
dirty: true,
}
}
/// Creates a `VecCache` from the passed in `vec`.
pub fn from_vec(vec: Vec<T>) -> VecCache<T> {
VecCache {
vec: vec.into_boxed_slice(),
dirty: false,
}
}
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
where
I: SliceIndex<[T]>,
{
self.vec.get(index)
}
/// Gets a reference to the underlying vector.
pub fn get_values(&self) -> &[T] {
&self.vec
}
/// Mark this cache element as clean.
pub fn mark_clean(&mut self) {
self.dirty = false;
}
/// Returns the number of elements in the vector.
pub fn len(&self) -> usize {
self.vec.len()
}
}
impl<T: 'static + Copy + Default> Cacheable for VecCache<T> {
fn dirty(&self) -> bool {
self.dirty
}
}
impl<T: 'static + Copy + Default> Index<usize> for VecCache<T> {
type Output = T;
fn index(&self, index: usize) -> &T {
self.vec.index(index)
}
}
impl<T: 'static + Copy + Default> IndexMut<usize> for VecCache<T> {
fn index_mut(&mut self, index: usize) -> &mut T {
self.dirty = true;
self.vec.index_mut(index)
}
}
#[derive(Clone, Debug)]
pub struct CacheMap<T: Cacheable> {
capacity: usize,
map: HashMap<usize, T>,
}
impl<T: Cacheable> CacheMap<T> {
pub fn new(capacity: usize) -> Self {
CacheMap {
capacity,
map: HashMap::with_capacity(capacity),
}
}
pub fn contains_key(&self, key: usize) -> bool {
self.map.contains_key(&key)
}
pub fn get(&self, index: usize) -> Option<&T> {
self.map.get(&index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.map.get_mut(&index)
}
pub fn iter_mut(&mut self) -> IterMut<usize, T> {
self.map.iter_mut()
}
// Check if the refblock cache is full and we need to evict.
pub fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()>
where
F: FnOnce(usize, T) -> io::Result<()>,
{
if self.map.len() == self.capacity {
// TODO(dgreid) - smarter eviction strategy.
let to_evict = *self.map.iter().next().unwrap().0;
if let Some(evicted) = self.map.remove(&to_evict) {
if evicted.dirty() {
write_callback(to_evict, evicted)?;
}
}
}
self.map.insert(index, block);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NumCache(pub u64);
impl Cacheable for NumCache {
fn dirty(&self) -> bool {
true
}
}
#[test]
fn evicts_when_full() {
let mut cache = CacheMap::<NumCache>::new(3);
let mut evicted = None;
cache
.insert(0, NumCache(5), |index, _| {
evicted = Some(index);
Ok(())
})
.unwrap();
assert_eq!(evicted, None);
cache
.insert(1, NumCache(6), |index, _| {
evicted = Some(index);
Ok(())
})
.unwrap();
assert_eq!(evicted, None);
cache
.insert(2, NumCache(7), |index, _| {
evicted = Some(index);
Ok(())
})
.unwrap();
assert_eq!(evicted, None);
cache
.insert(3, NumCache(8), |index, _| {
evicted = Some(index);
Ok(())
})
.unwrap();
assert!(evicted.is_some());
// Check that three of the four items inserted are still there and that the most recently
// inserted is one of them.
let num_items = (0..=3).filter(|k| cache.contains_key(*k)).count();
assert_eq!(num_items, 3);
assert!(cache.contains_key(3));
}
}

104
block/src/qcow_sync.rs Normal file
View File

@@ -0,0 +1,104 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
use crate::AsyncAdaptor;
use std::collections::VecDeque;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::sync::{Arc, Mutex, MutexGuard};
use vmm_sys_util::eventfd::EventFd;
pub struct QcowDiskSync {
qcow_file: Arc<Mutex<QcowFile>>,
}
impl QcowDiskSync {
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
Ok(QcowDiskSync {
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
})
}
}
impl DiskFile for QcowDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
let mut file = self.qcow_file.lock().unwrap();
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
}
}
pub struct QcowSync {
qcow_file: Arc<Mutex<QcowFile>>,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
}
impl QcowSync {
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
QcowSync {
qcow_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)
.expect("Failed creating EventFd for QcowSync"),
completion_list: VecDeque::new(),
}
}
}
impl AsyncAdaptor<QcowFile> for Arc<Mutex<QcowFile>> {
fn file(&mut self) -> MutexGuard<QcowFile> {
self.lock().unwrap()
}
}
impl AsyncIo for QcowSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
self.qcow_file.read_vectored_sync(
offset,
iovecs,
user_data,
&self.eventfd,
&mut self.completion_list,
)
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
self.qcow_file.write_vectored_sync(
offset,
iovecs,
user_data,
&self.eventfd,
&mut self.completion_list,
)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.qcow_file
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.completion_list.pop_front()
}
}

164
block/src/raw_async.rs Normal file
View File

@@ -0,0 +1,164 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
};
use io_uring::{opcode, squeue, types, IoUring};
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct RawFileDisk {
file: File,
}
impl RawFileDisk {
pub fn new(file: File) -> Self {
RawFileDisk { file }
}
}
impl DiskFile for RawFileDisk {
fn size(&mut self) -> DiskFileResult<u64> {
self.file
.seek(SeekFrom::End(0))
.map_err(DiskFileError::Size)
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn topology(&mut self) -> DiskTopology {
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
topology
} else {
warn!("Unable to get device topology. Using default topology");
DiskTopology::default()
}
}
}
pub struct RawFileAsync {
fd: RawFd,
io_uring: IoUring,
eventfd: EventFd,
}
impl RawFileAsync {
pub fn new(fd: RawFd, ring_depth: u32) -> std::io::Result<Self> {
let io_uring = IoUring::new(ring_depth)?;
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
// Register the io_uring eventfd that will notify when something in
// the completion queue is ready.
io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?;
Ok(RawFileAsync {
fd,
io_uring,
eventfd,
})
}
}
impl AsyncIo for RawFileAsync {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
let (submitter, mut sq, _) = self.io_uring.split();
// SAFETY: we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
let _ = unsafe {
sq.push(
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
.offset(offset)
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
)
};
// Update the submission queue and submit new operations to the
// io_uring instance.
sq.sync();
submitter.submit().map_err(AsyncIoError::ReadVectored)?;
Ok(())
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
let (submitter, mut sq, _) = self.io_uring.split();
// SAFETY: we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
let _ = unsafe {
sq.push(
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
.offset(offset)
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
)
};
// Update the submission queue and submit new operations to the
// io_uring instance.
sq.sync();
submitter.submit().map_err(AsyncIoError::WriteVectored)?;
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
if let Some(user_data) = user_data {
let (submitter, mut sq, _) = self.io_uring.split();
// SAFETY: we know the file descriptor is valid.
let _ = unsafe {
sq.push(
&opcode::Fsync::new(types::Fd(self.fd))
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
)
};
// Update the submission queue and submit new operations to the
// io_uring instance.
sq.sync();
submitter.submit().map_err(AsyncIoError::Fsync)?;
} else {
// SAFETY: FFI call with a valid fd
unsafe { libc::fsync(self.fd) };
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.io_uring
.completion()
.next()
.map(|entry| (entry.user_data(), entry.result()))
}
}

134
block/src/raw_sync.rs Normal file
View File

@@ -0,0 +1,134 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
};
use std::collections::VecDeque;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct RawFileDiskSync {
file: File,
}
impl RawFileDiskSync {
pub fn new(file: File) -> Self {
RawFileDiskSync { file }
}
}
impl DiskFile for RawFileDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
self.file
.seek(SeekFrom::End(0))
.map_err(DiskFileError::Size)
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
}
fn topology(&mut self) -> DiskTopology {
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
topology
} else {
warn!("Unable to get device topology. Using default topology");
DiskTopology::default()
}
}
}
pub struct RawFileSync {
fd: RawFd,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
}
impl RawFileSync {
pub fn new(fd: RawFd) -> Self {
RawFileSync {
fd,
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
completion_list: VecDeque::new(),
}
}
}
impl AsyncIo for RawFileSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
// SAFETY: FFI call with valid arguments
let result = unsafe {
libc::preadv(
self.fd as libc::c_int,
iovecs.as_ptr(),
iovecs.len() as libc::c_int,
offset,
)
};
if result < 0 {
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
}
self.completion_list.push_back((user_data, result as i32));
self.eventfd.write(1).unwrap();
Ok(())
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
// SAFETY: FFI call with valid arguments
let result = unsafe {
libc::pwritev(
self.fd as libc::c_int,
iovecs.as_ptr(),
iovecs.len() as libc::c_int,
offset,
)
};
if result < 0 {
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
}
self.completion_list.push_back((user_data, result as i32));
self.eventfd.write(1).unwrap();
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
// SAFETY: FFI call
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
if result < 0 {
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
}
if let Some(user_data) = user_data {
self.completion_list.push_back((user_data, result));
self.eventfd.write(1).unwrap();
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.completion_list.pop_front()
}
}

221
block/src/vhd.rs Normal file
View File

@@ -0,0 +1,221 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryInto;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
#[derive(Clone, Copy)]
pub struct VhdFooter {
cookie: u64,
features: u32,
file_format_version: u32,
data_offset: u64,
time_stamp: u32,
creator_application: u32,
creator_version: u32,
creator_host_os: u32,
original_size: u64,
current_size: u64,
disk_geometry: u32,
disk_type: u32,
checksum: u32,
unique_id: u128,
saved_state: u8,
}
impl VhdFooter {
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
// We must create a buffer aligned on 512 bytes with a size being a
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
#[repr(align(512))]
struct Sector {
data: [u8; 512],
}
let mut s = Sector { data: [0; 512] };
// Place the cursor 512 bytes before the end of the file, as this is
// where the footer starts.
file.seek(SeekFrom::End(-512))?;
// Fill in the VhdFooter structure
file.read_exact(&mut s.data)?;
Ok(VhdFooter {
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
})
}
pub fn cookie(&self) -> u64 {
self.cookie
}
pub fn features(&self) -> u32 {
self.features
}
pub fn file_format_version(&self) -> u32 {
self.file_format_version
}
pub fn data_offset(&self) -> u64 {
self.data_offset
}
pub fn time_stamp(&self) -> u32 {
self.time_stamp
}
pub fn creator_application(&self) -> u32 {
self.creator_application
}
pub fn creator_version(&self) -> u32 {
self.creator_version
}
pub fn creator_host_os(&self) -> u32 {
self.creator_host_os
}
pub fn original_size(&self) -> u64 {
self.original_size
}
pub fn current_size(&self) -> u64 {
self.current_size
}
pub fn disk_geometry(&self) -> u32 {
self.disk_geometry
}
pub fn disk_type(&self) -> u32 {
self.disk_type
}
pub fn checksum(&self) -> u32 {
self.checksum
}
pub fn unique_id(&self) -> u128 {
self.unique_id
}
pub fn saved_state(&self) -> u8 {
self.saved_state
}
}
/// Determine image type through file parsing.
pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
let footer = VhdFooter::new(f)?;
// "conectix" => 0x636f6e6563746978
Ok(footer.cookie() == 0x636f6e6563746978
&& footer.file_format_version() == 0x0001_0000
&& footer.data_offset() == 0xffff_ffff_ffff_ffff
&& footer.disk_type() == 0x2)
}
#[cfg(test)]
mod tests {
use super::{is_fixed_vhd, VhdFooter};
use std::fs::File;
use std::io::{Seek, SeekFrom, Write};
use vmm_sys_util::tempfile::TempFile;
fn valid_fixed_vhd_footer() -> Vec<u8> {
vec![
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
0x00, 0x00, 0x00, 0x02, // features
0x00, 0x01, 0x00, 0x00, // file format version
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // data offset
0x27, 0xa6, 0xa6, 0x5d, // time stamp
0x71, 0x65, 0x6d, 0x75, // creator application
0x00, 0x05, 0x00, 0x03, // creator version
0x57, 0x69, 0x32, 0x6b, // creator host os
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
0x11, 0xe0, 0x10, 0x3f, // disk geometry
0x00, 0x00, 0x00, 0x02, // disk type
0x00, 0x00, 0x00, 0x00, // checksum
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
0xf2, 0x23, // unique id
0x00, // saved state
]
}
fn valid_dynamic_vhd_footer() -> Vec<u8> {
vec![
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
0x00, 0x00, 0x00, 0x02, // features
0x00, 0x01, 0x00, 0x00, // file format version
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // data offset
0x27, 0xa6, 0xa6, 0x5d, // time stamp
0x71, 0x65, 0x6d, 0x75, // creator application
0x00, 0x05, 0x00, 0x03, // creator version
0x57, 0x69, 0x32, 0x6b, // creator host os
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
0x11, 0xe0, 0x10, 0x3f, // disk geometry
0x00, 0x00, 0x00, 0x03, // disk type
0x00, 0x00, 0x00, 0x00, // checksum
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
0xf2, 0x23, // unique id
0x00, // saved state
]
}
fn with_file<F>(footer: &[u8], mut testfn: F)
where
F: FnMut(File),
{
let mut disk_file: File = TempFile::new().unwrap().into_file();
disk_file.set_len(0x1000_0200).unwrap();
disk_file.seek(SeekFrom::Start(0x1000_0000)).unwrap();
disk_file.write_all(footer).unwrap();
testfn(disk_file); // File closed when the function exits.
}
#[test]
fn test_check_vhd_footer() {
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
let vhd_footer = VhdFooter::new(&mut file).expect("Failed to create VHD footer");
assert_eq!(vhd_footer.cookie(), 0x636f_6e65_6374_6978);
assert_eq!(vhd_footer.features(), 0x0000_0002);
assert_eq!(vhd_footer.file_format_version(), 0x0001_0000);
assert_eq!(vhd_footer.data_offset(), 0xffff_ffff_ffff_ffff);
assert_eq!(vhd_footer.time_stamp(), 0x27a6_a65d);
assert_eq!(vhd_footer.creator_application(), 0x7165_6d75);
assert_eq!(vhd_footer.creator_version(), 0x0005_0003);
assert_eq!(vhd_footer.creator_host_os(), 0x5769_326b);
assert_eq!(vhd_footer.original_size(), 0x0000_0000_1000_0000);
assert_eq!(vhd_footer.current_size(), 0x0000_0000_1000_0000);
assert_eq!(vhd_footer.disk_geometry(), 0x11e0_103f);
assert_eq!(vhd_footer.disk_type(), 0x0000_0002);
assert_eq!(vhd_footer.checksum(), 0x0000_0000);
assert_eq!(
vhd_footer.unique_id(),
0x987b_b1cd_8414_41fc_a4ab_d069_452b_f223
);
assert_eq!(vhd_footer.saved_state(), 0x00);
});
}
#[test]
fn test_is_fixed_vhd() {
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
assert!(is_fixed_vhd(&mut file).unwrap());
});
}
#[test]
fn test_is_not_fixed_vhd() {
with_file(&valid_dynamic_vhd_footer(), |mut file: File| {
assert!(!(is_fixed_vhd(&mut file).unwrap()));
});
}
}

237
block/src/vhdx/mod.rs Normal file
View File

@@ -0,0 +1,237 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::{
vhdx_bat::{BatEntry, VhdxBatError},
vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError},
vhdx_io::VhdxIoError,
vhdx_metadata::{DiskSpec, VhdxMetadataError},
};
use byteorder::{BigEndian, ByteOrder};
use remain::sorted;
use std::collections::btree_map::BTreeMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use thiserror::Error;
use uuid::Uuid;
macro_rules! div_round_up {
($n:expr,$d:expr) => {
($n + $d - 1) / $d
};
}
mod vhdx_bat;
mod vhdx_header;
mod vhdx_io;
mod vhdx_metadata;
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxError {
#[error("Not a VHDx file {0}")]
NotVhdx(#[source] VhdxHeaderError),
#[error("Failed to parse VHDx header {0}")]
ParseVhdxHeader(#[source] VhdxHeaderError),
#[error("Failed to parse VHDx metadata {0}")]
ParseVhdxMetadata(#[source] VhdxMetadataError),
#[error("Failed to parse VHDx region entries {0}")]
ParseVhdxRegionEntry(#[source] VhdxHeaderError),
#[error("Failed reading metadata {0}")]
ReadBatEntry(#[source] VhdxBatError),
#[error("Failed reading sector from disk {0}")]
ReadFailed(#[source] VhdxIoError),
#[error("Failed writing to sector on disk {0}")]
WriteFailed(#[source] VhdxIoError),
}
pub type Result<T> = std::result::Result<T, VhdxError>;
#[derive(Debug)]
pub struct Vhdx {
file: File,
vhdx_header: VhdxHeader,
region_entries: BTreeMap<u64, u64>,
bat_entry: RegionTableEntry,
mdr_entry: RegionTableEntry,
disk_spec: DiskSpec,
bat_entries: Vec<BatEntry>,
current_offset: u64,
first_write: bool,
}
impl Vhdx {
/// Parse the Vhdx header, BAT, and metadata from a file and store info
// in Vhdx structure.
pub fn new(mut file: File) -> Result<Vhdx> {
let vhdx_header = VhdxHeader::new(&mut file).map_err(VhdxError::ParseVhdxHeader)?;
let collected_entries = RegionInfo::new(
&mut file,
vhdx_header::REGION_TABLE_1_START,
vhdx_header.region_entry_count(),
)
.map_err(VhdxError::ParseVhdxRegionEntry)?;
let bat_entry = collected_entries.bat_entry;
let mdr_entry = collected_entries.mdr_entry;
let disk_spec =
DiskSpec::new(&mut file, &mdr_entry).map_err(VhdxError::ParseVhdxMetadata)?;
let bat_entries = BatEntry::collect_bat_entries(&mut file, &disk_spec, &bat_entry)
.map_err(VhdxError::ReadBatEntry)?;
Ok(Vhdx {
file,
vhdx_header,
region_entries: collected_entries.region_entries,
bat_entry,
mdr_entry,
disk_spec,
bat_entries,
current_offset: 0,
first_write: true,
})
}
pub fn virtual_disk_size(&self) -> u64 {
self.disk_spec.virtual_disk_size
}
}
impl Read for Vhdx {
/// Wrapper function to satisfy Read trait implementation for VHDx disk.
/// Convert the offset to sector index and buffer length to sector count.
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
let sector_count =
div_round_up!(buf.len() as u64, self.disk_spec.logical_sector_size as u64);
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
vhdx_io::read(
&mut self.file,
buf,
&self.disk_spec,
&self.bat_entries,
sector_index,
sector_count,
)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
),
)
})
}
}
impl Write for Vhdx {
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
self.file.flush()
}
/// Wrapper function to satisfy Write trait implementation for VHDx disk.
/// Convert the offset to sector index and buffer length to sector count.
fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
let sector_count =
div_round_up!(buf.len() as u64, self.disk_spec.logical_sector_size as u64);
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
if self.first_write {
self.first_write = false;
self.vhdx_header.update(&mut self.file).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to update VHDx header: {e}"),
)
})?;
}
vhdx_io::write(
&mut self.file,
buf,
&mut self.disk_spec,
self.bat_entry.file_offset,
&mut self.bat_entries,
sector_index,
sector_count,
)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
),
)
})
}
}
impl Seek for Vhdx {
/// Wrapper function to satisfy Seek trait implementation for VHDx disk.
/// Updates the offset field in the Vhdx struct.
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
let new_offset: Option<u64> = match pos {
SeekFrom::Start(off) => Some(off),
SeekFrom::End(off) => {
if off < 0 {
0i64.checked_sub(off).and_then(|increment| {
self.virtual_disk_size().checked_sub(increment as u64)
})
} else {
self.virtual_disk_size().checked_add(off as u64)
}
}
SeekFrom::Current(off) => {
if off < 0 {
0i64.checked_sub(off)
.and_then(|increment| self.current_offset.checked_sub(increment as u64))
} else {
self.current_offset.checked_add(off as u64)
}
}
};
if let Some(o) = new_offset {
if o <= self.virtual_disk_size() {
self.current_offset = o;
return Ok(o);
}
}
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Failed seek operation",
))
}
}
impl Clone for Vhdx {
fn clone(&self) -> Self {
Vhdx {
file: self.file.try_clone().unwrap(),
vhdx_header: self.vhdx_header.clone(),
region_entries: self.region_entries.clone(),
bat_entry: self.bat_entry,
mdr_entry: self.mdr_entry,
disk_spec: self.disk_spec.clone(),
bat_entries: self.bat_entries.clone(),
current_offset: self.current_offset,
first_write: self.first_write,
}
}
}
pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
// The first 3 fields of UUID are stored in Big Endian format, and
// the last 8 bytes are stored as byte array. Therefore, we read the
// first 3 fields in Big Endian format instead of Little Endian.
Uuid::from_fields_le(
BigEndian::read_u32(&buf[0..4]),
BigEndian::read_u16(&buf[4..6]),
BigEndian::read_u16(&buf[6..8]),
buf[8..16].try_into().unwrap(),
)
}

103
block/src/vhdx/vhdx_bat.rs Normal file
View File

@@ -0,0 +1,103 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::{vhdx_header::RegionTableEntry, vhdx_metadata::DiskSpec};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use remain::sorted;
use std::fs::File;
use std::io::{self, Seek, SeekFrom};
use std::mem::size_of;
use thiserror::Error;
// Payload BAT Entry States
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
pub const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
pub const PAYLOAD_BLOCK_ZERO: u64 = 2;
pub const PAYLOAD_BLOCK_UNMAPPED: u64 = 3;
pub const PAYLOAD_BLOCK_FULLY_PRESENT: u64 = 6;
pub const PAYLOAD_BLOCK_PARTIALLY_PRESENT: u64 = 7;
// Mask for the BAT state
pub const BAT_STATE_BIT_MASK: u64 = 0x07;
// Mask for the offset within the file in units of 1 MB
pub const BAT_FILE_OFF_MASK: u64 = 0xFFFFFFFFFFF00000;
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxBatError {
#[error("Invalid BAT entry")]
InvalidBatEntry,
#[error("Invalid BAT entry count")]
InvalidEntryCount,
#[error("Failed to read BAT entry {0}")]
ReadBat(#[source] io::Error),
#[error("Failed to write BAT entry {0}")]
WriteBat(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, VhdxBatError>;
#[derive(Default, Clone, Debug)]
pub struct BatEntry(pub u64);
impl BatEntry {
// Read all BAT entries presented on the disk and insert them to a vector
pub fn collect_bat_entries(
f: &mut File,
disk_spec: &DiskSpec,
bat_entry: &RegionTableEntry,
) -> Result<Vec<BatEntry>> {
let entry_count = BatEntry::calculate_entries(
disk_spec.block_size,
disk_spec.virtual_disk_size,
disk_spec.chunk_ratio,
);
if entry_count as usize > (bat_entry.length as usize / size_of::<BatEntry>()) {
return Err(VhdxBatError::InvalidEntryCount);
}
let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
let offset = bat_entry.file_offset;
for i in 0..entry_count {
f.seek(SeekFrom::Start(offset + i * size_of::<u64>() as u64))
.map_err(VhdxBatError::ReadBat)?;
let bat_entry = BatEntry(
f.read_u64::<LittleEndian>()
.map_err(VhdxBatError::ReadBat)?,
);
bat.insert(i as usize, bat_entry);
}
Ok(bat)
}
// Calculate the number of entries in the BAT
fn calculate_entries(block_size: u32, virtual_disk_size: u64, chunk_ratio: u64) -> u64 {
let data_blocks_count = div_round_up!(virtual_disk_size, block_size as u64);
data_blocks_count + (data_blocks_count - 1) / chunk_ratio
}
// Routine for writing BAT entries to the disk
pub fn write_bat_entries(
f: &mut File,
bat_offset: u64,
bat_entries: &[BatEntry],
) -> Result<()> {
for i in 0..bat_entries.len() as u64 {
f.seek(SeekFrom::Start(bat_offset + i * size_of::<u64>() as u64))
.map_err(VhdxBatError::WriteBat)?;
let bat_entry = match bat_entries.get(i as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxBatError::InvalidBatEntry);
}
};
f.write_u64::<LittleEndian>(bat_entry)
.map_err(VhdxBatError::WriteBat)?;
}
Ok(())
}
}

View File

@@ -0,0 +1,488 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
extern crate log;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use remain::sorted;
use std::collections::btree_map::BTreeMap;
use std::convert::TryInto;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use thiserror::Error;
use uuid::Uuid;
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
const HEADER_SIGN: u32 = 0x6461_6568; // "head"
const REGION_SIGN: u32 = 0x6967_6572; // "regi"
const FILE_START: u64 = 0; // The first element
const HEADER_1_START: u64 = 64 * 1024; // Header 1 start in Bytes
const HEADER_2_START: u64 = 128 * 1024; // Header 2 start in Bytes
pub const REGION_TABLE_1_START: u64 = 192 * 1024; // Region 1 start in Bytes
const REGION_TABLE_2_START: u64 = 256 * 1024; // Region 2 start in Bytes
const HEADER_SIZE: u64 = 4 * 1024; // Each header is 64 KiB, but only first 4 kiB contains info
const REGION_SIZE: u64 = 64 * 1024; // Each region size is 64 KiB
const REGION_ENTRY_REQUIRED: u32 = 1;
const BAT_GUID: &str = "2DC27766-F623-4200-9D64-115E9BFD4A08"; // BAT GUID
const MDR_GUID: &str = "8B7CA206-4790-4B9A-B8FE-575F050F886E"; // Metadata GUID
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxHeaderError {
#[error("Failed to calculate checksum")]
CalculateChecksum,
#[error("BAT entry is not unique")]
DuplicateBATEntry,
#[error("Metadata region entry is not unique")]
DuplicateMDREntry,
#[error("Checksum doesn't match for")]
InvalidChecksum(String),
#[error("Invalid entry count")]
InvalidEntryCount,
#[error("Not a valid VHDx header")]
InvalidHeaderSign,
#[error("Not a valid VHDx region")]
InvalidRegionSign,
#[error("Couldn't parse Uuid for region entry {0}")]
InvalidUuid(#[source] uuid::Error),
#[error("Not a VHDx file")]
InvalidVHDXSign,
#[error("No valid header found")]
NoValidHeader,
#[error("Cannot read checksum")]
ReadChecksum,
#[error("Failed to read File Type Identifier {0}")]
ReadFileTypeIdentifier(#[source] io::Error),
#[error("Failed to read headers {0}")]
ReadHeader(#[source] io::Error),
#[error("Failed to read metadata {0}")]
ReadMetadata(#[source] std::io::Error),
#[error("Failed to read region table entries {0}")]
ReadRegionTableEntries(#[source] io::Error),
#[error("Failed to read region table header {0}")]
ReadRegionTableHeader(#[source] io::Error),
#[error("Failed to read region entries")]
RegionEntryCollectionFailed,
#[error("Overlapping regions found")]
RegionOverlap,
#[error("Reserved region has non-zero value")]
ReservedIsNonZero,
#[error("Failed to seek in File Type Identifier {0}")]
SeekFileTypeIdentifier(#[source] io::Error),
#[error("Failed to seek in headers {0}")]
SeekHeader(#[source] io::Error),
#[error("Failed to seek in region table entries {0}")]
SeekRegionTableEntries(#[source] io::Error),
#[error("Failed to seek in region table header {0}")]
SeekRegionTableHeader(#[source] io::Error),
#[error("We do not recongize this entry")]
UnrecognizedRegionEntry,
#[error("Failed to write header {0}")]
WriteHeader(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
#[derive(Clone, Debug)]
pub struct FileTypeIdentifier {
pub signature: u64,
}
impl FileTypeIdentifier {
/// Reads the File Type Identifier structure from a reference VHDx file
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
f.seek(SeekFrom::Start(FILE_START))
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
let signature = f
.read_u64::<LittleEndian>()
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
if signature != VHDX_SIGN {
return Err(VhdxHeaderError::InvalidVHDXSign);
}
Ok(FileTypeIdentifier { signature })
}
}
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct Header {
pub signature: u32,
pub checksum: u32,
pub sequence_number: u64,
pub file_write_guid: u128,
pub data_write_guid: u128,
pub log_guid: u128,
pub log_version: u16,
pub version: u16,
pub log_length: u32,
pub log_offset: u64,
}
impl Header {
/// Reads the Header structure from a reference VHDx file
pub fn new(f: &mut File, start: u64) -> Result<Header> {
// Read the whole header in to a buffer. We will need it for
// calculating checksum.
let mut buffer = [0; HEADER_SIZE as usize];
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekHeader)?;
f.read_exact(&mut buffer)
.map_err(VhdxHeaderError::ReadHeader)?;
// SAFETY: buffer is of correct size and has been successfully filled.
let header = unsafe { *(buffer.as_ptr() as *mut Header) };
if header.signature != HEADER_SIGN {
return Err(VhdxHeaderError::InvalidHeaderSign);
}
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
if header.checksum != new_checksum {
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
}
Ok(header)
}
/// Converts the header structure into a buffer
fn get_header_as_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
// SAFETY: self is a valid header.
let reference = unsafe {
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
};
*buffer = reference.try_into().unwrap();
}
/// Creates and returns new updated header from the provided current header
pub fn update_header(
f: &mut File,
current_header: &Header,
change_data_guid: bool,
mut file_write_guid: u128,
start: u64,
) -> Result<Header> {
let mut buffer = [0u8; HEADER_SIZE as usize];
let mut data_write_guid = current_header.data_write_guid;
if change_data_guid {
data_write_guid = Uuid::new_v4().as_u128();
}
if file_write_guid == 0 {
file_write_guid = current_header.file_write_guid;
}
let mut new_header = Header {
signature: current_header.signature,
checksum: 0,
sequence_number: current_header.sequence_number + 1,
file_write_guid,
data_write_guid,
log_guid: current_header.log_guid,
log_version: current_header.log_version,
version: current_header.version,
log_length: current_header.log_length,
log_offset: current_header.log_offset,
};
new_header.get_header_as_buffer(&mut buffer);
new_header.checksum = crc32c::crc32c(&buffer);
new_header.get_header_as_buffer(&mut buffer);
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekHeader)?;
f.write(&buffer).map_err(VhdxHeaderError::WriteHeader)?;
Ok(new_header)
}
}
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
struct RegionTableHeader {
pub signature: u32,
pub checksum: u32,
pub entry_count: u32,
pub reserved: u32,
}
impl RegionTableHeader {
/// Reads the Region Table Header structure from a reference VHDx file
pub fn new(f: &mut File, start: u64) -> Result<RegionTableHeader> {
// Read the whole header into a buffer. We will need it for calculating
// checksum.
let mut buffer = [0u8; REGION_SIZE as usize];
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekRegionTableHeader)?;
f.read_exact(&mut buffer)
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
// SAFETY: buffer is of correct size and has been successfully filled.
let region_table_header = unsafe { *(buffer.as_ptr() as *mut RegionTableHeader) };
if region_table_header.signature != REGION_SIGN {
return Err(VhdxHeaderError::InvalidRegionSign);
}
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
if region_table_header.checksum != new_checksum {
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
}
if region_table_header.entry_count > 2047 {
return Err(VhdxHeaderError::InvalidEntryCount);
}
if region_table_header.reserved != 0 {
return Err(VhdxHeaderError::ReservedIsNonZero);
}
Ok(region_table_header)
}
}
pub struct RegionInfo {
pub bat_entry: RegionTableEntry,
pub mdr_entry: RegionTableEntry,
pub region_entries: BTreeMap<u64, u64>,
}
impl RegionInfo {
/// Collect all entries in a BTreeMap from the Region Table and identifies
/// BAT and metadata regions
pub fn new(f: &mut File, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
let mut bat_entry: Option<RegionTableEntry> = None;
let mut mdr_entry: Option<RegionTableEntry> = None;
let mut offset = 0;
let mut region_entries = BTreeMap::new();
let mut buffer = [0; REGION_SIZE as usize];
// Seek after the Region Table Header
f.seek(SeekFrom::Start(
region_start + size_of::<RegionTableHeader>() as u64,
))
.map_err(VhdxHeaderError::SeekRegionTableEntries)?;
f.read_exact(&mut buffer)
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
for _ in 0..entry_count {
let entry =
RegionTableEntry::new(&buffer[offset..offset + size_of::<RegionTableEntry>()])?;
offset += size_of::<RegionTableEntry>();
let start = entry.file_offset;
let end = start + entry.length as u64;
for (region_ent_start, region_ent_end) in region_entries.iter() {
if !((start >= *region_ent_start) || (end <= *region_ent_end)) {
return Err(VhdxHeaderError::RegionOverlap);
}
}
region_entries.insert(entry.file_offset, entry.file_offset + entry.length as u64);
if entry.guid == Uuid::parse_str(BAT_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
if bat_entry.is_none() {
bat_entry = Some(entry);
continue;
}
return Err(VhdxHeaderError::DuplicateBATEntry);
}
if entry.guid == Uuid::parse_str(MDR_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
if mdr_entry.is_none() {
mdr_entry = Some(entry);
continue;
}
return Err(VhdxHeaderError::DuplicateMDREntry);
}
if (entry.required & REGION_ENTRY_REQUIRED) == 1 {
// This implementation doesn't recognize this field.
// Therefore, accoding to the spec, we are throwing an error.
return Err(VhdxHeaderError::UnrecognizedRegionEntry);
}
}
if bat_entry.is_none() || mdr_entry.is_none() {
region_entries.clear();
return Err(VhdxHeaderError::RegionEntryCollectionFailed);
}
// It's safe to unwrap as we checked both entries have been filled.
// Otherwise, an error is already returned.
let bat_entry = bat_entry.unwrap();
let mdr_entry = mdr_entry.unwrap();
Ok(RegionInfo {
bat_entry,
mdr_entry,
region_entries,
})
}
}
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct RegionTableEntry {
pub guid: Uuid,
pub file_offset: u64,
pub length: u32,
pub required: u32,
}
impl RegionTableEntry {
/// Reads one Region Entry from a Region Table index that starts from 0
pub fn new(buffer: &[u8]) -> Result<RegionTableEntry> {
assert!(buffer.len() == std::mem::size_of::<RegionTableEntry>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let mut region_table_entry = unsafe { *(buffer.as_ptr() as *mut RegionTableEntry) };
let uuid = crate::vhdx::uuid_from_guid(buffer);
region_table_entry.guid = uuid;
Ok(region_table_entry)
}
}
#[derive(Clone, Debug)]
struct RegionEntry {
_start: u64,
_end: u64,
}
enum HeaderNo {
First,
Second,
}
/// Contains the information from the header of a VHDx file
#[derive(Clone, Debug)]
pub struct VhdxHeader {
_file_type_identifier: FileTypeIdentifier,
header_1: Header,
header_2: Header,
region_table_1: RegionTableHeader,
_region_table_2: RegionTableHeader,
}
impl VhdxHeader {
/// Creates a VhdxHeader from a reference to a file
pub fn new(f: &mut File) -> Result<VhdxHeader> {
let _file_type_identifier: FileTypeIdentifier = FileTypeIdentifier::new(f)?;
let header_1 = Header::new(f, HEADER_1_START);
let header_2 = Header::new(f, HEADER_2_START);
let mut file_write_guid: u128 = 0;
let metadata = f.metadata().map_err(VhdxHeaderError::ReadMetadata)?;
if !metadata.permissions().readonly() {
file_write_guid = Uuid::new_v4().as_u128();
}
let (header_1, header_2) =
VhdxHeader::update_headers(f, header_1, header_2, file_write_guid)?;
Ok(VhdxHeader {
_file_type_identifier,
header_1,
header_2,
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
})
}
/// Identify the current header and return both headers along with an
/// integer indicating the current header.
fn current_header(
header_1: Result<Header>,
header_2: Result<Header>,
) -> Result<(HeaderNo, Header)> {
let header_1 = header_1.ok();
let header_2 = header_2.ok();
match (header_1, header_2) {
(None, None) => Err(VhdxHeaderError::NoValidHeader),
(Some(header_1), None) => Ok((HeaderNo::First, header_1)),
(None, Some(header_2)) => Ok((HeaderNo::Second, header_2)),
(Some(header_1), Some(header_2)) => {
if header_1.sequence_number >= header_2.sequence_number {
Ok((HeaderNo::First, header_1))
} else {
Ok((HeaderNo::Second, header_2))
}
}
}
}
/// This takes two headers and update the noncurrent header with the
/// current one. Returns both headers as a tuple sequenced the way it was
/// received from the parameter list.
fn update_header(
f: &mut File,
header_1: Result<Header>,
header_2: Result<Header>,
guid: u128,
) -> Result<(Header, Header)> {
let (header_no, current_header) = VhdxHeader::current_header(header_1, header_2)?;
match header_no {
HeaderNo::First => {
let other_header =
Header::update_header(f, &current_header, true, guid, HEADER_2_START)?;
Ok((current_header, other_header))
}
HeaderNo::Second => {
let other_header =
Header::update_header(f, &current_header, true, guid, HEADER_1_START)?;
Ok((other_header, current_header))
}
}
}
// Update the provided headers accoding to the spec
fn update_headers(
f: &mut File,
header_1: Result<Header>,
header_2: Result<Header>,
guid: u128,
) -> Result<(Header, Header)> {
// According to the spec, update twice
let (header_1, header_2) = VhdxHeader::update_header(f, header_1, header_2, guid)?;
VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
}
pub fn update(&mut self, f: &mut File) -> Result<()> {
let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
self.header_1 = headers.0;
self.header_2 = headers.1;
Ok(())
}
pub fn region_entry_count(&self) -> u32 {
self.region_table_1.entry_count
}
}
/// Calculates the checksum of a buffer that itself containts its checksum
/// Therefore, before calculating, the existing checksum is retrieved and the
/// corresponding field is made zero. After the calculation, the existing checksum
/// is put back to the buffer.
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
// Read the checksum into a mutable slice
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
// Convert the checksum chunk in to a u32 integer
let orig_csum = LittleEndian::read_u32(csum_buf);
// Zero the checksum in the buffer
LittleEndian::write_u32(csum_buf, 0);
// Calculate the checksum on the resulting buffer
let new_csum = crc32c::crc32c(buffer);
// Put back the original checksum in the buffer
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
Ok(new_csum)
}

226
block/src/vhdx/vhdx_io.rs Normal file
View File

@@ -0,0 +1,226 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::{
vhdx_bat::{self, BatEntry, VhdxBatError},
vhdx_metadata::{self, DiskSpec},
};
use remain::sorted;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use thiserror::Error;
const SECTOR_SIZE: u64 = 512;
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxIoError {
#[error("Invalid BAT entry state")]
InvalidBatEntryState,
#[error("Invalid BAT entry count")]
InvalidBatIndex,
#[error("Invalid disk size")]
InvalidDiskSize,
#[error("Failed reading sector blocks from file {0}")]
ReadSectorBlock(#[source] io::Error),
#[error("Failed changing file length {0}")]
ResizeFile(#[source] io::Error),
#[error("Differencing mode is not supported yet")]
UnsupportedMode,
#[error("Failed writing BAT to file {0}")]
WriteBat(#[source] VhdxBatError),
}
pub type Result<T> = std::result::Result<T, VhdxIoError>;
macro_rules! align {
($n:expr, $align:expr) => {{
(($n + $align - 1) / $align) * $align
}};
}
#[derive(Default)]
struct Sector {
bat_index: u64,
free_sectors: u64,
free_bytes: u64,
file_offset: u64,
block_offset: u64,
}
impl Sector {
/// Translate sector index and count of data in file to actual offsets and
/// BAT index.
pub fn new(
disk_spec: &DiskSpec,
bat: &[BatEntry],
sector_index: u64,
sector_count: u64,
) -> Result<Sector> {
let mut sector = Sector::default();
sector.bat_index = sector_index / disk_spec.sectors_per_block as u64;
sector.block_offset = sector_index % disk_spec.sectors_per_block as u64;
sector.free_sectors = disk_spec.sectors_per_block as u64 - sector.block_offset;
if sector.free_sectors > sector_count {
sector.free_sectors = sector_count;
}
sector.free_bytes = sector.free_sectors * disk_spec.logical_sector_size as u64;
sector.block_offset *= disk_spec.logical_sector_size as u64;
let bat_entry = match bat.get(sector.bat_index as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxIoError::InvalidBatIndex);
}
};
sector.file_offset = bat_entry & vhdx_bat::BAT_FILE_OFF_MASK;
if sector.file_offset != 0 {
sector.file_offset += sector.block_offset;
}
Ok(sector)
}
}
/// VHDx IO read routine: requires relative sector index and count for the
/// requested data.
pub fn read(
f: &mut File,
buf: &mut [u8],
disk_spec: &DiskSpec,
bat: &[BatEntry],
mut sector_index: u64,
mut sector_count: u64,
) -> Result<usize> {
let mut read_count: usize = 0;
while sector_count > 0 {
if disk_spec.has_parent {
return Err(VhdxIoError::UnsupportedMode);
} else {
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
let bat_entry = match bat.get(sector.bat_index as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxIoError::InvalidBatIndex);
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
f.seek(SeekFrom::Start(sector.file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.read_exact(
&mut buf[read_count
..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
return Err(VhdxIoError::InvalidBatEntryState);
}
};
sector_count -= sector.free_sectors;
sector_index += sector.free_sectors;
read_count = sector.free_bytes as usize;
};
}
Ok(read_count)
}
/// VHDx IO write routine: requires relative sector index and count for the
/// requested data.
pub fn write(
f: &mut File,
buf: &[u8],
disk_spec: &mut DiskSpec,
bat_offset: u64,
bat: &mut [BatEntry],
mut sector_index: u64,
mut sector_count: u64,
) -> Result<usize> {
let mut write_count: usize = 0;
while sector_count > 0 {
if disk_spec.has_parent {
return Err(VhdxIoError::UnsupportedMode);
} else {
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
let bat_entry = match bat.get(sector.bat_index as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxIoError::InvalidBatIndex);
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
let file_offset =
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
let new_size = file_offset
.checked_add(disk_spec.block_size as u64)
.ok_or(VhdxIoError::InvalidDiskSize)?;
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
disk_spec.image_size = new_size;
let new_bat_entry = file_offset
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
BatEntry::write_bat_entries(f, bat_offset, bat)
.map_err(VhdxIoError::WriteBat)?;
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
break;
}
f.seek(SeekFrom::Start(file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
&buf[write_count
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
break;
}
f.seek(SeekFrom::Start(sector.file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
&buf[write_count
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
return Err(VhdxIoError::InvalidBatEntryState);
}
};
sector_count -= sector.free_sectors;
sector_index += sector.free_sectors;
write_count = sector.free_bytes as usize;
};
}
Ok(write_count)
}

View File

@@ -0,0 +1,323 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::vhdx_header::RegionTableEntry;
use byteorder::{LittleEndian, ReadBytesExt};
use remain::sorted;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::mem::size_of;
use thiserror::Error;
use uuid::Uuid;
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
const METADATA_ENTRY_SIZE: usize = 32;
const METADATA_MAX_ENTRIES: u16 = 2047;
// The size including the table header and entries
const METADATA_TABLE_MAX_SIZE: usize = METADATA_ENTRY_SIZE * (METADATA_MAX_ENTRIES as usize + 1);
const METADATA_FLAGS_IS_REQUIRED: u32 = 0x04;
pub const BLOCK_SIZE_MIN: u32 = 1 << 20; // 1 MiB
const BLOCK_SIZE_MAX: u32 = 256 << 20; // 256 MiB
const MAX_SECTORS_PER_BLOCK: u64 = 1 << 23;
const BLOCK_HAS_PARENT: u32 = 0x02; // Has a parent or a backing file
// GUID for known metadata items
const METADATA_FILE_PARAMETER: &str = "CAA16737-FA36-4D43-B3B6-33F0AA44E76B";
const METADATA_VIRTUAL_DISK_SIZE: &str = "2FA54224-CD1B-4876-B211-5DBED83BF4B8";
const METADATA_VIRTUAL_DISK_ID: &str = "BECA12AB-B2E6-4523-93EF-C309E000C746";
const METADATA_LOGICAL_SECTOR_SIZE: &str = "8141BF1D-A96F-4709-BA47-F233A8FAAB5F";
const METADATA_PHYSICAL_SECTOR_SIZE: &str = "CDA348C7-445D-4471-9CC9-E9885251C556";
const METADATA_PARENT_LOCATOR: &str = "A8D35F2D-B30B-454D-ABF7-D3D84834AB0C";
const METADATA_FILE_PARAMETER_PRESENT: u16 = 0x01;
const METADATA_VIRTUAL_DISK_SIZE_PRESENT: u16 = 0x02;
const METADATA_VIRTUAL_DISK_ID_PRESENT: u16 = 0x04;
const METADATA_LOGICAL_SECTOR_SIZE_PRESENT: u16 = 0x08;
const METADATA_PHYSICAL_SECTOR_SIZE_PRESENT: u16 = 0x10;
const METADATA_PARENT_LOCATOR_PRESENT: u16 = 0x20;
const METADATA_ALL_PRESENT: u16 = METADATA_FILE_PARAMETER_PRESENT
| METADATA_VIRTUAL_DISK_SIZE_PRESENT
| METADATA_VIRTUAL_DISK_ID_PRESENT
| METADATA_LOGICAL_SECTOR_SIZE_PRESENT
| METADATA_PHYSICAL_SECTOR_SIZE_PRESENT;
const METADATA_LENGTH_MAX: u32 = 1 << 20; // 1 MiB
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxMetadataError {
#[error("Invalid block size count")]
InvalidBlockSize,
#[error("Invalid metadata entry count")]
InvalidEntryCount,
#[error("Invalid logical sector size")]
InvalidLogicalSectorSize,
#[error("Invalid metadata ID")]
InvalidMetadataItem,
#[error("Invalid metadata length")]
InvalidMetadataLength,
#[error("Metadata sign doesn't match")]
InvalidMetadataSign,
#[error("Invalid physical sector size")]
InvalidPhysicalSectorSize,
#[error("Invalid UUID")]
InvalidUuid(#[source] uuid::Error),
#[error("Invalid value")]
InvalidValue,
#[error("Not all required metadata found")]
MissingMetadata,
#[error("Failed to read metadata headers {0}")]
ReadMetadata(#[source] io::Error),
#[error("Reserved region has non-zero value")]
ReservedIsNonZero,
#[error("This implementation doesn't support this metadata flag")]
UnsupportedFlag,
}
pub type Result<T> = std::result::Result<T, VhdxMetadataError>;
#[derive(Default, Clone, Debug)]
pub struct DiskSpec {
pub disk_id: u128,
pub image_size: u64,
pub block_size: u32,
pub has_parent: bool,
pub sectors_per_block: u32,
pub virtual_disk_size: u64,
pub logical_sector_size: u32,
pub physical_sector_size: u32,
pub chunk_ratio: u64,
pub total_sectors: u64,
}
impl DiskSpec {
/// Parse all meatadata from the provided file and store info in DiskSpec
/// structure.
pub fn new(f: &mut File, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
let mut disk_spec = DiskSpec::default();
let mut metadata_presence: u16 = 0;
let mut offset = 0;
let metadata = f.metadata().map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.image_size = metadata.len();
let mut buffer = [0u8; METADATA_TABLE_MAX_SIZE];
f.seek(SeekFrom::Start(metadata_region.file_offset))
.map_err(VhdxMetadataError::ReadMetadata)?;
f.read_exact(&mut buffer)
.map_err(VhdxMetadataError::ReadMetadata)?;
let metadata_header =
MetadataTableHeader::new(&buffer[0..size_of::<MetadataTableHeader>()])?;
offset += size_of::<MetadataTableHeader>();
for _ in 0..metadata_header.entry_count {
let metadata_entry =
MetadataTableEntry::new(&buffer[offset..offset + size_of::<MetadataTableEntry>()])?;
f.seek(SeekFrom::Start(
metadata_region.file_offset + metadata_entry.offset as u64,
))
.map_err(VhdxMetadataError::ReadMetadata)?;
if metadata_entry.item_id
== Uuid::parse_str(METADATA_FILE_PARAMETER)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.block_size = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
// MUST be at least 1 MiB and not greater than 256 MiB
if disk_spec.block_size < BLOCK_SIZE_MIN && disk_spec.block_size > BLOCK_SIZE_MAX {
return Err(VhdxMetadataError::InvalidBlockSize);
}
// MUST be power of 2
if !disk_spec.block_size.is_power_of_two() {
return Err(VhdxMetadataError::InvalidBlockSize);
}
let bits = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.has_parent = bits & BLOCK_HAS_PARENT != 0;
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_VIRTUAL_DISK_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.virtual_disk_size = f
.read_u64::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
metadata_presence |= METADATA_VIRTUAL_DISK_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_VIRTUAL_DISK_ID)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.disk_id = f
.read_u128::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
metadata_presence |= METADATA_VIRTUAL_DISK_ID_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_LOGICAL_SECTOR_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.logical_sector_size = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
if !(disk_spec.logical_sector_size == 512 || disk_spec.logical_sector_size == 4096)
{
return Err(VhdxMetadataError::InvalidLogicalSectorSize);
}
metadata_presence |= METADATA_LOGICAL_SECTOR_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_PHYSICAL_SECTOR_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.physical_sector_size = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
if !(disk_spec.physical_sector_size == 512
|| disk_spec.physical_sector_size == 4096)
{
return Err(VhdxMetadataError::InvalidPhysicalSectorSize);
}
metadata_presence |= METADATA_PHYSICAL_SECTOR_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_PARENT_LOCATOR)
.map_err(VhdxMetadataError::InvalidUuid)?
{
metadata_presence |= METADATA_PARENT_LOCATOR_PRESENT;
} else {
return Err(VhdxMetadataError::InvalidMetadataItem);
}
if (metadata_entry.flag_bits & METADATA_FLAGS_IS_REQUIRED) == 0 {
return Err(VhdxMetadataError::UnsupportedFlag);
}
offset += size_of::<MetadataTableEntry>();
}
// Check if all required metadata are present
if metadata_presence != METADATA_ALL_PRESENT {
return Err(VhdxMetadataError::MissingMetadata);
}
// Check if the virtual disk size is a multiple of the logical sector
// size.
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
&& (disk_spec.virtual_disk_size % disk_spec.logical_sector_size as u64 != 0)
{
return Err(VhdxMetadataError::InvalidBlockSize);
}
disk_spec.sectors_per_block =
DiskSpec::sectors_per_block(disk_spec.block_size, disk_spec.logical_sector_size)?;
disk_spec.chunk_ratio =
DiskSpec::chunk_ratio(disk_spec.block_size, disk_spec.logical_sector_size)?;
disk_spec.total_sectors =
disk_spec.virtual_disk_size / disk_spec.logical_sector_size as u64;
Ok(disk_spec)
}
/// Calculates the number of sectors per block
fn sectors_per_block(block_size: u32, logical_sector_size: u32) -> Result<u32> {
let sectors_per_block = block_size / logical_sector_size;
if !sectors_per_block.is_power_of_two() {
return Err(VhdxMetadataError::InvalidValue);
}
Ok(sectors_per_block)
}
/// Calculate the chunk ratio
fn chunk_ratio(block_size: u32, logical_sector_size: u32) -> Result<u64> {
let chunk_ratio = (MAX_SECTORS_PER_BLOCK * logical_sector_size as u64) / block_size as u64;
if !chunk_ratio.is_power_of_two() {
return Err(VhdxMetadataError::InvalidValue);
}
Ok(chunk_ratio)
}
}
#[repr(packed)]
#[derive(Default, Debug, Clone, Copy)]
struct MetadataTableHeader {
signature: u64,
reserved: u16,
entry_count: u16,
_reserved2: [u8; 20],
}
impl MetadataTableHeader {
pub fn new(buffer: &[u8]) -> Result<MetadataTableHeader> {
assert!(buffer.len() == std::mem::size_of::<MetadataTableHeader>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let metadata_table_header = unsafe { *(buffer.as_ptr() as *mut MetadataTableHeader) };
if metadata_table_header.signature != METADATA_SIGN {
return Err(VhdxMetadataError::InvalidMetadataSign);
}
if metadata_table_header.entry_count > METADATA_MAX_ENTRIES {
return Err(VhdxMetadataError::InvalidEntryCount);
}
if metadata_table_header.reserved != 0 {
return Err(VhdxMetadataError::ReservedIsNonZero);
}
Ok(metadata_table_header)
}
}
#[repr(packed)]
#[derive(Default, Debug, Clone, Copy)]
pub struct MetadataTableEntry {
item_id: Uuid,
offset: u32,
length: u32,
flag_bits: u32,
reserved: u32,
}
impl MetadataTableEntry {
/// Parse one metadata entry from the buffer
fn new(buffer: &[u8]) -> Result<MetadataTableEntry> {
assert!(buffer.len() == std::mem::size_of::<MetadataTableEntry>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let mut metadata_table_entry = unsafe { *(buffer.as_ptr() as *mut MetadataTableEntry) };
let uuid = crate::vhdx::uuid_from_guid(buffer);
metadata_table_entry.item_id = uuid;
if metadata_table_entry.length > METADATA_LENGTH_MAX {
return Err(VhdxMetadataError::InvalidMetadataLength);
}
if metadata_table_entry.length == 0 && metadata_table_entry.offset != 0 {
return Err(VhdxMetadataError::InvalidMetadataLength);
}
if metadata_table_entry.reserved != 0 {
return Err(VhdxMetadataError::ReservedIsNonZero);
}
Ok(metadata_table_entry)
}
}

103
block/src/vhdx_sync.rs Normal file
View File

@@ -0,0 +1,103 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
use crate::vhdx::{Result as VhdxResult, Vhdx};
use crate::AsyncAdaptor;
use std::collections::VecDeque;
use std::fs::File;
use std::sync::{Arc, Mutex, MutexGuard};
use vmm_sys_util::eventfd::EventFd;
pub struct VhdxDiskSync {
vhdx_file: Arc<Mutex<Vhdx>>,
}
impl VhdxDiskSync {
pub fn new(f: File) -> VhdxResult<Self> {
Ok(VhdxDiskSync {
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)),
})
}
}
impl DiskFile for VhdxDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(
Box::new(VhdxSync::new(self.vhdx_file.clone()).map_err(DiskFileError::NewAsyncIo)?)
as Box<dyn AsyncIo>,
)
}
}
pub struct VhdxSync {
vhdx_file: Arc<Mutex<Vhdx>>,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
}
impl VhdxSync {
pub fn new(vhdx_file: Arc<Mutex<Vhdx>>) -> std::io::Result<Self> {
Ok(VhdxSync {
vhdx_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
completion_list: VecDeque::new(),
})
}
}
impl AsyncAdaptor<Vhdx> for Arc<Mutex<Vhdx>> {
fn file(&mut self) -> MutexGuard<Vhdx> {
self.lock().unwrap()
}
}
impl AsyncIo for VhdxSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
self.vhdx_file.read_vectored_sync(
offset,
iovecs,
user_data,
&self.eventfd,
&mut self.completion_list,
)
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
self.vhdx_file.write_vectored_sync(
offset,
iovecs,
user_data,
&self.eventfd,
&mut self.completion_list,
)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.vhdx_file
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.completion_list.pop_front()
}
}