1#![expect(missing_docs)]
5#![cfg(windows)]
6#![expect(unsafe_code)]
8#![expect(clippy::undocumented_unsafe_blocks)]
9
10use disk_backend::DiskError;
11use disk_backend::DiskIo;
12use disk_backend::resolve::ResolveDiskParameters;
13use disk_backend::resolve::ResolvedDisk;
14use disk_file::FileDisk;
15use guid::Guid;
16use inspect::Inspect;
17use mesh::MeshPayload;
18use scsi_buffers::RequestBuffers;
19use std::fs;
20use std::os::windows::prelude::*;
21use std::path::Path;
22use thiserror::Error;
23use vm_resource::ResolveResource;
24use vm_resource::ResourceId;
25use vm_resource::declare_static_resolver;
26use vm_resource::kind::DiskHandleKind;
27
28mod virtdisk {
29 #![allow(
30 non_snake_case,
31 dead_code,
32 non_camel_case_types,
33 clippy::upper_case_acronyms
34 )]
35
36 use std::os::windows::prelude::*;
37 use winapi::shared::guiddef::GUID;
38 use winapi::shared::minwindef::BOOL;
39 use winapi::um::minwinbase::OVERLAPPED;
40 use winapi::um::winnt::SECURITY_DESCRIPTOR;
41
42 #[repr(C)]
43 #[derive(Copy, Clone)]
44 pub struct VIRTUAL_STORAGE_TYPE {
45 pub DeviceId: u32,
46 pub VendorId: GUID,
47 }
48
49 pub const OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS: u32 = 0x0000_0001;
52
53 pub const OPEN_VIRTUAL_DISK_FLAG_BLANK_FILE: u32 = 0x0000_0002;
56
57 pub const OPEN_VIRTUAL_DISK_FLAG_BOOT_DRIVE: u32 = 0x0000_0004;
60
61 pub const OPEN_VIRTUAL_DISK_FLAG_CACHED_IO: u32 = 0x0000_0008;
63
64 pub const OPEN_VIRTUAL_DISK_FLAG_CUSTOM_DIFF_CHAIN: u32 = 0x0000_0010;
68
69 pub const OPEN_VIRTUAL_DISK_FLAG_PARENT_CACHED_IO: u32 = 0x0000_0020;
72
73 pub const OPEN_VIRTUAL_DISK_FLAG_VHDSET_FILE_ONLY: u32 = 0x0000_0040;
75
76 pub const OPEN_VIRTUAL_DISK_FLAG_IGNORE_RELATIVE_PARENT_LOCATOR: u32 = 0x0000_0080;
79
80 pub const OPEN_VIRTUAL_DISK_FLAG_NO_WRITE_HARDENING: u32 = 0x0000_0100;
83
84 #[repr(C)]
85 pub struct OPEN_VIRTUAL_DISK_PARAMETERS {
86 pub Version: u32,
87 pub u: OPEN_VIRTUAL_DISK_PARAMETERS_u,
88 }
89
90 #[repr(C)]
91 pub union OPEN_VIRTUAL_DISK_PARAMETERS_u {
92 pub Version1: OPEN_VIRTUAL_DISK_PARAMETERS_1,
93 pub Version2: OPEN_VIRTUAL_DISK_PARAMETERS_2,
94 pub Version3: OPEN_VIRTUAL_DISK_PARAMETERS_3,
95 }
96
97 #[repr(C)]
98 #[derive(Copy, Clone)]
99 pub struct OPEN_VIRTUAL_DISK_PARAMETERS_1 {
100 pub RWDepth: u32,
101 }
102
103 #[repr(C)]
104 #[derive(Copy, Clone)]
105 pub struct OPEN_VIRTUAL_DISK_PARAMETERS_2 {
106 pub GetInfoOnly: BOOL,
107 pub ReadOnly: BOOL,
108 pub ResiliencyGuid: GUID,
109 }
110
111 #[repr(C)]
112 #[derive(Copy, Clone)]
113 pub struct OPEN_VIRTUAL_DISK_PARAMETERS_3 {
114 pub GetInfoOnly: BOOL,
115 pub ReadOnly: BOOL,
116 pub ResiliencyGuid: GUID,
117 pub SnapshotId: GUID,
118 }
119
120 pub const VIRTUAL_DISK_ACCESS_ATTACH_RO: u32 = 0x00010000;
121 pub const VIRTUAL_DISK_ACCESS_ATTACH_RW: u32 = 0x00020000;
122 pub const VIRTUAL_DISK_ACCESS_DETACH: u32 = 0x00040000;
123 pub const VIRTUAL_DISK_ACCESS_GET_INFO: u32 = 0x00080000;
124 pub const VIRTUAL_DISK_ACCESS_CREATE: u32 = 0x00100000;
125 pub const VIRTUAL_DISK_ACCESS_METAOPS: u32 = 0x00200000;
126 pub const VIRTUAL_DISK_ACCESS_READ: u32 = 0x000d0000;
127
128 pub const ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY: u32 = 0x0000_0001;
130
131 pub const ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER: u32 = 0x0000_0002;
134
135 pub const ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME: u32 = 0x0000_0004;
139
140 pub const ATTACH_VIRTUAL_DISK_FLAG_NO_LOCAL_HOST: u32 = 0x0000_0008;
143
144 pub const ATTACH_VIRTUAL_DISK_FLAG_NO_SECURITY_DESCRIPTOR: u32 = 0x0000_0010;
147
148 pub const ATTACH_VIRTUAL_DISK_FLAG_BYPASS_DEFAULT_ENCRYPTION_POLICY: u32 = 0x0000_0020;
151
152 pub const GET_VIRTUAL_DISK_INFO_UNSPECIFIED: u32 = 0;
153 pub const GET_VIRTUAL_DISK_INFO_SIZE: u32 = 1;
154 pub const GET_VIRTUAL_DISK_INFO_IDENTIFIER: u32 = 2;
155 pub const GET_VIRTUAL_DISK_INFO_PARENT_LOCATION: u32 = 3;
156 pub const GET_VIRTUAL_DISK_INFO_PARENT_IDENTIFIER: u32 = 4;
157 pub const GET_VIRTUAL_DISK_INFO_PARENT_TIMESTAMP: u32 = 5;
158 pub const GET_VIRTUAL_DISK_INFO_VIRTUAL_STORAGE_TYPE: u32 = 6;
159 pub const GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE: u32 = 7;
160 pub const GET_VIRTUAL_DISK_INFO_IS_4K_ALIGNED: u32 = 8;
161 pub const GET_VIRTUAL_DISK_INFO_PHYSICAL_DISK: u32 = 9;
162 pub const GET_VIRTUAL_DISK_INFO_VHD_PHYSICAL_SECTOR_SIZE: u32 = 10;
163 pub const GET_VIRTUAL_DISK_INFO_SMALLEST_SAFE_VIRTUAL_SIZE: u32 = 11;
164 pub const GET_VIRTUAL_DISK_INFO_FRAGMENTATION: u32 = 12;
165 pub const GET_VIRTUAL_DISK_INFO_IS_LOADED: u32 = 13;
166 pub const GET_VIRTUAL_DISK_INFO_VIRTUAL_DISK_ID: u32 = 14;
167 pub const GET_VIRTUAL_DISK_INFO_CHANGE_TRACKING_STATE: u32 = 15;
168
169 #[repr(C)]
170 #[derive(Copy, Clone)]
171 pub struct GET_VIRTUAL_DISK_INFO {
172 pub Version: u32,
173 pub u: GET_VIRTUAL_DISK_INFO_u,
174 }
175
176 #[repr(C)]
177 #[derive(Copy, Clone)]
178 pub union GET_VIRTUAL_DISK_INFO_u {
179 pub Size: GET_VIRTUAL_DISK_INFO_Size,
180 pub Identifier: GUID,
181 pub ParentIdentifier: GUID,
182 pub ParentTimestamp: u32,
183 pub VirtualStorageType: VIRTUAL_STORAGE_TYPE,
184 pub ProviderSubtype: u32,
185 pub Is4kAligned: BOOL,
186 pub IsLoaded: BOOL,
187 pub VhdPhysicalSectorSize: u32,
188 pub SmallestSafeVirtualSize: u64,
189 pub FragmentationPercentage: u32,
190 pub VirtualDiskId: GUID,
191 }
192
193 #[repr(C)]
194 #[derive(Copy, Clone)]
195 pub struct GET_VIRTUAL_DISK_INFO_Size {
196 pub VirtualSize: u64,
197 pub PhysicalSize: u64,
198 pub BlockSize: u32,
199 pub SectorSize: u32,
200 }
201
202 #[link(name = "virtdisk")]
203 unsafe extern "system" {
204 pub fn OpenVirtualDisk(
205 virtual_storage_type: &mut VIRTUAL_STORAGE_TYPE,
206 path: *const u16,
207 virtual_disk_access_mask: u32,
208 flags: u32,
209 parameters: Option<&mut OPEN_VIRTUAL_DISK_PARAMETERS>,
210 handle: &mut RawHandle,
211 ) -> u32;
212
213 pub fn AttachVirtualDisk(
214 virtual_disk_handle: RawHandle,
215 security_descriptor: Option<&mut SECURITY_DESCRIPTOR>,
216 flags: u32,
217 provider_specific_flags: u32,
218 parameters: usize,
219 overlapped: Option<&mut OVERLAPPED>,
220 ) -> u32;
221
222 pub fn GetVirtualDiskInformation(
223 virtual_disk_handle: RawHandle,
224 virtual_disk_info_size: &mut u32,
225 virtual_disk_info: Option<&mut GET_VIRTUAL_DISK_INFO>,
226 size_use: Option<&mut u32>,
227 ) -> u32;
228
229 }
230}
231
232#[derive(Debug, MeshPayload)]
233pub struct Vhd(fs::File);
234
235fn chk_win32(err: u32) -> std::io::Result<()> {
236 if err == 0 {
237 Ok(())
238 } else {
239 Err(std::io::Error::from_raw_os_error(err as i32))
240 }
241}
242
243impl Vhd {
244 fn open(path: &Path, read_only: bool) -> std::io::Result<Self> {
245 let file = unsafe {
246 let mut storage_type = std::mem::zeroed();
247 let resiliency_guid = Guid::new_random();
251 let mut parameters = virtdisk::OPEN_VIRTUAL_DISK_PARAMETERS {
252 Version: 2,
253 u: virtdisk::OPEN_VIRTUAL_DISK_PARAMETERS_u {
254 Version2: virtdisk::OPEN_VIRTUAL_DISK_PARAMETERS_2 {
255 ReadOnly: read_only.into(),
256 ResiliencyGuid: resiliency_guid.into(),
257 ..std::mem::zeroed()
258 },
259 },
260 };
261 let mut path16: Vec<_> = path.as_os_str().encode_wide().collect();
262 path16.push(0);
263 let mut handle = std::mem::zeroed();
264 chk_win32(virtdisk::OpenVirtualDisk(
265 &mut storage_type,
266 path16.as_ptr(),
267 0,
268 0,
269 Some(&mut parameters),
270 &mut handle,
271 ))?;
272 fs::File::from_raw_handle(handle)
273 };
274 Ok(Self(file))
275 }
276
277 fn attach_for_raw_access(&self, read_only: bool) -> std::io::Result<()> {
278 unsafe {
279 let mut flags = virtdisk::ATTACH_VIRTUAL_DISK_FLAG_NO_LOCAL_HOST;
280 if read_only {
281 flags |= virtdisk::ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY;
282 }
283 chk_win32(virtdisk::AttachVirtualDisk(
284 self.0.as_raw_handle(),
285 None,
286 flags,
287 0,
288 0,
289 None,
290 ))?;
291 }
292 Ok(())
293 }
294
295 fn info_static(&self, info_type: u32) -> std::io::Result<virtdisk::GET_VIRTUAL_DISK_INFO> {
296 unsafe {
297 let mut info = virtdisk::GET_VIRTUAL_DISK_INFO {
298 Version: info_type,
299 ..std::mem::zeroed()
300 };
301 let mut size = size_of_val(&info) as u32;
302 chk_win32(virtdisk::GetVirtualDiskInformation(
303 self.0.as_raw_handle(),
304 &mut size,
305 Some(&mut info),
306 None,
307 ))?;
308 Ok(info)
309 }
310 }
311
312 fn get_size(&self) -> std::io::Result<virtdisk::GET_VIRTUAL_DISK_INFO_Size> {
313 unsafe {
314 Ok(self
315 .info_static(virtdisk::GET_VIRTUAL_DISK_INFO_SIZE)?
316 .u
317 .Size)
318 }
319 }
320
321 fn get_physical_sector_size(&self) -> std::io::Result<u32> {
322 unsafe {
323 Ok(self
324 .info_static(virtdisk::GET_VIRTUAL_DISK_INFO_VHD_PHYSICAL_SECTOR_SIZE)?
325 .u
326 .VhdPhysicalSectorSize)
327 }
328 }
329
330 fn get_disk_id(&self) -> std::io::Result<Guid> {
331 unsafe {
332 Ok(self
333 .info_static(virtdisk::GET_VIRTUAL_DISK_INFO_VIRTUAL_DISK_ID)?
334 .u
335 .VirtualDiskId
336 .into())
337 }
338 }
339}
340
341#[derive(MeshPayload)]
342pub struct OpenVhdmpDiskConfig(pub Vhd);
343
344impl ResourceId<DiskHandleKind> for OpenVhdmpDiskConfig {
345 const ID: &'static str = "vhdmp";
346}
347
348pub struct VhdmpDiskResolver;
349declare_static_resolver!(VhdmpDiskResolver, (DiskHandleKind, OpenVhdmpDiskConfig));
350
351#[derive(Debug, Error)]
352pub enum ResolveVhdmpDiskError {
353 #[error("failed to open VHD")]
354 Vhdmp(#[source] Error),
355 #[error("invalid disk")]
356 InvalidDisk(#[source] disk_backend::InvalidDisk),
357}
358
359impl ResolveResource<DiskHandleKind, OpenVhdmpDiskConfig> for VhdmpDiskResolver {
360 type Output = ResolvedDisk;
361 type Error = ResolveVhdmpDiskError;
362
363 fn resolve(
364 &self,
365 rsrc: OpenVhdmpDiskConfig,
366 input: ResolveDiskParameters<'_>,
367 ) -> Result<Self::Output, Self::Error> {
368 ResolvedDisk::new(
369 VhdmpDisk::new(rsrc.0, input.read_only).map_err(ResolveVhdmpDiskError::Vhdmp)?,
370 )
371 .map_err(ResolveVhdmpDiskError::InvalidDisk)
372 }
373}
374
375#[derive(Debug, Inspect)]
378pub struct VhdmpDisk {
379 #[inspect(flatten)]
380 vhd: FileDisk,
381 #[inspect(skip)]
385 io_lock: futures::lock::Mutex<()>,
386 disk_id: Guid,
387}
388
389#[derive(Debug, Error)]
390pub enum Error {
391 #[error("failed to open VHD")]
392 Open(#[source] std::io::Error),
393 #[error("failed to attach VHD")]
394 Attach(#[source] std::io::Error),
395 #[error("failed to query VHD metadata")]
396 Query(#[source] std::io::Error),
397}
398
399impl VhdmpDisk {
400 pub fn open_vhd(path: &Path, read_only: bool) -> Result<Vhd, Error> {
402 let vhd = Vhd::open(path, read_only).map_err(Error::Open)?;
403
404 vhd.attach_for_raw_access(read_only)
408 .map_err(Error::Attach)?;
409 Ok(vhd)
410 }
411
412 pub fn new(vhd: Vhd, read_only: bool) -> Result<Self, Error> {
414 let size = vhd.get_size().map_err(Error::Query)?;
415 let disk_id = vhd.get_disk_id().map_err(Error::Query)?;
416 let metadata = disk_file::Metadata {
417 disk_size: size.VirtualSize,
418 sector_size: size.SectorSize,
419 physical_sector_size: vhd.get_physical_sector_size().map_err(Error::Query)?,
420 read_only,
421 };
422 let vhd = FileDisk::with_metadata(vhd.0, metadata);
423
424 Ok(Self {
425 vhd,
426 io_lock: Default::default(),
427 disk_id,
428 })
429 }
430}
431
432impl DiskIo for VhdmpDisk {
433 fn disk_type(&self) -> &str {
434 "vhdmp"
435 }
436
437 fn sector_count(&self) -> u64 {
438 self.vhd.sector_count()
439 }
440
441 fn sector_size(&self) -> u32 {
442 self.vhd.sector_size()
443 }
444
445 fn is_read_only(&self) -> bool {
446 self.vhd.is_read_only()
447 }
448
449 fn disk_id(&self) -> Option<[u8; 16]> {
450 Some(self.disk_id.into())
451 }
452
453 fn physical_sector_size(&self) -> u32 {
454 self.vhd.physical_sector_size()
455 }
456
457 fn is_fua_respected(&self) -> bool {
458 self.vhd.is_fua_respected()
459 }
460
461 async fn read_vectored(
462 &self,
463 buffers: &RequestBuffers<'_>,
464 sector: u64,
465 ) -> Result<(), DiskError> {
466 let _locked = self.io_lock.lock().await;
467 self.vhd.read(buffers, sector).await
468 }
469
470 async fn write_vectored(
471 &self,
472 buffers: &RequestBuffers<'_>,
473 sector: u64,
474 fua: bool,
475 ) -> Result<(), DiskError> {
476 let _locked = self.io_lock.lock().await;
477 self.vhd.write(buffers, sector, fua).await
478 }
479
480 async fn sync_cache(&self) -> Result<(), DiskError> {
481 let _locked = self.io_lock.lock().await;
482 self.vhd.flush().await
483 }
484
485 async fn unmap(
486 &self,
487 _sector: u64,
488 _count: u64,
489 _block_level_only: bool,
490 ) -> Result<(), DiskError> {
491 Ok(())
492 }
493
494 fn unmap_behavior(&self) -> disk_backend::UnmapBehavior {
495 disk_backend::UnmapBehavior::Ignored
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::VhdmpDisk;
502 use disk_backend::DiskError;
503 use disk_backend::DiskIo;
504 use disk_vhd1::Vhd1Disk;
505 use guestmem::GuestMemory;
506 use pal_async::async_test;
507 use scsi_buffers::OwnedRequestBuffers;
508 use std::io::Write;
509 use tempfile::TempPath;
510
511 fn make_test_vhd() -> TempPath {
512 let mut f = tempfile::Builder::new().suffix(".vhd").tempfile().unwrap();
513 let size = 0x300000;
514 f.write_all(&vec![0u8; size]).unwrap();
515 Vhd1Disk::make_fixed(f.as_file()).unwrap();
516 f.into_temp_path()
517 }
518
519 #[test]
520 fn open_readonly() {
521 let path = make_test_vhd();
522 let _vhd = VhdmpDisk::open_vhd(path.as_ref(), true).unwrap();
523 let _vhd = VhdmpDisk::open_vhd(path.as_ref(), true).unwrap();
524 let _vhd = VhdmpDisk::open_vhd(path.as_ref(), false).unwrap_err();
525 }
526
527 #[async_test]
528 async fn test_invalid_lba() {
529 let path = make_test_vhd();
530 let vhd = VhdmpDisk::open_vhd(path.as_ref(), true).unwrap();
531 let disk = VhdmpDisk::new(vhd, true).unwrap();
532 let gm = GuestMemory::allocate(512);
533 match disk
534 .read_vectored(
535 &OwnedRequestBuffers::linear(0, 512, true).buffer(&gm),
536 0x10000000,
537 )
538 .await
539 {
540 Err(DiskError::IllegalBlock) => {}
541 r => panic!("unexpected result: {:?}", r),
542 }
543 }
544}