use std::fmt;
use std::io::{Read, Seek, Write};
use gpt_disk_io::BlockIo;
use gpt_disk_types::{BlockSize, Lba};
use crate::Medium;
use crate::error::Error;
use crate::medium::{Len, SetLen, SyncData};
#[derive(Debug)]
pub struct VhdxBlockIoError(pub Error);
impl fmt::Display for VhdxBlockIoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for VhdxBlockIoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.source()
}
}
pub struct VhdxBlockDevice<T = std::fs::File> {
medium: Medium<T>,
sector_size: u32,
block_size: BlockSize,
num_blocks: u64,
}
impl<T> VhdxBlockDevice<T> {
pub fn new(medium: Medium<T>) -> Result<Self, Error>
where
T: Read + Seek,
{
let (sector_size, virtual_size) = {
let sections = medium.sections()?;
let metadata = sections.metadata()?;
let items = metadata.items();
(items.logical_sector_size()?, items.virtual_disk_size()?)
};
let block_size = BlockSize::new(sector_size).ok_or_else(|| {
Error::InvalidMetadata(format!(
"logical sector size {sector_size} is not a valid GPT block size (minimum 512)"
))
})?;
let num_blocks = virtual_size / u64::from(sector_size);
Ok(Self {
medium,
sector_size,
block_size,
num_blocks,
})
}
#[must_use]
pub fn medium(&self) -> &Medium<T> {
&self.medium
}
pub fn medium_mut(&mut self) -> &mut Medium<T> {
&mut self.medium
}
#[must_use]
pub fn into_medium(self) -> Medium<T> {
self.medium
}
}
impl<T> BlockIo for VhdxBlockDevice<T>
where
T: Read + Write + Seek + Len + SetLen + SyncData,
{
type Error = VhdxBlockIoError;
fn block_size(&self) -> BlockSize {
self.block_size
}
fn num_blocks(&mut self) -> Result<u64, Self::Error> {
Ok(self.num_blocks)
}
fn read_blocks(&mut self, start_lba: Lba, dst: &mut [u8]) -> Result<(), Self::Error> {
use std::io::Read;
let lba = start_lba.to_u64();
let sector_size = self.sector_size as usize;
assert!(
dst.len().is_multiple_of(sector_size),
"read_blocks: dst buffer length {} is not a multiple of sector size {}",
dst.len(),
sector_size,
);
let count = (dst.len() / sector_size) as u64;
let mut io = self.medium.io().map_err(VhdxBlockIoError)?;
let mut sector = io.sector(lba, count).map_err(VhdxBlockIoError)?;
sector
.read_exact(dst)
.map_err(|e| VhdxBlockIoError(Error::Io(e)))?;
Ok(())
}
fn write_blocks(&mut self, start_lba: Lba, src: &[u8]) -> Result<(), Self::Error> {
use std::io::Write;
let lba = start_lba.to_u64();
let sector_size = self.sector_size as usize;
assert!(
src.len().is_multiple_of(sector_size),
"write_blocks: src buffer length {} is not a multiple of sector size {}",
src.len(),
sector_size,
);
let count = (src.len() / sector_size) as u64;
let mut io = self.medium.io().map_err(VhdxBlockIoError)?;
let mut sector = io.sector(lba, count).map_err(VhdxBlockIoError)?;
sector
.write_all(src)
.map_err(|e| VhdxBlockIoError(Error::Io(e)))?;
sector.flush().map_err(|e| VhdxBlockIoError(Error::Io(e)))?;
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.medium
.inner_mut()
.sync_data()
.map_err(|e| VhdxBlockIoError(Error::Io(e)))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_device_accepts_cursor_backed_medium() {
let cursor = std::io::Cursor::new(Vec::new());
let medium = Medium::create(cursor)
.size(16 * 1024 * 1024)
.finish()
.expect("create cursor-backed medium");
let block_device = VhdxBlockDevice::new(medium).expect("wrap cursor-backed medium");
let medium = block_device.into_medium();
let _cursor = medium.into_inner();
}
}