Skip to main content

disk_vmdk/flat/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Opens flat VMDK disks and implements their read-only disk backend.
5//!
6//! The opener parses a descriptor, resolves its extent files, and constructs
7//! the resource handle transported to the VM worker. The backend revalidates
8//! that handle, maps virtual sectors across the ordered extents, and serves
9//! disk reads.
10
11mod descriptor;
12#[cfg(test)]
13mod tests;
14
15use crate::MAX_DESCRIPTOR_SIZE;
16use crate::OpenError;
17use async_trait::async_trait;
18use disk_backend::DiskError;
19use disk_backend::DiskIo;
20use disk_backend::UnmapBehavior;
21use disk_backend::resolve::ResolveDiskParameters;
22use disk_backend::resolve::ResolvedDisk;
23use disk_backend_resources::FlatVmdkDiskHandle;
24use disk_backend_resources::FlatVmdkExtent;
25use disk_backend_resources::VmdkExtentAccess;
26#[cfg(target_os = "linux")]
27use futures::StreamExt;
28use guestmem::MemoryWrite;
29use inspect::Inspect;
30use parking_lot::Mutex;
31use scsi_buffers::RequestBuffers;
32use std::collections::HashMap;
33use std::fs::File;
34use std::io;
35use std::io::Read;
36#[cfg(unix)]
37use std::os::unix::fs::FileExt;
38#[cfg(windows)]
39use std::os::windows::fs::FileExt;
40use std::path::Path;
41use std::sync::Arc;
42#[cfg(target_os = "linux")]
43use std::sync::atomic::AtomicBool;
44#[cfg(target_os = "linux")]
45use std::sync::atomic::Ordering;
46use thiserror::Error;
47use vm_resource::AsyncResolveResource;
48use vm_resource::ResourceResolver;
49use vm_resource::declare_static_async_resolver;
50use vm_resource::kind::DiskHandleKind;
51
52// Flat VMDK descriptors measure extent sizes and offsets in 512-byte sectors.
53const SECTOR_SIZE: u64 = 512;
54// Physical sector size in bytes reported by this backend.
55const DEFAULT_PHYSICAL_SECTOR_SIZE: u32 = 512;
56// Maximum number of 512-byte sectors that fit within i64::MAX bytes.
57const MAX_SECTOR_COUNT: u64 = (i64::MAX as u64) / SECTOR_SIZE;
58// Maximum temporary-buffer capacity retained for reuse after blocking reads.
59const MAX_CACHED_READ_BUFFER_SIZE: usize = 1024 * 1024;
60// Maximum concurrent native subreads per guest request.
61#[cfg(target_os = "linux")]
62const MAX_READ_SUB_WORKERS: usize = 1;
63
64/// Converts transported [`FlatVmdkDiskHandle`] resources into read-only disks.
65///
66/// The resolver revalidates every source handle and extent before exposing the
67/// disk in the VM worker.
68pub struct FlatVmdkResolver;
69declare_static_async_resolver!(FlatVmdkResolver, (DiskHandleKind, FlatVmdkDiskHandle));
70
71/// Errors that can occur when opening a flat VMDK resource as a disk.
72#[derive(Debug, Error)]
73pub enum ResolveFlatVmdkError {
74    /// Write access was requested, but VMDK disks are read-only.
75    #[error("VMDK disks are read-only")]
76    ReadWriteUnsupported,
77    /// The transported files or extent map failed VMDK validation.
78    #[error(transparent)]
79    Open(#[from] OpenError),
80    /// The constructed disk geometry is invalid.
81    #[error("invalid disk")]
82    InvalidDisk(#[source] disk_backend::InvalidDisk),
83}
84
85#[async_trait]
86impl AsyncResolveResource<DiskHandleKind, FlatVmdkDiskHandle> for FlatVmdkResolver {
87    type Output = ResolvedDisk;
88    type Error = ResolveFlatVmdkError;
89
90    async fn resolve(
91        &self,
92        _resolver: &ResourceResolver,
93        resource: FlatVmdkDiskHandle,
94        input: ResolveDiskParameters<'_>,
95    ) -> Result<Self::Output, Self::Error> {
96        // Flat VMDK disks do not expose writable access.
97        if !input.read_only {
98            return Err(ResolveFlatVmdkError::ReadWriteUnsupported);
99        }
100
101        // Revalidate the transported handle before wrapping the backend.
102        let disk = FlatVmdkDisk::new(
103            resource,
104            #[cfg(target_os = "linux")]
105            Some(Box::new(input.driver_source.current())),
106        )?;
107        ResolvedDisk::new(disk).map_err(ResolveFlatVmdkError::InvalidDisk)
108    }
109}
110
111/// Opens and validates a flat VMDK descriptor and its extent files.
112///
113/// Returns a [`FlatVmdkDiskHandle`] containing the opened source files and
114/// validated ordered extent map for transport to the VM worker.
115pub(crate) fn open_flat_vmdk(
116    path: &Path,
117    read_only: bool,
118) -> Result<FlatVmdkDiskHandle, OpenError> {
119    // Presently, we do not support write access to flat VMDK disks.
120    if !read_only {
121        return Err(OpenError::ReadWriteUnsupported);
122    }
123
124    // Open the descriptor file.
125    let descriptor = File::open(path).map_err(|source| OpenError::OpenDescriptor {
126        path: path.to_owned(),
127        source,
128    })?;
129
130    // Ensure that the descriptor is a regular file before reading it.
131    if !descriptor
132        .metadata()
133        .map_err(|source| OpenError::ReadDescriptorMetadata {
134            path: path.to_owned(),
135            source,
136        })?
137        .is_file()
138    {
139        return Err(OpenError::DescriptorNotRegularFile(path.to_owned()));
140    }
141
142    // Read one byte beyond the limit to detect a descriptor that is too large.
143    let mut bytes = Vec::new();
144    descriptor
145        .take(MAX_DESCRIPTOR_SIZE + 1)
146        .read_to_end(&mut bytes)
147        .map_err(|source| OpenError::ReadDescriptor {
148            path: path.to_owned(),
149            source,
150        })?;
151    if bytes.len() as u64 > MAX_DESCRIPTOR_SIZE {
152        return Err(OpenError::DescriptorTooLarge);
153    }
154
155    // Parse the descriptor to extract the extent entries.
156    let parsed_extents = descriptor::parse_descriptor(&bytes)?;
157
158    // Relative extent paths are resolved against the descriptor's directory;
159    // absolute extent paths replace it.
160    let descriptor_directory = path.parent().unwrap_or(Path::new("."));
161
162    let mut file_indices = HashMap::new();
163    let mut files = Vec::new();
164    let mut extents = Vec::with_capacity(parsed_extents.len());
165
166    // Open each source path once and build the transport extents in
167    // descriptor order.
168    for extent in parsed_extents {
169        let extent_path = descriptor_directory.join(&extent.path);
170        let file_index = if let Some(&file_index) = file_indices.get(&extent_path) {
171            // Reuse the existing handle when another extent names the same
172            // source path.
173            file_index
174        } else {
175            // Assign the next handle index before opening this source for the
176            // first time.
177            let source_index = files.len();
178            let file_index = u32::try_from(source_index)
179                .map_err(|_| OpenError::InvalidExtentMap("too many source files".into()))?;
180            let file = File::open(&extent_path).map_err(|source| OpenError::OpenExtent {
181                path: extent_path.clone(),
182                source,
183            })?;
184
185            // Error out if the source is not a regular file.
186            if !file
187                .metadata()
188                .map_err(|source| OpenError::ReadExtentMetadata {
189                    index: source_index,
190                    source,
191                })?
192                .is_file()
193            {
194                return Err(OpenError::ExtentNotRegularFile(source_index));
195            }
196
197            // Add the file and index to the trackers.
198            files.push(file);
199            file_indices.insert(extent_path, file_index);
200            file_index
201        };
202
203        // Preserve descriptor order while referencing the deduplicated source
204        // handle table by index.
205        extents.push(FlatVmdkExtent {
206            file_index,
207            access: match extent.access {
208                descriptor::ExtentAccess::ReadWrite => VmdkExtentAccess::ReadWrite,
209                descriptor::ExtentAccess::ReadOnly => VmdkExtentAccess::ReadOnly,
210            },
211            sector_count: extent.sector_count,
212            source_start_sector: extent.source_start_sector,
213        });
214    }
215
216    // Reject an invalid layout before transport. The VM worker validates it
217    // again because transported resource handles are untrusted.
218    FlatVmdkDisk::build_layout(&files, &extents)?;
219    Ok(FlatVmdkDiskHandle { files, extents })
220}
221
222/// An open VMDK source file and its cached size.
223struct Source {
224    /// Source file shared by concurrent native and blocking reads.
225    file: Arc<File>,
226    /// File size in bytes captured when the backend is initialized.
227    /// Determines where file data ends and final-sector zero padding begins.
228    length: u64,
229}
230
231/// Validated extent geometry used to map virtual sectors to a source file.
232#[derive(Inspect)]
233struct Extent {
234    /// Index into [`FlatVmdkDisk::sources`].
235    source_index: usize,
236    /// First virtual sector provided by this extent.
237    virtual_start_sector: u64,
238    /// End of this extent in virtual sectors (exclusive).
239    virtual_end_sector: u64,
240    /// First sector read from the source file.
241    source_start_sector: u64,
242}
243
244/// Validated data derived from a transported VMDK resource handle.
245struct Layout {
246    /// Source lengths corresponding to the transported source files.
247    source_lengths: Vec<u64>,
248    /// Ordered, normalized runtime extents.
249    extents: Vec<Extent>,
250    /// Total number of virtual sectors across all extents.
251    sector_count: u64,
252}
253
254/// A contiguous source-file range planned by [`FlatVmdkDisk::plan_read`].
255struct ReadSegment {
256    /// Index into [`FlatVmdkDisk::sources`] for the read path.
257    source_index: usize,
258    /// Byte offset in the source file where this read starts.
259    source_byte_offset: u64,
260    /// Number of output bytes for this segment, including any zero padding.
261    byte_count: usize,
262}
263
264/// Read-only disk created from a validated flat VMDK layout.
265#[derive(Inspect)]
266struct FlatVmdkDisk {
267    /// Shared table of opened source files and their cached sizes.
268    #[inspect(skip)]
269    sources: Arc<[Source]>,
270    /// Ordered mappings from virtual disk sectors to source-file ranges.
271    #[inspect(iter_by_index)]
272    extents: Vec<Extent>,
273    /// Total virtual disk size, in 512-byte sectors.
274    sector_count: u64,
275    /// Reusable temporary buffer for assembling blocking reads.
276    #[inspect(skip)]
277    read_buffer: Mutex<Vec<u8>>,
278    /// Shared driver for native reads; absent when io_uring is unavailable.
279    #[cfg(target_os = "linux")]
280    #[inspect(skip)]
281    driver: Option<Box<dyn pal_async::driver::Driver>>,
282}
283
284impl FlatVmdkDisk {
285    /// Revalidates a transported handle and builds the runtime disk state.
286    fn new(
287        handle: FlatVmdkDiskHandle,
288        #[cfg(target_os = "linux")] driver: Option<Box<dyn pal_async::driver::Driver>>,
289    ) -> Result<Self, OpenError> {
290        let Layout {
291            source_lengths,
292            extents,
293            sector_count,
294        } = Self::build_layout(&handle.files, &handle.extents)?;
295
296        // Pair each opened source file with the length used by runtime reads.
297        let sources = handle
298            .files
299            .into_iter()
300            .zip(source_lengths)
301            .map(|(file, length)| Source {
302                file: Arc::new(file),
303                length,
304            })
305            .collect();
306
307        // Store the driver if io_uring is supported, else None.
308        #[cfg(target_os = "linux")]
309        let driver = driver.and_then(|driver| {
310            if crate::io_uring::is_supported(driver.as_ref()) {
311                Some(driver)
312            } else {
313                tracing::debug!("VMDK native I/O unavailable; using pooled blocking reads");
314                None
315            }
316        });
317
318        Ok(Self {
319            sources,
320            extents,
321            sector_count,
322            read_buffer: Mutex::new(Vec::new()),
323            #[cfg(target_os = "linux")]
324            driver,
325        })
326    }
327
328    /// Validates opened source files and extent records, then builds the runtime layout.
329    fn build_layout(files: &[File], extents: &[FlatVmdkExtent]) -> Result<Layout, OpenError> {
330        // A disk must expose at least one extent.
331        if extents.is_empty() {
332            return Err(OpenError::InvalidExtentMap(
333                "the extent map is empty".into(),
334            ));
335        }
336
337        // Validate every transported source and cache its current length.
338        let mut source_lengths = Vec::with_capacity(files.len());
339        for (index, file) in files.iter().enumerate() {
340            let metadata = file
341                .metadata()
342                .map_err(|source| OpenError::ReadExtentMetadata { index, source })?;
343
344            if !metadata.is_file() {
345                return Err(OpenError::ExtentNotRegularFile(index));
346            }
347
348            source_lengths.push(metadata.len());
349        }
350
351        // Validate each transported extent and normalize its runtime geometry.
352        let mut current_virtual_sector_count = 0u64;
353        let mut layout_extents = Vec::with_capacity(extents.len());
354        for (index, extent) in extents.iter().enumerate() {
355            // Convert the transported file index to a native vector index.
356            // The source-table bounds check follows below.
357            let source_index = usize::try_from(extent.file_index).map_err(|_| {
358                OpenError::InvalidExtentMap(format!("extent {index} has an invalid source"))
359            })?;
360            let source_length = source_lengths.get(source_index).copied().ok_or_else(|| {
361                OpenError::InvalidExtentMap(format!(
362                    "extent {index} references missing source file {source_index}"
363                ))
364            })?;
365
366            if extent.sector_count == 0 {
367                return Err(OpenError::InvalidExtentMap(format!(
368                    "extent {index} has zero sectors"
369                )));
370            }
371
372            // Ensure the requested source range fits, allowing one partial
373            // final source sector.
374            let source_end = extent
375                .source_start_sector
376                .checked_add(extent.sector_count)
377                .ok_or_else(|| {
378                    OpenError::InvalidExtentMap(format!("extent {index} source range overflows"))
379                })?;
380            let source_sector_count = source_length.div_ceil(SECTOR_SIZE);
381            if source_end > source_sector_count {
382                return Err(OpenError::InvalidExtentMap(format!(
383                    "extent {index} ends at source sector {source_end}, but source file {source_index} has only {source_sector_count} sectors"
384                )));
385            }
386
387            // Append this extent at the next virtual sector without exceeding
388            // the host file-offset limit.
389            let next_virtual_sector_count = current_virtual_sector_count
390                .checked_add(extent.sector_count)
391                .filter(|count| *count <= MAX_SECTOR_COUNT)
392                .ok_or_else(|| {
393                    OpenError::InvalidExtentMap("virtual disk size is too large".into())
394                })?;
395
396            layout_extents.push(Extent {
397                source_index,
398                virtual_start_sector: current_virtual_sector_count,
399                virtual_end_sector: next_virtual_sector_count,
400                source_start_sector: extent.source_start_sector,
401            });
402
403            // Advance the virtual sector counter to the next available sector.
404            current_virtual_sector_count = next_virtual_sector_count;
405        }
406
407        Ok(Layout {
408            source_lengths,
409            extents: layout_extents,
410            sector_count: current_virtual_sector_count,
411        })
412    }
413
414    /// Reads a request using native I/O when available or the blocking fallback.
415    async fn read(&self, buffers: &RequestBuffers<'_>, sector: u64) -> Result<(), DiskError> {
416        let request_byte_count = buffers.len();
417
418        // Validate the whole-sector request and disk bounds before choosing a read path.
419        if !request_byte_count.is_multiple_of(SECTOR_SIZE as usize) {
420            return Err(DiskError::InvalidInput);
421        }
422        let request_sector_count = (request_byte_count / SECTOR_SIZE as usize) as u64;
423        let request_end_sector = sector
424            .checked_add(request_sector_count)
425            .filter(|end| *end <= self.sector_count)
426            .ok_or(DiskError::IllegalBlock)?;
427
428        // Find the last extent starting at or before the requested sector.
429        // The first extent starts at sector 0, so subtracting 1 is safe.
430        let extent_index = self
431            .extents
432            .partition_point(|extent| extent.virtual_start_sector <= sector)
433            - 1;
434
435        // Check if native IO is supported.
436        #[cfg(target_os = "linux")]
437        let driver = self
438            .driver
439            .as_deref()
440            .filter(|_| !buffers.is_empty() && buffers.guest_memory().supports_locking());
441
442        // Fast path: if the read fits within one extent, use native I/O without building a segment list.
443        #[cfg(target_os = "linux")]
444        if let Some(driver) = driver {
445            let extent = &self.extents[extent_index];
446            if request_end_sector <= extent.virtual_end_sector {
447                // Translate the virtual sector to a byte offset in the source file.
448                // Example: the extent starts at virtual sector 10 and source sector 2.
449                // Requesting virtual sector 13 is 3 sectors into the extent.
450                // The source byte offset is (2 + 3) * 512 = 2560.
451                let source_byte_offset = (extent.source_start_sector
452                    + (sector - extent.virtual_start_sector))
453                    * SECTOR_SIZE;
454
455                // Read into guest memory, padding a partial final sector if needed.
456                return Self::read_native_range(
457                    driver,
458                    &self.sources[extent.source_index],
459                    buffers.clone(),
460                    source_byte_offset,
461                )
462                .await;
463            }
464        }
465
466        // The fast path was not used; split the request into reads from the backing files.
467        let segments = self.plan_read(sector, request_end_sector, extent_index);
468
469        // Read the planned source ranges directly into guest memory.
470        #[cfg(target_os = "linux")]
471        if let Some(driver) = driver {
472            let mut completed_request_byte_count = 0;
473            // Set by `fold` below when a read fails; checked before each new read.
474            let failed = AtomicBool::new(false);
475
476            // Lazily build one read per segment. Nothing runs here: `buffered`
477            // below pulls each read only when it has room for another.
478            let reads = segments
479                .into_iter()
480                // Checked at pull time, so reads after a failure are never built.
481                .take_while(|_| !failed.load(Ordering::Relaxed))
482                .map(|segment| {
483                    // Select the guest buffer range that receives this segment.
484                    let segment_output =
485                        buffers.subrange(completed_request_byte_count, segment.byte_count);
486
487                    // Advance past this segment for the next one.
488                    completed_request_byte_count += segment.byte_count;
489
490                    // Create the read future; I/O starts when it is first polled.
491                    Self::read_native_range(
492                        driver,
493                        &self.sources[segment.source_index],
494                        segment_output,
495                        segment.source_byte_offset,
496                    )
497                });
498
499            // Run up to MAX_READ_SUB_WORKERS reads at once. After a failure, no
500            // new reads start, but in-flight ones finish (dropping them aborts).
501            return futures::stream::iter(reads)
502                .buffered(MAX_READ_SUB_WORKERS)
503                .fold(Ok(()), |result, read_result| {
504                    if read_result.is_err() {
505                        failed.store(true, Ordering::Relaxed);
506                    }
507                    // Keep the earliest error.
508                    async move { result.and(read_result) }
509                })
510                .await;
511        }
512
513        // At this point, native IO was not used, so fall back to blocking reads.
514        // Try to reuse the cached buffer; larger reads get a separate allocation.
515        let mut request_output = if request_byte_count <= MAX_CACHED_READ_BUFFER_SIZE {
516            std::mem::take(&mut *self.read_buffer.lock())
517        } else {
518            Vec::new()
519        };
520        request_output.resize(request_byte_count, 0);
521
522        // Share the source files with the worker thread.
523        let sources = self.sources.clone();
524
525        // Read on a worker thread so file I/O does not block the async executor.
526        let (request_output, read_result) = blocking::unblock(move || {
527            let result = Self::read_segments_blocking(&sources, &mut request_output, segments);
528
529            // Return the buffer even on failure so it can be cached for reuse.
530            (request_output, result)
531        })
532        .await;
533
534        // Copy data to the guest only after a successful read. Keep any error
535        // for the final return so the temporary buffer can still be reused.
536        let result = read_result.map_err(DiskError::Io).and_then(|()| {
537            buffers
538                .writer()
539                .write(&request_output)
540                .map_err(DiskError::from)
541        });
542
543        // Cache this buffer if it fits the size limit and has more capacity than the cached one.
544        if request_output.capacity() <= MAX_CACHED_READ_BUFFER_SIZE {
545            let mut read_buffer = self.read_buffer.lock();
546            if request_output.capacity() > read_buffer.capacity() {
547                *read_buffer = request_output;
548            }
549        }
550
551        result
552    }
553
554    /// Plans source ranges for a validated request, starting at `extent_index`.
555    fn plan_read(
556        &self,
557        request_start_sector: u64,
558        request_end_sector: u64,
559        mut extent_index: usize,
560    ) -> Vec<ReadSegment> {
561        let mut current_virtual_sector = request_start_sector;
562        let mut segments = Vec::new();
563
564        while current_virtual_sector < request_end_sector {
565            // The validated layout covers every sector up to sector_count.
566            let extent = &self.extents[extent_index];
567
568            // Limit this segment to the part covered by the current extent.
569            let segment_sector_count = (request_end_sector - current_virtual_sector)
570                .min(extent.virtual_end_sector - current_virtual_sector);
571
572            // Translate the virtual sector into a sector in the source file.
573            let source_sector =
574                extent.source_start_sector + (current_virtual_sector - extent.virtual_start_sector);
575
576            // Record the source range in virtual-disk order.
577            segments.push(ReadSegment {
578                source_index: extent.source_index,
579                source_byte_offset: source_sector * SECTOR_SIZE,
580                byte_count: segment_sector_count as usize * SECTOR_SIZE as usize,
581            });
582
583            // Advance past the segment just planned.
584            current_virtual_sector += segment_sector_count;
585            extent_index += 1;
586        }
587
588        segments
589    }
590
591    /// Reads a source range and pads only bytes beyond its cached length.
592    #[cfg(target_os = "linux")]
593    async fn read_native_range(
594        driver: &dyn pal_async::driver::Driver,
595        source: &Source,
596        segment_output: RequestBuffers<'_>,
597        source_byte_offset: u64,
598    ) -> Result<(), DiskError> {
599        // The file's last sector may contain fewer than 512 bytes.
600        // Read the file bytes in this range; the rest will be filled with zeros.
601        // Example: with a cached file size of 513 bytes, a 512-byte read at offset
602        // 512 reads 1 file byte and fills the remaining 511 bytes with zeros.
603        let readable_byte_count = source
604            .length
605            .saturating_sub(source_byte_offset)
606            .min(segment_output.len() as u64) as usize;
607
608        // Missing bytes inside this range are a truncation error, not padding.
609        crate::io_uring::read_exact_at(
610            driver,
611            source.file.clone(),
612            &segment_output.subrange(0, readable_byte_count),
613            source_byte_offset,
614        )
615        .await?;
616
617        // Zero only the final sector's padding, preserving the file bytes just read.
618        if readable_byte_count < segment_output.len() {
619            segment_output
620                .subrange(
621                    readable_byte_count,
622                    segment_output.len() - readable_byte_count,
623                )
624                .writer()
625                .zero(segment_output.len() - readable_byte_count)?;
626        }
627
628        Ok(())
629    }
630
631    /// Reads planned ranges with blocking positional I/O and zero padding.
632    fn read_segments_blocking(
633        sources: &[Source],
634        mut request_output: &mut [u8],
635        segments: Vec<ReadSegment>,
636    ) -> io::Result<()> {
637        for segment in segments {
638            let source = &sources[segment.source_index];
639
640            // Take this segment's bytes, leaving the rest for later segments.
641            let segment_output = request_output
642                .split_off_mut(..segment.byte_count)
643                .ok_or_else(|| io::Error::other("VMDK read segment exceeds request buffer"))?;
644
645            // Read up to the cached end of the file; the rest of the segment is padding.
646            let bytes_left_in_file = source.length.saturating_sub(segment.source_byte_offset);
647            let readable_byte_count = bytes_left_in_file.min(segment_output.len() as u64) as usize;
648            let (mut destination, padding) = segment_output.split_at_mut(readable_byte_count);
649
650            // Zero the padding, including any old data from a reused buffer.
651            padding.fill(0);
652
653            // Read at explicit file offsets until this segment is filled.
654            let mut source_byte_offset = segment.source_byte_offset;
655            while !destination.is_empty() {
656                #[cfg(unix)]
657                let result = source.file.read_at(destination, source_byte_offset);
658                #[cfg(windows)]
659                let result = source.file.seek_read(destination, source_byte_offset);
660
661                match result {
662                    Ok(0) => {
663                        // Bytes expected from the cached length are now missing.
664                        return Err(io::Error::new(
665                            io::ErrorKind::UnexpectedEof,
666                            "VMDK source file was truncated",
667                        ));
668                    }
669                    Ok(byte_count) => {
670                        // Continue after the bytes returned by a partial read.
671                        destination = &mut destination[byte_count..];
672                        source_byte_offset += byte_count as u64;
673                    }
674                    // Retry interruptions without advancing either position.
675                    Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
676                    Err(error) => return Err(error),
677                }
678            }
679        }
680
681        // Reject incomplete plans rather than returning stale buffer contents.
682        if !request_output.is_empty() {
683            return Err(io::Error::other(
684                "VMDK read segments do not fill the request buffer",
685            ));
686        }
687
688        Ok(())
689    }
690}
691
692impl DiskIo for FlatVmdkDisk {
693    fn disk_type(&self) -> &str {
694        "vmdk_flat"
695    }
696
697    fn sector_count(&self) -> u64 {
698        self.sector_count
699    }
700
701    fn sector_size(&self) -> u32 {
702        SECTOR_SIZE as u32
703    }
704
705    fn disk_id(&self) -> Option<[u8; 16]> {
706        None
707    }
708
709    fn physical_sector_size(&self) -> u32 {
710        DEFAULT_PHYSICAL_SECTOR_SIZE
711    }
712
713    fn is_fua_respected(&self) -> bool {
714        false
715    }
716
717    fn is_read_only(&self) -> bool {
718        true
719    }
720
721    async fn unmap(
722        &self,
723        _sector: u64,
724        _count: u64,
725        _block_level_only: bool,
726    ) -> Result<(), DiskError> {
727        Ok(())
728    }
729
730    fn unmap_behavior(&self) -> UnmapBehavior {
731        UnmapBehavior::Ignored
732    }
733
734    async fn read_vectored(
735        &self,
736        buffers: &RequestBuffers<'_>,
737        sector: u64,
738    ) -> Result<(), DiskError> {
739        self.read(buffers, sector).await
740    }
741
742    async fn write_vectored(
743        &self,
744        _buffers: &RequestBuffers<'_>,
745        _sector: u64,
746        _fua: bool,
747    ) -> Result<(), DiskError> {
748        Err(DiskError::ReadOnly)
749    }
750
751    async fn sync_cache(&self) -> Result<(), DiskError> {
752        Ok(())
753    }
754}