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