1use bitvec::prelude::*;
4use crc32c::crc32c;
5use std::io::{BufWriter, Read, Seek, Write};
6
7use super::{
8 CreateOptions, Len, LogReplayPolicy, Medium, ParentCreateInfo, ParentMedium, SetLen, SyncData,
9 read_exact_at, write_all_at,
10};
11use crate::constants::{
12 BAT_REGION_GUID, HEADER_BUFFER_SIZE, HEADER_SIZE, HEADER1_OFFSET, HEADER2_OFFSET, LOG_OFFSET,
13 METADATA_REGION_GUID, MIB, VHDX_SIGNATURE_BYTES,
14};
15use crate::constants::{
16 BAT_REGION_OFFSET, KV_ENTRY_SIZE, LOCATOR_HEADER_SIZE, LOG_LENGTH, METADATA_REGION_SIZE,
17 METADATA_TABLE_SIZE, REGION_TABLE_SIZE, REGION_TABLE1_OFFSET, REGION_TABLE2_OFFSET,
18 TABLE_ENTRY_SIZE, TABLE_HEADER_SIZE, TIB,
19};
20use crate::error::{Error, Result};
21use crate::types::{self, Guid};
22use std::sync::atomic::AtomicU64;
23
24struct MetadataEntryMeta {
25 guid: Guid,
26 rel_offset: u32,
27 length: u32,
28 flags: u32,
29}
30
31impl<T> CreateOptions<T> {
32 #[must_use]
38 pub fn size(mut self, virtual_size: u64) -> Self {
39 self.virtual_size = virtual_size;
40 self
41 }
42
43 #[must_use]
45 pub fn fixed(mut self, fixed: bool) -> Self {
46 self.fixed = fixed;
47 self
48 }
49
50 #[must_use]
54 pub fn block_size(mut self, size: u32) -> Self {
55 self.block_size = size;
56 self
57 }
58
59 #[must_use]
63 pub fn logical_sector_size(mut self, size: u32) -> Self {
64 self.logical_sector_size = size;
65 self
66 }
67
68 #[must_use]
72 pub fn physical_sector_size(mut self, size: u32) -> Self {
73 self.physical_sector_size = size;
74 self
75 }
76
77 pub fn parent<P>(
83 mut self, parent: &mut Medium<P>, relative_path: impl AsRef<std::path::Path>,
84 ) -> Result<Self>
85 where
86 P: Read + Seek,
87 {
88 self.parent = Some(ParentCreateInfo {
89 relative_path: relative_path.as_ref().to_path_buf(),
90 data_write_guid: parent.data_write_guid()?,
91 });
92 Ok(self)
93 }
94
95 fn validate(&self) -> Result<()> {
98 if self.virtual_size == 0 {
99 return Err(Error::InvalidParameter(
100 "virtual disk size must be set".into(),
101 ));
102 }
103
104 if self.virtual_size > 64 * TIB {
105 return Err(Error::InvalidParameter(
106 "virtual disk size must not exceed 64 TB".into(),
107 ));
108 }
109
110 if !self
111 .virtual_size
112 .is_multiple_of(u64::from(self.logical_sector_size))
113 {
114 return Err(Error::InvalidParameter(
115 "virtual disk size must be a multiple of logical sector size".into(),
116 ));
117 }
118
119 if self.block_size < MIB || self.block_size > 256 * MIB {
120 return Err(Error::InvalidParameter(
121 "block size must be between 1 MB and 256 MB".into(),
122 ));
123 }
124 if !self.block_size.is_power_of_two() {
125 return Err(Error::InvalidParameter(
126 "block size must be a power of 2".into(),
127 ));
128 }
129
130 if !matches!(self.logical_sector_size, 512 | 4096) {
131 return Err(Error::InvalidParameter(
132 "logical sector size must be 512 or 4096".into(),
133 ));
134 }
135
136 if !matches!(self.physical_sector_size, 512 | 4096) {
137 return Err(Error::InvalidParameter(
138 "physical sector size must be 512 or 4096".into(),
139 ));
140 }
141
142 if self.fixed && self.parent.is_some() {
143 return Err(Error::InvalidParameter(
144 "fixed disk cannot have a parent".into(),
145 ));
146 }
147
148 Ok(())
149 }
150
151 pub fn finish(mut self) -> Result<Medium<T>>
169 where
170 T: Read + Write + Seek + Len + SetLen + SyncData,
171 {
172 self.validate()?;
173
174 let inner = self
175 .inner
176 .take()
177 .expect("CreateOptions always owns a medium before finish");
178 let mut w = BufWriter::new(inner);
179
180 let bat_size =
181 Self::calculate_bat_size(self.virtual_size, self.block_size, self.logical_sector_size);
182 let metadata_offset = u64::from(BAT_REGION_OFFSET) + u64::from(bat_size);
183
184 Self::write_file_type_identifier(&mut w)?;
186
187 let file_write_guid = Guid::new_v4();
189 let data_write_guid = Guid::new_v4();
190 let log_guid = Guid::zero(); let header1 = Self::build_header(0, &file_write_guid, &data_write_guid, &log_guid);
194 write_all_at(&mut w, u64::from(HEADER1_OFFSET), &header1)?;
195
196 let header2 = Self::build_header(1, &file_write_guid, &data_write_guid, &log_guid);
198 write_all_at(&mut w, u64::from(HEADER2_OFFSET), &header2)?;
199
200 let region = Self::build_region_table(bat_size, metadata_offset);
202
203 write_all_at(&mut w, u64::from(REGION_TABLE1_OFFSET), ®ion)?;
204
205 write_all_at(&mut w, u64::from(REGION_TABLE2_OFFSET), ®ion)?;
206
207 let _first_payload_offset_mb = if self.fixed {
212 let (num_payload, _num_sb, _total_entries, _chunk_ratio) =
213 Self::compute_bat_entry_counts(
214 self.virtual_size,
215 self.block_size,
216 self.logical_sector_size,
217 );
218 let payload_align = u64::from(self.block_size / MIB);
219 let raw_first_mb =
220 (metadata_offset + u64::from(METADATA_REGION_SIZE)).div_ceil(u64::from(MIB));
221 let first_payload_offset_mb = raw_first_mb.div_ceil(payload_align) * payload_align;
222 let total_payload = num_payload * u64::from(self.block_size);
223 let end = first_payload_offset_mb * u64::from(MIB) + total_payload;
224 w.flush()?;
225 w.get_mut().set_len(end)?;
226 first_payload_offset_mb
227 } else {
228 let end = metadata_offset + u64::from(METADATA_REGION_SIZE);
229 w.flush()?;
230 w.get_mut().set_len(end)?;
231 0
232 };
233
234 Self::write_bat_entries(
236 &mut w,
237 self.virtual_size,
238 self.block_size,
239 self.logical_sector_size,
240 self.fixed,
241 metadata_offset,
242 )?;
243
244 let parent_data_write_guid = self.parent.as_ref().map(|parent| parent.data_write_guid);
245
246 self.write_metadata(&mut w, metadata_offset, parent_data_write_guid)?;
248
249 w.flush()?;
250 w.get_mut().sync_data()?;
251 let mut inner = w
252 .into_inner()
253 .map_err(std::io::IntoInnerError::into_error)?;
254
255 let mut header_buf = vec![0u8; HEADER_BUFFER_SIZE];
257 read_exact_at(&mut inner, 0, &mut header_buf)?;
258
259 Ok(Medium {
260 inner: std::sync::Mutex::new(inner),
261 header_buf: std::sync::RwLock::new(Some(super::CacheEntry::new(
262 0,
263 std::sync::Arc::from(header_buf),
264 ))),
265 bat_buf: std::sync::RwLock::new(None),
266 metadata_buf: std::sync::RwLock::new(None),
267 log_buf: std::sync::RwLock::new(None),
268 generation: AtomicU64::new(0),
269 write: true,
270 strict: true,
271 log_replay_policy: LogReplayPolicy::Require,
272 replay_overlay: None,
273 parent_resolver: std::sync::Mutex::new(None),
274 validator_buf: std::sync::RwLock::new(None),
275 })
276 }
277
278 pub(crate) fn calculate_bat_size(
281 virtual_size: u64, block_size: u32, logical_sector_size: u32,
282 ) -> u32 {
283 let (_num_payload, _num_sb, total_entries, _chunk_ratio) =
284 Self::compute_bat_entry_counts(virtual_size, block_size, logical_sector_size);
285 let bat_bytes = total_entries * 8;
286 let bat_mb = std::cmp::max(bat_bytes.div_ceil(u64::from(MIB)), 1);
287 u32::try_from(bat_mb).unwrap() * (1024 * 1024)
288 }
289
290 fn write_file_type_identifier(w: &mut (impl Write + Seek)) -> Result<()> {
291 let mut creator = [0u8; 512];
292 let ident = "vhdx-rs\0";
293 for (i, ch) in ident.encode_utf16().enumerate() {
294 let off = i * 2;
295 if off + 1 < 512 {
296 creator[off..off + 2].copy_from_slice(&ch.to_le_bytes());
297 }
298 }
299 write_all_at(w, 0, &VHDX_SIGNATURE_BYTES.into_inner().to_le_bytes())?;
300 write_all_at(w, 8, &creator)?;
301 Ok(())
302 }
303
304 fn build_header(
305 sequence_number: u64, file_write_guid: &Guid, data_write_guid: &Guid, log_guid: &Guid,
306 ) -> [u8; HEADER_SIZE as usize] {
307 let mut buf = [0u8; HEADER_SIZE as usize];
308 buf[..4].copy_from_slice(b"head");
309 buf[4..8].copy_from_slice(&0u32.to_le_bytes());
310 buf[8..16].copy_from_slice(&sequence_number.to_le_bytes());
311 buf[16..32].copy_from_slice(&file_write_guid.to_bytes());
312 buf[32..48].copy_from_slice(&data_write_guid.to_bytes());
313 buf[48..64].copy_from_slice(&log_guid.to_bytes());
314 buf[64..66].copy_from_slice(&0u16.to_le_bytes());
315 buf[66..68].copy_from_slice(&1u16.to_le_bytes());
316 buf[68..72].copy_from_slice(&LOG_LENGTH.to_le_bytes());
317 buf[72..80].copy_from_slice(&u64::from(LOG_OFFSET).to_le_bytes());
318
319 let checksum = crc32c(&buf);
320 buf[4..8].copy_from_slice(&checksum.to_le_bytes());
321
322 buf
323 }
324
325 fn build_region_table(bat_size: u32, metadata_offset: u64) -> Vec<u8> {
326 let mut buf = vec![0u8; REGION_TABLE_SIZE as usize];
327 buf[..4].copy_from_slice(b"regi");
328 buf[4..8].copy_from_slice(&0u32.to_le_bytes());
329 buf[8..12].copy_from_slice(&2u32.to_le_bytes());
330 buf[12..16].copy_from_slice(&0u32.to_le_bytes());
331
332 buf[16..32].copy_from_slice(&BAT_REGION_GUID.to_bytes());
333 buf[32..40].copy_from_slice(&u64::from(BAT_REGION_OFFSET).to_le_bytes());
334 buf[40..44].copy_from_slice(&bat_size.to_le_bytes());
335 buf[44..48].view_bits_mut::<Lsb0>().set(0, true); buf[48..64].copy_from_slice(&METADATA_REGION_GUID.to_bytes());
338 buf[64..72].copy_from_slice(&metadata_offset.to_le_bytes());
339 buf[72..76].copy_from_slice(&METADATA_REGION_SIZE.to_le_bytes());
340 buf[76..80].view_bits_mut::<Lsb0>().set(0, true); let checksum = crc32c(&buf);
343 buf[4..8].copy_from_slice(&checksum.to_le_bytes());
344
345 buf
346 }
347
348 pub(crate) fn compute_bat_entry_counts(
352 virtual_size: u64, block_size: u32, logical_sector_size: u32,
353 ) -> (u64, u64, u64, u64) {
354 let num_payload = virtual_size.div_ceil(u64::from(block_size));
355 let chunk_ratio = (1u64 << 23) * u64::from(logical_sector_size) / u64::from(block_size);
356 let num_sb = num_payload.div_ceil(chunk_ratio);
357 let total = num_payload + num_sb;
358 (num_payload, num_sb, total, chunk_ratio)
359 }
360
361 fn write_bat_entries(
367 w: &mut (impl Write + Seek), virtual_size: u64, block_size: u32, logical_sector_size: u32,
368 fixed: bool, metadata_offset: u64,
369 ) -> Result<()> {
370 let (_num_payload, num_sb, total_entries, chunk_ratio) =
371 Self::compute_bat_entry_counts(virtual_size, block_size, logical_sector_size);
372
373 if !fixed {
374 return Ok(());
376 }
377
378 let payload_align = u64::from(block_size / MIB);
382 let raw_first_payload_mb =
383 (metadata_offset + u64::from(METADATA_REGION_SIZE)).div_ceil(u64::from(MIB));
384 let first_payload_offset_mb = raw_first_payload_mb.div_ceil(payload_align) * payload_align;
385
386 let mut sb_written: u64 = 0;
387 for i in 0..total_entries {
388 let entry_offset = u64::from(BAT_REGION_OFFSET)
389 .checked_add(i.checked_mul(8).expect("BAT entry offset fits u64"))
390 .expect("BAT entry offset fits u64");
391 let payloads_written = i - sb_written;
395 let is_sb = payloads_written > 0
396 && payloads_written.is_multiple_of(chunk_ratio)
397 && sb_written < num_sb;
398 if is_sb {
399 write_all_at(w, entry_offset, &0u64.to_le_bytes())?;
401 sb_written += 1;
402 } else {
403 let payload_idx = payloads_written;
405 let offset_mb = first_payload_offset_mb + payload_idx * u64::from(block_size / MIB);
406 let mut raw_bytes = [0u8; 8];
407 let bits = raw_bytes.view_bits_mut::<Lsb0>();
408 bits[0..3].store::<u8>(6u8); bits[20..64].store::<u64>(offset_mb);
410 write_all_at(w, entry_offset, &raw_bytes)?;
411 }
412 }
413
414 Ok(())
415 }
416
417 fn write_metadata(
420 &self, w: &mut (impl Write + Seek), metadata_offset: u64,
421 parent_data_write_guid: Option<Guid>,
422 ) -> Result<()> {
423 let has_parent = self.parent.is_some();
424 let (items_buf, item_metas) =
425 self.build_metadata_items(has_parent, parent_data_write_guid)?;
426 let table = Self::build_metadata_table(if has_parent { 6 } else { 5 }, &item_metas);
427 write_all_at(w, metadata_offset, &table)?;
428 write_all_at(
429 w,
430 metadata_offset + u64::from(METADATA_TABLE_SIZE),
431 &items_buf,
432 )?;
433 Ok(())
434 }
435
436 fn rel_metadata_offset(items_buf: &[u8]) -> Result<u32> {
437 let base = METADATA_TABLE_SIZE;
438 let rel = u32::try_from(items_buf.len())
439 .map_err(|_| Error::InvalidParameter("metadata items buffer too large".into()))?;
440 base.checked_add(rel)
441 .ok_or_else(|| Error::InvalidParameter("metadata relative offset overflow".into()))
442 }
443
444 fn metadata_flags(is_virtual_disk: bool, is_required: bool) -> u32 {
445 let mut buf = [0u8; 4];
446 let bits = buf.view_bits_mut::<Lsb0>();
447 bits.set(1, is_virtual_disk);
448 bits.set(2, is_required);
449 u32::from_le_bytes(buf)
450 }
451
452 fn build_metadata_items(
453 &self, has_parent: bool, parent_data_write_guid: Option<Guid>,
454 ) -> Result<(Vec<u8>, Vec<MetadataEntryMeta>)> {
455 let virtual_disk_id = Guid::new_v4();
456 let mut items_buf = Vec::new();
457 let mut item_metas = Vec::with_capacity(if has_parent { 6 } else { 5 });
458 self.push_file_parameters_item(&mut items_buf, &mut item_metas, has_parent)?;
459 Self::push_simple_item(
460 &mut items_buf,
461 &mut item_metas,
462 types::StandardItems::VIRTUAL_DISK_SIZE,
463 &self.virtual_size.to_le_bytes(),
464 true,
465 )?;
466 Self::push_simple_item(
467 &mut items_buf,
468 &mut item_metas,
469 types::StandardItems::VIRTUAL_DISK_ID,
470 &virtual_disk_id.to_bytes(),
471 true,
472 )?;
473 Self::push_simple_item(
474 &mut items_buf,
475 &mut item_metas,
476 types::StandardItems::LOGICAL_SECTOR_SIZE,
477 &self.logical_sector_size.to_le_bytes(),
478 true,
479 )?;
480 Self::push_simple_item(
481 &mut items_buf,
482 &mut item_metas,
483 types::StandardItems::PHYSICAL_SECTOR_SIZE,
484 &self.physical_sector_size.to_le_bytes(),
485 true,
486 )?;
487 if has_parent {
488 self.push_parent_locator_item(
489 &mut items_buf,
490 &mut item_metas,
491 parent_data_write_guid
492 .expect("parent_data_write_guid must be set when has_parent is true"),
493 )?;
494 }
495 Ok((items_buf, item_metas))
496 }
497
498 fn push_file_parameters_item(
499 &self, items_buf: &mut Vec<u8>, metas: &mut Vec<MetadataEntryMeta>, has_parent: bool,
500 ) -> Result<()> {
501 let rel = Self::rel_metadata_offset(items_buf)?;
502 let mut fp_buf = [0u8; 8];
503 let fp_bits = fp_buf.view_bits_mut::<Lsb0>();
504 fp_bits[0..32].store_le::<u32>(self.block_size);
505 fp_bits.set(32, self.fixed);
506 fp_bits.set(33, has_parent);
507 items_buf.extend_from_slice(&fp_buf);
508 metas.push(MetadataEntryMeta {
509 guid: types::StandardItems::FILE_PARAMETERS,
510 rel_offset: rel,
511 length: 8,
512 flags: Self::metadata_flags(false, true),
513 });
514 Ok(())
515 }
516
517 fn push_simple_item(
518 items_buf: &mut Vec<u8>, metas: &mut Vec<MetadataEntryMeta>, guid: Guid, bytes: &[u8],
519 is_virtual_disk: bool,
520 ) -> Result<()> {
521 let rel = Self::rel_metadata_offset(items_buf)?;
522 items_buf.extend_from_slice(bytes);
523 metas.push(MetadataEntryMeta {
524 guid,
525 rel_offset: rel,
526 length: u32::try_from(bytes.len()).expect("metadata item length fits u32"),
527 flags: Self::metadata_flags(is_virtual_disk, true),
528 });
529 Ok(())
530 }
531
532 fn push_parent_locator_item(
533 &self, items_buf: &mut Vec<u8>, metas: &mut Vec<MetadataEntryMeta>, parent_guid: Guid,
534 ) -> Result<()> {
535 let rel = Self::rel_metadata_offset(items_buf)?;
536 let pl_data = self.build_parent_locator(parent_guid);
537 let pl_length = u32::try_from(pl_data.len())
538 .map_err(|_| Error::InvalidParameter("parent locator metadata too large".into()))?;
539 items_buf.extend_from_slice(&pl_data);
540 metas.push(MetadataEntryMeta {
541 guid: types::StandardItems::PARENT_LOCATOR,
542 rel_offset: rel,
543 length: pl_length,
544 flags: Self::metadata_flags(false, true),
545 });
546 Ok(())
547 }
548
549 fn build_metadata_table(entry_count: u16, item_metas: &[MetadataEntryMeta]) -> Vec<u8> {
550 let mut table = vec![0u8; METADATA_TABLE_SIZE as usize];
551 table[0..8].copy_from_slice(b"metadata");
552 table[10..12].copy_from_slice(&entry_count.to_le_bytes());
553 let mut entry_off: usize = TABLE_HEADER_SIZE as usize;
554 for meta in item_metas {
555 table[entry_off..entry_off + 16].copy_from_slice(&meta.guid.to_bytes());
556 table[entry_off + 16..entry_off + 20].copy_from_slice(&meta.rel_offset.to_le_bytes());
557 table[entry_off + 20..entry_off + 24].copy_from_slice(&meta.length.to_le_bytes());
558 table[entry_off + 24..entry_off + 28].copy_from_slice(&meta.flags.to_le_bytes());
559 entry_off += TABLE_ENTRY_SIZE as usize;
560 }
561 table
562 }
563
564 fn build_parent_locator(&self, parent_data_write_guid: Guid) -> Vec<u8> {
578 let guid_str = parent_data_write_guid.to_uuid().hyphenated().to_string();
581 let parent_linkage_str = format!("{{{guid_str}}}");
582 let relative_path = self
583 .parent
584 .as_ref()
585 .map(|p| p.relative_path.to_string_lossy().to_string())
586 .unwrap_or_default();
587
588 let key1 = "parent_linkage";
589 let key2 = "relative_path";
590
591 let key1_utf16: Vec<u8> = key1.encode_utf16().flat_map(u16::to_le_bytes).collect();
592 let val1_utf16: Vec<u8> = parent_linkage_str
593 .encode_utf16()
594 .flat_map(u16::to_le_bytes)
595 .collect();
596 let key2_utf16: Vec<u8> = key2.encode_utf16().flat_map(u16::to_le_bytes).collect();
597 let val2_utf16: Vec<u8> = relative_path
598 .encode_utf16()
599 .flat_map(u16::to_le_bytes)
600 .collect();
601
602 let kv_data_start = LOCATOR_HEADER_SIZE as usize + 2 * KV_ENTRY_SIZE as usize;
603
604 let key1_off = kv_data_start;
605 let val1_off = key1_off + key1_utf16.len();
606 let key2_off = val1_off + val1_utf16.len();
607 let val2_off = key2_off + key2_utf16.len();
608
609 let total_len = val2_off + val2_utf16.len();
610 let mut buf = vec![0u8; total_len];
611
612 buf[0..16].copy_from_slice(&types::StandardItems::LOCATOR_TYPE_VHDX.to_bytes());
614 buf[18..20].copy_from_slice(&2u16.to_le_bytes()); let kv0_off = LOCATOR_HEADER_SIZE as usize;
619 buf[kv0_off..kv0_off + 4].copy_from_slice(&u32::try_from(key1_off).unwrap().to_le_bytes());
620 buf[kv0_off + 4..kv0_off + 8]
621 .copy_from_slice(&u32::try_from(val1_off).unwrap().to_le_bytes());
622 buf[kv0_off + 8..kv0_off + 10]
623 .copy_from_slice(&u16::try_from(key1_utf16.len()).unwrap().to_le_bytes());
624 buf[kv0_off + 10..kv0_off + 12]
625 .copy_from_slice(&u16::try_from(val1_utf16.len()).unwrap().to_le_bytes());
626
627 let kv1_off = LOCATOR_HEADER_SIZE as usize + KV_ENTRY_SIZE as usize;
629 buf[kv1_off..kv1_off + 4].copy_from_slice(&u32::try_from(key2_off).unwrap().to_le_bytes());
630 buf[kv1_off + 4..kv1_off + 8]
631 .copy_from_slice(&u32::try_from(val2_off).unwrap().to_le_bytes());
632 buf[kv1_off + 8..kv1_off + 10]
633 .copy_from_slice(&u16::try_from(key2_utf16.len()).unwrap().to_le_bytes());
634 buf[kv1_off + 10..kv1_off + 12]
635 .copy_from_slice(&u16::try_from(val2_utf16.len()).unwrap().to_le_bytes());
636
637 buf[key1_off..key1_off + key1_utf16.len()].copy_from_slice(&key1_utf16);
639 buf[val1_off..val1_off + val1_utf16.len()].copy_from_slice(&val1_utf16);
640 buf[key2_off..key2_off + key2_utf16.len()].copy_from_slice(&key2_utf16);
641 buf[val2_off..val2_off + val2_utf16.len()].copy_from_slice(&val2_utf16);
642
643 buf
644 }
645}