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}