1use std::fs::File;
2use std::io::{self, Read, Seek, SeekFrom};
3use std::path::Path;
4
5use crate::backing::Backing;
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 fn parse_bytes(mut data: Vec<u8>, parent: Option<Box<VhdxReader>>) -> Result<Self> {
85 if data.len() < 8 || &data[0..8] != FILE_MAGIC {
86 return Err(VhdxError::BadMagic);
87 }
88 if (data.len() as u64) < MIN_CONTAINER_SIZE {
89 return Err(VhdxError::ContainerTooSmall(MIN_CONTAINER_SIZE));
90 }
91 crate::log::apply(&mut data)?;
92 let backing = Backing::from_bytes(data);
95 Self::assemble(backing, LogOverlay::default(), parent)
96 }
97
98 fn parse_backing(backing: Backing, parent: Option<Box<VhdxReader>>) -> Result<Self> {
101 let mut magic = [0u8; 8];
102 let n = backing.read_at(&mut magic, 0)?;
103 if n < 8 || &magic != FILE_MAGIC {
104 return Err(VhdxError::BadMagic);
105 }
106 if backing.len() < MIN_CONTAINER_SIZE {
107 return Err(VhdxError::ContainerTooSmall(MIN_CONTAINER_SIZE));
108 }
109 let overlay = {
110 let head = backing.read_exact_at(0, MIN_CONTAINER_SIZE as usize)?;
113 let header = parse_active_header(&head)?;
114 crate::log::build_overlay(&backing, &header)?
115 };
116 Self::assemble(backing, overlay, parent)
117 }
118
119 fn assemble(
123 backing: Backing,
124 overlay: LogOverlay,
125 parent: Option<Box<VhdxReader>>,
126 ) -> Result<Self> {
127 let mut head = backing.read_exact_at(0, MIN_CONTAINER_SIZE as usize)?;
131 overlay.patch(&mut head, 0);
132
133 let container_len = backing.len();
134 let regions = parse_region_table(&head, REGION_TABLE1_OFFSET as usize, container_len)
135 .or_else(|_| parse_region_table(&head, REGION_TABLE2_OFFSET as usize, container_len))?;
136
137 let meta_region = read_region(
140 &backing,
141 &overlay,
142 regions.metadata.file_offset,
143 regions.metadata.length,
144 )?;
145 let meta = parse_metadata(&meta_region, 0, regions.metadata.length)?;
146 meta.validate()?;
147 if meta.has_parent && parent.is_none() {
148 return Err(VhdxError::DifferencingNotSupported);
149 }
150
151 let bat_region = read_region(
152 &backing,
153 &overlay,
154 regions.bat.file_offset,
155 regions.bat.length,
156 )?;
157 let bat = Bat::parse(&bat_region, 0, regions.bat.length, meta.clone())?;
158
159 Ok(Self {
160 backing,
161 overlay,
162 bat,
163 meta,
164 pos: 0,
165 parent,
166 })
167 }
168
169 pub fn virtual_disk_size(&self) -> u64 {
170 self.meta.virtual_disk_size
171 }
172
173 pub fn logical_sector_size(&self) -> u32 {
174 self.meta.logical_sector_size
175 }
176}
177
178fn read_region(
184 backing: &Backing,
185 overlay: &LogOverlay,
186 file_offset: u64,
187 length: u32,
188) -> Result<Vec<u8>> {
189 let end = file_offset.saturating_add(u64::from(length));
190 if backing.len() < end {
191 return Err(VhdxError::OffsetOutOfBounds);
192 }
193 let mut buf = backing.read_exact_at(file_offset, length as usize)?;
194 if buf.len() < length as usize {
195 return Err(VhdxError::OffsetOutOfBounds);
196 }
197 overlay.patch(&mut buf, file_offset);
198 Ok(buf)
199}
200
201impl Read for VhdxReader {
202 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
203 if self.pos >= self.meta.virtual_disk_size {
204 return Ok(0);
205 }
206 let remaining = self.meta.virtual_disk_size - self.pos;
207 let to_read = buf.len().min(remaining as usize);
208 let block_size = u64::from(self.meta.block_size);
209 let mut written = 0;
210
211 while written < to_read {
212 let virtual_byte = self.pos + written as u64;
213 let block_end = ((virtual_byte / block_size) + 1) * block_size;
214 let this_chunk = (to_read - written).min((block_end - virtual_byte) as usize);
215
216 match self.bat.file_offset_for_byte(virtual_byte) {
217 Ok(file_off) => {
218 let dst = &mut buf[written..written + this_chunk];
219 let n = self.backing.read_at(dst, file_off)?;
220 if n < this_chunk {
221 return Err(io::Error::new(
222 io::ErrorKind::UnexpectedEof,
223 "VHDX data truncated",
224 ));
225 }
226 self.overlay.patch(dst, file_off);
228 }
229 Err(VhdxError::BlockNotPresent(_)) => {
230 if let Some(ref mut p) = self.parent {
231 p.seek(SeekFrom::Start(virtual_byte))
232 .map_err(io::Error::other)?;
233 p.read_exact(&mut buf[written..written + this_chunk])?;
234 } else {
235 buf[written..written + this_chunk].fill(0);
236 }
237 }
238 Err(e) => return Err(io::Error::other(e.to_string())),
239 }
240 written += this_chunk;
241 }
242
243 self.pos += written as u64;
244 Ok(written)
245 }
246}
247
248impl Seek for VhdxReader {
249 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
250 let new_pos = match pos {
251 SeekFrom::Start(n) => n as i64,
252 SeekFrom::Current(n) => self.pos as i64 + n,
253 SeekFrom::End(n) => self.meta.virtual_disk_size as i64 + n,
254 };
255 if new_pos < 0 {
256 return Err(io::Error::new(
257 io::ErrorKind::InvalidInput,
258 "seek before start",
259 ));
260 }
261 self.pos = new_pos as u64;
262 Ok(self.pos)
263 }
264}