disk_backend/lib.rs
1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The shared disk backend abstraction for OpenVMM storage.
5//!
6//! This crate defines [`Disk`] and the [`DiskIo`] trait, the central
7//! interface between storage frontends (NVMe, SCSI/StorVSP, IDE) and disk
8//! backends (host files, block devices, remote blobs, and more).
9//!
10//! # Architecture
11//!
12//! Every disk backend implements [`DiskIo`]. Frontends don't interact with
13//! backends directly — they hold a [`Disk`], which wraps a type-erased
14//! backend (`DynDisk`, an adapter around [`DiskIo`] that normalizes return
15//! futures) behind an `Arc` for cheap, concurrent cloning. The `Disk`
16//! wrapper caches immutable metadata (sector size, physical sector size,
17//! disk ID, FUA support) at construction time and validates that sector
18//! sizes are powers of two and at least 512 bytes.
19//!
20//! # I/O model
21//!
22//! All I/O is **async** and uses **scatter-gather** buffers via
23//! [`RequestBuffers`]. Callers must pass
24//! buffers that are an integral number of sectors.
25//!
26//! The key operations are:
27//!
28//! - [`DiskIo::read_vectored`] / [`DiskIo::write_vectored`] — async
29//! scatter-gather read and write. The `fua` parameter on writes requests
30//! Force Unit Access (write-through to stable storage). Whether FUA is
31//! actually respected depends on the backend — check
32//! [`DiskIo::is_fua_respected`].
33//! - [`DiskIo::sync_cache`] — flush (equivalent to SCSI SYNCHRONIZE CACHE
34//! or NVMe FLUSH).
35//! - [`DiskIo::unmap`] — trim / deallocate sectors. The
36//! [`DiskIo::unmap_behavior`] method reports whether unmapped sectors
37//! become zero, become indeterminate, or whether unmap is ignored
38//! entirely.
39//! - [`DiskIo::eject`] — eject media (optical drives only). The default
40//! returns [`DiskError::UnsupportedEject`]. Eject is a media state change
41//! managed by the SCSI DVD layer, not by the backend.
42//! - [`DiskIo::wait_resize`] — block until the disk's sector count changes.
43//! The default returns [`std::future::pending()`], meaning the backend
44//! never signals a resize. Only backends that can detect runtime capacity
45//! changes (e.g., `BlockDeviceDisk` via Linux uevent, `NvmeDisk` via AEN)
46//! should override this. Decorators and layered disks delegate to the
47//! inner backend.
48//!
49//! # Error model
50//!
51//! All I/O methods return [`DiskError`], which frontends translate into
52//! protocol-specific errors (NVMe status codes, SCSI sense keys). The
53//! variants cover out-of-range LBAs, I/O errors, medium errors with
54//! sub-classification, guest memory access failures, read-only violations,
55//! persistent reservation conflicts, and unsupported eject.
56//!
57//! # Available backends
58//!
59//! | Backend | Crate | Description |
60//! |---------|-------|-------------|
61//! | `FileDisk` | `disk_file` | Host file, cross-platform |
62//! | `Vhd1Disk` | `disk_vhd1` | VHD1 fixed format |
63//! | `VhdmpDisk` | `disk_vhdmp` | Windows vhdmp driver |
64//! | `BlobDisk` | `disk_blob` | Read-only HTTP / Azure Blob |
65//! | `BlockDeviceDisk` | `disk_blockdevice` | Linux block device (io_uring) |
66//! | `NvmeDisk` | `disk_nvme` | Physical NVMe (user-mode driver) |
67//! | `StripedDisk` | `disk_striped` | Striped across multiple disks |
68//! | `CryptDisk` | `disk_crypt` | XTS-AES-256 encryption wrapper |
69//! | `DelayDisk` | `disk_delay` | Injected I/O latency wrapper |
70//! | `DiskWithReservations` | `disk_prwrap` | In-memory PR emulation wrapper |
71//! | `LayeredDisk` | `disk_layered` | Layered disk with per-sector presence |
72
73#![forbid(unsafe_code)]
74
75pub mod pr;
76pub mod resolve;
77pub mod sync_wrapper;
78
79use guestmem::AccessError;
80use inspect::Inspect;
81use scsi_buffers::RequestBuffers;
82use stackfuture::StackFuture;
83use std::fmt::Debug;
84use std::future::Future;
85use std::future::ready;
86use std::pin::Pin;
87use std::sync::Arc;
88use thiserror::Error;
89
90/// A disk operation error.
91#[derive(Debug, Error)]
92pub enum DiskError {
93 /// The request failed due to a preempt and abort status.
94 #[error("aborted command")]
95 AbortDueToPreemptAndAbort,
96 /// The LBA was out of range.
97 #[error("illegal request")]
98 IllegalBlock,
99 /// The request failed due to invalid input.
100 #[error("invalid input")]
101 InvalidInput,
102 /// The request failed due to an unrecovered IO error.
103 #[error("io error")]
104 Io(#[source] std::io::Error),
105 /// The request failed due to a reportable medium error.
106 #[error("medium error")]
107 MediumError(#[source] std::io::Error, MediumErrorDetails),
108 /// The request failed due to a failure to access the specified buffers.
109 #[error("failed to access guest memory")]
110 MemoryAccess(#[from] AccessError),
111 /// The request failed because the disk is read-only.
112 #[error("attempt to write to read-only disk/range")]
113 ReadOnly,
114 /// The request failed due to a persistent reservation conflict.
115 #[error("reservation conflict")]
116 ReservationConflict,
117 /// The request failed because eject is not supported.
118 #[error("unsupported eject")]
119 UnsupportedEject,
120}
121
122/// Failure details for [`DiskError::MediumError`].
123#[derive(Debug)]
124pub enum MediumErrorDetails {
125 /// The medium had an application tag check failure.
126 ApplicationTagCheckFailed,
127 /// The medium had a guard check failure.
128 GuardCheckFailed,
129 /// The medium had a reference tag check failure.
130 ReferenceTagCheckFailed,
131 /// The medium had an unrecovered read error.
132 UnrecoveredReadError,
133 /// The medium had a write fault.
134 WriteFault,
135}
136
137/// Disk metadata and IO operations.
138///
139/// # Sector range validation
140///
141/// Sector numbers reaching a backend originate with the guest, so
142/// implementations **must not panic** for any sector value, and must return
143/// [`DiskError::IllegalBlock`] for requests that fall outside the disk.
144/// Callers are *not* required to validate the range beforehand — they cannot
145/// do so meaningfully, since [`DiskIo::sector_count`] may change at runtime,
146/// so a range checked by a caller can be invalidated before the request is
147/// issued. Only the backend can validate against its own state.
148///
149/// An implementation may delegate this to whatever it is layered on top of,
150/// but only if the backing object's bounds coincide exactly with the disk's,
151/// out-of-range operations fail rather than silently succeeding, and the
152/// resulting error is mapped to [`DiskError::IllegalBlock`].
153///
154/// In exchange, implementations may rely on one guarantee from [`Disk`]: the
155/// end byte offset of any request — that is, `(sector + count) * sector_size`
156/// — is representable and no greater than [`i64::MAX`]. Sector arithmetic
157/// therefore cannot overflow, and a backend that transforms the offset (adding
158/// a header size, a chunk base, and so on) has 2^63 bytes of headroom in which
159/// to do so. Note that this says nothing about how large the disk is, so it is
160/// not a substitute for the range check above.
161pub trait DiskIo: 'static + Send + Sync + Inspect {
162 /// Returns the disk type name as a string.
163 ///
164 /// This is used for diagnostic purposes.
165 fn disk_type(&self) -> &str;
166
167 /// Returns the current sector count.
168 ///
169 /// For some backing stores, this may change at runtime. If it does, then
170 /// the backing store must also implement [`DiskIo::wait_resize`].
171 fn sector_count(&self) -> u64;
172
173 /// Returns the logical sector size of the backing store.
174 ///
175 /// This must not change at runtime.
176 fn sector_size(&self) -> u32;
177
178 /// Optionally returns a 16-byte identifier for the disk, if there is a
179 /// natural one for this backing store.
180 ///
181 /// This may be exposed to the guest as a unique disk identifier.
182 /// This must not change at runtime.
183 fn disk_id(&self) -> Option<[u8; 16]>;
184
185 /// Returns the physical sector size of the backing store.
186 ///
187 /// This must not change at runtime.
188 fn physical_sector_size(&self) -> u32;
189
190 /// Returns true if the `fua` parameter to [`DiskIo::write_vectored`] is
191 /// respected by the backing store by ensuring that the IO is immediately
192 /// committed to disk.
193 fn is_fua_respected(&self) -> bool;
194
195 /// Returns true if the disk is read only.
196 fn is_read_only(&self) -> bool;
197
198 /// Unmap sectors from the layer.
199 ///
200 /// See the [trait documentation](DiskIo#sector-range-validation) for the
201 /// requirements on out-of-range requests.
202 fn unmap(
203 &self,
204 sector: u64,
205 count: u64,
206 block_level_only: bool,
207 ) -> impl Future<Output = Result<(), DiskError>> + Send;
208
209 /// Returns the behavior of the unmap operation.
210 ///
211 /// This tells callers what happens to the content of unmapped sectors:
212 ///
213 /// - [`UnmapBehavior::Zeroes`] — unmapped sectors read back as zero.
214 /// - [`UnmapBehavior::Unspecified`] — content may or may not change, and
215 /// not necessarily to zero.
216 /// - [`UnmapBehavior::Ignored`] — unmap is a no-op; content is unchanged.
217 fn unmap_behavior(&self) -> UnmapBehavior;
218
219 /// Returns the optimal granularity for unmaps, in sectors.
220 fn optimal_unmap_sectors(&self) -> u32 {
221 1
222 }
223
224 /// Optionally returns a trait object to issue persistent reservation
225 /// requests.
226 fn pr(&self) -> Option<&dyn pr::PersistentReservation> {
227 None
228 }
229
230 /// Issues an asynchronous eject media operation to the disk.
231 ///
232 /// The default implementation returns [`DiskError::UnsupportedEject`].
233 /// Eject is primarily a media state change managed by the SCSI DVD layer
234 /// (`SimpleScsiDvd`), not by disk backends. Backends generally do not
235 /// need to override this.
236 fn eject(&self) -> impl Future<Output = Result<(), DiskError>> + Send {
237 ready(Err(DiskError::UnsupportedEject))
238 }
239
240 /// Issues an asynchronous read-scatter operation to the disk.
241 ///
242 /// # Arguments
243 /// * `buffers` - An object representing the data buffers into which the disk data will be transferred.
244 /// * `sector` - The logical sector at which the read operation starts.
245 fn read_vectored(
246 &self,
247 buffers: &RequestBuffers<'_>,
248 sector: u64,
249 ) -> impl Future<Output = Result<(), DiskError>> + Send;
250
251 /// Issues an asynchronous write-gather operation to the disk.
252 /// # Arguments
253 /// * `buffers` - An object representing the data buffers containing the data to transfer to the disk.
254 /// * `sector` - The logical sector at which the write operation starts.
255 /// * `fua` - A flag indicates if FUA (force unit access) is requested.
256 fn write_vectored(
257 &self,
258 buffers: &RequestBuffers<'_>,
259 sector: u64,
260 fua: bool,
261 ) -> impl Future<Output = Result<(), DiskError>> + Send;
262
263 /// Issues an asynchronous flush operation to the disk.
264 fn sync_cache(&self) -> impl Future<Output = Result<(), DiskError>> + Send;
265
266 /// Waits for the disk sector count to change from the specified value.
267 ///
268 /// Returns the new sector count once [`DiskIo::sector_count`] would return
269 /// a value different from `sector_count`. Frontends use this to detect
270 /// runtime capacity changes and notify the guest (NVMe via AEN, SCSI via
271 /// UNIT_ATTENTION).
272 ///
273 /// The default implementation returns [`std::future::pending()`], meaning
274 /// the disk never signals a resize. Only backends that can detect runtime
275 /// capacity changes should override this — for example, `BlockDeviceDisk`
276 /// (via Linux uevent) and `NvmeDisk` (via NVMe AEN). Decorator wrappers
277 /// and `LayeredDisk` should delegate to the inner disk.
278 fn wait_resize(&self, sector_count: u64) -> impl Future<Output = u64> + Send {
279 let _ = sector_count;
280 std::future::pending()
281 }
282}
283
284/// An asynchronous block device.
285///
286/// This type is cheap to clone, for sharing the disk among multiple concurrent
287/// users.
288#[derive(Inspect, Clone)]
289#[inspect(extra = "Self::inspect_extra")]
290pub struct Disk(#[inspect(flatten)] Arc<DiskInner>);
291
292impl Disk {
293 fn inspect_extra(&self, resp: &mut inspect::Response<'_>) {
294 resp.field("disk_type", self.0.disk.disk_type())
295 .field("sector_count", self.0.disk.sector_count())
296 .field("supports_pr", self.0.disk.pr().is_some());
297 }
298}
299
300impl Debug for Disk {
301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302 f.debug_tuple("Disk").finish()
303 }
304}
305
306#[derive(Inspect)]
307#[inspect(bound = "T: DynDisk")]
308struct DiskInner<T: ?Sized = dyn DynDisk> {
309 sector_size: u32,
310 sector_shift: u32,
311 physical_sector_size: u32,
312 disk_id: Option<[u8; 16]>,
313 is_fua_respected: bool,
314 is_read_only: bool,
315 unmap_behavior: UnmapBehavior,
316 optimal_unmap_sectors: u32,
317 disk: T,
318}
319
320/// Errors that can occur when creating a `Disk`.
321#[derive(Debug, Error)]
322pub enum InvalidDisk {
323 /// The sector size is invalid.
324 #[error("invalid sector size: {0}")]
325 InvalidSectorSize(u32),
326 /// The physical sector size is invalid.
327 #[error("invalid physical sector size: {0}")]
328 InvalidPhysicalSectorSize(u32),
329}
330
331impl Disk {
332 /// Returns a new disk wrapping the given backing object.
333 pub fn new(disk: impl 'static + DiskIo) -> Result<Self, InvalidDisk> {
334 // Cache the metadata locally to validate it and so that it can be
335 // accessed without needing to go through the trait object. This is more
336 // efficient and ensures the backing disk does not change these values
337 // during the lifetime of the disk.
338 let sector_size = disk.sector_size();
339 if !sector_size.is_power_of_two() || sector_size < 512 {
340 return Err(InvalidDisk::InvalidSectorSize(sector_size));
341 }
342 let physical_sector_size = disk.physical_sector_size();
343 if !physical_sector_size.is_power_of_two() || physical_sector_size < sector_size {
344 return Err(InvalidDisk::InvalidPhysicalSectorSize(physical_sector_size));
345 }
346 Ok(Self(Arc::new(DiskInner {
347 sector_size,
348 sector_shift: sector_size.trailing_zeros(),
349 physical_sector_size,
350 disk_id: disk.disk_id(),
351 is_fua_respected: disk.is_fua_respected(),
352 is_read_only: disk.is_read_only(),
353 optimal_unmap_sectors: disk.optimal_unmap_sectors(),
354 unmap_behavior: disk.unmap_behavior(),
355 disk,
356 })))
357 }
358
359 /// Returns the current sector count.
360 ///
361 /// For some backing stores, this may change at runtime. Use
362 /// [`wait_resize`](Self::wait_resize) to detect this change.
363 pub fn sector_count(&self) -> u64 {
364 self.0.disk.sector_count()
365 }
366
367 /// Returns the logical sector size of the backing store.
368 pub fn sector_size(&self) -> u32 {
369 self.0.sector_size
370 }
371
372 /// Returns log2 of the logical sector size of the backing store.
373 pub fn sector_shift(&self) -> u32 {
374 self.0.sector_shift
375 }
376
377 /// Optionally returns a 16-byte identifier for the disk, if there is a
378 /// natural one for this backing store.
379 ///
380 /// This may be exposed to the guest as a unique disk identifier.
381 pub fn disk_id(&self) -> Option<[u8; 16]> {
382 self.0.disk_id
383 }
384
385 /// Returns the physical sector size of the backing store.
386 pub fn physical_sector_size(&self) -> u32 {
387 self.0.physical_sector_size
388 }
389
390 /// Returns true if the `fua` parameter to
391 /// [`write_vectored`](Self::write_vectored) is respected by the backing
392 /// store by ensuring that the IO is immediately committed to disk.
393 pub fn is_fua_respected(&self) -> bool {
394 self.0.is_fua_respected
395 }
396
397 /// Returns true if the disk is read only.
398 pub fn is_read_only(&self) -> bool {
399 self.0.is_read_only
400 }
401
402 /// Returns the largest sector number that may appear as the end of a
403 /// request while keeping the end byte offset representable.
404 fn max_sector(&self) -> u64 {
405 (i64::MAX as u64) >> self.0.sector_shift
406 }
407
408 /// Checks that a request's end byte offset is representable.
409 ///
410 /// This is deliberately *not* a range check: it never consults
411 /// [`sector_count`](Self::sector_count), so it cannot mask a bug in a
412 /// backend that fails to validate the range itself, and it cannot be
413 /// invalidated by the disk being resized. Range validation belongs to the
414 /// backend, which is the only component that can perform it atomically
415 /// with the I/O.
416 ///
417 /// What it does guarantee is that `(sector + count) * sector_size` does not
418 /// overflow and is at most [`i64::MAX`], which is the real limit imposed by
419 /// `pread64`/`pwrite64` and the Windows file APIs. Backends may rely on
420 /// this to do sector and offset arithmetic without worrying about
421 /// wraparound.
422 fn check_representable(&self, sector: u64, count: u64) -> Result<(), DiskError> {
423 match sector.checked_add(count) {
424 Some(end) if end <= self.max_sector() => Ok(()),
425 // No disk can be 2^63 bytes, so such a sector is out of range for
426 // any disk.
427 _ => Err(DiskError::IllegalBlock),
428 }
429 }
430
431 /// Returns the number of sectors spanned by `buffers`.
432 ///
433 /// Callers must pass a whole number of sectors, so this is normally exact.
434 /// Rounding down is nonetheless the right choice for a caller that does
435 /// not: `max_sector` is `i64::MAX` rounded *down* to a sector, which for a
436 /// power-of-two sector size leaves exactly `sector_size - 1` bytes of slack
437 /// below `i64::MAX` — enough to cover a partial trailing sector. So the end
438 /// byte offset stays representable either way.
439 fn buffer_sectors(&self, buffers: &RequestBuffers<'_>) -> u64 {
440 (buffers.len() as u64) >> self.0.sector_shift
441 }
442
443 /// Unmap sectors from the disk.
444 ///
445 /// If the disk reports [`UnmapBehavior::Ignored`], the request is not
446 /// passed to the backing object at all, since by definition it would do
447 /// nothing. The range is still validated first — a no-op is still not a
448 /// legal response to a request naming sectors the disk does not have.
449 pub async fn unmap(
450 &self,
451 sector: u64,
452 count: u64,
453 block_level_only: bool,
454 ) -> Result<(), DiskError> {
455 self.check_representable(sector, count)?;
456 if self.unmap_behavior() == UnmapBehavior::Ignored {
457 // This is the one place where `Disk` range checks a request, and it
458 // is sound precisely because it is the one place where `Disk` does
459 // not delegate: there is no backend check for it to be redundant
460 // with, and none for it to mask. The check being momentarily stale
461 // if the disk is resized is harmless here, because the operation
462 // does nothing either way — only the status code is observable.
463 //
464 // The addition cannot overflow because of `check_representable`.
465 if sector + count > self.sector_count() {
466 return Err(DiskError::IllegalBlock);
467 }
468 return Ok(());
469 }
470 self.0.disk.unmap(sector, count, block_level_only).await
471 }
472
473 /// Returns the behavior of the unmap operation.
474 pub fn unmap_behavior(&self) -> UnmapBehavior {
475 self.0.unmap_behavior
476 }
477
478 /// Returns the optimal granularity for unmaps, in sectors.
479 pub fn optimal_unmap_sectors(&self) -> u32 {
480 self.0.optimal_unmap_sectors
481 }
482
483 /// Optionally returns a trait object to issue persistent reservation
484 /// requests.
485 pub fn pr(&self) -> Option<&dyn pr::PersistentReservation> {
486 self.0.disk.pr()
487 }
488
489 /// Issues an asynchronous eject media operation to the disk.
490 pub fn eject(&self) -> impl use<'_> + Future<Output = Result<(), DiskError>> + Send {
491 self.0.disk.eject()
492 }
493
494 /// Issues an asynchronous read-scatter operation to the disk.
495 ///
496 /// # Arguments
497 ///
498 /// * `buffers` - An object representing the data buffers into which the disk data will be transferred.
499 /// * `sector` - The logical sector at which the read operation starts.
500 pub async fn read_vectored(
501 &self,
502 buffers: &RequestBuffers<'_>,
503 sector: u64,
504 ) -> Result<(), DiskError> {
505 self.check_representable(sector, self.buffer_sectors(buffers))?;
506 self.0.disk.read_vectored(buffers, sector).await
507 }
508
509 /// Issues an asynchronous write-gather operation to the disk.
510 ///
511 /// # Arguments
512 ///
513 /// * `buffers` - An object representing the data buffers containing the data to transfer to the disk.
514 /// * `sector` - The logical sector at which the write operation starts.
515 /// * `fua` - A flag indicates if FUA (force unit access) is requested.
516 ///
517 /// # Panics
518 ///
519 /// The caller must pass a buffer with an integer number of sectors.
520 pub async fn write_vectored(
521 &self,
522 buffers: &RequestBuffers<'_>,
523 sector: u64,
524 fua: bool,
525 ) -> Result<(), DiskError> {
526 self.check_representable(sector, self.buffer_sectors(buffers))?;
527 self.0.disk.write_vectored(buffers, sector, fua).await
528 }
529
530 /// Issues an asynchronous flush operation to the disk.
531 pub fn sync_cache(&self) -> impl use<'_> + Future<Output = Result<(), DiskError>> + Send {
532 self.0.disk.sync_cache()
533 }
534
535 /// Waits for the disk sector count to change from the specified value.
536 pub fn wait_resize(&self, sector_count: u64) -> impl use<'_> + Future<Output = u64> {
537 self.0.disk.wait_resize(sector_count)
538 }
539}
540
541/// The behavior of the [`DiskIo::unmap`] operation.
542///
543/// This describes what happens to the content of unmapped sectors. Frontends
544/// use this to report the correct behavior to the guest (e.g., SCSI
545/// `LBPRZ` bit or NVMe DLFEAT field).
546#[derive(Clone, Copy, Debug, PartialEq, Eq, Inspect)]
547pub enum UnmapBehavior {
548 /// Unmap may or may not change the content, and not necessarily to zero.
549 /// The guest cannot assume anything about the content of unmapped sectors.
550 Unspecified,
551 /// Unmaps are guaranteed to be ignored — the content is unchanged.
552 /// The disk reports that unmap is not supported.
553 Ignored,
554 /// Unmap will deterministically zero the content. The guest can rely on
555 /// reading back zeroes from unmapped sectors.
556 Zeroes,
557}
558
559/// The amount of space reserved for a DiskIo future
560///
561/// This was chosen by running `cargo test -p storvsp -- --no-capture` and looking at the required
562/// size that was given in the failure message
563const ASYNC_DISK_STACK_SIZE: usize = 1256;
564
565type IoFuture<'a> = StackFuture<'a, Result<(), DiskError>, { ASYNC_DISK_STACK_SIZE }>;
566
567trait DynDisk: Send + Sync + Inspect {
568 fn disk_type(&self) -> &str;
569 fn sector_count(&self) -> u64;
570
571 fn unmap(&self, sector_offset: u64, sector_count: u64, block_level_only: bool) -> IoFuture<'_>;
572
573 fn pr(&self) -> Option<&dyn pr::PersistentReservation>;
574 fn eject(&self) -> IoFuture<'_>;
575
576 fn read_vectored<'a>(&'a self, buffers: &'a RequestBuffers<'_>, sector: u64) -> IoFuture<'a>;
577
578 fn write_vectored<'a>(
579 &'a self,
580 buffers: &'a RequestBuffers<'_>,
581 sector: u64,
582 fua: bool,
583 ) -> IoFuture<'a>;
584
585 fn sync_cache(&self) -> IoFuture<'_>;
586
587 fn wait_resize<'a>(
588 &'a self,
589 sector_count: u64,
590 ) -> Pin<Box<dyn 'a + Send + Future<Output = u64>>> {
591 let _ = sector_count;
592 Box::pin(std::future::pending())
593 }
594}
595
596impl<T: DiskIo> DynDisk for T {
597 fn disk_type(&self) -> &str {
598 self.disk_type()
599 }
600
601 fn sector_count(&self) -> u64 {
602 self.sector_count()
603 }
604
605 fn unmap(
606 &self,
607 sector_offset: u64,
608 sector_count: u64,
609 block_level_only: bool,
610 ) -> StackFuture<'_, Result<(), DiskError>, { ASYNC_DISK_STACK_SIZE }> {
611 StackFuture::from_or_box(self.unmap(sector_offset, sector_count, block_level_only))
612 }
613
614 fn pr(&self) -> Option<&dyn pr::PersistentReservation> {
615 self.pr()
616 }
617
618 fn eject(&self) -> IoFuture<'_> {
619 StackFuture::from_or_box(self.eject())
620 }
621
622 fn read_vectored<'a>(&'a self, buffers: &'a RequestBuffers<'_>, sector: u64) -> IoFuture<'a> {
623 StackFuture::from_or_box(self.read_vectored(buffers, sector))
624 }
625
626 fn write_vectored<'a>(
627 &'a self,
628 buffers: &'a RequestBuffers<'a>,
629 sector: u64,
630 fua: bool,
631 ) -> IoFuture<'a> {
632 StackFuture::from_or_box(self.write_vectored(buffers, sector, fua))
633 }
634
635 fn sync_cache(&self) -> IoFuture<'_> {
636 StackFuture::from_or_box(self.sync_cache())
637 }
638}