Skip to main content

imago/format/
drivers.rs

1//! Internal image format driver interface.
2//!
3//! Provides the internal interface for image format drivers to provide their services, on which
4//! the publically visible interface [`FormatAccess`] is built.
5
6use super::{Format, PreallocateMode};
7use crate::io_buffers::IoVectorMut;
8use crate::{FormatAccess, Storage};
9use maybe_async::maybe_async;
10use std::any::Any;
11use std::fmt::{Debug, Display};
12use std::io;
13
14/// Implementation of a disk image format.
15#[maybe_async(?Send)]
16#[allow(clippy::double_must_use)] // strange interaction between `maybe_async(?Send)` and `async`
17pub trait FormatDriverInstance: Any + Debug + Display + Send + Sync {
18    /// Type of storage used.
19    type Storage: Storage;
20
21    /// Return which format this is.
22    fn format(&self) -> Format;
23
24    /// Check whether `storage` has this format.
25    ///
26    /// This is only a rough test and does not guarantee that opening `storage` under this format
27    /// will succeed.  Generally, it will only check the magic bytes (if available).  For formats
28    /// that do not have distinct features (like raw), this will always return `true`.
29    ///
30    /// # Safety
31    /// Probing is inherently dangerous: Image formats like qcow2 allow referencing external files;
32    /// if you use imago to give untrusted parties (like VM guests) access to VM disk image files,
33    /// this will give those parties access to data in those files.  Opening images from untrusted
34    /// sources can therefore be quite dangerous.  Gating
35    /// ([`ImplicitOpenGate`](super::gate::ImplicitOpenGate)) can help mitigate this.
36    ///
37    /// If you do not know an image’s format, that is a sign it does not come from a trusted
38    /// source, and so opening it in a non-raw format may be quite dangerous.
39    ///
40    /// Perhaps most important to note is that giving an untrusted party (like a VM guest) access
41    /// to a raw image file allows that party to modify the whole file.  It may write image headers
42    /// into this image file, causing a subsequent probe operation to recognize it as a non-raw
43    /// image, referencing arbitrary files on the host filesystem!
44    ///
45    /// When using imago to give an untrusted third party access to VM disk images, the guidelines
46    /// for probing are thus:
47    /// - Do not probe.  If at all possible, obtain an image’s format from a trusted side channel.
48    /// - If there is no other way, probe each given image only once, before that untrusted third
49    ///   party (like a VM guest) had write access to it; remember the probed format, and open the
50    ///   image exclusively as that format.
51    ///
52    /// When working with even potentially untrusted images, you should always use an
53    /// [`ImplicitOpenGate`](super::gate::ImplicitOpenGate) to prevent access to files you do not
54    /// wish to access.
55    async unsafe fn probe(storage: &Self::Storage) -> io::Result<bool>
56    where
57        Self: Sized;
58
59    /// Size of the disk represented by this image.
60    fn size(&self) -> u64;
61
62    /// Granularity on which blocks can be marked as zero.
63    ///
64    /// This is the granularity for [`FormatDriverInstance::ensure_zero_mapping()`].
65    ///
66    /// Return `None` if zero blocks are not supported.
67    fn zero_granularity(&self) -> Option<u64> {
68        None
69    }
70
71    /// Recursively collect all storage objects associated with this image.
72    ///
73    /// “Recursive” means to recurse to other images like e.g. a backing file.
74    fn collect_storage_dependencies(&self) -> Vec<&Self::Storage>;
75
76    /// Return whether this image may be modified.
77    ///
78    /// This state must not change via interior mutability, i.e. as long as this FDI is wrapped in
79    /// a `FormatAccess`, its writability must remain constant.
80    fn writable(&self) -> bool;
81
82    /// Return the mapping at `offset`.
83    ///
84    /// Find what `offset` is mapped to, return that mapping information, and the length of that
85    /// continuous mapping (from `offset`).
86    ///
87    /// To determine that continuous mapping length, drivers should not perform additional I/O
88    /// beyond what is necessary to get mapping information for `offset` itself.
89    ///
90    /// `max_length` is a hint how long of a range is required at all, but the returned length may
91    /// exceed that value if that simplifies the implementation.
92    ///
93    /// The returned length must only be 0 if `ShallowMapping::Eof` is returned.
94    #[allow(clippy::needless_lifetimes)] // Elidable in sync, but async needs a named lifetime for the boxed future bound
95    async fn get_mapping<'a>(
96        &'a self,
97        offset: u64,
98        max_length: u64,
99    ) -> io::Result<(ShallowMapping<'a, Self::Storage>, u64)>;
100
101    /// Ensure that `offset` is directly mapped to some storage object, up to a length of `length`.
102    ///
103    /// Return the storage object, the corresponding offset there, and the continuous length that
104    /// the driver was able to map (less than or equal to `length`).
105    ///
106    /// If the returned length is less than `length`, drivers can expect subsequent calls to
107    /// allocate the rest of the original range.  Therefore, if a driver knows in advance that it
108    /// is impossible to fully map the given range (e.g. because it lies partially or fully beyond
109    /// the end of the disk), it should return an error immediately.
110    ///
111    /// If `overwrite` is true, the contents in the range are supposed to be overwritten and may be
112    /// discarded.  Otherwise, they must be kept.
113    ///
114    /// Should not break existing data mappings, i.e. not discard or repurpose existing data
115    /// mappings.  Making them unused, but retaining them as allocated so they can safely be
116    /// written to (albeit with no effect) is OK; discarding them so that they may be reused for
117    /// other mappings is not.
118    #[allow(clippy::needless_lifetimes)] // Elidable in sync, but async needs a named lifetime for the boxed future bound
119    async fn ensure_data_mapping<'a>(
120        &'a self,
121        offset: u64,
122        length: u64,
123        overwrite: bool,
124    ) -> io::Result<(&'a Self::Storage, u64, u64)>;
125
126    /// Ensure that the given range is efficiently mapped as zeroes.
127    ///
128    /// Must not write any data.  Return the range (offset and length) that could actually be
129    /// zeroed, which must be a subset of the range given by `offset` and `length`.  The returned
130    /// offset must be as close to `offset` as possible, i.e. no zero mapping is possible between
131    /// `offset` and the returned offset (e.g. because of format-inherent granularity).
132    ///
133    /// The returned length may be zero in case zeroing would theoretically be possible, but not
134    /// for this range at this granularity.
135    ///
136    /// Should not break existing data mappings, i.e. not discard or repurpose existing data
137    /// mappings.  Making them unused, but retaining them as allocated so they can safely be
138    /// written to (albeit with no effect) is OK; discarding them so that they may be reused for
139    /// other mappings is not.
140    async fn ensure_zero_mapping(&self, _offset: u64, _length: u64) -> io::Result<(u64, u64)> {
141        Err(io::ErrorKind::Unsupported.into())
142    }
143
144    /// Discard the given range, ensure it is read back as zeroes.
145    ///
146    /// Effectively the same as [`FormatDriverInstance::ensure_zero_mapping()`], but may break
147    /// existing data mappings thanks to the mutable `self` reference, which ensures that old data
148    /// mappings returned by [`FormatDriverInstance::get_mapping()`] cannot be held onto.
149    async fn discard_to_zero(&mut self, offset: u64, length: u64) -> io::Result<(u64, u64)> {
150        // Safe: `&mut self` guarantees nobody has concurrent data mappings
151        unsafe { self.discard_to_zero_unsafe(offset, length).await }
152    }
153
154    /// Discard the given range, ensure it is read back as zeroes.
155    ///
156    /// Unsafe variant of [FormatDriverInstance::discard_to_zero()], only requiring an immutable
157    /// &self
158    ///
159    /// # Safety
160    /// This function is marked as unsafe because:
161    /// - It may invalidate all existing data mappings.
162    ///
163    /// The caller must ensure that no other references to this driver instance exist and that
164    /// the caller must ensure that all previously looked up mappings are no longer assumed to
165    /// be valid after this operation.
166    ///
167    /// Because mappings contain references to the block driver instance, one way to do so is
168    /// to have a mutable reference to the block driver instance, which will automatically
169    /// ensure there are no other references (and thus no mappings).  In that case, you can use
170    /// the safe variant [`Self::discard_to_zero()`].
171    async unsafe fn discard_to_zero_unsafe(
172        &self,
173        _offset: u64,
174        _length: u64,
175    ) -> io::Result<(u64, u64)> {
176        Err(io::ErrorKind::Unsupported.into())
177    }
178
179    /// Discard the given range.
180    ///
181    /// Effectively the same as [`FormatDriverInstance::discard_to_zero()`], but the discarded area
182    /// may read as any data.  Backing file data should not reappear, however.
183    async fn discard_to_any(&mut self, offset: u64, length: u64) -> io::Result<(u64, u64)> {
184        // Safe: `&mut self` guarantees nobody has concurrent data mappings
185        unsafe { self.discard_to_any_unsafe(offset, length).await }
186    }
187
188    /// Discard the given range.
189    ///
190    /// Unsafe variant of [FormatDriverInstance::discard_to_any()], only requiring an immutable
191    /// &self
192    ///
193    /// # Safety
194    /// This function is marked as unsafe because:
195    /// - It may invalidate all existing data mappings.
196    ///
197    /// The caller must ensure that no other references to this driver instance exist and that
198    /// the caller must ensure that all previously looked up mappings are no longer assumed to
199    /// be valid after this operation.
200    ///
201    /// Because mappings contain references to the block driver instance, one way to do so is
202    /// to have a mutable reference to the block driver instance, which will automatically
203    /// ensure there are no other references (and thus no mappings).  In that case, you can use
204    /// the safe variant [`Self::discard_to_any()`].
205    async unsafe fn discard_to_any_unsafe(
206        &self,
207        _offset: u64,
208        _length: u64,
209    ) -> io::Result<(u64, u64)> {
210        Err(io::ErrorKind::Unsupported.into())
211    }
212
213    /// Discard the given range, such that the backing image becomes visible.
214    ///
215    /// Deallocate the range such that in deallocated blocks, the backing image’s data (if one
216    /// exists) will show, i.e. [`FormatDriverInstance::get_mapping()`] should return an indirect
217    /// mapping.  When there is no backing image, those blocks should appear as zero.
218    ///
219    /// Return the range (offset and length) that could actually be discarded, which must be a
220    /// subset of `offset` and `length`, and the returned offset must be as close to `offset` as
221    /// possible (like for [`FormatDriverInstance::discard_to_backing()`].
222    ///
223    /// May break existing data mappings thanks to the mutable `self` reference.
224    async fn discard_to_backing(&mut self, offset: u64, length: u64) -> io::Result<(u64, u64)> {
225        // Safe: `&mut self` guarantees nobody has concurrent data mappings
226        unsafe { self.discard_to_backing_unsafe(offset, length).await }
227    }
228
229    /// Discard the given range, such that the backing image becomes visible.
230    ///
231    /// Unsafe variant of [FormatDriverInstance::discard_to_backing()], only requiring an immutable
232    /// &self
233    ///
234    /// # Safety
235    /// This function is marked as unsafe because:
236    /// - It may invalidate all existing data mappings.
237    ///
238    /// The caller must ensure that no other references to this driver instance exist and that
239    /// the caller must ensure that all previously looked up mappings are no longer assumed to
240    /// be valid after this operation.
241    ///
242    /// Because mappings contain references to the block driver instance, one way to do so is
243    /// to have a mutable reference to the block driver instance, which will automatically
244    /// ensure there are no other references (and thus no mappings).  In that case, you can use
245    /// the safe variant [`Self::discard_to_backing()`].
246    async unsafe fn discard_to_backing_unsafe(
247        &self,
248        _offset: u64,
249        _length: u64,
250    ) -> io::Result<(u64, u64)> {
251        Err(io::ErrorKind::Unsupported.into())
252    }
253
254    /// Read data from a `ShallowMapping::Special` area.
255    async fn readv_special(&self, _bufv: IoVectorMut<'_>, _offset: u64) -> io::Result<()> {
256        Err(io::ErrorKind::Unsupported.into())
257    }
258
259    /// Flush internal buffers.
260    ///
261    /// Does not need to ensure those buffers are synced to disk (hardware), and does not need to
262    /// drop them, i.e. they may still be used on later accesses.
263    async fn flush(&self) -> io::Result<()>;
264
265    /// Sync data already written to the storage hardware.
266    ///
267    /// Does not need to ensure internal buffers are written, i.e. should generally just be passed
268    /// through to `Storage::sync()` for all underlying storage objects.
269    async fn sync(&self) -> io::Result<()>;
270
271    /// Drop internal buffers.
272    ///
273    /// Drop all internal buffers, but do not flush them!  All internal data must then be reloaded
274    /// from disk.
275    ///
276    /// # Safety
277    /// Not flushing internal buffers may cause image corruption.  The caller must ensure the
278    /// on-disk state is consistent.
279    async unsafe fn invalidate_cache(&self) -> io::Result<()>;
280
281    /// Resize to the given size, which must be greater than the current size.
282    ///
283    /// Set the disk size to `new_size`, preallocating the new space according to `prealloc_mode`.
284    /// Depending on the image format, it is possible some preallocation modes are not supported,
285    /// in which case an [`std::io::ErrorKind::Unsupported`] is returned.
286    ///
287    /// If the current size is already `new_size` or greater, do nothing.
288    async fn resize_grow(&self, new_size: u64, prealloc_mode: PreallocateMode) -> io::Result<()>;
289
290    /// Truncate to the given size, which must be smaller than the current size.
291    ///
292    /// Set the disk size to `new_size`, discarding the data after `new_size`.
293    ///
294    /// May break existing data mappings thanks to the mutable `self` reference.
295    ///
296    /// If the current size is already `new_size` or smaller, do nothing.
297    async fn resize_shrink(&mut self, new_size: u64) -> io::Result<()>;
298}
299
300/// Non-recursive mapping information.
301///
302/// Mapping information as returned by [`FormatDriverInstance::get_mapping()`], only looking at
303/// that format layer’s information.
304#[derive(Debug)]
305#[non_exhaustive]
306pub enum ShallowMapping<'a, S: Storage + 'static> {
307    /// Raw data.
308    #[non_exhaustive]
309    Raw {
310        /// Storage object where this data is stored.
311        storage: &'a S,
312
313        /// Offset in `storage` where this data is stored.
314        offset: u64,
315
316        /// Whether this mapping may be written to.
317        ///
318        /// If `true`, you can directly write to `offset` on `storage` to change the disk image’s
319        /// data accordingly.
320        ///
321        /// If `false`, the disk image format does not allow writing to `offset` on `storage`; a
322        /// new mapping must be allocated first.
323        writable: bool,
324    },
325
326    /// Data lives in a different disk image (e.g. a backing file).
327    #[non_exhaustive]
328    Indirect {
329        /// Format instance where this data can be obtained.
330        layer: &'a FormatAccess<S>,
331
332        /// Offset in `layer` where this data can be obtained.
333        offset: u64,
334
335        /// Whether this mapping may be written to.
336        ///
337        /// If `true`, you can directly write to `offset` on `layer` to change the disk image’s
338        /// data accordingly.
339        ///
340        /// If `false`, the disk image format does not allow writing to `offset` on `layer`; a new
341        /// mapping must be allocated first.
342        writable: bool,
343    },
344
345    /// Range is to be read as zeroes.
346    #[non_exhaustive]
347    Zero {
348        /// Whether these zeroes are explicit on this layer.
349        ///
350        /// Differential image formats (like qcow2) track information about the status for all
351        /// blocks in the image (called clusters in case of qcow2).  Perhaps most importantly, they
352        /// track whether a block is allocated or not:
353        /// - Allocated blocks have their data in the image.
354        /// - Unallocated blocks do not have their data in this image, but have to be read from a
355        ///   backing image (which results in [`ShallowMapping::Indirect`] mappings).
356        ///
357        /// Thus, such images represent the difference from their backing image (hence
358        /// “differential”).
359        ///
360        /// Without a backing image, this feature can be used for sparse allocation: Unallocated
361        /// blocks are simply interpreted to be zero.  These ranges will be noted as
362        /// [`ShallowMapping::Zero`] with `explicit` set to false.
363        ///
364        /// Formats like qcow2 can track more information beyond just the allocation status,
365        /// though, for example, whether a block should read as zero. Such blocks similarly do not
366        /// need to have their data stored in the image file, but are still not treated as
367        /// unallocated, so will never be read from a backing image, regardless of whether one
368        /// exists or not.
369        ///
370        /// These ranges are noted as [`ShallowMapping::Zero`] with `explicit` set to true.
371        explicit: bool,
372    },
373
374    /// End of file reached.
375    #[non_exhaustive]
376    Eof {},
377
378    /// Data is encoded in some manner, e.g. compressed or encrypted.
379    ///
380    /// Such data cannot be accessed directly, but must be interpreted by the image format driver.
381    #[non_exhaustive]
382    Special {
383        /// Original (“guest”) offset to pass to `FormatDriverInstance::readv_special()`.
384        offset: u64,
385    },
386}