Skip to main content

imago/vmdk/
mod.rs

1//! VMDK implementation.
2
3use 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
22/// As usual, VMDK sector size is 512 bytes as a fixed value
23const VMDK_SECTOR_SIZE: u64 = 512;
24/// VMDK SPARSE data signature
25const VMDK4_MAGIC: u32 = 0x564d444b; // 'KDMV'
26/// Supported version range
27const VMDK_VERSION_RANGE: RangeInclusive<u32> = 1..=3;
28
29/// Represents the data storage for a VMDK extent
30#[derive(Debug, Clone)]
31enum VmdkStorage<S: Storage + 'static> {
32    /// A FLAT extent with a RAW file starting from the exact offset
33    Flat {
34        /// Storage object containing linear (raw) data
35        file: S,
36        /// Offset in `file` where the data for this extent begins
37        offset: u64,
38    },
39    /// A zero-filled extent
40    Zero,
41}
42
43/// VMDK extent information after parsing, before opening
44#[derive(Debug)]
45enum VmdkParsedStorage {
46    /// A FLAT extent with a RAW file starting from the descriptor offset
47    Flat {
48        /// Path to storage object containing linear (raw) data
49        filename: String,
50        /// Offset in sectors where the data for this extent begins
51        offset: u64,
52    },
53    /// A zero-filled extent
54    Zero,
55}
56
57/// Access type for VMDK extents
58#[derive(Debug, Clone, PartialEq)]
59enum VmdkAccessType {
60    /// Read-write access
61    RW,
62    /// Read-only access
63    RdOnly,
64    /// No access
65    NoAccess,
66}
67
68/// VMDK extent
69#[derive(Debug)]
70struct VmdkExtent<S: Storage + 'static> {
71    /// Access type (RW, RDONLY, NOACCESS).
72    access_type: VmdkAccessType,
73    /// Part of the virtual disk covered by this extent.
74    ///
75    /// The start is equal to the end of the extent before it (0 if none), and the end is equal to
76    /// the start plus this extent’s length.
77    disk_range: Range<u64>,
78    /// Data source
79    ///
80    /// Present if and only if the access type is not NOACCESS.
81    storage: Option<VmdkStorage<S>>,
82}
83
84/// VMDK extent descriptor information after parsing, before opening
85#[derive(Debug)]
86struct VmdkParsedExtent {
87    /// Access type (RW, RDONLY, NOACCESS).
88    access_type: VmdkAccessType,
89    /// Number of sectors.
90    sectors: u64,
91    /// Data source
92    ///
93    /// Present if and only if the access type is not NOACCESS.
94    storage: Option<VmdkParsedStorage>,
95}
96
97/// VMDK disk image format implementation.
98#[derive(Debug)]
99pub struct Vmdk<S: Storage + 'static, F: WrappedFormat<S> + 'static = FormatAccess<S>> {
100    /// Storage object containing the VMDK descriptor file
101    descriptor_file: Arc<S>,
102
103    /// Backing image type.
104    ///
105    /// We do not support backing (parent) images yet, but capture the type so that when we do
106    /// support it, the change will be syntactically compatible.
107    parent_type: PhantomData<F>,
108
109    /// Base options to be used for implicitly opened storage objects.
110    storage_open_options: StorageOpenOptions,
111
112    /// Virtual disk size in bytes.
113    size: AtomicU64,
114
115    /// Parsed VMDK descriptor.
116    desc: VmdkDesc,
117
118    /// Extent information as parsed from the VMDK descriptor file.
119    parsed_extents: Vec<VmdkParsedExtent>,
120
121    /// Storage objects for each extent.
122    extents: Vec<VmdkExtent<S>>,
123}
124
125/// VMDK descriptor information.
126#[derive(Debug, Clone)]
127struct VmdkDesc {
128    /// Version number of the VMDK descriptor
129    version: u32,
130    /// Content ID
131    cid: String,
132    /// Content ID of the parent link
133    parent_cid: String,
134    /// Type of virtual disk
135    create_type: String,
136    /// The disk geometry value (sectors)
137    sectors: u64,
138    /// The disk geometry value (heads)
139    heads: u64,
140    /// The disk geometry value (cylinders)
141    cylinders: u64,
142}
143
144impl VmdkParsedExtent {
145    /// Parse an extent descriptor line.
146    fn try_from_descriptor_line(line: &str) -> io::Result<VmdkParsedExtent> {
147        // See https://github.com/libyal/libvmdk/blob/main/documentation/VMWare%20Virtual%20Disk%20Format%20(VMDK).asciidoc#221-extent-descriptor
148
149        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        // filename is enclosed in quotes and may contain spaces, so split the whole line by quotes
193        // (We could simplify this if we could do `line.splitn_whitespace(4)` at the beginning of
194        // this function, but `splitn_whitespace()` does not exist.)
195        let mut quote_split = line.splitn(3, '"').map(|part| part.trim());
196        // We know the line isn’t empty, so we must at least get one part
197        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        // Continue parsing after filename
213        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
231/// Remove double quotes around `input` if there are any.
232fn strip_quotes(input: &str) -> &str {
233    input
234        .strip_prefix('"')
235        .and_then(|value| value.strip_suffix('"'))
236        .unwrap_or(input)
237}
238
239/// Helper to parse an integer from the descriptor file.
240fn 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    /// Create a new [`FormatDriverBuilder`] instance for the given image.
251    pub fn builder(image: S) -> VmdkOpenBuilder<S, F> {
252        VmdkOpenBuilder::new(image)
253    }
254
255    /// Create a new [`FormatDriverBuilder`] instance for an image under the given path.
256    pub fn builder_path<P: AsRef<Path>>(image_path: P) -> VmdkOpenBuilder<S, F> {
257        VmdkOpenBuilder::new_path(image_path)
258    }
259
260    /// Open an extent from the information in `extent`.
261    ///
262    /// `in_disk_offset` is the offset in the virtual disk where this extent fits in.  It should be
263    /// the end offset of the extent before it.
264    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    /// Checks if the VMDK version is supported and returns an error if not
324    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    /// Parse a line in the VMDK descriptor file
336    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        // Parse extent descriptors (RW/RDONLY/NOACCESS)
344        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            // Silently ignore
354            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            // Ignore unidentified "ddb." (The Disk Database) items
379            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    /// Read and parse the VMDK descriptor by reading in lines until we find the end
392    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        // Sanity check to avoid unbounded allocation
398        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        // Check if it's a SPARSE format, bail it out now
411        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    /// Internal implementation for opening a VMDK image.
451    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    /// Opens a VMDK file.
478    ///
479    /// This will not open any other storage objects needed, i.e. no extent data files.  Handling
480    /// those manually is not yet supported, so you have to make use of the implicit references
481    /// given in the image header, for which you can use
482    /// [`Vmdk::open_implicit_dependencies_gated()`].
483    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    /// Open all implicit dependencies.
494    ///
495    /// In the case of VMDK, these are the extent data files.
496    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    /// Return the extent covering `offset`, if any.
515    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                    // disk_range is half-open [start, end); use <= so that
522                    // end == offset returns Less, not Greater.
523                    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        // Check that the potential descriptor file has a reasonable length, is utf8, and contains
552        // a supported `version` key.
553        // (Or has the `VMDK4_MAGIC`.)
554
555        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)] // Elidable in sync, but async needs a named lifetime for the boxed future bound
615    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        // `get_extent_at` guarantees this won’t underflow
629        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                // Is that right?  Should this be ::Special?
636                return Err(io::Error::other("NOACCESS extent is accessed"));
637            }
638        };
639
640        // `access_type != NoAccess`, so `unwrap()` is safe
641        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)] // Elidable in sync, but async needs a named lifetime for the boxed future bound
665    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
695/// Options builder for opening a VMDK image.
696pub 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}