1use crate::format::builder::{FormatDriverBuilder, FormatDriverBuilderBase};
4use crate::format::drivers::FormatDriverInstance;
5use crate::format::gate::ImplicitOpenGate;
6use crate::format::wrapped::WrappedFormat;
7use crate::format::{Format, PreallocateMode};
8use crate::io_buffers::IoBuffer;
9use crate::misc_helpers::{invalid_data, ResultErrorContext};
10use crate::storage::ext::StorageExt;
11use crate::{FormatAccess, ShallowMapping, Storage, StorageOpenOptions};
12use async_trait::async_trait;
13use std::fmt::{self, Display, Formatter};
14use std::marker::PhantomData;
15use std::ops::{Range, RangeInclusive};
16use std::path::{Path, PathBuf};
17use std::str::FromStr;
18use std::sync::atomic::{AtomicU64, Ordering};
19use std::sync::Arc;
20use std::{cmp, io};
21
22const VMDK_SECTOR_SIZE: u64 = 512;
24const VMDK4_MAGIC: u32 = 0x564d444b; const VMDK_VERSION_RANGE: RangeInclusive<u32> = 1..=3;
28
29#[derive(Debug, Clone)]
31enum VmdkStorage<S: Storage + 'static> {
32 Flat {
34 file: S,
36 offset: u64,
38 },
39 Zero,
41}
42
43#[derive(Debug)]
45enum VmdkParsedStorage {
46 Flat {
48 filename: String,
50 offset: u64,
52 },
53 Zero,
55}
56
57#[derive(Debug, Clone, PartialEq)]
59enum VmdkAccessType {
60 RW,
62 RdOnly,
64 NoAccess,
66}
67
68#[derive(Debug)]
70struct VmdkExtent<S: Storage + 'static> {
71 access_type: VmdkAccessType,
73 disk_range: Range<u64>,
78 storage: Option<VmdkStorage<S>>,
82}
83
84#[derive(Debug)]
86struct VmdkParsedExtent {
87 access_type: VmdkAccessType,
89 sectors: u64,
91 storage: Option<VmdkParsedStorage>,
95}
96
97#[derive(Debug)]
99pub struct Vmdk<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>> {
100 descriptor_file: Arc<S>,
102
103 parent_type: PhantomData<F>,
108
109 storage_open_options: StorageOpenOptions,
111
112 size: AtomicU64,
114
115 desc: VmdkDesc,
117
118 parsed_extents: Vec<VmdkParsedExtent>,
120
121 extents: Vec<VmdkExtent<S>>,
123}
124
125#[derive(Debug, Clone)]
127struct VmdkDesc {
128 version: u32,
130 cid: String,
132 parent_cid: String,
134 create_type: String,
136 sectors: u64,
138 heads: u64,
140 cylinders: u64,
142}
143
144impl VmdkParsedExtent {
145 fn try_from_descriptor_line(line: &str) -> io::Result<VmdkParsedExtent> {
147 let mut parts = line.split_whitespace();
150
151 let access_type = match parts
152 .next()
153 .ok_or_else(|| invalid_data("Access type missing"))?
154 {
155 "RW" => VmdkAccessType::RW,
156 "RDONLY" => VmdkAccessType::RdOnly,
157 "NOACCESS" => VmdkAccessType::NoAccess,
158 other => return Err(invalid_data(format!("Invalid access type '{other}'"))),
159 };
160
161 let sectors = parts
162 .next()
163 .ok_or_else(|| invalid_data("Sector count missing"))?
164 .parse()
165 .map_err(|_| invalid_data("Invalid sector count"))?;
166
167 if access_type == VmdkAccessType::NoAccess {
168 return Ok(VmdkParsedExtent {
169 access_type,
170 sectors,
171 storage: None,
172 });
173 }
174
175 let extent_type = parts
176 .next()
177 .ok_or_else(|| invalid_data("Extent type missing"))?;
178 if extent_type == "ZERO" {
179 return Ok(VmdkParsedExtent {
180 access_type,
181 sectors,
182 storage: Some(VmdkParsedStorage::Zero),
183 });
184 }
185 if extent_type != "FLAT" {
186 return Err(io::Error::new(
187 io::ErrorKind::Unsupported,
188 format!("Unsupported extent type {extent_type}"),
189 ));
190 }
191
192 let mut quote_split = line.splitn(3, '"').map(|part| part.trim());
196 let before_filename = quote_split.next().unwrap();
198 let filename = quote_split
199 .next()
200 .ok_or_else(|| invalid_data("Extent filename missing"))?;
201 let after_filename = quote_split
202 .next()
203 .ok_or_else(|| invalid_data("Extent filename not terminated"))?;
204
205 let part_count_before_filename = before_filename.split_whitespace().count();
206 if part_count_before_filename != 3 {
207 return Err(invalid_data(format!(
208 "Expected filename at field index 3, found at {part_count_before_filename}"
209 )));
210 }
211
212 parts = after_filename.split_whitespace();
214
215 let offset = parts
216 .next()
217 .map_or(Ok(0), |ofs_str| ofs_str.parse())
218 .map_err(|_| invalid_data("Invalid offset"))?;
219
220 Ok(VmdkParsedExtent {
221 access_type,
222 sectors,
223 storage: Some(VmdkParsedStorage::Flat {
224 filename: filename.to_string(),
225 offset,
226 }),
227 })
228 }
229}
230
231fn strip_quotes(input: &str) -> &str {
233 input
234 .strip_prefix('"')
235 .and_then(|value| value.strip_suffix('"'))
236 .unwrap_or(input)
237}
238
239fn parse_desc_value<F: FromStr>(key: &str, value: &str) -> io::Result<F> {
241 let stripped = strip_quotes(value);
242
243 stripped
244 .parse::<F>()
245 .map_err(|_| invalid_data(format!("Invalid '{key}' value: {stripped}")))
246}
247
248impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Vmdk<S, F> {
249 pub fn builder(image: S) -> VmdkOpenBuilder<S, F> {
251 VmdkOpenBuilder::new(image)
252 }
253
254 pub fn builder_path<P: AsRef<Path>>(image_path: P) -> VmdkOpenBuilder<S, F> {
256 VmdkOpenBuilder::new_path(image_path)
257 }
258
259 async fn open_implicit_extent<G: ImplicitOpenGate<S>>(
264 &self,
265 extent: &VmdkParsedExtent,
266 in_disk_offset: u64,
267 open_gate: &mut G,
268 ) -> io::Result<VmdkExtent<S>> {
269 let sectors = extent.sectors;
270 let size = sectors.checked_mul(VMDK_SECTOR_SIZE).ok_or_else(|| {
271 invalid_data(format!(
272 "Extent size overflow: {sectors} * {VMDK_SECTOR_SIZE}"
273 ))
274 })?;
275 let disk_range = in_disk_offset..in_disk_offset.checked_add(size).ok_or_else(|| {
276 invalid_data(format!("Extent offset overflow: {in_disk_offset} + {size}"))
277 })?;
278
279 let Some(storage) = extent.storage.as_ref() else {
280 return Ok(VmdkExtent {
281 access_type: extent.access_type.clone(),
282 disk_range,
283 storage: None,
284 });
285 };
286
287 let storage = match storage {
288 VmdkParsedStorage::Flat { filename, offset } => {
289 let absolute = self
290 .descriptor_file
291 .resolve_relative_path(filename)
292 .err_context(|| format!("Cannot resolve storage file name {filename}"))?;
293
294 let mut file_opts = self.storage_open_options.clone().filename(absolute.clone());
295 if extent.access_type == VmdkAccessType::RdOnly {
296 file_opts = file_opts.write(false);
297 }
298
299 let file = open_gate
300 .open_storage(file_opts)
301 .await
302 .err_context(|| format!("Data storage file {absolute:?}"))?;
303
304 VmdkStorage::Flat {
305 file,
306 offset: *offset,
307 }
308 }
309
310 VmdkParsedStorage::Zero => VmdkStorage::Zero,
311 };
312
313 Ok(VmdkExtent {
314 access_type: extent.access_type.clone(),
315 disk_range,
316 storage: Some(storage),
317 })
318 }
319
320 fn error_out_unsupported_version(&self) -> io::Result<()> {
322 let version = self.desc.version;
323 if !VMDK_VERSION_RANGE.contains(&version) {
324 return Err(io::Error::new(
325 io::ErrorKind::Unsupported,
326 format!("unsupported version {version}"),
327 ));
328 }
329 Ok(())
330 }
331
332 fn parse_descriptor_line(&mut self, line: &str) -> io::Result<()> {
334 let line = line.trim();
335
336 if line.is_empty() || line.starts_with('#') {
337 return Ok(());
338 }
339
340 if let Some((access, _)) = line.split_once(char::is_whitespace) {
342 if matches!(access, "RW" | "RDONLY" | "NOACCESS") {
343 let extent = VmdkParsedExtent::try_from_descriptor_line(line)?;
344 self.parsed_extents.push(extent);
345 return Ok(());
346 }
347 }
348
349 let Some((key, value)) = line.split_once('=') else {
350 return Ok(());
352 };
353 let key = key.trim();
354 let value = value.trim();
355
356 match key {
357 "version" => {
358 self.desc.version = value
359 .parse()
360 .map_err(|_| invalid_data("Invalid version format"))?;
361 }
362 "CID" => self.desc.cid = value.to_string(),
363 "parentCID" => self.desc.parent_cid = value.to_string(),
364 "createType" => self.desc.create_type = strip_quotes(value).to_string(),
365 "parentFileNameHint" => {
366 return Err(io::Error::new(
367 io::ErrorKind::Unsupported,
368 "unsupported VMDK differential image (delta link)",
369 ))
370 }
371 "ddb.geometry.sectors" => self.desc.sectors = parse_desc_value(key, value)?,
372 "ddb.geometry.heads" => self.desc.heads = parse_desc_value(key, value)?,
373 "ddb.geometry.cylinders" => self.desc.cylinders = parse_desc_value(key, value)?,
374
375 key if key.starts_with("ddb.") => (),
377
378 key => {
379 return Err(invalid_data(format!(
380 "Unrecognized VMDK descriptor file key '{key}'"
381 )))
382 }
383 }
384
385 Ok(())
386 }
387
388 async fn parse_descriptor_file(&mut self) -> io::Result<()> {
390 let desc_file_sz = self.descriptor_file.size()?;
391 if desc_file_sz < 4 {
392 return Err(invalid_data("VMDK descriptor file too short"));
393 }
394 if desc_file_sz > 2 * 1024 * 1024 {
396 return Err(invalid_data(
397 "VMDK descriptor file too long (max. 2 MB supported)",
398 ));
399 }
400
401 let desc_file_sz: usize = desc_file_sz.try_into().unwrap();
402 let mut desc_file = IoBuffer::new(desc_file_sz, self.descriptor_file.mem_align())?;
403 self.descriptor_file.read(desc_file.as_mut(), 0).await?;
404
405 let desc_file = desc_file.as_ref().into_slice();
406
407 if u32::from_le_bytes(desc_file[..4].try_into().unwrap()) == VMDK4_MAGIC {
409 return Err(io::Error::new(
410 io::ErrorKind::Unsupported,
411 "Unsupported VMDK sparse data file",
412 ));
413 }
414
415 for (line_i, line) in desc_file.split(|chr| *chr == b'\n').enumerate() {
416 let line = str::from_utf8(line).map_err(|e| {
417 invalid_data(format!(
418 "{}: Line {}: {e}",
419 self.descriptor_file,
420 line_i + 1
421 ))
422 })?;
423
424 self.parse_descriptor_line(line)
425 .err_context(|| format!("{}: Line {}", self.descriptor_file, line_i + 1))?;
426 }
427
428 self.error_out_unsupported_version()?;
429 self.size = self
430 .parsed_extents
431 .iter()
432 .try_fold(0u64, |sum, extent| {
433 let sectors = extent.sectors;
434 let size = sectors.checked_mul(VMDK_SECTOR_SIZE).ok_or_else(|| {
435 invalid_data(format!(
436 "Extent size overflow: {sectors} * {VMDK_SECTOR_SIZE}"
437 ))
438 })?;
439 sum.checked_add(size)
440 .ok_or_else(|| invalid_data(format!("Extent offset overflow: {sum} + {size}")))
441 })?
442 .into();
443
444 Ok(())
445 }
446
447 async fn do_open(
449 descriptor_file: S,
450 storage_open_options: StorageOpenOptions,
451 ) -> io::Result<Self> {
452 let mut vmdk = Vmdk {
453 descriptor_file: Arc::new(descriptor_file),
454 parent_type: PhantomData,
455 desc: VmdkDesc {
456 version: 0,
457 cid: String::new(),
458 parent_cid: String::new(),
459 create_type: String::new(),
460 sectors: 0,
461 heads: 0,
462 cylinders: 0,
463 },
464 parsed_extents: vec![],
465 extents: vec![],
466 size: 0.into(),
467 storage_open_options,
468 };
469
470 vmdk.parse_descriptor_file().await?;
471 Ok(vmdk)
472 }
473
474 pub async fn open_image(descriptor_file: S, writable: bool) -> io::Result<Self> {
481 if writable {
482 return Err(io::Error::new(
483 io::ErrorKind::Unsupported,
484 "No VMDK write support",
485 ));
486 }
487 Self::do_open(descriptor_file, StorageOpenOptions::new()).await
488 }
489
490 pub async fn open_implicit_dependencies_gated<G: ImplicitOpenGate<S>>(
494 &mut self,
495 mut gate: G,
496 ) -> io::Result<()> {
497 if self.extents.is_empty() {
498 let mut in_disk_offset = 0;
499 for extent in &self.parsed_extents {
500 let opened = self
501 .open_implicit_extent(extent, in_disk_offset, &mut gate)
502 .await?;
503 in_disk_offset = opened.disk_range.end;
504 self.extents.push(opened);
505 }
506 }
507
508 Ok(())
509 }
510
511 fn get_extent_at(&self, offset: u64) -> Option<&VmdkExtent<S>> {
513 self.extents
514 .binary_search_by(|extent| {
515 if extent.disk_range.contains(&offset) {
516 cmp::Ordering::Equal
517 } else if extent.disk_range.end < offset {
518 cmp::Ordering::Less
519 } else {
520 cmp::Ordering::Greater
521 }
522 })
523 .ok()
524 .map(|index| &self.extents[index])
525 }
526}
527
528impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Display for Vmdk<S, F> {
529 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
530 write!(f, "vmdk[{}]", self.descriptor_file)
531 }
532}
533
534#[async_trait(?Send)]
535impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatDriverInstance for Vmdk<S, F> {
536 type Storage = S;
537
538 fn format(&self) -> Format {
539 Format::Vmdk
540 }
541
542 async unsafe fn probe(storage: &S) -> io::Result<bool>
543 where
544 Self: Sized,
545 {
546 let desc_file_size = storage.size()?;
551 if !(4..=2 * 1024 * 1024).contains(&desc_file_size) {
552 return Ok(false);
553 }
554
555 let desc_file_size: usize = desc_file_size.try_into().unwrap();
556 let mut desc_file = IoBuffer::new(desc_file_size, storage.mem_align())?;
557 storage.read(desc_file.as_mut(), 0).await?;
558
559 let desc_file = desc_file.as_ref().into_slice();
560 if u32::from_le_bytes(desc_file[..4].try_into().unwrap()) == VMDK4_MAGIC {
561 return Ok(true);
562 }
563
564 for line in desc_file.split(|chr| *chr == b'\n') {
565 let Ok(line) = str::from_utf8(line) else {
566 return Ok(false);
567 };
568
569 let Some((key, value)) = line.split_once('=') else {
570 continue;
571 };
572 if key.trim() == "version" {
573 let Ok(version) = value.trim().parse() else {
574 return Ok(false);
575 };
576 return Ok(VMDK_VERSION_RANGE.contains(&version));
577 }
578 }
579
580 Ok(false)
581 }
582
583 fn size(&self) -> u64 {
584 self.size.load(Ordering::Relaxed)
585 }
586
587 fn zero_granularity(&self) -> Option<u64> {
588 None
589 }
590
591 fn collect_storage_dependencies(&self) -> Vec<&S> {
592 let mut v = vec![self.descriptor_file.as_ref()];
593 for e in &self.extents {
594 let Some(storage) = e.storage.as_ref() else {
595 continue;
596 };
597 match storage {
598 VmdkStorage::Flat { file, offset: _ } => v.push(file),
599 VmdkStorage::Zero => (),
600 }
601 }
602 v
603 }
604
605 fn writable(&self) -> bool {
606 false
607 }
608
609 async fn get_mapping<'a>(
610 &'a self,
611 offset: u64,
612 max_length: u64,
613 ) -> io::Result<(ShallowMapping<'a, S>, u64)> {
614 let max_length = match self.size().checked_sub(offset) {
615 None | Some(0) => return Ok((ShallowMapping::Eof {}, 0)),
616 Some(remaining) => cmp::min(remaining, max_length),
617 };
618
619 let Some(extent) = self.get_extent_at(offset) else {
620 return Ok((ShallowMapping::Eof {}, 0));
621 };
622 let in_extent_offset = offset - extent.disk_range.start;
624
625 let writable = match extent.access_type {
626 VmdkAccessType::RW => true,
627 VmdkAccessType::RdOnly => false,
628 VmdkAccessType::NoAccess => {
629 return Err(io::Error::other("NOACCESS extent is accessed"));
631 }
632 };
633
634 let mapping = match extent.storage.as_ref().unwrap() {
636 VmdkStorage::Flat {
637 file,
638 offset: base_offset,
639 } => ShallowMapping::Raw {
640 storage: file,
641 offset: base_offset.checked_add(in_extent_offset).ok_or_else(|| {
642 invalid_data(format!(
643 "Extent offset overflow: {base_offset} + {in_extent_offset}"
644 ))
645 })?,
646 writable,
647 },
648
649 VmdkStorage::Zero => ShallowMapping::Zero { explicit: true },
650 };
651
652 Ok((
653 mapping,
654 cmp::min(max_length, extent.disk_range.end - offset),
655 ))
656 }
657
658 async fn ensure_data_mapping<'a>(
659 &'a self,
660 _offset: u64,
661 _length: u64,
662 _overwrite: bool,
663 ) -> io::Result<(&'a S, u64, u64)> {
664 Err(io::Error::other("Image is read-only"))
665 }
666
667 async fn flush(&self) -> io::Result<()> {
668 Ok(())
669 }
670
671 async fn sync(&self) -> io::Result<()> {
672 Ok(())
673 }
674
675 async unsafe fn invalidate_cache(&self) -> io::Result<()> {
676 Ok(())
677 }
678
679 async fn resize_grow(&self, _new_size: u64, _prealloc_mode: PreallocateMode) -> io::Result<()> {
680 Err(io::Error::other("Image is read-only"))
681 }
682
683 async fn resize_shrink(&mut self, _new_size: u64) -> io::Result<()> {
684 Err(io::Error::other("Image is read-only"))
685 }
686}
687
688pub struct VmdkOpenBuilder<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>>(
690 FormatDriverBuilderBase<S>,
691 PhantomData<F>,
692);
693
694impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatDriverBuilder<S>
695 for VmdkOpenBuilder<S, F>
696{
697 type Format = Vmdk<S, F>;
698 const FORMAT: Format = Format::Vmdk;
699
700 fn new(image: S) -> Self {
701 VmdkOpenBuilder(FormatDriverBuilderBase::new(image), PhantomData)
702 }
703
704 fn new_path<P: AsRef<Path>>(path: P) -> Self {
705 VmdkOpenBuilder(FormatDriverBuilderBase::new_path(path), PhantomData)
706 }
707
708 fn write(mut self, writable: bool) -> Self {
709 self.0.set_write(writable);
710 self
711 }
712
713 fn storage_open_options(mut self, options: StorageOpenOptions) -> Self {
714 self.0.set_storage_open_options(options);
715 self
716 }
717
718 async fn open<G: ImplicitOpenGate<S>>(self, mut gate: G) -> io::Result<Self::Format> {
719 if self.0.get_writable() {
720 return Err(io::Error::new(
721 io::ErrorKind::Unsupported,
722 "No VMDK write support",
723 ));
724 }
725
726 let file = self.0.open_image(&mut gate).await?;
727 let mut vmdk = Vmdk::open_image(file, false).await?;
728 vmdk.open_implicit_dependencies_gated(gate).await?;
729 Ok(vmdk)
730 }
731
732 fn get_image_path(&self) -> Option<PathBuf> {
733 self.0.get_image_path()
734 }
735
736 fn get_writable(&self) -> bool {
737 self.0.get_writable()
738 }
739
740 fn get_storage_open_options(&self) -> Option<&StorageOpenOptions> {
741 self.0.get_storage_opts()
742 }
743}