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}