Skip to main content

imago/format/
access.rs

1//! Actual public image access functionality.
2//!
3//! Provides access to different image formats via `FormatAccess` objects.
4
5use super::drivers::{FormatDriverInstance, ShallowMapping};
6use super::PreallocateMode;
7use crate::io_buffers::{IoVector, IoVectorMut};
8use crate::storage::ext::write_full_zeroes;
9use crate::{Storage, StorageExt};
10#[cfg(feature = "async")]
11use futures::stream::{FuturesUnordered, StreamExt};
12use maybe_async::maybe_async;
13use std::fmt::{self, Display, Formatter};
14use std::{cmp, io, ptr};
15
16/// Provides access to a disk image.
17#[derive(Debug)]
18pub struct FormatAccess<S: Storage + 'static> {
19    /// Image format driver.
20    inner: Box<dyn FormatDriverInstance<Storage = S>>,
21
22    /// Whether this image may be modified.
23    writable: bool,
24
25    /// How many asynchronous requests to perform per read request in parallel.
26    #[cfg(feature = "async")]
27    read_parallelization: usize,
28
29    /// How many asynchronous requests to perform per write request in parallel.
30    #[cfg(feature = "async")]
31    write_parallelization: usize,
32}
33
34/// Fully recursive mapping information.
35///
36/// Mapping information that resolves down to the storage object layer (except for special data).
37#[derive(Debug)]
38#[non_exhaustive]
39pub enum Mapping<'a, S: Storage + 'static> {
40    /// Raw data.
41    #[non_exhaustive]
42    Raw {
43        /// Storage object where this data is stored.
44        storage: &'a S,
45
46        /// Offset in `storage` where this data is stored.
47        offset: u64,
48
49        /// Whether this mapping may be written to.
50        ///
51        /// If `true`, you can directly write to `offset` on `storage` to change the disk image’s
52        /// data accordingly.
53        ///
54        /// If `false`, the disk image format does not allow writing to `offset` on `storage`; a
55        /// new mapping must be allocated first.
56        writable: bool,
57    },
58
59    /// Range is to be read as zeroes.
60    #[non_exhaustive]
61    Zero {
62        /// Whether these zeroes are explicit on this image (the top layer).
63        ///
64        /// Differential image formats (like qcow2) track information about the status for all
65        /// blocks in the image (called clusters in case of qcow2).  Perhaps most importantly, they
66        /// track whether a block is allocated or not:
67        /// - Allocated blocks have their data in the image.
68        /// - Unallocated blocks do not have their data in this image, but have to be read from a
69        ///   backing image (which results in [`ShallowMapping::Indirect`] mappings).
70        ///
71        /// Thus, such images represent the difference from their backing image (hence
72        /// “differential”).
73        ///
74        /// Without a backing image, this feature can be used for sparse allocation: Unallocated
75        /// blocks are simply interpreted to be zero.  These ranges will be noted as
76        /// [`Mapping::Zero`] with `explicit` set to false.
77        ///
78        /// Formats like qcow2 can track more information beyond just the allocation status,
79        /// though, for example, whether a block should read as zero. Such blocks similarly do not
80        /// need to have their data stored in the image file, but are still not treated as
81        /// unallocated, so will never be read from a backing image, regardless of whether one
82        /// exists or not.
83        ///
84        /// These ranges are noted as [`Mapping::Zero`] with `explicit` set to true.
85        explicit: bool,
86    },
87
88    /// End of file reached.
89    ///
90    /// The accompanying length is always 0.
91    #[non_exhaustive]
92    Eof {},
93
94    /// Data is encoded in some manner, e.g. compressed or encrypted.
95    ///
96    /// Such data cannot be accessed directly, but must be interpreted by the image format driver.
97    #[non_exhaustive]
98    Special {
99        /// Format layer where this special data was encountered.
100        layer: &'a FormatAccess<S>,
101
102        /// Original (“guest”) offset on `layer` to pass to `readv_special()`.
103        offset: u64,
104    },
105}
106
107// When adding new public methods, don’t forget to add them to sync_wrappers, too.
108#[maybe_async]
109impl<S: Storage + 'static> FormatAccess<S> {
110    /// Wrap a format driver instance in `FormatAccess`.
111    ///
112    /// `FormatAccess` provides I/O access to disk images, based on the functionality offered by
113    /// the individual format drivers via `FormatDriverInstance`.
114    pub fn new<D: FormatDriverInstance<Storage = S> + 'static>(inner: D) -> Self {
115        let writable = inner.writable();
116        FormatAccess {
117            inner: Box::new(inner),
118            #[cfg(feature = "async")]
119            read_parallelization: 1,
120            #[cfg(feature = "async")]
121            write_parallelization: 1,
122            writable,
123        }
124    }
125
126    /// Return the contained format driver instance.
127    pub fn inner(&self) -> &dyn FormatDriverInstance<Storage = S> {
128        self.inner.as_ref()
129    }
130
131    /// Return the contained format driver instance.
132    pub fn inner_mut(&mut self) -> &mut dyn FormatDriverInstance<Storage = S> {
133        self.inner.as_mut()
134    }
135
136    /// Return the disk size in bytes.
137    pub fn size(&self) -> u64 {
138        self.inner.size()
139    }
140
141    /// Set the number of simultaneous async requests per read.
142    ///
143    /// When issuing read requests, issue this many async requests in parallel (still in a single
144    /// thread).  The default count is `1`, i.e. no parallel requests.
145    #[cfg(feature = "async")]
146    pub fn set_async_read_parallelization(&mut self, count: usize) {
147        self.read_parallelization = count;
148    }
149
150    /// Set the number of simultaneous async requests per write.
151    ///
152    /// When issuing write requests, issue this many async requests in parallel (still in a single
153    /// thread).  The default count is `1`, i.e. no parallel requests.
154    #[cfg(feature = "async")]
155    pub fn set_async_write_parallelization(&mut self, count: usize) {
156        self.write_parallelization = count;
157    }
158
159    /// Return all storage dependencies of this image.
160    ///
161    /// Includes recursive dependencies, i.e. those from other image dependencies like backing
162    /// images.
163    pub(crate) fn collect_storage_dependencies(&self) -> Vec<&S> {
164        self.inner.collect_storage_dependencies()
165    }
166
167    /// Minimal I/O alignment, for both length and offset.
168    ///
169    /// All requests to this image should be aligned to this value, both in length and offset.
170    ///
171    /// Requests that do not match this alignment will be realigned internally, which requires
172    /// creating bounce buffers and read-modify-write cycles for write requests, which is costly,
173    /// so should be avoided.
174    pub fn req_align(&self) -> usize {
175        self.inner
176            .collect_storage_dependencies()
177            .into_iter()
178            .fold(1, |max, s| cmp::max(max, s.req_align()))
179    }
180
181    /// Minimal memory buffer alignment, for both address and length.
182    ///
183    /// All buffers used in requests to this image should be aligned to this value, both their
184    /// address and length.
185    ///
186    /// Request buffers that do not match this alignment will be realigned internally, which
187    /// requires creating bounce buffers, which is costly, so should be avoided.
188    pub fn mem_align(&self) -> usize {
189        self.inner
190            .collect_storage_dependencies()
191            .into_iter()
192            .fold(1, |max, s| cmp::max(max, s.mem_align()))
193    }
194
195    /// Read the data from the given mapping.
196    async fn read_chunk(
197        &self,
198        mut bufv: IoVectorMut<'_>,
199        mapping: Mapping<'_, S>,
200    ) -> io::Result<()> {
201        match mapping {
202            Mapping::Raw {
203                storage,
204                offset,
205                writable: _,
206            } => storage.readv(bufv, offset).await,
207
208            Mapping::Zero { explicit: _ } | Mapping::Eof {} => {
209                bufv.fill(0);
210                Ok(())
211            }
212
213            // FIXME: TOCTTOU problem.  Not sure how to fully fix it, if possible at all.
214            // (Concurrent writes can change the mapping, but the driver will have to reload the
215            // mapping because it cannot pass it in `NonRecursiveMapping::Special`.  It may then
216            // find that this is no longer a “special” range.  Even passing the low-level mapping
217            // information in `Mapping::Special` wouldn’t fully fix it, though: If concurrent
218            // writes change the low-level cluster type, and the driver then tries to e.g.
219            // decompress the data that was there, that may well fail.)
220            Mapping::Special { layer, offset } => layer.inner.readv_special(bufv, offset).await,
221        }
222    }
223
224    /// Return the shallow mapping at `offset`.
225    ///
226    /// Find what `offset` is mapped to, which may be another format layer, return that
227    /// information, and the length of the continuous mapping (from `offset`).
228    ///
229    /// Use [`FormatAccess::get_mapping()`] to recursively fully resolve references to other format
230    /// layers.
231    pub async fn get_shallow_mapping(
232        &self,
233        offset: u64,
234        max_length: u64,
235    ) -> io::Result<(ShallowMapping<'_, S>, u64)> {
236        self.inner
237            .get_mapping(offset, max_length)
238            .await
239            .map(|(m, l)| (m, cmp::min(l, max_length)))
240    }
241
242    /// Return the recursively resolved mapping at `offset`.
243    ///
244    /// Find what `offset` is mapped to, return that mapping information, and the length of that
245    /// continuous mapping (from `offset`).
246    ///
247    /// All data references to other format layers are automatically resolved (recursively), so
248    /// that the result are more “trivial” mappings (unless prevented by special mappings like
249    /// compressed clusters).
250    pub async fn get_mapping(
251        &self,
252        mut offset: u64,
253        mut max_length: u64,
254    ) -> io::Result<(Mapping<'_, S>, u64)> {
255        let mut format_layer = self;
256        let mut writable_gate = true;
257
258        loop {
259            let (mapping, length) = format_layer.get_shallow_mapping(offset, max_length).await?;
260
261            match mapping {
262                ShallowMapping::Raw {
263                    storage,
264                    offset,
265                    writable,
266                } => {
267                    return Ok((
268                        Mapping::Raw {
269                            storage,
270                            offset,
271                            writable: writable && writable_gate,
272                        },
273                        length,
274                    ))
275                }
276
277                ShallowMapping::Indirect {
278                    layer: recurse_layer,
279                    offset: recurse_offset,
280                    writable: recurse_writable,
281                } => {
282                    format_layer = recurse_layer;
283                    offset = recurse_offset;
284                    writable_gate = recurse_writable;
285                    max_length = length;
286                }
287
288                ShallowMapping::Zero { explicit } => {
289                    // If this is not the top layer, always clear `explicit`
290                    return if explicit && ptr::eq(format_layer, self) {
291                        Ok((Mapping::Zero { explicit: true }, length))
292                    } else {
293                        Ok((Mapping::Zero { explicit: false }, length))
294                    };
295                }
296
297                ShallowMapping::Eof {} => {
298                    // Return EOF only on top layer, zero otherwise
299                    return if ptr::eq(format_layer, self) {
300                        Ok((Mapping::Eof {}, 0))
301                    } else {
302                        Ok((Mapping::Zero { explicit: false }, max_length))
303                    };
304                }
305
306                ShallowMapping::Special { offset } => {
307                    return Ok((
308                        Mapping::Special {
309                            layer: format_layer,
310                            offset,
311                        },
312                        length,
313                    ));
314                }
315            }
316        }
317    }
318
319    /// Create a raw data mapping at `offset`.
320    ///
321    /// Ensure that `offset` is directly mapped to some storage object, up to a length of `length`.
322    /// Return the storage object, the corresponding offset there, and the continuous length that
323    /// we were able to map (less than or equal to `length`).
324    ///
325    /// If `overwrite` is true, the contents in the range are supposed to be overwritten and may be
326    /// discarded.  Otherwise, they are kept.
327    pub async fn ensure_data_mapping(
328        &self,
329        offset: u64,
330        length: u64,
331        overwrite: bool,
332    ) -> io::Result<(&S, u64, u64)> {
333        let (storage, mapped_offset, mapped_length) = self
334            .inner
335            .ensure_data_mapping(offset, length, overwrite)
336            .await?;
337        let mapped_length = cmp::min(length, mapped_length);
338        assert!(mapped_length > 0);
339        Ok((storage, mapped_offset, mapped_length))
340    }
341
342    /// Read data at `offset` into `bufv`.
343    ///
344    /// Reads until `bufv` is filled completely, i.e. will not do short reads.  When reaching the
345    /// end of file, the rest of `bufv` is filled with 0.
346    pub async fn readv(&self, mut bufv: IoVectorMut<'_>, mut offset: u64) -> io::Result<()> {
347        #[cfg(feature = "async")]
348        let mut workers = (self.read_parallelization > 1).then(FuturesUnordered::new);
349
350        while !bufv.is_empty() {
351            let (mapping, chunk_length) = self.get_mapping(offset, bufv.len()).await?;
352            if chunk_length == 0 {
353                assert!(mapping.is_eof());
354                bufv.fill(0);
355                break;
356            }
357
358            #[cfg(feature = "async")]
359            if let Some(workers) = workers.as_mut() {
360                while workers.len() >= self.read_parallelization {
361                    workers.next().await.unwrap()?;
362                }
363            }
364
365            let (chunk, remainder) = bufv.split_at(chunk_length);
366            bufv = remainder;
367            offset += chunk_length;
368
369            #[cfg(feature = "async")]
370            if let Some(workers) = workers.as_mut() {
371                workers.push(self.read_chunk(chunk, mapping));
372            } else {
373                self.read_chunk(chunk, mapping).await?;
374            }
375            #[cfg(feature = "sync")]
376            self.read_chunk(chunk, mapping)?;
377        }
378
379        #[cfg(feature = "async")]
380        if let Some(mut workers) = workers {
381            while workers.next().await.transpose()?.is_some() {}
382        }
383
384        Ok(())
385    }
386
387    /// Read data at `offset` into `buf`.
388    ///
389    /// Reads until `buf` is filled completely, i.e. will not do short reads.  When reaching the
390    /// end of file, the rest of `buf` is filled with 0.
391    pub async fn read<'a>(
392        &'a self,
393        buf: impl Into<IoVectorMut<'a>>,
394        offset: u64,
395    ) -> io::Result<()> {
396        self.readv(buf.into(), offset).await
397    }
398
399    /// Write data from `bufv` to `offset`.
400    ///
401    /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes.  Reaching
402    /// the end of file before the end of the buffer results in an error.
403    pub async fn writev(&self, mut bufv: IoVector<'_>, mut offset: u64) -> io::Result<()> {
404        if !self.writable {
405            return Err(io::Error::other("Image is read-only"));
406        }
407
408        // Limit to disk size
409        let disk_size = self.inner.size();
410        if offset >= disk_size {
411            return Ok(());
412        }
413        if bufv.len() > disk_size - offset {
414            bufv = bufv.split_at(disk_size - offset).0;
415        }
416
417        #[cfg(feature = "async")]
418        let mut workers = (self.write_parallelization > 1).then(FuturesUnordered::new);
419
420        while !bufv.is_empty() {
421            let (storage, st_offset, st_length) =
422                self.ensure_data_mapping(offset, bufv.len(), true).await?;
423
424            #[cfg(feature = "async")]
425            if let Some(workers) = workers.as_mut() {
426                while workers.len() >= self.write_parallelization {
427                    workers.next().await.unwrap()?;
428                }
429            }
430
431            let (chunk, remainder) = bufv.split_at(st_length);
432            bufv = remainder;
433            offset += st_length;
434
435            #[cfg(feature = "async")]
436            if let Some(workers) = workers.as_mut() {
437                workers.push(storage.writev(chunk, st_offset));
438            } else {
439                storage.writev(chunk, st_offset).await?;
440            }
441            #[cfg(feature = "sync")]
442            storage.writev(chunk, st_offset)?;
443        }
444
445        #[cfg(feature = "async")]
446        if let Some(mut workers) = workers {
447            while workers.next().await.transpose()?.is_some() {}
448        }
449
450        Ok(())
451    }
452
453    /// Write data from `buf` to `offset`.
454    ///
455    /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes.  Reaching
456    /// the end of file before the end of the buffer results in an error.
457    pub async fn write<'a>(&'a self, buf: impl Into<IoVector<'a>>, offset: u64) -> io::Result<()> {
458        self.writev(buf.into(), offset).await
459    }
460
461    /// Check whether the given range is zero.
462    ///
463    /// Checks for zero mappings, not zero data (although this might be changed in the future).
464    ///
465    /// Errors are treated as non-zero areas.
466    async fn is_range_zero(&self, mut offset: u64, mut length: u64) -> bool {
467        while length > 0 {
468            match self.get_mapping(offset, length).await {
469                Ok((Mapping::Zero { explicit: _ }, mlen)) => {
470                    offset += mlen;
471                    length -= mlen;
472                }
473                _ => return false,
474            };
475        }
476
477        true
478    }
479
480    /// Ensure the given range reads as zeroes, without write-zeroes support.
481    ///
482    /// Does not require support for efficient zeroing, instead writing zeroes when the range is
483    /// not zero yet.  If `allocate` is true, areas that are not currently allocated will be
484    /// allocated to write zeroes there; if it is false, unallocated areas that currently read as
485    /// zero are left alone.
486    ///
487    /// However, can still use efficient zero support if present.
488    ///
489    /// The main use case is to handle unaligned zero requests.  Quite inefficient for large areas.
490    async fn soft_ensure_zero(&self, mut offset: u64, mut length: u64) -> io::Result<()> {
491        // “Fast” path: Try to efficiently zero as much as possible
492        if let Some(gran) = self.inner.zero_granularity() {
493            let end = offset.checked_add(length).ok_or_else(|| {
494                io::Error::new(
495                    io::ErrorKind::InvalidInput,
496                    format!("Write-zero wrap-around: {offset} + {length}"),
497                )
498            })?;
499            let mut aligned_start = offset - offset % gran;
500            // Could be handled, but don’t bother
501            let mut aligned_end = end.checked_next_multiple_of(gran).ok_or_else(|| {
502                io::Error::new(
503                    io::ErrorKind::InvalidInput,
504                    "Write-zero wrap-around at cluster granularity",
505                )
506            })?;
507
508            aligned_end = cmp::min(aligned_end, self.size());
509
510            // Whether the whole area could be efficiently zeroed
511            let mut fully_zeroed = true;
512
513            if offset > aligned_start
514                && !self
515                    .is_range_zero(aligned_start, offset - aligned_start)
516                    .await
517            {
518                // Non-zero head, we cannot zero that cluster.  Still try to zero as much as
519                // possible.
520                fully_zeroed = false;
521                aligned_start += gran;
522            }
523            if end < aligned_end && !self.is_range_zero(end, aligned_end - end).await {
524                // Non-zero tail, we cannot zero that cluster.  Still try to zero as much as
525                // possible.
526                fully_zeroed = false;
527                aligned_end -= gran;
528            }
529
530            while aligned_start < aligned_end {
531                let res = self
532                    .inner
533                    .ensure_zero_mapping(aligned_start, aligned_end - aligned_start)
534                    .await;
535                if let Ok((zofs, zlen)) = res {
536                    if zofs != aligned_start || zlen == 0 {
537                        // Produced a gap, so will need to fall back, but still try to zero as
538                        // much as possible
539                        fully_zeroed = false;
540                        if zlen == 0 {
541                            // Cannot go on
542                            break;
543                        }
544                    }
545                    aligned_start = zofs + zlen;
546                } else {
547                    // Ignore errors, just fall back
548                    fully_zeroed = false;
549                    break;
550                }
551            }
552
553            if fully_zeroed {
554                // Everything zeroed, no need to check
555                return Ok(());
556            }
557        }
558
559        // Slow path: Everything that is not zero in this layer is allocated as data and zeroes are
560        // written.  The more we zeroed in the fast path, the quicker this will be.
561        while length > 0 {
562            let (mapping, mlen) = self.inner.get_mapping(offset, length).await?;
563            let mlen = cmp::min(mlen, length);
564
565            let mapping = match mapping {
566                ShallowMapping::Raw {
567                    storage,
568                    offset,
569                    writable,
570                } => writable.then_some((storage, offset)),
571                // For already zero clusters, we don’t need to do anything
572                ShallowMapping::Zero { explicit: true } => {
573                    // Nothing to be done
574                    offset += mlen;
575                    length -= mlen;
576                    continue;
577                }
578                // For unallocated clusters, we should establish zero data
579                ShallowMapping::Zero { explicit: false }
580                | ShallowMapping::Indirect {
581                    layer: _,
582                    offset: _,
583                    writable: _,
584                } => None,
585                ShallowMapping::Eof {} => {
586                    return Err(io::ErrorKind::UnexpectedEof.into());
587                }
588                ShallowMapping::Special { offset: _ } => None,
589            };
590
591            let (file, mofs, mlen) = if let Some((file, mofs)) = mapping {
592                (file, mofs, mlen)
593            } else {
594                self.ensure_data_mapping(offset, mlen, true).await?
595            };
596
597            write_full_zeroes(file, mofs, mlen).await?;
598            offset += mlen;
599            length -= mlen;
600        }
601
602        Ok(())
603    }
604
605    /// Ensure the given range reads as zeroes.
606    ///
607    /// May use efficient zeroing for a subset of the given range, if supported by the format.
608    /// Will not discard anything, which keeps existing data mappings usable, albeit writing to
609    /// mappings that are now zeroed may have no effect.
610    ///
611    /// Check if [`FormatAccess::discard_to_zero()`] better suits your needs: It may work better on
612    /// a wider range of formats (`write_zeroes()` requires support for preallocated zero clusters,
613    /// which qcow2 does have, but other formats may not), and can actually free up space.
614    /// However, because it can break existing data mappings, it requires a mutable `self`
615    /// reference.
616    pub async fn write_zeroes(&self, mut offset: u64, length: u64) -> io::Result<()> {
617        let max_offset = offset.checked_add(length).ok_or_else(|| {
618            io::Error::new(io::ErrorKind::InvalidInput, "Write-zeroes range overflow")
619        })?;
620
621        while offset < max_offset {
622            let (zofs, zlen) = self
623                .inner
624                .ensure_zero_mapping(offset, max_offset - offset)
625                .await?;
626            if zlen == 0 {
627                break;
628            }
629            // Fill up head, i.e. the range [offset, zofs)
630            self.soft_ensure_zero(offset, zofs - offset).await?;
631            offset = zofs + zlen;
632        }
633
634        // Fill up tail, i.e. the remaining range [offset, max_offset)
635        self.soft_ensure_zero(offset, max_offset - offset).await?;
636        Ok(())
637    }
638
639    /// Discard the given range, ensure it is read back as zeroes.
640    ///
641    /// Effectively the same as [`FormatAccess::write_zeroes()`], but discard as much of the
642    /// existing allocation as possible.  This breaks existing data mappings, so needs a mutable
643    /// reference to `self`, which ensures that existing data references (which have the lifetime
644    /// of an immutable `self` reference) cannot be kept.
645    ///
646    /// Areas that cannot be discarded (because of format-inherent alignment restrictions) are
647    /// still overwritten with zeroes, unless discarding is not supported altogether.
648    pub async fn discard_to_zero(&mut self, offset: u64, length: u64) -> io::Result<()> {
649        // Safe: `&mut self` guarantees nobody has concurrent data mappings
650        unsafe { self.discard_to_zero_unsafe(offset, length).await }
651    }
652
653    /// Discard the given range, ensure it is read back as zeroes.
654    ///
655    /// Unsafe variant of [`FormatAccess::discard_to_zero()`], only requiring an immutable `&self`.
656    ///
657    /// # Safety
658    ///
659    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
660    /// all concurrently existing data mappings they have.  Note that this includes concurrent
661    /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
662    /// while they run.
663    ///
664    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
665    /// safe variant [`FormatAccess::discard_to_zero()`].
666    pub async unsafe fn discard_to_zero_unsafe(
667        &self,
668        mut offset: u64,
669        length: u64,
670    ) -> io::Result<()> {
671        let max_offset = offset.checked_add(length).ok_or_else(|| {
672            io::Error::new(
673                io::ErrorKind::InvalidInput,
674                "Discard-to-zero range overflow",
675            )
676        })?;
677
678        while offset < max_offset {
679            // Safe: Caller guarantees this is safe
680            let (zofs, zlen) = unsafe {
681                self.inner
682                    .discard_to_zero_unsafe(offset, max_offset - offset)
683                    .await?
684            };
685            if zlen == 0 {
686                break;
687            }
688            // Fill up head, i.e. the range [offset, zofs)
689            self.soft_ensure_zero(offset, zofs - offset).await?;
690            offset = zofs + zlen;
691        }
692
693        // Fill up tail, i.e. the remaining range [offset, max_offset)
694        self.soft_ensure_zero(offset, max_offset - offset).await?;
695        Ok(())
696    }
697
698    /// Discard the given range, not guaranteeing specific data on read-back.
699    ///
700    /// Discard as much of the given range as possible, and keep the rest as-is.  Does not
701    /// guarantee any specific data on read-back, in contrast to
702    /// [`FormatAccess::discard_to_zero()`].
703    ///
704    /// Discarding being unsupported by this format is still returned as an error
705    /// ([`std::io::ErrorKind::Unsupported`])
706    pub async fn discard_to_any(&mut self, offset: u64, length: u64) -> io::Result<()> {
707        unsafe { self.discard_to_any_unsafe(offset, length).await }
708    }
709
710    /// Discard the given range, not guaranteeing specific data on read-back.
711    ///
712    /// Unsafe variant of [`FormatAccess::discard_to_any()`], only requiring an immutable `&self`.
713    ///
714    /// # Safety
715    ///
716    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
717    /// all concurrently existing data mappings they have.  Note that this includes concurrent
718    /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
719    /// while they run.
720    ///
721    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
722    /// safe variant [`FormatAccess::discard_to_any()`].
723    pub async unsafe fn discard_to_any_unsafe(
724        &self,
725        mut offset: u64,
726        length: u64,
727    ) -> io::Result<()> {
728        let max_offset = offset.checked_add(length).ok_or_else(|| {
729            io::Error::new(io::ErrorKind::InvalidInput, "Discard-to-any range overflow")
730        })?;
731
732        while offset < max_offset {
733            // Safe: Caller guarantees this is safe
734            let (dofs, dlen) = unsafe {
735                self.inner
736                    .discard_to_any_unsafe(offset, max_offset - offset)
737                    .await?
738            };
739            if dlen == 0 {
740                break;
741            }
742            offset = dofs + dlen;
743        }
744
745        Ok(())
746    }
747
748    /// Discard the given range, such that the backing image becomes visible.
749    ///
750    /// Discard as much of the given range as possible so that a backing image’s data becomes
751    /// visible, and keep the rest as-is.  This breaks existing data mappings, so needs a mutable
752    /// reference to `self`, which ensures that existing data references (which have the lifetime
753    /// of an immutable `self` reference) cannot be kept.
754    pub async fn discard_to_backing(&mut self, offset: u64, length: u64) -> io::Result<()> {
755        // Safe: `&mut self` guarantees nobody has concurrent data mappings
756        unsafe { self.discard_to_backing_unsafe(offset, length).await }
757    }
758
759    /// Discard the given range, such that the backing image becomes visible.
760    ///
761    /// Unsafe variant of [`FormatAccess::discard_to_backing()`], only requiring an immutable
762    /// `&self`.
763    ///
764    /// # Safety
765    ///
766    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
767    /// all concurrently existing data mappings they have.  Note that this includes concurrent
768    /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
769    /// while they run.
770    ///
771    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
772    /// safe variant [`FormatAccess::discard_to_backing()`].
773    pub async unsafe fn discard_to_backing_unsafe(
774        &self,
775        mut offset: u64,
776        length: u64,
777    ) -> io::Result<()> {
778        let max_offset = offset.checked_add(length).ok_or_else(|| {
779            io::Error::new(
780                io::ErrorKind::InvalidInput,
781                "Discard-to-backing range overflow",
782            )
783        })?;
784
785        while offset < max_offset {
786            // Safe: Caller guarantees this is safe
787            let (dofs, dlen) = unsafe {
788                self.inner
789                    .discard_to_backing_unsafe(offset, max_offset - offset)
790                    .await?
791            };
792            if dlen == 0 {
793                break;
794            }
795            offset = dofs + dlen;
796        }
797
798        Ok(())
799    }
800
801    /// Flush internal buffers.  Always call this before drop! (except in sync mode)
802    ///
803    /// Does not necessarily sync those buffers to disk.  When using `flush()`, consider whether
804    /// you want to call `sync()` afterwards.
805    ///
806    /// In async mode, because of the current lack of stable `async_drop`, you must manually call
807    /// this before dropping a `FormatAccess` instance!  (Not necessarily for read-only images,
808    /// though.)  In sync mode, `Drop` calls this automatically.
809    ///
810    /// Note that this will not drop the buffers, so they may still be used to serve later
811    /// accesses.  Use [`FormatAccess::invalidate_cache()`] to drop all buffers.
812    pub async fn flush(&self) -> io::Result<()> {
813        self.inner.flush().await
814    }
815
816    /// Sync data already written to the storage hardware.
817    ///
818    /// This does not necessarily include flushing internal buffers, i.e. `flush`.  When using
819    /// `sync()`, consider whether you want to call `flush()` before it.
820    pub async fn sync(&self) -> io::Result<()> {
821        self.inner.sync().await
822    }
823
824    /// Drop internal buffers.
825    ///
826    /// This drops all internal buffers, but does not flush them!  All cached data is reloaded from
827    /// disk on subsequent accesses.
828    ///
829    /// # Safety
830    /// Not flushing internal buffers may cause image corruption.  You must ensure the on-disk
831    /// state is consistent.
832    pub async unsafe fn invalidate_cache(&self) -> io::Result<()> {
833        // Safety ensured by caller
834        unsafe { self.inner.invalidate_cache() }.await
835    }
836
837    /// Resize to the given size.
838    ///
839    /// Set the disk size to `new_size`.  If `new_size` is smaller than the current size, ignore
840    /// both preallocation modes and discard the data after `new_size`.
841    ///
842    /// If `new_size` is larger than the current size, `prealloc_mode` determines whether and how
843    /// the new range should be allocated; depending on the image format, is possible some
844    /// preallocation modes are not supported, in which case an [`std::io::ErrorKind::Unsupported`]
845    /// is returned.
846    ///
847    /// This may break existing data mappings, so needs a mutable reference to `self`, which
848    /// ensures that existing data references (which have the lifetime of an immutable `self`
849    /// reference) cannot be kept.
850    ///
851    /// See also [`FormatAccess::resize_grow()`] and [`FormatAccess::resize_shrink()`], whose more
852    /// specialized interface may be useful when you know whether you want to grow or shrink the
853    /// image.
854    pub async fn resize(
855        &mut self,
856        new_size: u64,
857        prealloc_mode: PreallocateMode,
858    ) -> io::Result<()> {
859        match new_size.cmp(&self.size()) {
860            std::cmp::Ordering::Less => self.resize_shrink(new_size).await,
861            std::cmp::Ordering::Equal => Ok(()),
862            std::cmp::Ordering::Greater => self.resize_grow(new_size, prealloc_mode).await,
863        }
864    }
865
866    /// Resize to the given size, which must be greater than the current size.
867    ///
868    /// Set the disk size to `new_size`, preallocating the new space according to `prealloc_mode`.
869    /// Depending on the image format, it is possible some preallocation modes are not supported,
870    /// in which case an [`std::io::ErrorKind::Unsupported`] is returned.
871    ///
872    /// If the current size is already `new_size` or greater, do nothing.
873    pub async fn resize_grow(
874        &self,
875        new_size: u64,
876        prealloc_mode: PreallocateMode,
877    ) -> io::Result<()> {
878        self.inner.resize_grow(new_size, prealloc_mode).await
879    }
880
881    /// Truncate to the given size, which must be smaller than the current size.
882    ///
883    /// Set the disk size to `new_size`, discarding the data after `new_size`.
884    ///
885    /// May break existing data mappings thanks to the mutable `self` reference.
886    ///
887    /// If the current size is already `new_size` or smaller, do nothing.
888    pub async fn resize_shrink(&mut self, new_size: u64) -> io::Result<()> {
889        self.inner.resize_shrink(new_size).await
890    }
891}
892
893impl<S: Storage> Mapping<'_, S> {
894    /// Return `true` if and only if this mapping signifies the end of file.
895    pub fn is_eof(&self) -> bool {
896        matches!(self, Mapping::Eof {})
897    }
898}
899
900impl<S: Storage> Display for FormatAccess<S> {
901    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
902        self.inner.fmt(f)
903    }
904}
905
906impl<S: Storage> Display for Mapping<'_, S> {
907    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
908        match self {
909            Mapping::Raw {
910                storage,
911                offset,
912                writable,
913            } => {
914                let writable = if *writable { "rw" } else { "ro" };
915                write!(f, "{storage}:0x{offset:x}/{writable}")
916            }
917
918            Mapping::Zero { explicit } => {
919                let explicit = if *explicit { "explicit" } else { "unallocated" };
920                write!(f, "<zero:{explicit}>")
921            }
922
923            Mapping::Eof {} => write!(f, "<eof>"),
924
925            Mapping::Special { layer, offset } => {
926                write!(f, "<special:{layer}:0x{offset:x}>")
927            }
928        }
929    }
930}
931
932#[cfg(feature = "sync")]
933impl<S: Storage> Drop for FormatAccess<S> {
934    fn drop(&mut self) {
935        if let Err(err) = self.flush() {
936            let inner = &self.inner;
937            tracing::error!("Failed to flush {inner}: {err}");
938        }
939    }
940}
941
942/*
943#[cfg(feature = "async-drop")]
944impl<S: Storage> std::future::AsyncDrop for FormatAccess<S> {
945    type Dropper<'a> = std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 'a>> where S: 'a;
946
947    fn async_drop(self: std::pin::Pin<&mut Self>) -> Self::Dropper<'_> {
948        Box::pin(async move {
949            if let Err(err) = self.flush().await {
950                let inner = &self.inner;
951                tracing::error!("Failed to flush {inner}: {err}");
952            }
953        })
954    }
955}
956*/