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