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