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, ReadTarget};
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 mut this_chunk = (to_read - written).min((block_end - virtual_byte) as usize);
241
242 match self
243 .bat
244 .read_target_for_byte(virtual_byte)
245 .map_err(|e| io::Error::other(e.to_string()))?
246 {
247 ReadTarget::File(file_offset) => {
248 self.read_file_chunk(&mut buf[written..written + this_chunk], file_offset)?;
249 }
250 ReadTarget::Parent => {
251 self.read_parent_chunk(&mut buf[written..written + this_chunk], virtual_byte)?;
252 }
253 ReadTarget::Zero => {
254 buf[written..written + this_chunk].fill(0);
255 }
256 ReadTarget::Partial {
257 file_offset,
258 bitmap_byte_file_offset,
259 bitmap_bit,
260 } => {
261 let mut bitmap = [0u8; 1];
262 let n = self.backing.read_at(&mut bitmap, bitmap_byte_file_offset)?;
263 if n < bitmap.len() {
264 return Err(io::Error::new(
265 io::ErrorKind::UnexpectedEof,
266 "VHDX sector bitmap truncated",
267 ));
268 }
269 self.overlay.patch(&mut bitmap, bitmap_byte_file_offset);
270
271 let owned_by_child = bitmap[0] & (1 << bitmap_bit) != 0;
278 let mut run_sectors = 1u64;
279 for bit in (bitmap_bit + 1)..8 {
280 if (bitmap[0] & (1 << bit) != 0) != owned_by_child {
281 break;
282 }
283 run_sectors += 1;
284 }
285
286 let logical_sector_size = u64::from(self.meta.logical_sector_size);
287 let run_end =
288 ((virtual_byte / logical_sector_size) + run_sectors) * logical_sector_size;
289 this_chunk = this_chunk.min((run_end - virtual_byte) as usize);
290
291 if owned_by_child {
292 self.read_file_chunk(&mut buf[written..written + this_chunk], file_offset)?;
293 } else {
294 self.read_parent_chunk(
295 &mut buf[written..written + this_chunk],
296 virtual_byte,
297 )?;
298 }
299 }
300 }
301 written += this_chunk;
302 }
303
304 self.pos += written as u64;
305 Ok(written)
306 }
307}
308
309impl VhdxReader {
310 fn read_file_chunk(&self, dst: &mut [u8], file_offset: u64) -> io::Result<()> {
311 let n = self.backing.read_at(dst, file_offset)?;
312 if n < dst.len() {
313 return Err(io::Error::new(
314 io::ErrorKind::UnexpectedEof,
315 "VHDX data truncated",
316 ));
317 }
318 self.overlay.patch(dst, file_offset);
319 Ok(())
320 }
321
322 fn read_parent_chunk(&mut self, dst: &mut [u8], virtual_byte: u64) -> io::Result<()> {
325 if let Some(ref mut parent) = self.parent {
326 parent
327 .seek(SeekFrom::Start(virtual_byte))
328 .map_err(io::Error::other)?;
329 parent.read_exact(dst)
330 } else {
331 dst.fill(0);
332 Ok(())
333 }
334 }
335}
336
337impl Seek for VhdxReader {
338 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
339 let new_pos = match pos {
340 SeekFrom::Start(n) => n as i64,
341 SeekFrom::Current(n) => self.pos as i64 + n,
342 SeekFrom::End(n) => self.meta.virtual_disk_size as i64 + n,
343 };
344 if new_pos < 0 {
345 return Err(io::Error::new(
346 io::ErrorKind::InvalidInput,
347 "seek before start",
348 ));
349 }
350 self.pos = new_pos as u64;
351 Ok(self.pos)
352 }
353}