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 maybe_async::maybe_async;
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
248#[maybe_async]
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 offset = offset.checked_mul(VMDK_SECTOR_SIZE).ok_or_else(|| {
291 invalid_data(format!(
292 "Extent storage offset overflow: {offset} * {VMDK_SECTOR_SIZE}"
293 ))
294 })?;
295 let absolute = self
296 .descriptor_file
297 .resolve_relative_path(filename)
298 .err_context(|| format!("Cannot resolve storage file name {filename}"))?;
299
300 let mut file_opts = self.storage_open_options.clone().filename(absolute.clone());
301 if extent.access_type == VmdkAccessType::RdOnly {
302 file_opts = file_opts.write(false);
303 }
304
305 let file = open_gate
306 .open_storage(file_opts)
307 .await
308 .err_context(|| format!("Data storage file {absolute:?}"))?;
309
310 VmdkStorage::Flat { file, offset }
311 }
312
313 VmdkParsedStorage::Zero => VmdkStorage::Zero,
314 };
315
316 Ok(VmdkExtent {
317 access_type: extent.access_type.clone(),
318 disk_range,
319 storage: Some(storage),
320 })
321 }
322
323 fn error_out_unsupported_version(&self) -> io::Result<()> {
325 let version = self.desc.version;
326 if !VMDK_VERSION_RANGE.contains(&version) {
327 return Err(io::Error::new(
328 io::ErrorKind::Unsupported,
329 format!("unsupported version {version}"),
330 ));
331 }
332 Ok(())
333 }
334
335 fn parse_descriptor_line(&mut self, line: &str) -> io::Result<()> {
337 let line = line.trim();
338
339 if line.is_empty() || line.starts_with('#') {
340 return Ok(());
341 }
342
343 if let Some((access, _)) = line.split_once(char::is_whitespace) {
345 if matches!(access, "RW" | "RDONLY" | "NOACCESS") {
346 let extent = VmdkParsedExtent::try_from_descriptor_line(line)?;
347 self.parsed_extents.push(extent);
348 return Ok(());
349 }
350 }
351
352 let Some((key, value)) = line.split_once('=') else {
353 return Ok(());
355 };
356 let key = key.trim();
357 let value = value.trim();
358
359 match key {
360 "version" => {
361 self.desc.version = value
362 .parse()
363 .map_err(|_| invalid_data("Invalid version format"))?;
364 }
365 "CID" => self.desc.cid = value.to_string(),
366 "parentCID" => self.desc.parent_cid = value.to_string(),
367 "createType" => self.desc.create_type = strip_quotes(value).to_string(),
368 "parentFileNameHint" => {
369 return Err(io::Error::new(
370 io::ErrorKind::Unsupported,
371 "unsupported VMDK differential image (delta link)",
372 ))
373 }
374 "ddb.geometry.sectors" => self.desc.sectors = parse_desc_value(key, value)?,
375 "ddb.geometry.heads" => self.desc.heads = parse_desc_value(key, value)?,
376 "ddb.geometry.cylinders" => self.desc.cylinders = parse_desc_value(key, value)?,
377
378 key if key.starts_with("ddb.") => (),
380
381 key => {
382 return Err(invalid_data(format!(
383 "Unrecognized VMDK descriptor file key '{key}'"
384 )))
385 }
386 }
387
388 Ok(())
389 }
390
391 async fn parse_descriptor_file(&mut self) -> io::Result<()> {
393 let desc_file_sz = self.descriptor_file.size()?;
394 if desc_file_sz < 4 {
395 return Err(invalid_data("VMDK descriptor file too short"));
396 }
397 if desc_file_sz > 2 * 1024 * 1024 {
399 return Err(invalid_data(
400 "VMDK descriptor file too long (max. 2 MB supported)",
401 ));
402 }
403
404 let desc_file_sz: usize = desc_file_sz.try_into().unwrap();
405 let mut desc_file = IoBuffer::new(desc_file_sz, self.descriptor_file.mem_align())?;
406 self.descriptor_file.read(desc_file.as_mut(), 0).await?;
407
408 let desc_file = desc_file.as_ref().into_slice();
409
410 if u32::from_le_bytes(desc_file[..4].try_into().unwrap()) == VMDK4_MAGIC {
412 return Err(io::Error::new(
413 io::ErrorKind::Unsupported,
414 "Unsupported VMDK sparse data file",
415 ));
416 }
417
418 for (line_i, line) in desc_file.split(|chr| *chr == b'\n').enumerate() {
419 let line = str::from_utf8(line).map_err(|e| {
420 invalid_data(format!(
421 "{}: Line {}: {e}",
422 self.descriptor_file,
423 line_i + 1
424 ))
425 })?;
426
427 self.parse_descriptor_line(line)
428 .err_context(|| format!("{}: Line {}", self.descriptor_file, line_i + 1))?;
429 }
430
431 self.error_out_unsupported_version()?;
432 self.size = self
433 .parsed_extents
434 .iter()
435 .try_fold(0u64, |sum, extent| {
436 let sectors = extent.sectors;
437 let size = sectors.checked_mul(VMDK_SECTOR_SIZE).ok_or_else(|| {
438 invalid_data(format!(
439 "Extent size overflow: {sectors} * {VMDK_SECTOR_SIZE}"
440 ))
441 })?;
442 sum.checked_add(size)
443 .ok_or_else(|| invalid_data(format!("Extent offset overflow: {sum} + {size}")))
444 })?
445 .into();
446
447 Ok(())
448 }
449
450 async fn do_open(
452 descriptor_file: S,
453 storage_open_options: StorageOpenOptions,
454 ) -> io::Result<Self> {
455 let mut vmdk = Vmdk {
456 descriptor_file: Arc::new(descriptor_file),
457 parent_type: PhantomData,
458 desc: VmdkDesc {
459 version: 0,
460 cid: String::new(),
461 parent_cid: String::new(),
462 create_type: String::new(),
463 sectors: 0,
464 heads: 0,
465 cylinders: 0,
466 },
467 parsed_extents: vec![],
468 extents: vec![],
469 size: 0.into(),
470 storage_open_options,
471 };
472
473 vmdk.parse_descriptor_file().await?;
474 Ok(vmdk)
475 }
476
477 pub async fn open_image(descriptor_file: S, writable: bool) -> io::Result<Self> {
484 if writable {
485 return Err(io::Error::new(
486 io::ErrorKind::Unsupported,
487 "No VMDK write support",
488 ));
489 }
490 Self::do_open(descriptor_file, StorageOpenOptions::new()).await
491 }
492
493 pub async fn open_implicit_dependencies_gated<G: ImplicitOpenGate<S>>(
497 &mut self,
498 mut gate: G,
499 ) -> io::Result<()> {
500 if self.extents.is_empty() {
501 let mut in_disk_offset = 0;
502 for extent in &self.parsed_extents {
503 let opened = self
504 .open_implicit_extent(extent, in_disk_offset, &mut gate)
505 .await?;
506 in_disk_offset = opened.disk_range.end;
507 self.extents.push(opened);
508 }
509 }
510
511 Ok(())
512 }
513
514 fn get_extent_at(&self, offset: u64) -> Option<&VmdkExtent<S>> {
516 self.extents
517 .binary_search_by(|extent| {
518 if extent.disk_range.contains(&offset) {
519 cmp::Ordering::Equal
520 } else if extent.disk_range.end <= offset {
521 cmp::Ordering::Less
524 } else {
525 cmp::Ordering::Greater
526 }
527 })
528 .ok()
529 .map(|index| &self.extents[index])
530 }
531}
532
533impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Display for Vmdk<S, F> {
534 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
535 write!(f, "vmdk[{}]", self.descriptor_file)
536 }
537}
538
539#[maybe_async(?Send)]
540impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatDriverInstance for Vmdk<S, F> {
541 type Storage = S;
542
543 fn format(&self) -> Format {
544 Format::Vmdk
545 }
546
547 async unsafe fn probe(storage: &S) -> io::Result<bool>
548 where
549 Self: Sized,
550 {
551 let desc_file_size = storage.size()?;
556 if !(4..=2 * 1024 * 1024).contains(&desc_file_size) {
557 return Ok(false);
558 }
559
560 let desc_file_size: usize = desc_file_size.try_into().unwrap();
561 let mut desc_file = IoBuffer::new(desc_file_size, storage.mem_align())?;
562 storage.read(desc_file.as_mut(), 0).await?;
563
564 let desc_file = desc_file.as_ref().into_slice();
565 if u32::from_le_bytes(desc_file[..4].try_into().unwrap()) == VMDK4_MAGIC {
566 return Ok(true);
567 }
568
569 for line in desc_file.split(|chr| *chr == b'\n') {
570 let Ok(line) = str::from_utf8(line) else {
571 return Ok(false);
572 };
573
574 let Some((key, value)) = line.split_once('=') else {
575 continue;
576 };
577 if key.trim() == "version" {
578 let Ok(version) = value.trim().parse() else {
579 return Ok(false);
580 };
581 return Ok(VMDK_VERSION_RANGE.contains(&version));
582 }
583 }
584
585 Ok(false)
586 }
587
588 fn size(&self) -> u64 {
589 self.size.load(Ordering::Relaxed)
590 }
591
592 fn zero_granularity(&self) -> Option<u64> {
593 None
594 }
595
596 fn collect_storage_dependencies(&self) -> Vec<&S> {
597 let mut v = vec![self.descriptor_file.as_ref()];
598 for e in &self.extents {
599 let Some(storage) = e.storage.as_ref() else {
600 continue;
601 };
602 match storage {
603 VmdkStorage::Flat { file, offset: _ } => v.push(file),
604 VmdkStorage::Zero => (),
605 }
606 }
607 v
608 }
609
610 fn writable(&self) -> bool {
611 false
612 }
613
614 #[allow(clippy::needless_lifetimes)] async fn get_mapping<'a>(
616 &'a self,
617 offset: u64,
618 max_length: u64,
619 ) -> io::Result<(ShallowMapping<'a, S>, u64)> {
620 let max_length = match self.size().checked_sub(offset) {
621 None | Some(0) => return Ok((ShallowMapping::Eof {}, 0)),
622 Some(remaining) => cmp::min(remaining, max_length),
623 };
624
625 let Some(extent) = self.get_extent_at(offset) else {
626 return Ok((ShallowMapping::Eof {}, 0));
627 };
628 let in_extent_offset = offset - extent.disk_range.start;
630
631 let writable = match extent.access_type {
632 VmdkAccessType::RW => true,
633 VmdkAccessType::RdOnly => false,
634 VmdkAccessType::NoAccess => {
635 return Err(io::Error::other("NOACCESS extent is accessed"));
637 }
638 };
639
640 let mapping = match extent.storage.as_ref().unwrap() {
642 VmdkStorage::Flat {
643 file,
644 offset: base_offset,
645 } => ShallowMapping::Raw {
646 storage: file,
647 offset: base_offset.checked_add(in_extent_offset).ok_or_else(|| {
648 invalid_data(format!(
649 "Extent offset overflow: {base_offset} + {in_extent_offset}"
650 ))
651 })?,
652 writable,
653 },
654
655 VmdkStorage::Zero => ShallowMapping::Zero { explicit: true },
656 };
657
658 Ok((
659 mapping,
660 cmp::min(max_length, extent.disk_range.end - offset),
661 ))
662 }
663
664 #[allow(clippy::needless_lifetimes)] async fn ensure_data_mapping<'a>(
666 &'a self,
667 _offset: u64,
668 _length: u64,
669 _overwrite: bool,
670 ) -> io::Result<(&'a S, u64, u64)> {
671 Err(io::Error::other("Image is read-only"))
672 }
673
674 async fn flush(&self) -> io::Result<()> {
675 Ok(())
676 }
677
678 async fn sync(&self) -> io::Result<()> {
679 Ok(())
680 }
681
682 async unsafe fn invalidate_cache(&self) -> io::Result<()> {
683 Ok(())
684 }
685
686 async fn resize_grow(&self, _new_size: u64, _prealloc_mode: PreallocateMode) -> io::Result<()> {
687 Err(io::Error::other("Image is read-only"))
688 }
689
690 async fn resize_shrink(&mut self, _new_size: u64) -> io::Result<()> {
691 Err(io::Error::other("Image is read-only"))
692 }
693}
694
695pub struct VmdkOpenBuilder<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>>(
697 FormatDriverBuilderBase<S>,
698 PhantomData<F>,
699);
700
701#[maybe_async(AFIT)]
702impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> FormatDriverBuilder<S>
703 for VmdkOpenBuilder<S, F>
704{
705 type Format = Vmdk<S, F>;
706 const FORMAT: Format = Format::Vmdk;
707
708 fn new(image: S) -> Self {
709 VmdkOpenBuilder(FormatDriverBuilderBase::new(image), PhantomData)
710 }
711
712 fn new_path<P: AsRef<Path>>(path: P) -> Self {
713 VmdkOpenBuilder(FormatDriverBuilderBase::new_path(path), PhantomData)
714 }
715
716 fn write(mut self, writable: bool) -> Self {
717 self.0.set_write(writable);
718 self
719 }
720
721 fn storage_open_options(mut self, options: StorageOpenOptions) -> Self {
722 self.0.set_storage_open_options(options);
723 self
724 }
725
726 async fn open<G: ImplicitOpenGate<S>>(self, mut gate: G) -> io::Result<Self::Format> {
727 if self.0.get_writable() {
728 return Err(io::Error::new(
729 io::ErrorKind::Unsupported,
730 "No VMDK write support",
731 ));
732 }
733
734 let file = self.0.open_image(&mut gate).await?;
735 let mut vmdk = Vmdk::open_image(file, false).await?;
736 vmdk.open_implicit_dependencies_gated(gate).await?;
737 Ok(vmdk)
738 }
739
740 fn get_image_path(&self) -> Option<PathBuf> {
741 self.0.get_image_path()
742 }
743
744 fn get_writable(&self) -> bool {
745 self.0.get_writable()
746 }
747
748 fn get_storage_open_options(&self) -> Option<&StorageOpenOptions> {
749 self.0.get_storage_opts()
750 }
751}