1use std::io::{self, Read, Seek, SeekFrom};
2use std::path::Path;
3
4use crate::bat::Bat;
5use crate::error::{Result, VhdxError};
6use crate::header::{parse_active_header, REGION_TABLE1_OFFSET, REGION_TABLE2_OFFSET};
7use crate::metadata::{parse_metadata, VhdxMetadata};
8use crate::region::parse_region_table;
9use crate::FILE_MAGIC;
10
11#[derive(Debug)]
15pub struct VhdxReader {
16 data: Vec<u8>,
17 bat: Bat,
18 meta: VhdxMetadata,
19 pos: u64,
20 parent: Option<Box<VhdxReader>>,
21}
22
23impl VhdxReader {
24 pub fn open(path: &Path) -> Result<Self> {
25 let data = std::fs::read(path)?;
26 Self::from_bytes(data)
27 }
28
29 const MIN_CONTAINER_SIZE: u64 = 0x0025_0000;
31
32 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
33 Self::parse(data, None)
34 }
35
36 pub fn from_bytes_with_parent(data: Vec<u8>, parent: VhdxReader) -> Result<Self> {
40 Self::parse(data, Some(Box::new(parent)))
41 }
42
43 fn parse(mut data: Vec<u8>, parent: Option<Box<VhdxReader>>) -> Result<Self> {
44 if data.len() < 8 || &data[0..8] != FILE_MAGIC {
45 return Err(VhdxError::BadMagic);
46 }
47 if (data.len() as u64) < Self::MIN_CONTAINER_SIZE {
48 return Err(VhdxError::ContainerTooSmall(Self::MIN_CONTAINER_SIZE));
49 }
50 crate::log::apply(&mut data)?;
51 let _header = parse_active_header(&data)?;
52 let regions = parse_region_table(&data, REGION_TABLE1_OFFSET as usize)
53 .or_else(|_| parse_region_table(&data, REGION_TABLE2_OFFSET as usize))?;
54 let meta = parse_metadata(&data, regions.metadata.file_offset, regions.metadata.length)?;
55 meta.validate()?;
56 if meta.has_parent && parent.is_none() {
57 return Err(VhdxError::DifferencingNotSupported);
58 }
59 let bat = Bat::parse(
60 &data,
61 regions.bat.file_offset,
62 regions.bat.length,
63 meta.clone(),
64 )?;
65 Ok(Self {
66 data,
67 bat,
68 meta,
69 pos: 0,
70 parent,
71 })
72 }
73
74 pub fn virtual_disk_size(&self) -> u64 {
75 self.meta.virtual_disk_size
76 }
77
78 pub fn logical_sector_size(&self) -> u32 {
79 self.meta.logical_sector_size
80 }
81}
82
83impl Read for VhdxReader {
84 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
85 if self.pos >= self.meta.virtual_disk_size {
86 return Ok(0);
87 }
88 let remaining = self.meta.virtual_disk_size - self.pos;
89 let to_read = buf.len().min(remaining as usize);
90 let block_size = u64::from(self.meta.block_size);
91 let mut written = 0;
92
93 while written < to_read {
94 let virtual_byte = self.pos + written as u64;
95 let block_end = ((virtual_byte / block_size) + 1) * block_size;
96 let this_chunk = (to_read - written).min((block_end - virtual_byte) as usize);
97
98 match self.bat.file_offset_for_byte(virtual_byte) {
99 Ok(file_off) => {
100 let src_end = (file_off as usize).saturating_add(this_chunk);
101 if src_end > self.data.len() {
102 return Err(io::Error::new(
103 io::ErrorKind::UnexpectedEof,
104 "VHDX data truncated",
105 ));
106 }
107 buf[written..written + this_chunk]
108 .copy_from_slice(&self.data[file_off as usize..src_end]);
109 }
110 Err(VhdxError::BlockNotPresent(_)) => {
111 if let Some(ref mut p) = self.parent {
112 p.seek(SeekFrom::Start(virtual_byte))
113 .map_err(io::Error::other)?;
114 p.read_exact(&mut buf[written..written + this_chunk])?;
115 } else {
116 buf[written..written + this_chunk].fill(0);
117 }
118 }
119 Err(e) => return Err(io::Error::other(e.to_string())),
120 }
121 written += this_chunk;
122 }
123
124 self.pos += written as u64;
125 Ok(written)
126 }
127}
128
129impl Seek for VhdxReader {
130 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
131 let new_pos = match pos {
132 SeekFrom::Start(n) => n as i64,
133 SeekFrom::Current(n) => self.pos as i64 + n,
134 SeekFrom::End(n) => self.meta.virtual_disk_size as i64 + n,
135 };
136 if new_pos < 0 {
137 return Err(io::Error::new(
138 io::ErrorKind::InvalidInput,
139 "seek before start",
140 ));
141 }
142 self.pos = new_pos as u64;
143 Ok(self.pos)
144 }
145}