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, PreparedDataMapping, 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            Mapping::Special { layer, offset } => {
214                // Concurrent writes may have changed the mapping since we looked it up, so the
215                // driver may find that (some tail of) this range is no longer special. It then
216                // returns that tail (unread), which we read according to its current mapping.
217                let Some((tail, tail_offset)) = layer.inner.try_readv_special(bufv, offset).await?
218                else {
219                    return Ok(());
220                };
221
222                // Recursion, so we need to `Box::pin()` in async mode (and `return` is needed
223                // because `#[cfg()]` requires full statements ending in `;`).
224                #[cfg(feature = "async")]
225                return Box::pin(layer.readv(tail, tail_offset)).await;
226                #[cfg(feature = "sync")]
227                return layer.readv(tail, tail_offset);
228            }
229        }
230    }
231
232    /// Return the shallow mapping at `offset`.
233    ///
234    /// Find what `offset` is mapped to, which may be another format layer, return that
235    /// information, and the length of the continuous mapping (from `offset`).
236    ///
237    /// Use [`FormatAccess::get_mapping()`] to recursively fully resolve references to other format
238    /// layers.
239    pub async fn get_shallow_mapping(
240        &self,
241        offset: u64,
242        max_length: u64,
243    ) -> io::Result<(ShallowMapping<'_, S>, u64)> {
244        self.inner
245            .get_mapping(offset, max_length)
246            .await
247            .map(|(m, l)| (m, cmp::min(l, max_length)))
248    }
249
250    /// Return the recursively resolved mapping at `offset`.
251    ///
252    /// Find what `offset` is mapped to, return that mapping information, and the length of that
253    /// continuous mapping (from `offset`).
254    ///
255    /// All data references to other format layers are automatically resolved (recursively), so
256    /// that the result are more “trivial” mappings (unless prevented by special mappings like
257    /// compressed clusters).
258    pub async fn get_mapping(
259        &self,
260        mut offset: u64,
261        mut max_length: u64,
262    ) -> io::Result<(Mapping<'_, S>, u64)> {
263        let mut format_layer = self;
264        let mut writable_gate = true;
265
266        loop {
267            let (mapping, length) = format_layer.get_shallow_mapping(offset, max_length).await?;
268
269            match mapping {
270                ShallowMapping::Raw {
271                    storage,
272                    offset,
273                    writable,
274                } => {
275                    return Ok((
276                        Mapping::Raw {
277                            storage,
278                            offset,
279                            writable: writable && writable_gate,
280                        },
281                        length,
282                    ))
283                }
284
285                ShallowMapping::Indirect {
286                    layer: recurse_layer,
287                    offset: recurse_offset,
288                    writable: recurse_writable,
289                } => {
290                    format_layer = recurse_layer;
291                    offset = recurse_offset;
292                    writable_gate = writable_gate && recurse_writable;
293                    max_length = length;
294                }
295
296                ShallowMapping::Zero { explicit } => {
297                    // If this is not the top layer, always clear `explicit`
298                    return if explicit && ptr::eq(format_layer, self) {
299                        Ok((Mapping::Zero { explicit: true }, length))
300                    } else {
301                        Ok((Mapping::Zero { explicit: false }, length))
302                    };
303                }
304
305                ShallowMapping::Eof {} => {
306                    // Return EOF only on top layer, zero otherwise
307                    return if ptr::eq(format_layer, self) {
308                        Ok((Mapping::Eof {}, 0))
309                    } else {
310                        Ok((Mapping::Zero { explicit: false }, max_length))
311                    };
312                }
313
314                ShallowMapping::Special { offset } => {
315                    return Ok((
316                        Mapping::Special {
317                            layer: format_layer,
318                            offset,
319                        },
320                        length,
321                    ));
322                }
323            }
324        }
325    }
326
327    /// Create a raw data mapping at `offset`.
328    ///
329    /// Ensure that `offset` is directly mapped to some storage object, up to a length of `length`.
330    /// Return the storage object, the corresponding offset there, and the continuous length that
331    /// we were able to map (less than or equal to `length`).
332    ///
333    /// If `overwrite` is true, the contents in the range are supposed to be overwritten and may be
334    /// discarded.  Otherwise, they are kept.
335    ///
336    /// Reaching the end of file before the end of the range results in an error.
337    ///
338    /// NOTE: Will be deprecated in imago 0.3 in favor of [`FormatAccess::prepare_data_mapping()`]
339    /// and [`FormatAccess::commit_data_mapping()`].
340    pub async fn ensure_data_mapping(
341        &self,
342        offset: u64,
343        length: u64,
344        overwrite: bool,
345    ) -> io::Result<(&S, u64, u64)> {
346        self.check_disk_bounds(offset, length, "allocate")?;
347
348        let (storage, mapped_offset, mapped_length) = self
349            .inner
350            .ensure_data_mapping(offset, length, overwrite)
351            .await?;
352        let mapped_length = cmp::min(length, mapped_length);
353        assert!(length == 0 || mapped_length > 0);
354        Ok((storage, mapped_offset, mapped_length))
355    }
356
357    /// Prepare a raw data mapping at `offset`.
358    ///
359    /// Ensure that `offset` is directly mapped to some storage object, up to a length of `length`.
360    ///
361    /// This mapping is not immediately visible (unless it exists already); the returned
362    /// [`PreparedDataMapping`] says where to write the data for the range (storage object, offset
363    /// there, and the continuous length that was mapped, which is less than or equal to `length`).
364    /// Once that data has been written, call [`FormatAccess::commit_data_mapping()`] to make the
365    /// mapping actually visible.
366    ///
367    /// Until committed, the range keeps reading as before.  Dropping the returned object without
368    /// committing it discards the prepared mapping.
369    ///
370    /// Until the returned object is committed or dropped, any other I/O on the format layer may be
371    /// blocked.  In particular, you should not prepare any other mapping before that, unless you
372    /// can handle those calls being blocked.
373    ///
374    /// If `overwrite` is true, the contents in the range are supposed to be overwritten and may be
375    /// discarded.  Otherwise, they are kept.
376    ///
377    /// Reaching the end of file before the end of the range results in an error.
378    pub async fn prepare_data_mapping(
379        &self,
380        offset: u64,
381        length: u64,
382        overwrite: bool,
383    ) -> io::Result<PreparedDataMapping<'_, S>> {
384        self.check_disk_bounds(offset, length, "allocate")?;
385
386        let mut mapping = self
387            .inner
388            .prepare_data_mapping(offset, length, overwrite)
389            .await?;
390        mapping.limit_length(length);
391        assert!(length == 0 || mapping.length() > 0);
392        Ok(mapping)
393    }
394
395    /// Make a mapping prepared by [`FormatAccess::prepare_data_mapping()`] visible.
396    ///
397    /// Call this once the data has been written to the location given by `mapping`.  `mapping`
398    /// must have been returned by `prepare_data_mapping()` on this same object.
399    pub async fn commit_data_mapping(&self, mapping: PreparedDataMapping<'_, S>) -> io::Result<()> {
400        self.inner.commit_data_mapping(mapping).await
401    }
402
403    /// Read data at `offset` into `bufv`.
404    ///
405    /// Reads until `bufv` is filled completely, i.e. will not do short reads.  When reaching the
406    /// end of file, the rest of `bufv` is filled with 0.
407    pub async fn readv(&self, mut bufv: IoVectorMut<'_>, mut offset: u64) -> io::Result<()> {
408        #[cfg(feature = "async")]
409        let mut workers = (self.read_parallelization > 1).then(FuturesUnordered::new);
410
411        while !bufv.is_empty() {
412            let (mapping, chunk_length) = self.get_mapping(offset, bufv.len()).await?;
413            if chunk_length == 0 {
414                assert!(mapping.is_eof());
415                bufv.fill(0);
416                break;
417            }
418
419            #[cfg(feature = "async")]
420            if let Some(workers) = workers.as_mut() {
421                while workers.len() >= self.read_parallelization {
422                    workers.next().await.unwrap()?;
423                }
424            }
425
426            let (chunk, remainder) = bufv.split_at(chunk_length);
427            bufv = remainder;
428            offset += chunk_length;
429
430            #[cfg(feature = "async")]
431            if let Some(workers) = workers.as_mut() {
432                workers.push(self.read_chunk(chunk, mapping));
433            } else {
434                self.read_chunk(chunk, mapping).await?;
435            }
436            #[cfg(feature = "sync")]
437            self.read_chunk(chunk, mapping)?;
438        }
439
440        #[cfg(feature = "async")]
441        if let Some(mut workers) = workers {
442            while workers.next().await.transpose()?.is_some() {}
443        }
444
445        Ok(())
446    }
447
448    /// Read data at `offset` into `buf`.
449    ///
450    /// Reads until `buf` is filled completely, i.e. will not do short reads.  When reaching the
451    /// end of file, the rest of `buf` is filled with 0.
452    pub async fn read<'a>(
453        &'a self,
454        buf: impl Into<IoVectorMut<'a>>,
455        offset: u64,
456    ) -> io::Result<()> {
457        self.readv(buf.into(), offset).await
458    }
459
460    /// Check whether `offset + length` is within the disk size.
461    fn check_disk_bounds(&self, offset: u64, length: u64, req: &str) -> io::Result<()> {
462        let size = self.inner.size();
463        if offset <= size && length <= size - offset {
464            Ok(())
465        } else {
466            Err(io::Error::new(
467                io::ErrorKind::UnexpectedEof,
468                format!("Cannot {req} beyond the disk size ({length} + {offset} > {size})"),
469            ))
470        }
471    }
472
473    /// Write data from `bufv` to `offset`.
474    ///
475    /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes.  Reaching
476    /// the end of file before the end of the buffer results in an error.
477    pub async fn writev(&self, mut bufv: IoVector<'_>, mut offset: u64) -> io::Result<()> {
478        if !self.writable {
479            return Err(io::Error::other("Image is read-only"));
480        }
481
482        self.check_disk_bounds(offset, bufv.len(), "write")?;
483
484        #[cfg(feature = "async")]
485        let mut workers = (self.write_parallelization > 1).then(FuturesUnordered::new);
486
487        while !bufv.is_empty() {
488            let mapping = self.prepare_data_mapping(offset, bufv.len(), true).await?;
489            let storage = mapping.storage();
490            let st_offset = mapping.offset();
491            let st_length = mapping.length();
492
493            let (chunk, remainder) = bufv.split_at(st_length);
494            bufv = remainder;
495            offset += st_length;
496
497            #[cfg(feature = "async")]
498            if let Some(workers) = workers.as_mut() {
499                if mapping.is_nonblocking() {
500                    // We can only parallelize writes with non-blocking mappings.
501
502                    while workers.len() >= self.write_parallelization {
503                        workers.next().await.unwrap()?;
504                    }
505
506                    workers.push(async move {
507                        storage.writev(chunk, st_offset).await?;
508                        self.commit_data_mapping(mapping).await
509                    });
510
511                    continue;
512                }
513
514                // In case we have a blocking mapping, we need to drain the ongoing requests before
515                // going on, because they would not be polled while we await our write.  If one of
516                // them is in the middle of a write and holds a write blocker that intersects with
517                // ours, we would wait for it forever.
518                while workers.next().await.transpose()?.is_some() {}
519            }
520
521            storage.writev(chunk, st_offset).await?;
522            self.commit_data_mapping(mapping).await?;
523        }
524
525        #[cfg(feature = "async")]
526        if let Some(mut workers) = workers {
527            while workers.next().await.transpose()?.is_some() {}
528        }
529
530        Ok(())
531    }
532
533    /// Write data from `buf` to `offset`.
534    ///
535    /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes.  Reaching
536    /// the end of file before the end of the buffer results in an error.
537    pub async fn write<'a>(&'a self, buf: impl Into<IoVector<'a>>, offset: u64) -> io::Result<()> {
538        self.writev(buf.into(), offset).await
539    }
540
541    /// Check whether the given range is zero.
542    ///
543    /// Checks for zero mappings, not zero data (although this might be changed in the future).
544    ///
545    /// Errors are treated as non-zero areas.
546    async fn is_range_zero(&self, mut offset: u64, mut length: u64) -> bool {
547        while length > 0 {
548            match self.get_mapping(offset, length).await {
549                Ok((Mapping::Zero { explicit: _ }, mlen)) => {
550                    offset += mlen;
551                    length -= mlen;
552                }
553                _ => return false,
554            };
555        }
556
557        true
558    }
559
560    /// Ensure the given range reads as zeroes, without write-zeroes support.
561    ///
562    /// Does not require support for efficient zeroing, instead writing zeroes when the range is
563    /// not zero yet.  If `allocate` is true, areas that are not currently allocated will be
564    /// allocated to write zeroes there; if it is false, unallocated areas that currently read as
565    /// zero are left alone.
566    ///
567    /// However, can still use efficient zero support if present.
568    ///
569    /// The main use case is to handle unaligned zero requests.  Quite inefficient for large areas.
570    async fn soft_ensure_zero(&self, mut offset: u64, mut length: u64) -> io::Result<()> {
571        // “Fast” path: Try to efficiently zero as much as possible
572        if let Some(gran) = self.inner.zero_granularity() {
573            let end = offset.checked_add(length).ok_or_else(|| {
574                io::Error::new(
575                    io::ErrorKind::InvalidInput,
576                    format!("Write-zero wrap-around: {offset} + {length}"),
577                )
578            })?;
579            let mut aligned_start = offset - offset % gran;
580            // Could be handled, but don’t bother
581            let mut aligned_end = end.checked_next_multiple_of(gran).ok_or_else(|| {
582                io::Error::new(
583                    io::ErrorKind::InvalidInput,
584                    "Write-zero wrap-around at cluster granularity",
585                )
586            })?;
587
588            aligned_end = cmp::min(aligned_end, self.size());
589
590            // Whether the whole area could be efficiently zeroed
591            let mut fully_zeroed = true;
592
593            if offset > aligned_start
594                && !self
595                    .is_range_zero(aligned_start, offset - aligned_start)
596                    .await
597            {
598                // Non-zero head, we cannot zero that cluster.  Still try to zero as much as
599                // possible.
600                fully_zeroed = false;
601                aligned_start += gran;
602            }
603            if end < aligned_end && !self.is_range_zero(end, aligned_end - end).await {
604                // Non-zero tail, we cannot zero that cluster.  Still try to zero as much as
605                // possible.
606                fully_zeroed = false;
607                aligned_end -= gran;
608            }
609
610            while aligned_start < aligned_end {
611                let res = self
612                    .inner
613                    .ensure_zero_mapping(aligned_start, aligned_end - aligned_start)
614                    .await;
615                if let Ok((zofs, zlen)) = res {
616                    if zofs != aligned_start || zlen == 0 {
617                        // Produced a gap, so will need to fall back, but still try to zero as
618                        // much as possible
619                        fully_zeroed = false;
620                        if zlen == 0 {
621                            // Cannot go on
622                            break;
623                        }
624                    }
625                    aligned_start = zofs + zlen;
626                } else {
627                    // Ignore errors, just fall back
628                    fully_zeroed = false;
629                    break;
630                }
631            }
632
633            if fully_zeroed {
634                // Everything zeroed, no need to check
635                return Ok(());
636            }
637        }
638
639        // Slow path: Everything that is not zero in this layer is allocated as data and zeroes are
640        // written.  The more we zeroed in the fast path, the quicker this will be.
641        while length > 0 {
642            let (mapping, mlen) = self.inner.get_mapping(offset, length).await?;
643            let mlen = cmp::min(mlen, length);
644
645            let mapping = match mapping {
646                ShallowMapping::Raw {
647                    storage,
648                    offset,
649                    writable,
650                } => writable.then_some((storage, offset)),
651                // For already zero clusters, we don’t need to do anything
652                ShallowMapping::Zero { explicit: true } => {
653                    // Nothing to be done
654                    offset += mlen;
655                    length -= mlen;
656                    continue;
657                }
658                // For unallocated clusters, we should establish zero data
659                ShallowMapping::Zero { explicit: false }
660                | ShallowMapping::Indirect {
661                    layer: _,
662                    offset: _,
663                    writable: _,
664                } => None,
665                ShallowMapping::Eof {} => {
666                    return Err(io::ErrorKind::UnexpectedEof.into());
667                }
668                ShallowMapping::Special { offset: _ } => None,
669            };
670
671            let mlen = if let Some((file, mofs)) = mapping {
672                write_full_zeroes(file, mofs, mlen).await?;
673                mlen
674            } else {
675                // Need a new mapping to write zeroes into
676                let mapping = self.prepare_data_mapping(offset, mlen, true).await?;
677                let mlen = mapping.length();
678                write_full_zeroes(mapping.storage(), mapping.offset(), mlen).await?;
679                self.commit_data_mapping(mapping).await?;
680                mlen
681            };
682
683            offset += mlen;
684            length -= mlen;
685        }
686
687        Ok(())
688    }
689
690    /// Ensure the given range reads as zeroes.
691    ///
692    /// May use efficient zeroing for a subset of the given range, if supported by the format.
693    /// Will not discard anything, which keeps existing data mappings usable, albeit writing to
694    /// mappings that are now zeroed may have no effect.
695    ///
696    /// Reaching the end of file before the end of the range results in an error.
697    ///
698    /// Check if [`FormatAccess::discard_to_zero()`] better suits your needs: It may work better on
699    /// a wider range of formats (`write_zeroes()` requires support for preallocated zero clusters,
700    /// which qcow2 does have, but other formats may not), and can actually free up space.
701    /// However, because it can break existing data mappings, it requires a mutable `self`
702    /// reference.
703    pub async fn write_zeroes(&self, mut offset: u64, length: u64) -> io::Result<()> {
704        let max_offset = offset.checked_add(length).ok_or_else(|| {
705            io::Error::new(io::ErrorKind::InvalidInput, "Write-zeroes range overflow")
706        })?;
707        self.check_disk_bounds(offset, length, "write zeroes")?;
708
709        while offset < max_offset {
710            let (zofs, zlen) = self
711                .inner
712                .ensure_zero_mapping(offset, max_offset - offset)
713                .await?;
714            if zlen == 0 {
715                break;
716            }
717            // Fill up head, i.e. the range [offset, zofs)
718            self.soft_ensure_zero(offset, zofs - offset).await?;
719            offset = zofs + zlen;
720        }
721
722        // Fill up tail, i.e. the remaining range [offset, max_offset)
723        self.soft_ensure_zero(offset, max_offset - offset).await?;
724        Ok(())
725    }
726
727    /// Discard the given range, ensure it is read back as zeroes.
728    ///
729    /// Effectively the same as [`FormatAccess::write_zeroes()`], but discard as much of the
730    /// existing allocation as possible.  This breaks existing data mappings, so needs a mutable
731    /// reference to `self`, which ensures that existing data references (which have the lifetime
732    /// of an immutable `self` reference) cannot be kept.
733    ///
734    /// Areas that cannot be discarded (because of format-inherent alignment restrictions) are
735    /// still overwritten with zeroes, unless discarding is not supported altogether.
736    ///
737    /// Reaching the end of file before the end of the range results in an error.
738    pub async fn discard_to_zero(&mut self, offset: u64, length: u64) -> io::Result<()> {
739        // Safe: `&mut self` guarantees nobody has concurrent data mappings
740        unsafe { self.discard_to_zero_unsafe(offset, length).await }
741    }
742
743    /// Discard the given range, ensure it is read back as zeroes.
744    ///
745    /// Unsafe variant of [`FormatAccess::discard_to_zero()`], only requiring an immutable `&self`.
746    ///
747    /// # Safety
748    ///
749    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
750    /// all concurrently existing data mappings they have.  Note that this includes concurrent
751    /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
752    /// while they run.
753    ///
754    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
755    /// safe variant [`FormatAccess::discard_to_zero()`].
756    pub async unsafe fn discard_to_zero_unsafe(
757        &self,
758        mut offset: u64,
759        length: u64,
760    ) -> io::Result<()> {
761        let max_offset = offset.checked_add(length).ok_or_else(|| {
762            io::Error::new(
763                io::ErrorKind::InvalidInput,
764                "Discard-to-zero range overflow",
765            )
766        })?;
767        self.check_disk_bounds(offset, length, "discard")?;
768
769        while offset < max_offset {
770            // Safe: Caller guarantees this is safe
771            let (zofs, zlen) = unsafe {
772                self.inner
773                    .discard_to_zero_unsafe(offset, max_offset - offset)
774                    .await?
775            };
776            if zlen == 0 {
777                break;
778            }
779            // Fill up head, i.e. the range [offset, zofs)
780            self.soft_ensure_zero(offset, zofs - offset).await?;
781            offset = zofs + zlen;
782        }
783
784        // Fill up tail, i.e. the remaining range [offset, max_offset)
785        self.soft_ensure_zero(offset, max_offset - offset).await?;
786        Ok(())
787    }
788
789    /// Discard the given range, not guaranteeing specific data on read-back.
790    ///
791    /// Discard as much of the given range as possible, and keep the rest as-is.  Does not
792    /// guarantee any specific data on read-back, in contrast to
793    /// [`FormatAccess::discard_to_zero()`].
794    ///
795    /// Discarding being unsupported by this format is still returned as an error
796    /// ([`std::io::ErrorKind::Unsupported`])
797    ///
798    /// Reaching the end of file before the end of the range results in an error.
799    pub async fn discard_to_any(&mut self, offset: u64, length: u64) -> io::Result<()> {
800        unsafe { self.discard_to_any_unsafe(offset, length).await }
801    }
802
803    /// Discard the given range, not guaranteeing specific data on read-back.
804    ///
805    /// Unsafe variant of [`FormatAccess::discard_to_any()`], only requiring an immutable `&self`.
806    ///
807    /// # Safety
808    ///
809    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
810    /// all concurrently existing data mappings they have.  Note that this includes concurrent
811    /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
812    /// while they run.
813    ///
814    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
815    /// safe variant [`FormatAccess::discard_to_any()`].
816    pub async unsafe fn discard_to_any_unsafe(
817        &self,
818        mut offset: u64,
819        length: u64,
820    ) -> io::Result<()> {
821        let max_offset = offset.checked_add(length).ok_or_else(|| {
822            io::Error::new(io::ErrorKind::InvalidInput, "Discard-to-any range overflow")
823        })?;
824        self.check_disk_bounds(offset, length, "discard")?;
825
826        while offset < max_offset {
827            // Safe: Caller guarantees this is safe
828            let (dofs, dlen) = unsafe {
829                self.inner
830                    .discard_to_any_unsafe(offset, max_offset - offset)
831                    .await?
832            };
833            if dlen == 0 {
834                break;
835            }
836            offset = dofs + dlen;
837        }
838
839        Ok(())
840    }
841
842    /// Discard the given range, such that the backing image becomes visible.
843    ///
844    /// Discard as much of the given range as possible so that a backing image’s data becomes
845    /// visible, and keep the rest as-is.  This breaks existing data mappings, so needs a mutable
846    /// reference to `self`, which ensures that existing data references (which have the lifetime
847    /// of an immutable `self` reference) cannot be kept.
848    ///
849    /// Reaching the end of file before the end of the range results in an error.
850    pub async fn discard_to_backing(&mut self, offset: u64, length: u64) -> io::Result<()> {
851        // Safe: `&mut self` guarantees nobody has concurrent data mappings
852        unsafe { self.discard_to_backing_unsafe(offset, length).await }
853    }
854
855    /// Discard the given range, such that the backing image becomes visible.
856    ///
857    /// Unsafe variant of [`FormatAccess::discard_to_backing()`], only requiring an immutable
858    /// `&self`.
859    ///
860    /// # Safety
861    ///
862    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
863    /// all concurrently existing data mappings they have.  Note that this includes concurrent
864    /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
865    /// while they run.
866    ///
867    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
868    /// safe variant [`FormatAccess::discard_to_backing()`].
869    pub async unsafe fn discard_to_backing_unsafe(
870        &self,
871        mut offset: u64,
872        length: u64,
873    ) -> io::Result<()> {
874        let max_offset = offset.checked_add(length).ok_or_else(|| {
875            io::Error::new(
876                io::ErrorKind::InvalidInput,
877                "Discard-to-backing range overflow",
878            )
879        })?;
880        self.check_disk_bounds(offset, length, "discard")?;
881
882        while offset < max_offset {
883            // Safe: Caller guarantees this is safe
884            let (dofs, dlen) = unsafe {
885                self.inner
886                    .discard_to_backing_unsafe(offset, max_offset - offset)
887                    .await?
888            };
889            if dlen == 0 {
890                break;
891            }
892            offset = dofs + dlen;
893        }
894
895        Ok(())
896    }
897
898    /// Flush internal buffers.  Always call this before drop! (except in sync mode)
899    ///
900    /// Does not necessarily sync those buffers to disk.  When using `flush()`, consider whether
901    /// you want to call `sync()` afterwards.
902    ///
903    /// In async mode, because of the current lack of stable `async_drop`, you must manually call
904    /// this before dropping a `FormatAccess` instance!  (Not necessarily for read-only images,
905    /// though.)  In sync mode, `Drop` calls this automatically.
906    ///
907    /// Note that this will not drop the buffers, so they may still be used to serve later
908    /// accesses.  Use [`FormatAccess::invalidate_cache()`] to drop all buffers.
909    pub async fn flush(&self) -> io::Result<()> {
910        self.inner.flush().await
911    }
912
913    /// Sync data already written to the storage hardware.
914    ///
915    /// This does not necessarily include flushing internal buffers, i.e. `flush`.  When using
916    /// `sync()`, consider whether you want to call `flush()` before it.
917    pub async fn sync(&self) -> io::Result<()> {
918        self.inner.sync().await
919    }
920
921    /// Drop internal buffers.
922    ///
923    /// This drops all internal buffers, but does not flush them!  All cached data is reloaded from
924    /// disk on subsequent accesses.
925    ///
926    /// # Safety
927    /// Not flushing internal buffers may cause image corruption.  You must ensure the on-disk
928    /// state is consistent.
929    pub async unsafe fn invalidate_cache(&self) -> io::Result<()> {
930        // Safety ensured by caller
931        unsafe { self.inner.invalidate_cache() }.await
932    }
933
934    /// Resize to the given size.
935    ///
936    /// Set the disk size to `new_size`.  If `new_size` is smaller than the current size, ignore
937    /// both preallocation modes and discard the data after `new_size`.
938    ///
939    /// If `new_size` is larger than the current size, `prealloc_mode` determines whether and how
940    /// the new range should be allocated; depending on the image format, is possible some
941    /// preallocation modes are not supported, in which case an [`std::io::ErrorKind::Unsupported`]
942    /// is returned.
943    ///
944    /// This may break existing data mappings, so needs a mutable reference to `self`, which
945    /// ensures that existing data references (which have the lifetime of an immutable `self`
946    /// reference) cannot be kept.
947    ///
948    /// See also [`FormatAccess::resize_grow()`] and [`FormatAccess::resize_shrink()`], whose more
949    /// specialized interface may be useful when you know whether you want to grow or shrink the
950    /// image.
951    pub async fn resize(
952        &mut self,
953        new_size: u64,
954        prealloc_mode: PreallocateMode,
955    ) -> io::Result<()> {
956        match new_size.cmp(&self.size()) {
957            std::cmp::Ordering::Less => self.resize_shrink(new_size).await,
958            std::cmp::Ordering::Equal => Ok(()),
959            std::cmp::Ordering::Greater => self.resize_grow(new_size, prealloc_mode).await,
960        }
961    }
962
963    /// Resize to the given size, which must be greater than the current size.
964    ///
965    /// Set the disk size to `new_size`, preallocating the new space according to `prealloc_mode`.
966    /// Depending on the image format, it is possible some preallocation modes are not supported,
967    /// in which case an [`std::io::ErrorKind::Unsupported`] is returned.
968    ///
969    /// If the current size is already `new_size` or greater, do nothing.
970    pub async fn resize_grow(
971        &self,
972        new_size: u64,
973        prealloc_mode: PreallocateMode,
974    ) -> io::Result<()> {
975        self.inner.resize_grow(new_size, prealloc_mode).await
976    }
977
978    /// Truncate to the given size, which must be smaller than the current size.
979    ///
980    /// Set the disk size to `new_size`, discarding the data after `new_size`.
981    ///
982    /// May break existing data mappings thanks to the mutable `self` reference.
983    ///
984    /// If the current size is already `new_size` or smaller, do nothing.
985    pub async fn resize_shrink(&mut self, new_size: u64) -> io::Result<()> {
986        self.inner.resize_shrink(new_size).await
987    }
988}
989
990impl<S: Storage> Mapping<'_, S> {
991    /// Return `true` if and only if this mapping signifies the end of file.
992    pub fn is_eof(&self) -> bool {
993        matches!(self, Mapping::Eof {})
994    }
995}
996
997impl<S: Storage> Display for FormatAccess<S> {
998    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
999        self.inner.fmt(f)
1000    }
1001}
1002
1003impl<S: Storage> Display for Mapping<'_, S> {
1004    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1005        match self {
1006            Mapping::Raw {
1007                storage,
1008                offset,
1009                writable,
1010            } => {
1011                let writable = if *writable { "rw" } else { "ro" };
1012                write!(f, "{storage}:0x{offset:x}/{writable}")
1013            }
1014
1015            Mapping::Zero { explicit } => {
1016                let explicit = if *explicit { "explicit" } else { "unallocated" };
1017                write!(f, "<zero:{explicit}>")
1018            }
1019
1020            Mapping::Eof {} => write!(f, "<eof>"),
1021
1022            Mapping::Special { layer, offset } => {
1023                write!(f, "<special:{layer}:0x{offset:x}>")
1024            }
1025        }
1026    }
1027}
1028
1029#[cfg(feature = "sync")]
1030impl<S: Storage> Drop for FormatAccess<S> {
1031    fn drop(&mut self) {
1032        if let Err(err) = self.flush() {
1033            let inner = &self.inner;
1034            tracing::error!("Failed to flush {inner}: {err}");
1035        }
1036    }
1037}
1038
1039/*
1040#[cfg(feature = "async-drop")]
1041impl<S: Storage> std::future::AsyncDrop for FormatAccess<S> {
1042    type Dropper<'a> = std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 'a>> where S: 'a;
1043
1044    fn async_drop(self: std::pin::Pin<&mut Self>) -> Self::Dropper<'_> {
1045        Box::pin(async move {
1046            if let Err(err) = self.flush().await {
1047                let inner = &self.inner;
1048                tracing::error!("Failed to flush {inner}: {err}");
1049            }
1050        })
1051    }
1052}
1053*/
1054
1055#[cfg(test)]
1056mod tests {
1057    use super::*;
1058    use crate::format::Format;
1059    use crate::null::Null;
1060    use crate::raw::Raw;
1061
1062    /// Driver that maps everything to another layer, with a configurable `writable` flag.
1063    #[derive(Debug)]
1064    struct IndirectDriver {
1065        /// Layer to map to.
1066        layer: FormatAccess<Null>,
1067
1068        /// `writable` flag for all mappings.
1069        writable: bool,
1070    }
1071
1072    impl Display for IndirectDriver {
1073        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1074            write!(f, "indirect[{}]", self.layer.inner())
1075        }
1076    }
1077
1078    #[maybe_async(?Send)]
1079    impl FormatDriverInstance for IndirectDriver {
1080        type Storage = Null;
1081
1082        fn format(&self) -> Format {
1083            Format::Raw
1084        }
1085
1086        async unsafe fn probe(_storage: &Null) -> io::Result<bool>
1087        where
1088            Self: Sized,
1089        {
1090            Ok(false)
1091        }
1092
1093        fn size(&self) -> u64 {
1094            self.layer.size()
1095        }
1096
1097        fn collect_storage_dependencies(&self) -> Vec<&Null> {
1098            self.layer.collect_storage_dependencies()
1099        }
1100
1101        fn writable(&self) -> bool {
1102            false
1103        }
1104
1105        // Elidable in sync, but async needs a named lifetime for the boxed future bound
1106        #[allow(clippy::needless_lifetimes)]
1107        async fn get_mapping<'a>(
1108            &'a self,
1109            offset: u64,
1110            max_length: u64,
1111        ) -> io::Result<(ShallowMapping<'a, Null>, u64)> {
1112            let remaining = self.size().saturating_sub(offset);
1113            if remaining == 0 {
1114                return Ok((ShallowMapping::Eof {}, 0));
1115            }
1116
1117            Ok((
1118                ShallowMapping::Indirect {
1119                    layer: &self.layer,
1120                    offset,
1121                    writable: self.writable,
1122                },
1123                cmp::min(max_length, remaining),
1124            ))
1125        }
1126
1127        // Elidable in sync, but async needs a named lifetime for the boxed future bound
1128        #[allow(clippy::needless_lifetimes)]
1129        async fn ensure_data_mapping<'a>(
1130            &'a self,
1131            _offset: u64,
1132            _length: u64,
1133            _overwrite: bool,
1134        ) -> io::Result<(&'a Null, u64, u64)> {
1135            Err(io::ErrorKind::Unsupported.into())
1136        }
1137
1138        async fn flush(&self) -> io::Result<()> {
1139            Ok(())
1140        }
1141
1142        async fn sync(&self) -> io::Result<()> {
1143            Ok(())
1144        }
1145
1146        async unsafe fn invalidate_cache(&self) -> io::Result<()> {
1147            Ok(())
1148        }
1149
1150        async fn resize_grow(
1151            &self,
1152            _new_size: u64,
1153            _prealloc_mode: PreallocateMode,
1154        ) -> io::Result<()> {
1155            Err(io::ErrorKind::Unsupported.into())
1156        }
1157
1158        async fn resize_shrink(&mut self, _new_size: u64) -> io::Result<()> {
1159            Err(io::ErrorKind::Unsupported.into())
1160        }
1161    }
1162
1163    /// A mapping reached through a non-writable indirect mapping must not be writable, even if a
1164    /// later indirect mapping is writable.
1165    #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
1166    async fn test_indirect_mapping_writable() {
1167        let raw = Raw::open_image(Null::new(1024), true).await.unwrap();
1168        let middle = FormatAccess::new(IndirectDriver {
1169            layer: FormatAccess::new(raw),
1170            writable: true,
1171        });
1172
1173        // Only writable hops: writable
1174        let (mapping, _) = middle.get_mapping(0, 512).await.unwrap();
1175        assert!(
1176            matches!(mapping, Mapping::Raw { writable: true, .. }),
1177            "{mapping:?}"
1178        );
1179
1180        let top = FormatAccess::new(IndirectDriver {
1181            layer: middle,
1182            writable: false,
1183        });
1184
1185        // Non-writable hop first, then a writable one: not writable
1186        let (mapping, _) = top.get_mapping(0, 512).await.unwrap();
1187        assert!(
1188            matches!(
1189                mapping,
1190                Mapping::Raw {
1191                    writable: false,
1192                    ..
1193                }
1194            ),
1195            "{mapping:?}"
1196        );
1197    }
1198}