1use std::fs::File;
2use std::io::{self, Read, Seek, SeekFrom};
3use std::path::Path;
4
5use crate::backing::{Backing, ReadSeekSend};
6use crate::bat::Bat;
7use crate::error::{Result, VhdxError};
8use crate::header::{parse_active_header, REGION_TABLE1_OFFSET, REGION_TABLE2_OFFSET};
9use crate::log::LogOverlay;
10use crate::metadata::{parse_metadata, VhdxMetadata};
11use crate::region::parse_region_table;
12use crate::FILE_MAGIC;
13
14#[derive(Debug)]
26pub struct VhdxReader {
27 backing: Backing,
28 overlay: LogOverlay,
29 bat: Bat,
30 meta: VhdxMetadata,
31 pos: u64,
32 parent: Option<Box<VhdxReader>>,
33}
34
35const MIN_CONTAINER_SIZE: u64 = 0x0025_0000;
37
38impl VhdxReader {
39 pub fn open(path: &Path) -> Result<Self> {
46 let file = File::open(path)?;
47 Self::from_backing(Backing::File(file), None)
48 }
49
50 pub fn from_backing(backing: Backing, parent: Option<Box<VhdxReader>>) -> Result<Self> {
57 Self::parse_backing(backing, parent)
58 }
59
60 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
66 Self::parse_bytes(data, None)
67 }
68
69 pub fn from_bytes_with_parent(data: Vec<u8>, parent: VhdxReader) -> Result<Self> {
73 Self::parse_bytes(data, Some(Box::new(parent)))
74 }
75
76 pub fn from_backing_with_parent(backing: Backing, parent: VhdxReader) -> Result<Self> {
79 Self::parse_backing(backing, Some(Box::new(parent)))
80 }
81
82 pub fn open_reader(mut reader: Box<dyn ReadSeekSend>) -> Result<Self> {
96 let len = reader
99 .seek(std::io::SeekFrom::End(0))
100 .map_err(VhdxError::Io)?;
101 reader
102 .seek(std::io::SeekFrom::Start(0))
103 .map_err(VhdxError::Io)?;
104 let inner = std::sync::Mutex::new(reader);
105 Self::from_backing(Backing::Reader { inner, len }, None)
106 }
107
108 fn parse_bytes(mut data: Vec<u8>, parent: Option<Box<VhdxReader>>) -> Result<Self> {
111 if data.len() < 8 || &data[0..8] != FILE_MAGIC {
112 return Err(VhdxError::BadMagic);
113 }
114 if (data.len() as u64) < MIN_CONTAINER_SIZE {
115 return Err(VhdxError::ContainerTooSmall(MIN_CONTAINER_SIZE));
116 }
117 crate::log::apply(&mut data)?;
118 let backing = Backing::from_bytes(data);
121 Self::assemble(backing, LogOverlay::default(), parent)
122 }
123
124 fn parse_backing(backing: Backing, parent: Option<Box<VhdxReader>>) -> Result<Self> {
127 let mut magic = [0u8; 8];
128 let n = backing.read_at(&mut magic, 0)?;
129 if n < 8 || &magic != FILE_MAGIC {
130 return Err(VhdxError::BadMagic);
131 }
132 if backing.len() < MIN_CONTAINER_SIZE {
133 return Err(VhdxError::ContainerTooSmall(MIN_CONTAINER_SIZE));
134 }
135 let overlay = {
136 let head = backing.read_exact_at(0, MIN_CONTAINER_SIZE as usize)?;
139 let header = parse_active_header(&head)?;
140 crate::log::build_overlay(&backing, &header)?
141 };
142 Self::assemble(backing, overlay, parent)
143 }
144
145 fn assemble(
149 backing: Backing,
150 overlay: LogOverlay,
151 parent: Option<Box<VhdxReader>>,
152 ) -> Result<Self> {
153 let mut head = backing.read_exact_at(0, MIN_CONTAINER_SIZE as usize)?;
157 overlay.patch(&mut head, 0);
158
159 let container_len = backing.len();
160 let regions = parse_region_table(&head, REGION_TABLE1_OFFSET as usize, container_len)
161 .or_else(|_| parse_region_table(&head, REGION_TABLE2_OFFSET as usize, container_len))?;
162
163 let meta_region = read_region(
166 &backing,
167 &overlay,
168 regions.metadata.file_offset,
169 regions.metadata.length,
170 )?;
171 let meta = parse_metadata(&meta_region, 0, regions.metadata.length)?;
172 meta.validate()?;
173 if meta.has_parent && parent.is_none() {
174 return Err(VhdxError::DifferencingNotSupported);
175 }
176
177 let bat_region = read_region(
178 &backing,
179 &overlay,
180 regions.bat.file_offset,
181 regions.bat.length,
182 )?;
183 let bat = Bat::parse(&bat_region, 0, regions.bat.length, meta.clone())?;
184
185 Ok(Self {
186 backing,
187 overlay,
188 bat,
189 meta,
190 pos: 0,
191 parent,
192 })
193 }
194
195 pub fn virtual_disk_size(&self) -> u64 {
196 self.meta.virtual_disk_size
197 }
198
199 pub fn logical_sector_size(&self) -> u32 {
200 self.meta.logical_sector_size
201 }
202}
203
204fn read_region(
210 backing: &Backing,
211 overlay: &LogOverlay,
212 file_offset: u64,
213 length: u32,
214) -> Result<Vec<u8>> {
215 let end = file_offset.saturating_add(u64::from(length));
216 if backing.len() < end {
217 return Err(VhdxError::OffsetOutOfBounds);
218 }
219 let mut buf = backing.read_exact_at(file_offset, length as usize)?;
220 if buf.len() < length as usize {
221 return Err(VhdxError::OffsetOutOfBounds);
222 }
223 overlay.patch(&mut buf, file_offset);
224 Ok(buf)
225}
226
227impl Read for VhdxReader {
228 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
229 if self.pos >= self.meta.virtual_disk_size {
230 return Ok(0);
231 }
232 let remaining = self.meta.virtual_disk_size - self.pos;
233 let to_read = buf.len().min(remaining as usize);
234 let block_size = u64::from(self.meta.block_size);
235 let mut written = 0;
236
237 while written < to_read {
238 let virtual_byte = self.pos + written as u64;
239 let block_end = ((virtual_byte / block_size) + 1) * block_size;
240 let this_chunk = (to_read - written).min((block_end - virtual_byte) as usize);
241
242 match self.bat.file_offset_for_byte(virtual_byte) {
243 Ok(file_off) => {
244 let dst = &mut buf[written..written + this_chunk];
245 let n = self.backing.read_at(dst, file_off)?;
246 if n < this_chunk {
247 return Err(io::Error::new(
248 io::ErrorKind::UnexpectedEof,
249 "VHDX data truncated",
250 ));
251 }
252 self.overlay.patch(dst, file_off);
254 }
255 Err(VhdxError::BlockNotPresent(_)) => {
256 if let Some(ref mut p) = self.parent {
257 p.seek(SeekFrom::Start(virtual_byte))
258 .map_err(io::Error::other)?;
259 p.read_exact(&mut buf[written..written + this_chunk])?;
260 } else {
261 buf[written..written + this_chunk].fill(0);
262 }
263 }
264 Err(e) => return Err(io::Error::other(e.to_string())),
265 }
266 written += this_chunk;
267 }
268
269 self.pos += written as u64;
270 Ok(written)
271 }
272}
273
274impl Seek for VhdxReader {
275 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
276 let new_pos = match pos {
277 SeekFrom::Start(n) => n as i64,
278 SeekFrom::Current(n) => self.pos as i64 + n,
279 SeekFrom::End(n) => self.meta.virtual_disk_size as i64 + n,
280 };
281 if new_pos < 0 {
282 return Err(io::Error::new(
283 io::ErrorKind::InvalidInput,
284 "seek before start",
285 ));
286 }
287 self.pos = new_pos as u64;
288 Ok(self.pos)
289 }
290}