1use bitvec::prelude::*;
4use crc32c::crc32c;
5use std::io::{ErrorKind, Read, Seek, Write};
6
7use super::{
8 CacheEntry, Len, LogReplayPolicy, Medium, OpenOptions, ParentResolver, ReadOnly, ReadWrite,
9 SetLen, SyncData, is_known_metadata_guid, is_known_region_guid, read_exact_at, write_all_at,
10};
11use crate::constants::{
12 HEADER_BUFFER_SIZE, HEADER_SIZE, HEADER1_OFFSET, HEADER2_OFFSET, METADATA_REGION_GUID, MIB,
13 VHDX_SIGNATURE_BYTES,
14};
15use crate::error::{Error, Result, SignaturePosition};
16use crate::header::Header;
17use crate::log::Log;
18use crate::log_replay;
19use crate::types::Guid;
20use std::sync::atomic::AtomicU64;
21use std::sync::{Arc, Mutex, RwLock};
22
23impl<T, Mode> OpenOptions<T, Mode> {
24 fn validate_policy_compatibility(write: bool, policy: LogReplayPolicy) -> Result<()> {
25 match policy {
26 LogReplayPolicy::InMemoryOnReadOnly | LogReplayPolicy::ReadOnlyNoReplay if write => {
27 Err(Error::InvalidParameter(
28 "log replay policy incompatible with write access".into(),
29 ))
30 }
31 _ => Ok(()),
32 }
33 }
34
35 fn read_header(inner: &mut T) -> Result<Vec<u8>>
36 where
37 T: Read + Seek,
38 {
39 let mut header_buf = vec![0u8; HEADER_BUFFER_SIZE];
40 let mut signature = [0u8; 8];
41 match read_exact_at(inner, 0, &mut signature) {
42 Ok(()) => {}
43 Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
44 return Err(Error::InvalidFile(
45 "file too small to contain VHDX signature".into(),
46 ));
47 }
48 Err(err) => return Err(err.into()),
49 }
50 match read_exact_at(inner, 0, &mut header_buf) {
51 Ok(()) => {}
52 Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
53 return Err(Error::InvalidFile(format!(
54 "header section too small: need at least {HEADER_BUFFER_SIZE}"
55 )));
56 }
57 Err(err) => return Err(err.into()),
58 }
59 Ok(header_buf)
60 }
61
62 fn validate_file_signature(header_buf: &[u8]) -> Result<()> {
63 let sig = &header_buf[..VHDX_SIGNATURE_BYTES.len() / 8];
64 if sig.view_bits::<Lsb0>() == *VHDX_SIGNATURE_BYTES {
65 return Ok(());
66 }
67 let mut actual_bytes = [0u8; 8];
68 actual_bytes.copy_from_slice(sig);
69 Err(Error::InvalidSignature {
70 position: SignaturePosition::FileTypeIdentifier,
71 expected: VHDX_SIGNATURE_BYTES.into_inner().to_le_bytes(),
72 found: actual_bytes,
73 })
74 }
75
76 fn validate_current_header(current: &crate::header::HeaderStructure<'_>) -> Result<()> {
77 if current.version() != 1 {
78 return Err(Error::UnsupportedVersion {
79 version: current.version(),
80 });
81 }
82 if current.log_version() != 0 && current.log_guid() != Guid::zero() {
83 return Err(Error::UnsupportedLogVersion {
84 version: current.log_version(),
85 });
86 }
87 Ok(())
88 }
89
90 fn validate_region_table_and_metadata(
91 inner: &mut T, header: &Header, strict: bool,
92 ) -> Result<()>
93 where
94 T: Read + Seek,
95 {
96 let rt = header.region_table(0)?;
97 Self::validate_region_table_entries(&rt, strict)?;
98 Self::validate_unknown_metadata(inner, &rt, strict)
99 }
100
101 fn validate_region_table_entries(
102 rt: &crate::header::RegionTable<'_>, strict: bool,
103 ) -> Result<()> {
104 let entries: Vec<_> = rt.entries().collect();
105 for (i, entry) in entries.iter().enumerate() {
106 let file_offset = entry.file_offset();
107 let length = entry.length();
108 if file_offset % u64::from(MIB) != 0 {
109 return Err(Error::InvalidRegionTable(format!(
110 "REGION_ENTRY_ALIGNMENT: entry {i} file_offset {file_offset:#x} not 1MB-aligned"
111 )));
112 }
113 if file_offset < u64::from(MIB) {
114 return Err(Error::InvalidRegionTable(format!(
115 "REGION_ENTRY_OFFSET_MINIMUM: entry {i} file_offset {file_offset} < 1MB minimum"
116 )));
117 }
118 if u64::from(length) % u64::from(MIB) != 0 {
119 return Err(Error::InvalidRegionTable(format!(
120 "REGION_ENTRY_ALIGNMENT: entry {i} length {length} not 1MB-aligned"
121 )));
122 }
123 let end = file_offset + u64::from(length);
124 for (j, prev) in entries[..i].iter().enumerate() {
125 let prev_end = prev.file_offset() + u64::from(prev.length());
126 if file_offset < prev_end && prev.file_offset() < end {
127 return Err(Error::InvalidRegionTable(format!(
128 "REGION_ENTRY_OVERLAP: entries {j} and {i} overlap"
129 )));
130 }
131 }
132 if !is_known_region_guid(&entry.guid()) {
133 if entry.required() {
134 return Err(Error::RegionRequiredUnknown { guid: entry.guid() });
135 }
136 if strict {
137 return Err(Error::RegionOptionalUnknown { guid: entry.guid() });
138 }
139 }
140 }
141 Ok(())
142 }
143
144 fn validate_unknown_metadata(
145 inner: &mut T, rt: &crate::header::RegionTable<'_>, strict: bool,
146 ) -> Result<()>
147 where
148 T: Read + Seek,
149 {
150 for entry in rt.entries() {
151 if entry.guid() != METADATA_REGION_GUID {
152 continue;
153 }
154 let mut meta_data = vec![0u8; entry.length() as usize];
155 read_exact_at(inner, entry.file_offset(), &mut meta_data)?;
156 let meta = crate::metadata::Metadata::new(&meta_data)?;
157 for table_entry in meta.table().entries() {
158 if table_entry.flags().is_required()
159 && !is_known_metadata_guid(&table_entry.item_id())
160 {
161 return Err(Error::MetadataRequiredUnknown {
162 guid: table_entry.item_id(),
163 });
164 }
165 if strict
166 && !table_entry.flags().is_required()
167 && !is_known_metadata_guid(&table_entry.item_id())
168 {
169 return Err(Error::MetadataOptionalUnknown {
170 guid: table_entry.item_id(),
171 });
172 }
173 }
174 break;
175 }
176 Ok(())
177 }
178
179 fn load_log_data(inner: &mut T, offset: u64, length: u32) -> Result<Vec<u8>>
180 where
181 T: Read + Seek,
182 {
183 let mut log_data = vec![0u8; length as usize];
184 read_exact_at(inner, offset, &mut log_data)?;
185 Ok(log_data)
186 }
187
188 fn apply_writable_header_update(
189 write: bool, inner: &mut T, header_buf: &mut Vec<u8>,
190 ) -> Result<()>
191 where
192 T: Write + Seek + SyncData,
193 {
194 if !write {
195 return Ok(());
196 }
197 if header_buf.len() < HEADER_BUFFER_SIZE {
198 header_buf.resize(HEADER_BUFFER_SIZE, 0);
199 }
200 let hdr = Header::new(header_buf)?;
201 let h1 = hdr.header(1)?;
202 let h2 = hdr.header(2)?;
203 let current_idx = if h1.sequence_number() > h2.sequence_number() {
204 1
205 } else {
206 2
207 };
208 let noncurrent_idx = if current_idx == 1 { 2 } else { 1 };
209 let noncurrent_offset = if noncurrent_idx == 1 {
210 u64::from(HEADER1_OFFSET)
211 } else {
212 u64::from(HEADER2_OFFSET)
213 };
214 let current_header = hdr.header(0)?;
215 let updated_header = Self::build_updated_header(¤t_header);
216 write_all_at(inner, noncurrent_offset, &updated_header)?;
217 inner.sync_data()?;
218 let start = usize::try_from(noncurrent_offset).unwrap();
219 header_buf[start..start + HEADER_SIZE as usize].copy_from_slice(&updated_header);
220 Ok(())
221 }
222
223 fn build_updated_header(
224 current_header: &crate::header::HeaderStructure<'_>,
225 ) -> [u8; HEADER_SIZE as usize] {
226 let mut updated_header = [0u8; HEADER_SIZE as usize];
227 updated_header[..4].copy_from_slice(b"head");
228 updated_header[4..8].copy_from_slice(&0u32.to_le_bytes());
229 updated_header[8..16]
230 .copy_from_slice(&(current_header.sequence_number() + 1).to_le_bytes());
231 updated_header[16..32].copy_from_slice(&Guid::new_v4().to_bytes());
232 updated_header[32..48].copy_from_slice(¤t_header.data_write_guid().to_bytes());
233 updated_header[48..64].copy_from_slice(¤t_header.log_guid().to_bytes());
234 updated_header[64..66].copy_from_slice(¤t_header.log_version().to_le_bytes());
235 updated_header[66..68].copy_from_slice(¤t_header.version().to_le_bytes());
236 updated_header[68..72].copy_from_slice(¤t_header.log_length().to_le_bytes());
237 updated_header[72..80].copy_from_slice(¤t_header.log_offset().to_le_bytes());
238 let checksum = crc32c(&updated_header);
239 updated_header[4..8].copy_from_slice(&checksum.to_le_bytes());
240 updated_header
241 }
242
243 #[must_use]
244 pub fn strict(mut self, strict: bool) -> Self {
245 self.strict = strict;
246 self
247 }
248
249 #[must_use]
250 pub fn log_replay(mut self, policy: LogReplayPolicy) -> Self {
251 self.log_replay_policy = policy;
252 self
253 }
254
255 #[must_use]
257 pub fn with_parent_resolver<R>(mut self, resolver: R) -> Self
258 where
259 R: ParentResolver + Send + 'static,
260 {
261 self.parent_resolver = Some(Box::new(resolver));
262 self
263 }
264}
265
266impl<T> OpenOptions<T, ReadOnly> {
267 #[must_use]
268 pub fn write(self) -> OpenOptions<T, ReadWrite>
269 where
270 T: Read + Write + Seek + Len + SetLen + SyncData,
271 {
272 OpenOptions {
273 inner: self.inner,
274 strict: self.strict,
275 log_replay_policy: self.log_replay_policy,
276 parent_resolver: self.parent_resolver,
277 _mode: std::marker::PhantomData,
278 }
279 }
280
281 pub fn finish(mut self) -> Result<Medium<T>>
288 where
289 T: Read + Seek,
290 {
291 Self::validate_policy_compatibility(false, self.log_replay_policy)?;
292 let strict = self.strict;
293 let log_replay_policy = self.log_replay_policy;
294 let mut header_buf = Self::read_header(&mut self.inner)?;
295 Self::validate_file_signature(&header_buf)?;
296 let header = Header::new(&header_buf)?;
297 let current = header.header(0)?;
298 Self::validate_current_header(¤t)?;
299 let log_offset = current.log_offset();
300 let log_length = current.log_length();
301 let log_guid = current.log_guid();
302 Self::validate_region_table_and_metadata(&mut self.inner, &header, strict)?;
303 let log_data = Self::load_log_data(&mut self.inner, log_offset, log_length)?;
304
305 let replay_overlay = match log_replay_policy {
306 LogReplayPolicy::Require => {
307 let log = Log::new(&log_data)?;
308 if log_replay::has_pending_log(&log, &log_guid) {
309 return Err(Error::LogReplayRequired);
310 }
311 None
312 }
313 LogReplayPolicy::Auto | LogReplayPolicy::InMemoryOnReadOnly => {
314 let log = Log::new(&log_data)?;
315 if log_replay::has_pending_log(&log, &log_guid) {
316 let active = log_replay::detect_active_sequence(&log, &log_guid)?;
317 Some(Arc::new(log_replay::build_replay_overlay(&active)?))
318 } else {
319 None
320 }
321 }
322 LogReplayPolicy::ReadOnlyNoReplay => None,
323 };
324
325 if let Some(ref overlay) = replay_overlay {
326 if header_buf.len() < HEADER_BUFFER_SIZE {
327 header_buf.resize(HEADER_BUFFER_SIZE, 0);
328 }
329 overlay.apply_to_region(&mut header_buf, 0);
330 }
331
332 Ok(Medium {
333 inner: Mutex::new(self.inner),
334 header_buf: RwLock::new(Some(CacheEntry::new(0, Arc::from(header_buf)))),
335 bat_buf: RwLock::new(None),
336 metadata_buf: RwLock::new(None),
337 log_buf: RwLock::new(Some(CacheEntry::new(0, Arc::from(log_data)))),
338 generation: AtomicU64::new(0),
339 write: false,
340 strict,
341 log_replay_policy,
342 replay_overlay,
343 parent_resolver: Mutex::new(self.parent_resolver),
344 validator_buf: RwLock::new(None),
345 })
346 }
347}
348
349impl<T> OpenOptions<T, ReadWrite> {
350 pub fn finish(mut self) -> Result<Medium<T>>
357 where
358 T: Read + Write + Seek + Len + SetLen + SyncData,
359 {
360 Self::validate_policy_compatibility(true, self.log_replay_policy)?;
361 let strict = self.strict;
362 let log_replay_policy = self.log_replay_policy;
363 let mut header_buf = Self::read_header(&mut self.inner)?;
364 Self::validate_file_signature(&header_buf)?;
365 let header = Header::new(&header_buf)?;
366 let current = header.header(0)?;
367 Self::validate_current_header(¤t)?;
368 let log_offset = current.log_offset();
369 let log_length = current.log_length();
370 let log_guid = current.log_guid();
371 Self::validate_region_table_and_metadata(&mut self.inner, &header, strict)?;
372 let log_data = Self::load_log_data(&mut self.inner, log_offset, log_length)?;
373
374 let replay_overlay = match log_replay_policy {
375 LogReplayPolicy::Require => {
376 let log = Log::new(&log_data)?;
377 if log_replay::has_pending_log(&log, &log_guid) {
378 return Err(Error::LogReplayRequired);
379 }
380 None
381 }
382 LogReplayPolicy::Auto => {
383 let log = Log::new(&log_data)?;
384 if log_replay::has_pending_log(&log, &log_guid) {
385 let active = log_replay::detect_active_sequence(&log, &log_guid)?;
386 let file_size = self.inner.len()?;
387 if file_size < active.flushed_file_offset() {
388 return Err(Error::CorruptedHeader(format!(
389 "file truncated: size {} < FlushedFileOffset {}",
390 file_size,
391 active.flushed_file_offset()
392 )));
393 }
394 log_replay::replay_to_file(&mut self.inner, &active)?;
395 }
396 None
397 }
398 LogReplayPolicy::InMemoryOnReadOnly | LogReplayPolicy::ReadOnlyNoReplay => {
399 unreachable!()
400 }
401 };
402
403 Self::apply_writable_header_update(true, &mut self.inner, &mut header_buf)?;
404
405 Ok(Medium {
406 inner: Mutex::new(self.inner),
407 header_buf: RwLock::new(Some(CacheEntry::new(0, Arc::from(header_buf)))),
408 bat_buf: RwLock::new(None),
409 metadata_buf: RwLock::new(None),
410 log_buf: RwLock::new(Some(CacheEntry::new(0, Arc::from(log_data)))),
411 generation: AtomicU64::new(0),
412 write: true,
413 strict,
414 log_replay_policy,
415 replay_overlay,
416 parent_resolver: Mutex::new(self.parent_resolver),
417 validator_buf: RwLock::new(None),
418 })
419 }
420}