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 exact offset
47    Flat {
48        /// Path to storage object containing linear (raw) data
49        filename: String,
50        /// Offset in the storage object 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 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    /// Checks if the VMDK version is supported and returns an error if not
322    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    /// Parse a line in the VMDK descriptor file
334    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        // Parse extent descriptors (RW/RDONLY/NOACCESS)
342        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            // Silently ignore
352            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            // Ignore unidentified "ddb." (The Disk Database) items
377            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    /// Read and parse the VMDK descriptor by reading in lines until we find the end
390    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        // Sanity check to avoid unbounded allocation
396        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        // Check if it's a SPARSE format, bail it out now
409        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    /// Internal implementation for opening a VMDK image.
449    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    /// Opens a VMDK file.
476    ///
477    /// This will not open any other storage objects needed, i.e. no extent data files.  Handling
478    /// those manually is not yet supported, so you have to make use of the implicit references
479    /// given in the image header, for which you can use
480    /// [`Vmdk::open_implicit_dependencies_gated()`].
481    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    /// Open all implicit dependencies.
492    ///
493    /// In the case of VMDK, these are the extent data files.
494    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    /// Return the extent covering `offset`, if any.
513    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                    // disk_range is half-open [start, end); use <= so that
520                    // end == offset returns Less, not Greater.
521                    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        // Check that the potential descriptor file has a reasonable length, is utf8, and contains
550        // a supported `version` key.
551        // (Or has the `VMDK4_MAGIC`.)
552
553        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)] // Elidable in sync, but async needs a named lifetime for the boxed future bound
613    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        // `get_extent_at` guarantees this won’t underflow
627        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                // Is that right?  Should this be ::Special?
634                return Err(io::Error::other("NOACCESS extent is accessed"));
635            }
636        };
637
638        // `access_type != NoAccess`, so `unwrap()` is safe
639        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)] // Elidable in sync, but async needs a named lifetime for the boxed future bound
663    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
693/// Options builder for opening a VMDK image.
694pub 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}