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,
53 },
54 Zero,
56}
57
58#[derive(Debug, Clone, PartialEq)]
60enum VmdkAccessType {
61 RW,
63 RdOnly,
65 NoAccess,
67}
68
69#[derive(Debug)]
71struct VmdkExtent<S: Storage + 'static> {
72 access_type: VmdkAccessType,
74 disk_range: Range<u64>,
79 storage: Option<VmdkStorage<S>>,
83}
84
85#[derive(Debug)]
87struct VmdkParsedExtent {
88 access_type: VmdkAccessType,
90 sectors: u64,
92 storage: Option<VmdkParsedStorage>,
96}
97
98#[derive(Debug)]
100pub struct Vmdk<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>> {
101 descriptor_file: Arc<S>,
103
104 parent_type: PhantomData<F>,
109
110 storage_open_options: StorageOpenOptions,
112
113 size: AtomicU64,
115
116 desc: VmdkDesc,
118
119 parsed_extents: Vec<VmdkParsedExtent>,
121
122 extents: Vec<VmdkExtent<S>>,
124}
125
126#[derive(Debug, Clone)]
128struct VmdkDesc {
129 version: u32,
131 cid: String,
133 parent_cid: String,
135 create_type: String,
137 sectors: u64,
139 heads: u64,
141 cylinders: u64,
143}
144
145impl VmdkParsedExtent {
146 fn try_from_descriptor_line(line: &str) -> io::Result<VmdkParsedExtent> {
148 let mut parts = line.split_whitespace();
151
152 let access_type = match parts
153 .next()
154 .ok_or_else(|| invalid_data("Access type missing"))?
155 {
156 "RW" => VmdkAccessType::RW,
157 "RDONLY" => VmdkAccessType::RdOnly,
158 "NOACCESS" => VmdkAccessType::NoAccess,
159 other => return Err(invalid_data(format!("Invalid access type '{other}'"))),
160 };
161
162 let sectors = parts
163 .next()
164 .ok_or_else(|| invalid_data("Sector count missing"))?
165 .parse()
166 .map_err(|_| invalid_data("Invalid sector count"))?;
167
168 if access_type == VmdkAccessType::NoAccess {
169 return Ok(VmdkParsedExtent {
170 access_type,
171 sectors,
172 storage: None,
173 });
174 }
175
176 let extent_type = parts
177 .next()
178 .ok_or_else(|| invalid_data("Extent type missing"))?;
179 if extent_type == "ZERO" {
180 return Ok(VmdkParsedExtent {
181 access_type,
182 sectors,
183 storage: Some(VmdkParsedStorage::Zero),
184 });
185 }
186 if extent_type != "FLAT" {
187 return Err(io::Error::new(
188 io::ErrorKind::Unsupported,
189 format!("Unsupported extent type {extent_type}"),
190 ));
191 }
192
193 let mut quote_split = line.splitn(3, '"').map(|part| part.trim());
197 let before_filename = quote_split.next().unwrap();
199 let filename = quote_split
200 .next()
201 .ok_or_else(|| invalid_data("Extent filename missing"))?;
202 let after_filename = quote_split
203 .next()
204 .ok_or_else(|| invalid_data("Extent filename not terminated"))?;
205
206 let part_count_before_filename = before_filename.split_whitespace().count();
207 if part_count_before_filename != 3 {
208 return Err(invalid_data(format!(
209 "Expected filename at field index 3, found at {part_count_before_filename}"
210 )));
211 }
212
213 parts = after_filename.split_whitespace();
215
216 let offset = parts
217 .next()
218 .map_or(Ok(0), |ofs_str| ofs_str.parse())
219 .map_err(|_| invalid_data("Invalid offset"))?;
220
221 Ok(VmdkParsedExtent {
222 access_type,
223 sectors,
224 storage: Some(VmdkParsedStorage::Flat {
225 filename: filename.to_string(),
226 offset,
227 }),
228 })
229 }
230}
231
232fn strip_quotes(input: &str) -> &str {
234 input
235 .strip_prefix('"')
236 .and_then(|value| value.strip_suffix('"'))
237 .unwrap_or(input)
238}
239
240fn parse_desc_value<F: FromStr>(key: &str, value: &str) -> io::Result<F> {
242 let stripped = strip_quotes(value);
243
244 stripped
245 .parse::<F>()
246 .map_err(|_| invalid_data(format!("Invalid '{key}' value: {stripped}")))
247}
248
249impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Vmdk<S, F> {
250 pub fn builder(image: S) -> VmdkOpenBuilder<S, F> {
252 VmdkOpenBuilder::new(image)
253 }
254
255 pub fn builder_path<P: AsRef<Path>>(image_path: P) -> VmdkOpenBuilder<S, F> {
257 VmdkOpenBuilder::new_path(image_path)
258 }
259
260 async fn open_implicit_extent<G: ImplicitOpenGate<S>>(
265 &self,
266 extent: &VmdkParsedExtent,
267 in_disk_offset: u64,
268 open_gate: &mut G,
269 ) -> io::Result<VmdkExtent<S>> {
270 let sectors = extent.sectors;
271 let size = sectors.checked_mul(VMDK_SECTOR_SIZE).ok_or_else(|| {
272 invalid_data(format!(
273 "Extent size overflow: {sectors} * {VMDK_SECTOR_SIZE}"
274 ))
275 })?;
276 let disk_range = in_disk_offset..in_disk_offset.checked_add(size).ok_or_else(|| {
277 invalid_data(format!("Extent offset overflow: {in_disk_offset} + {size}"))
278 })?;
279
280 let Some(storage) = extent.storage.as_ref() else {
281 return Ok(VmdkExtent {
282 access_type: extent.access_type.clone(),
283 disk_range,
284 storage: None,
285 });
286 };
287
288 let storage = match storage {
289 VmdkParsedStorage::Flat { filename, offset } => {
290 let absolute = self
291 .descriptor_file
292 .resolve_relative_path(filename)
293 .err_context(|| format!("Cannot resolve storage file name {filename}"))?;
294
295 let mut file_opts = self.storage_open_options.clone().filename(absolute.clone());
296 if extent.access_type == VmdkAccessType::RdOnly {
297 file_opts = file_opts.write(false);
298 }
299
300 let file = open_gate
301 .open_storage(file_opts)
302 .await
303 .err_context(|| format!("Data storage file {absolute:?}"))?;
304
305 VmdkStorage::Flat {
306 file,
307 offset: (*offset).checked_mul(VMDK_SECTOR_SIZE).ok_or_else(|| {
310 invalid_data(format!(
311 "Extent offset overflow: {offset} * {VMDK_SECTOR_SIZE}"
312 ))
313 })?,
314 }
315 }
316
317 VmdkParsedStorage::Zero => VmdkStorage::Zero,
318 };
319
320 Ok(VmdkExtent {
321 access_type: extent.access_type.clone(),
322 disk_range,
323 storage: Some(storage),
324 })
325 }
326
327 fn error_out_unsupported_version(&self) -> io::Result<()> {
329 let version = self.desc.version;
330 if !VMDK_VERSION_RANGE.contains(&version) {
331 return Err(io::Error::new(
332 io::ErrorKind::Unsupported,
333 format!("unsupported version {version}"),
334 ));
335 }
336 Ok(())
337 }
338
339 fn parse_descriptor_line(&mut self, line: &str) -> io::Result<()> {
341 let line = line.trim();
342
343 if line.is_empty() || line.starts_with('#') {
344 return Ok(());
345 }
346
347 if let Some((access, _)) = line.split_once(char::is_whitespace) {
349 if matches!(access, "RW" | "RDONLY" | "NOACCESS") {
350 let extent = VmdkParsedExtent::try_from_descriptor_line(line)?;
351 self.parsed_extents.push(extent);
352 return Ok(());
353 }
354 }
355
356 let Some((key, value)) = line.split_once('=') else {
357 return Ok(());
359 };
360 let key = key.trim();
361 let value = value.trim();
362
363 match key {
364 "version" => {
365 self.desc.version = value
366 .parse()
367 .map_err(|_| invalid_data("Invalid version format"))?;
368 }
369 "CID" => self.desc.cid = value.to_string(),
370 "parentCID" => self.desc.parent_cid = value.to_string(),
371 "createType" => self.desc.create_type = strip_quotes(value).to_string(),
372 "parentFileNameHint" => {
373 return Err(io::Error::new(
374 io::ErrorKind::Unsupported,
375 "unsupported VMDK differential image (delta link)",
376 ))
377 }
378 "ddb.geometry.sectors" => self.desc.sectors = parse_desc_value(key, value)?,
379 "ddb.geometry.heads" => self.desc.heads = parse_desc_value(key, value)?,
380 "ddb.geometry.cylinders" => self.desc.cylinders = parse_desc_value(key, value)?,
381
382 key if key.starts_with("ddb.") => (),
384
385 key => {
386 return Err(invalid_data(format!(
387 "Unrecognized VMDK descriptor file key '{key}'"
388 )))
389 }
390 }
391
392 Ok(())
393 }
394
395 async fn parse_descriptor_file(&mut self) -> io::Result<()> {
397 let desc_file_sz = self.descriptor_file.size()?;
398 if desc_file_sz < 4 {
399 return Err(invalid_data("VMDK descriptor file too short"));
400 }
401 if desc_file_sz > 2 * 1024 * 1024 {
403 return Err(invalid_data(
404 "VMDK descriptor file too long (max. 2 MB supported)",
405 ));
406 }
407
408 let desc_file_sz: usize = desc_file_sz.try_into().unwrap();
409 let mut desc_file = IoBuffer::new(desc_file_sz, self.descriptor_file.mem_align())?;
410 self.descriptor_file.read(desc_file.as_mut(), 0).await?;
411
412 let desc_file = desc_file.as_ref().into_slice();
413
414 if u32::from_le_bytes(desc_file[..4].try_into().unwrap()) == VMDK4_MAGIC {
416 return Err(io::Error::new(
417 io::ErrorKind::Unsupported,
418 "Unsupported VMDK sparse data file",
419 ));
420 }
421
422 for (line_i, line) in desc_file.split(|chr| *chr == b'\n').enumerate() {
423 let line = str::from_utf8(line).map_err(|e| {
424 invalid_data(format!(
425 "{}: Line {}: {e}",
426 self.descriptor_file,
427 line_i + 1
428 ))
429 })?;
430
431 self.parse_descriptor_line(line)
432 .err_context(|| format!("{}: Line {}", self.descriptor_file, line_i + 1))?;
433 }
434
435 self.error_out_unsupported_version()?;
436 self.size = self
437 .parsed_extents
438 .iter()
439 .try_fold(0u64, |sum, extent| {
440 let sectors = extent.sectors;
441 let size = sectors.checked_mul(VMDK_SECTOR_SIZE).ok_or_else(|| {
442 invalid_data(format!(
443 "Extent size overflow: {sectors} * {VMDK_SECTOR_SIZE}"
444 ))
445 })?;
446 sum.checked_add(size)
447 .ok_or_else(|| invalid_data(format!("Extent offset overflow: {sum} + {size}")))
448 })?
449 .into();
450
451 Ok(())
452 }
453
454 async fn do_open(
456 descriptor_file: S,
457 storage_open_options: StorageOpenOptions,
458 ) -> io::Result<Self> {
459 let mut vmdk = Vmdk {
460 descriptor_file: Arc::new(descriptor_file),
461 parent_type: PhantomData,
462 desc: VmdkDesc {
463 version: 0,
464 cid: String::new(),
465 parent_cid: String::new(),
466 create_type: String::new(),
467 sectors: 0,
468 heads: 0,
469 cylinders: 0,
470 },
471 parsed_extents: vec![],
472 extents: vec![],
473 size: 0.into(),
474 storage_open_options,
475 };
476
477 vmdk.parse_descriptor_file().await?;
478 Ok(vmdk)
479 }
480
481 pub async fn open_image(descriptor_file: S, writable: bool) -> io::Result<Self> {
488 if writable {
489 return Err(io::Error::new(
490 io::ErrorKind::Unsupported,
491 "No VMDK write support",
492 ));
493 }
494 Self::do_open(descriptor_file, StorageOpenOptions::new()).await
495 }
496
497 pub async fn open_implicit_dependencies_gated<G: ImplicitOpenGate<S>>(
501 &mut self,
502 mut gate: G,
503 ) -> io::Result<()> {
504 if self.extents.is_empty() {
505 let mut in_disk_offset = 0;
506 for extent in &self.parsed_extents {
507 let opened = self
508 .open_implicit_extent(extent, in_disk_offset, &mut gate)
509 .await?;
510 in_disk_offset = opened.disk_range.end;
511 self.extents.push(opened);
512 }
513 }
514
515 Ok(())
516 }
517
518 fn get_extent_at(&self, offset: u64) -> Option<&VmdkExtent<S>> {
520 self.extents
521 .binary_search_by(|extent| {
522 if extent.disk_range.contains(&offset) {
523 cmp::Ordering::Equal
524 } else if extent.disk_range.end <= offset {
525 cmp::Ordering::Less
528 } else {
529 cmp::Ordering::Greater
530 }
531 })
532 .ok()
533 .map(|index| &self.extents[index])
534 }
535}
536
537impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Display for Vmdk<S, F> {
538 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
539 write!(f, "vmdk[{}]", self.descriptor_file)
540 }
541}
542
543#[async_trait(?Send)]
544impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatDriverInstance for Vmdk<S, F> {
545 type Storage = S;
546
547 fn format(&self) -> Format {
548 Format::Vmdk
549 }
550
551 async unsafe fn probe(storage: &S) -> io::Result<bool>
552 where
553 Self: Sized,
554 {
555 let desc_file_size = storage.size()?;
560 if !(4..=2 * 1024 * 1024).contains(&desc_file_size) {
561 return Ok(false);
562 }
563
564 let desc_file_size: usize = desc_file_size.try_into().unwrap();
565 let mut desc_file = IoBuffer::new(desc_file_size, storage.mem_align())?;
566 storage.read(desc_file.as_mut(), 0).await?;
567
568 let desc_file = desc_file.as_ref().into_slice();
569 if u32::from_le_bytes(desc_file[..4].try_into().unwrap()) == VMDK4_MAGIC {
570 return Ok(true);
571 }
572
573 for line in desc_file.split(|chr| *chr == b'\n') {
574 let Ok(line) = str::from_utf8(line) else {
575 return Ok(false);
576 };
577
578 let Some((key, value)) = line.split_once('=') else {
579 continue;
580 };
581 if key.trim() == "version" {
582 let Ok(version) = value.trim().parse() else {
583 return Ok(false);
584 };
585 return Ok(VMDK_VERSION_RANGE.contains(&version));
586 }
587 }
588
589 Ok(false)
590 }
591
592 fn size(&self) -> u64 {
593 self.size.load(Ordering::Relaxed)
594 }
595
596 fn zero_granularity(&self) -> Option<u64> {
597 None
598 }
599
600 fn collect_storage_dependencies(&self) -> Vec<&S> {
601 let mut v = vec![self.descriptor_file.as_ref()];
602 for e in &self.extents {
603 let Some(storage) = e.storage.as_ref() else {
604 continue;
605 };
606 match storage {
607 VmdkStorage::Flat { file, offset: _ } => v.push(file),
608 VmdkStorage::Zero => (),
609 }
610 }
611 v
612 }
613
614 fn writable(&self) -> bool {
615 false
616 }
617
618 async fn get_mapping<'a>(
619 &'a self,
620 offset: u64,
621 max_length: u64,
622 ) -> io::Result<(ShallowMapping<'a, S>, u64)> {
623 let max_length = match self.size().checked_sub(offset) {
624 None | Some(0) => return Ok((ShallowMapping::Eof {}, 0)),
625 Some(remaining) => cmp::min(remaining, max_length),
626 };
627
628 let Some(extent) = self.get_extent_at(offset) else {
629 return Ok((ShallowMapping::Eof {}, 0));
630 };
631 let in_extent_offset = offset - extent.disk_range.start;
633
634 let writable = match extent.access_type {
635 VmdkAccessType::RW => true,
636 VmdkAccessType::RdOnly => false,
637 VmdkAccessType::NoAccess => {
638 return Err(io::Error::other("NOACCESS extent is accessed"));
640 }
641 };
642
643 let mapping = match extent.storage.as_ref().unwrap() {
645 VmdkStorage::Flat {
646 file,
647 offset: base_offset,
648 } => ShallowMapping::Raw {
649 storage: file,
650 offset: base_offset.checked_add(in_extent_offset).ok_or_else(|| {
651 invalid_data(format!(
652 "Extent offset overflow: {base_offset} + {in_extent_offset}"
653 ))
654 })?,
655 writable,
656 },
657
658 VmdkStorage::Zero => ShallowMapping::Zero { explicit: true },
659 };
660
661 Ok((
662 mapping,
663 cmp::min(max_length, extent.disk_range.end - offset),
664 ))
665 }
666
667 async fn ensure_data_mapping<'a>(
668 &'a self,
669 _offset: u64,
670 _length: u64,
671 _overwrite: bool,
672 ) -> io::Result<(&'a S, u64, u64)> {
673 Err(io::Error::other("Image is read-only"))
674 }
675
676 async fn flush(&self) -> io::Result<()> {
677 Ok(())
678 }
679
680 async fn sync(&self) -> io::Result<()> {
681 Ok(())
682 }
683
684 async unsafe fn invalidate_cache(&self) -> io::Result<()> {
685 Ok(())
686 }
687
688 async fn resize_grow(&self, _new_size: u64, _prealloc_mode: PreallocateMode) -> io::Result<()> {
689 Err(io::Error::other("Image is read-only"))
690 }
691
692 async fn resize_shrink(&mut self, _new_size: u64) -> io::Result<()> {
693 Err(io::Error::other("Image is read-only"))
694 }
695}
696
697pub struct VmdkOpenBuilder<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>>(
699 FormatDriverBuilderBase<S>,
700 PhantomData<F>,
701);
702
703impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatDriverBuilder<S>
704 for VmdkOpenBuilder<S, F>
705{
706 type Format = Vmdk<S, F>;
707 const FORMAT: Format = Format::Vmdk;
708
709 fn new(image: S) -> Self {
710 VmdkOpenBuilder(FormatDriverBuilderBase::new(image), PhantomData)
711 }
712
713 fn new_path<P: AsRef<Path>>(path: P) -> Self {
714 VmdkOpenBuilder(FormatDriverBuilderBase::new_path(path), PhantomData)
715 }
716
717 fn write(mut self, writable: bool) -> Self {
718 self.0.set_write(writable);
719 self
720 }
721
722 fn storage_open_options(mut self, options: StorageOpenOptions) -> Self {
723 self.0.set_storage_open_options(options);
724 self
725 }
726
727 async fn open<G: ImplicitOpenGate<S>>(self, mut gate: G) -> io::Result<Self::Format> {
728 if self.0.get_writable() {
729 return Err(io::Error::new(
730 io::ErrorKind::Unsupported,
731 "No VMDK write support",
732 ));
733 }
734
735 let file = self.0.open_image(&mut gate).await?;
736 let mut vmdk = Vmdk::open_image(file, false).await?;
737 vmdk.open_implicit_dependencies_gated(gate).await?;
738 Ok(vmdk)
739 }
740
741 fn get_image_path(&self) -> Option<PathBuf> {
742 self.0.get_image_path()
743 }
744
745 fn get_writable(&self) -> bool {
746 self.0.get_writable()
747 }
748
749 fn get_storage_open_options(&self) -> Option<&StorageOpenOptions> {
750 self.0.get_storage_opts()
751 }
752}
753
754#[cfg(test)]
759mod tests {
760 use super::Vmdk;
761 use crate::file::File;
762 use crate::format::access::{FormatAccess, FormatReadPlanStep};
763 use crate::{FormatDriverBuilder, PermissiveImplicitOpenGate};
764 use std::io;
765
766 #[test]
769 fn flat_nonzero_offset_is_scaled_sectors_to_bytes() -> io::Result<()> {
770 let runtime = tokio::runtime::Builder::new_current_thread().build()?;
771 runtime.block_on(async {
772 let dir = std::env::temp_dir().join(format!("imago_vmdk_off_{}", std::process::id()));
774 std::fs::create_dir_all(&dir)?;
775 let flat_path = dir.join("layer.flat");
776 let desc_path = dir.join("disk.vmdk");
777
778 std::fs::write(&flat_path, vec![0u8; 4 * 512])?;
780
781 let desc = "# Disk DescriptorFile\n\
783 version=1\n\
784 CID=fffffffe\n\
785 parentCID=ffffffff\n\
786 createType=\"twoGbMaxExtentFlat\"\n\
787 \n\
788 RW 2 FLAT \"layer.flat\" 0\n\
789 RW 2 FLAT \"layer.flat\" 2\n\
790 \n\
791 ddb.geometry.cylinders = \"1\"\n\
792 ddb.geometry.heads = \"16\"\n\
793 ddb.geometry.sectors = \"63\"\n";
794 std::fs::write(&desc_path, desc)?;
795
796 let vmdk = Vmdk::<File>::builder_path(&desc_path)
797 .open(PermissiveImplicitOpenGate::default())
798 .await?;
799 let image = FormatAccess::new(vmdk);
800
801 let plan = image.plan_read(1024, 512).await?;
803 let steps = plan.steps();
804 assert!(!steps.is_empty(), "expected a read step, got none");
805 let storage_offset = match &steps[0] {
808 FormatReadPlanStep::Raw { offset, .. } => *offset,
809 step => panic!("expected a Raw step, got {step:?}"),
810 };
811
812 assert_eq!(
814 storage_offset, 1024,
815 "FLAT offset must be scaled sectors->bytes; got {storage_offset}"
816 );
817
818 std::fs::remove_dir_all(&dir).ok();
819 Ok(())
820 })
821 }
822}