use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::Path;
use crate::backing::Backing;
use crate::bat::Bat;
use crate::error::{Result, VhdxError};
use crate::header::{parse_active_header, REGION_TABLE1_OFFSET, REGION_TABLE2_OFFSET};
use crate::log::LogOverlay;
use crate::metadata::{parse_metadata, VhdxMetadata};
use crate::region::parse_region_table;
use crate::FILE_MAGIC;
#[derive(Debug)]
pub struct VhdxReader {
backing: Backing,
overlay: LogOverlay,
bat: Bat,
meta: VhdxMetadata,
pos: u64,
parent: Option<Box<VhdxReader>>,
}
const MIN_CONTAINER_SIZE: u64 = 0x0025_0000;
impl VhdxReader {
pub fn open(path: &Path) -> Result<Self> {
let file = File::open(path)?;
Self::from_backing(Backing::File(file), None)
}
pub fn from_backing(backing: Backing, parent: Option<Box<VhdxReader>>) -> Result<Self> {
Self::parse_backing(backing, parent)
}
pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
Self::parse_bytes(data, None)
}
pub fn from_bytes_with_parent(data: Vec<u8>, parent: VhdxReader) -> Result<Self> {
Self::parse_bytes(data, Some(Box::new(parent)))
}
pub fn from_backing_with_parent(backing: Backing, parent: VhdxReader) -> Result<Self> {
Self::parse_backing(backing, Some(Box::new(parent)))
}
fn parse_bytes(mut data: Vec<u8>, parent: Option<Box<VhdxReader>>) -> Result<Self> {
if data.len() < 8 || &data[0..8] != FILE_MAGIC {
return Err(VhdxError::BadMagic);
}
if (data.len() as u64) < MIN_CONTAINER_SIZE {
return Err(VhdxError::ContainerTooSmall(MIN_CONTAINER_SIZE));
}
crate::log::apply(&mut data)?;
let backing = Backing::from_bytes(data);
Self::assemble(backing, LogOverlay::default(), parent)
}
fn parse_backing(backing: Backing, parent: Option<Box<VhdxReader>>) -> Result<Self> {
let mut magic = [0u8; 8];
let n = backing.read_at(&mut magic, 0)?;
if n < 8 || &magic != FILE_MAGIC {
return Err(VhdxError::BadMagic);
}
if backing.len() < MIN_CONTAINER_SIZE {
return Err(VhdxError::ContainerTooSmall(MIN_CONTAINER_SIZE));
}
let overlay = {
let head = backing.read_exact_at(0, MIN_CONTAINER_SIZE as usize)?;
let header = parse_active_header(&head)?;
crate::log::build_overlay(&backing, &header)?
};
Self::assemble(backing, overlay, parent)
}
fn assemble(
backing: Backing,
overlay: LogOverlay,
parent: Option<Box<VhdxReader>>,
) -> Result<Self> {
let mut head = backing.read_exact_at(0, MIN_CONTAINER_SIZE as usize)?;
overlay.patch(&mut head, 0);
let container_len = backing.len();
let regions = parse_region_table(&head, REGION_TABLE1_OFFSET as usize, container_len)
.or_else(|_| parse_region_table(&head, REGION_TABLE2_OFFSET as usize, container_len))?;
let meta_region = read_region(
&backing,
&overlay,
regions.metadata.file_offset,
regions.metadata.length,
)?;
let meta = parse_metadata(&meta_region, 0, regions.metadata.length)?;
meta.validate()?;
if meta.has_parent && parent.is_none() {
return Err(VhdxError::DifferencingNotSupported);
}
let bat_region = read_region(
&backing,
&overlay,
regions.bat.file_offset,
regions.bat.length,
)?;
let bat = Bat::parse(&bat_region, 0, regions.bat.length, meta.clone())?;
Ok(Self {
backing,
overlay,
bat,
meta,
pos: 0,
parent,
})
}
pub fn virtual_disk_size(&self) -> u64 {
self.meta.virtual_disk_size
}
pub fn logical_sector_size(&self) -> u32 {
self.meta.logical_sector_size
}
}
fn read_region(
backing: &Backing,
overlay: &LogOverlay,
file_offset: u64,
length: u32,
) -> Result<Vec<u8>> {
let end = file_offset.saturating_add(u64::from(length));
if backing.len() < end {
return Err(VhdxError::OffsetOutOfBounds);
}
let mut buf = backing.read_exact_at(file_offset, length as usize)?;
if buf.len() < length as usize {
return Err(VhdxError::OffsetOutOfBounds);
}
overlay.patch(&mut buf, file_offset);
Ok(buf)
}
impl Read for VhdxReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.pos >= self.meta.virtual_disk_size {
return Ok(0);
}
let remaining = self.meta.virtual_disk_size - self.pos;
let to_read = buf.len().min(remaining as usize);
let block_size = u64::from(self.meta.block_size);
let mut written = 0;
while written < to_read {
let virtual_byte = self.pos + written as u64;
let block_end = ((virtual_byte / block_size) + 1) * block_size;
let this_chunk = (to_read - written).min((block_end - virtual_byte) as usize);
match self.bat.file_offset_for_byte(virtual_byte) {
Ok(file_off) => {
let dst = &mut buf[written..written + this_chunk];
let n = self.backing.read_at(dst, file_off)?;
if n < this_chunk {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"VHDX data truncated",
));
}
self.overlay.patch(dst, file_off);
}
Err(VhdxError::BlockNotPresent(_)) => {
if let Some(ref mut p) = self.parent {
p.seek(SeekFrom::Start(virtual_byte))
.map_err(io::Error::other)?;
p.read_exact(&mut buf[written..written + this_chunk])?;
} else {
buf[written..written + this_chunk].fill(0);
}
}
Err(e) => return Err(io::Error::other(e.to_string())),
}
written += this_chunk;
}
self.pos += written as u64;
Ok(written)
}
}
impl Seek for VhdxReader {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let new_pos = match pos {
SeekFrom::Start(n) => n as i64,
SeekFrom::Current(n) => self.pos as i64 + n,
SeekFrom::End(n) => self.meta.virtual_disk_size as i64 + n,
};
if new_pos < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"seek before start",
));
}
self.pos = new_pos as u64;
Ok(self.pos)
}
}