1mod 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
52const SECTOR_SIZE: u64 = 512;
54const DEFAULT_PHYSICAL_SECTOR_SIZE: u32 = 512;
56const MAX_SECTOR_COUNT: u64 = (i64::MAX as u64) / SECTOR_SIZE;
58const MAX_CACHED_READ_BUFFER_SIZE: usize = 1024 * 1024;
60#[cfg(target_os = "linux")]
62const MAX_READ_SUB_WORKERS: usize = 1;
63
64pub struct FlatVmdkResolver;
69declare_static_async_resolver!(FlatVmdkResolver, (DiskHandleKind, FlatVmdkDiskHandle));
70
71#[derive(Debug, Error)]
73pub enum ResolveFlatVmdkError {
74 #[error("VMDK disks are read-only")]
76 ReadWriteUnsupported,
77 #[error(transparent)]
79 Open(#[from] OpenError),
80 #[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 if !input.read_only {
98 return Err(ResolveFlatVmdkError::ReadWriteUnsupported);
99 }
100
101 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
111pub(crate) fn open_flat_vmdk(
116 path: &Path,
117 read_only: bool,
118) -> Result<FlatVmdkDiskHandle, OpenError> {
119 if !read_only {
121 return Err(OpenError::ReadWriteUnsupported);
122 }
123
124 let descriptor = File::open(path).map_err(|source| OpenError::OpenDescriptor {
126 path: path.to_owned(),
127 source,
128 })?;
129
130 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 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 let parsed_extents = descriptor::parse_descriptor(&bytes)?;
157
158 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 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 file_index
174 } else {
175 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 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 files.push(file);
199 file_indices.insert(extent_path, file_index);
200 file_index
201 };
202
203 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 FlatVmdkDisk::build_layout(&files, &extents)?;
219 Ok(FlatVmdkDiskHandle { files, extents })
220}
221
222struct Source {
224 file: Arc<File>,
226 length: u64,
229}
230
231#[derive(Inspect)]
233struct Extent {
234 source_index: usize,
236 virtual_start_sector: u64,
238 virtual_end_sector: u64,
240 source_start_sector: u64,
242}
243
244struct Layout {
246 source_lengths: Vec<u64>,
248 extents: Vec<Extent>,
250 sector_count: u64,
252}
253
254struct ReadSegment {
256 source_index: usize,
258 source_byte_offset: u64,
260 byte_count: usize,
262}
263
264#[derive(Inspect)]
266struct FlatVmdkDisk {
267 #[inspect(skip)]
269 sources: Arc<[Source]>,
270 #[inspect(iter_by_index)]
272 extents: Vec<Extent>,
273 sector_count: u64,
275 #[inspect(skip)]
277 read_buffer: Mutex<Vec<u8>>,
278 #[cfg(target_os = "linux")]
280 #[inspect(skip)]
281 driver: Option<Box<dyn pal_async::driver::Driver>>,
282}
283
284impl FlatVmdkDisk {
285 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 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 #[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 fn build_layout(files: &[File], extents: &[FlatVmdkExtent]) -> Result<Layout, OpenError> {
330 if extents.is_empty() {
332 return Err(OpenError::InvalidExtentMap(
333 "the extent map is empty".into(),
334 ));
335 }
336
337 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 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 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 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 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 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 async fn read(&self, buffers: &RequestBuffers<'_>, sector: u64) -> Result<(), DiskError> {
416 let request_byte_count = buffers.len();
417
418 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 let extent_index = self
431 .extents
432 .partition_point(|extent| extent.virtual_start_sector <= sector)
433 - 1;
434
435 #[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 #[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 let source_byte_offset = (extent.source_start_sector
452 + (sector - extent.virtual_start_sector))
453 * SECTOR_SIZE;
454
455 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 let segments = self.plan_read(sector, request_end_sector, extent_index);
468
469 #[cfg(target_os = "linux")]
471 if let Some(driver) = driver {
472 let mut completed_request_byte_count = 0;
473 let failed = AtomicBool::new(false);
475
476 let reads = segments
479 .into_iter()
480 .take_while(|_| !failed.load(Ordering::Relaxed))
482 .map(|segment| {
483 let segment_output =
485 buffers.subrange(completed_request_byte_count, segment.byte_count);
486
487 completed_request_byte_count += segment.byte_count;
489
490 Self::read_native_range(
492 driver,
493 &self.sources[segment.source_index],
494 segment_output,
495 segment.source_byte_offset,
496 )
497 });
498
499 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 async move { result.and(read_result) }
509 })
510 .await;
511 }
512
513 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 let sources = self.sources.clone();
524
525 let (request_output, read_result) = blocking::unblock(move || {
527 let result = Self::read_segments_blocking(&sources, &mut request_output, segments);
528
529 (request_output, result)
531 })
532 .await;
533
534 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 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 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 let extent = &self.extents[extent_index];
567
568 let segment_sector_count = (request_end_sector - current_virtual_sector)
570 .min(extent.virtual_end_sector - current_virtual_sector);
571
572 let source_sector =
574 extent.source_start_sector + (current_virtual_sector - extent.virtual_start_sector);
575
576 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 current_virtual_sector += segment_sector_count;
585 extent_index += 1;
586 }
587
588 segments
589 }
590
591 #[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 let readable_byte_count = source
604 .length
605 .saturating_sub(source_byte_offset)
606 .min(segment_output.len() as u64) as usize;
607
608 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 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 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 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 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 padding.fill(0);
652
653 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 return Err(io::Error::new(
665 io::ErrorKind::UnexpectedEof,
666 "VMDK source file was truncated",
667 ));
668 }
669 Ok(byte_count) => {
670 destination = &mut destination[byte_count..];
672 source_byte_offset += byte_count as u64;
673 }
674 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
676 Err(error) => return Err(error),
677 }
678 }
679 }
680
681 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}