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, 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 // FIXME: TOCTTOU problem. Not sure how to fully fix it, if possible at all.
214 // (Concurrent writes can change the mapping, but the driver will have to reload the
215 // mapping because it cannot pass it in `NonRecursiveMapping::Special`. It may then
216 // find that this is no longer a “special” range. Even passing the low-level mapping
217 // information in `Mapping::Special` wouldn’t fully fix it, though: If concurrent
218 // writes change the low-level cluster type, and the driver then tries to e.g.
219 // decompress the data that was there, that may well fail.)
220 Mapping::Special { layer, offset } => layer.inner.readv_special(bufv, offset).await,
221 }
222 }
223
224 /// Return the shallow mapping at `offset`.
225 ///
226 /// Find what `offset` is mapped to, which may be another format layer, return that
227 /// information, and the length of the continuous mapping (from `offset`).
228 ///
229 /// Use [`FormatAccess::get_mapping()`] to recursively fully resolve references to other format
230 /// layers.
231 pub async fn get_shallow_mapping(
232 &self,
233 offset: u64,
234 max_length: u64,
235 ) -> io::Result<(ShallowMapping<'_, S>, u64)> {
236 self.inner
237 .get_mapping(offset, max_length)
238 .await
239 .map(|(m, l)| (m, cmp::min(l, max_length)))
240 }
241
242 /// Return the recursively resolved mapping at `offset`.
243 ///
244 /// Find what `offset` is mapped to, return that mapping information, and the length of that
245 /// continuous mapping (from `offset`).
246 ///
247 /// All data references to other format layers are automatically resolved (recursively), so
248 /// that the result are more “trivial” mappings (unless prevented by special mappings like
249 /// compressed clusters).
250 pub async fn get_mapping(
251 &self,
252 mut offset: u64,
253 mut max_length: u64,
254 ) -> io::Result<(Mapping<'_, S>, u64)> {
255 let mut format_layer = self;
256 let mut writable_gate = true;
257
258 loop {
259 let (mapping, length) = format_layer.get_shallow_mapping(offset, max_length).await?;
260
261 match mapping {
262 ShallowMapping::Raw {
263 storage,
264 offset,
265 writable,
266 } => {
267 return Ok((
268 Mapping::Raw {
269 storage,
270 offset,
271 writable: writable && writable_gate,
272 },
273 length,
274 ))
275 }
276
277 ShallowMapping::Indirect {
278 layer: recurse_layer,
279 offset: recurse_offset,
280 writable: recurse_writable,
281 } => {
282 format_layer = recurse_layer;
283 offset = recurse_offset;
284 writable_gate = recurse_writable;
285 max_length = length;
286 }
287
288 ShallowMapping::Zero { explicit } => {
289 // If this is not the top layer, always clear `explicit`
290 return if explicit && ptr::eq(format_layer, self) {
291 Ok((Mapping::Zero { explicit: true }, length))
292 } else {
293 Ok((Mapping::Zero { explicit: false }, length))
294 };
295 }
296
297 ShallowMapping::Eof {} => {
298 // Return EOF only on top layer, zero otherwise
299 return if ptr::eq(format_layer, self) {
300 Ok((Mapping::Eof {}, 0))
301 } else {
302 Ok((Mapping::Zero { explicit: false }, max_length))
303 };
304 }
305
306 ShallowMapping::Special { offset } => {
307 return Ok((
308 Mapping::Special {
309 layer: format_layer,
310 offset,
311 },
312 length,
313 ));
314 }
315 }
316 }
317 }
318
319 /// Create a raw data mapping at `offset`.
320 ///
321 /// Ensure that `offset` is directly mapped to some storage object, up to a length of `length`.
322 /// Return the storage object, the corresponding offset there, and the continuous length that
323 /// we were able to map (less than or equal to `length`).
324 ///
325 /// If `overwrite` is true, the contents in the range are supposed to be overwritten and may be
326 /// discarded. Otherwise, they are kept.
327 pub async fn ensure_data_mapping(
328 &self,
329 offset: u64,
330 length: u64,
331 overwrite: bool,
332 ) -> io::Result<(&S, u64, u64)> {
333 let (storage, mapped_offset, mapped_length) = self
334 .inner
335 .ensure_data_mapping(offset, length, overwrite)
336 .await?;
337 let mapped_length = cmp::min(length, mapped_length);
338 assert!(mapped_length > 0);
339 Ok((storage, mapped_offset, mapped_length))
340 }
341
342 /// Read data at `offset` into `bufv`.
343 ///
344 /// Reads until `bufv` is filled completely, i.e. will not do short reads. When reaching the
345 /// end of file, the rest of `bufv` is filled with 0.
346 pub async fn readv(&self, mut bufv: IoVectorMut<'_>, mut offset: u64) -> io::Result<()> {
347 #[cfg(feature = "async")]
348 let mut workers = (self.read_parallelization > 1).then(FuturesUnordered::new);
349
350 while !bufv.is_empty() {
351 let (mapping, chunk_length) = self.get_mapping(offset, bufv.len()).await?;
352 if chunk_length == 0 {
353 assert!(mapping.is_eof());
354 bufv.fill(0);
355 break;
356 }
357
358 #[cfg(feature = "async")]
359 if let Some(workers) = workers.as_mut() {
360 while workers.len() >= self.read_parallelization {
361 workers.next().await.unwrap()?;
362 }
363 }
364
365 let (chunk, remainder) = bufv.split_at(chunk_length);
366 bufv = remainder;
367 offset += chunk_length;
368
369 #[cfg(feature = "async")]
370 if let Some(workers) = workers.as_mut() {
371 workers.push(self.read_chunk(chunk, mapping));
372 } else {
373 self.read_chunk(chunk, mapping).await?;
374 }
375 #[cfg(feature = "sync")]
376 self.read_chunk(chunk, mapping)?;
377 }
378
379 #[cfg(feature = "async")]
380 if let Some(mut workers) = workers {
381 while workers.next().await.transpose()?.is_some() {}
382 }
383
384 Ok(())
385 }
386
387 /// Read data at `offset` into `buf`.
388 ///
389 /// Reads until `buf` is filled completely, i.e. will not do short reads. When reaching the
390 /// end of file, the rest of `buf` is filled with 0.
391 pub async fn read<'a>(
392 &'a self,
393 buf: impl Into<IoVectorMut<'a>>,
394 offset: u64,
395 ) -> io::Result<()> {
396 self.readv(buf.into(), offset).await
397 }
398
399 /// Write data from `bufv` to `offset`.
400 ///
401 /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes. Reaching
402 /// the end of file before the end of the buffer results in an error.
403 pub async fn writev(&self, mut bufv: IoVector<'_>, mut offset: u64) -> io::Result<()> {
404 if !self.writable {
405 return Err(io::Error::other("Image is read-only"));
406 }
407
408 // Limit to disk size
409 let disk_size = self.inner.size();
410 if offset >= disk_size {
411 return Ok(());
412 }
413 if bufv.len() > disk_size - offset {
414 bufv = bufv.split_at(disk_size - offset).0;
415 }
416
417 #[cfg(feature = "async")]
418 let mut workers = (self.write_parallelization > 1).then(FuturesUnordered::new);
419
420 while !bufv.is_empty() {
421 let (storage, st_offset, st_length) =
422 self.ensure_data_mapping(offset, bufv.len(), true).await?;
423
424 #[cfg(feature = "async")]
425 if let Some(workers) = workers.as_mut() {
426 while workers.len() >= self.write_parallelization {
427 workers.next().await.unwrap()?;
428 }
429 }
430
431 let (chunk, remainder) = bufv.split_at(st_length);
432 bufv = remainder;
433 offset += st_length;
434
435 #[cfg(feature = "async")]
436 if let Some(workers) = workers.as_mut() {
437 workers.push(storage.writev(chunk, st_offset));
438 } else {
439 storage.writev(chunk, st_offset).await?;
440 }
441 #[cfg(feature = "sync")]
442 storage.writev(chunk, st_offset)?;
443 }
444
445 #[cfg(feature = "async")]
446 if let Some(mut workers) = workers {
447 while workers.next().await.transpose()?.is_some() {}
448 }
449
450 Ok(())
451 }
452
453 /// Write data from `buf` to `offset`.
454 ///
455 /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes. Reaching
456 /// the end of file before the end of the buffer results in an error.
457 pub async fn write<'a>(&'a self, buf: impl Into<IoVector<'a>>, offset: u64) -> io::Result<()> {
458 self.writev(buf.into(), offset).await
459 }
460
461 /// Check whether the given range is zero.
462 ///
463 /// Checks for zero mappings, not zero data (although this might be changed in the future).
464 ///
465 /// Errors are treated as non-zero areas.
466 async fn is_range_zero(&self, mut offset: u64, mut length: u64) -> bool {
467 while length > 0 {
468 match self.get_mapping(offset, length).await {
469 Ok((Mapping::Zero { explicit: _ }, mlen)) => {
470 offset += mlen;
471 length -= mlen;
472 }
473 _ => return false,
474 };
475 }
476
477 true
478 }
479
480 /// Ensure the given range reads as zeroes, without write-zeroes support.
481 ///
482 /// Does not require support for efficient zeroing, instead writing zeroes when the range is
483 /// not zero yet. If `allocate` is true, areas that are not currently allocated will be
484 /// allocated to write zeroes there; if it is false, unallocated areas that currently read as
485 /// zero are left alone.
486 ///
487 /// However, can still use efficient zero support if present.
488 ///
489 /// The main use case is to handle unaligned zero requests. Quite inefficient for large areas.
490 async fn soft_ensure_zero(&self, mut offset: u64, mut length: u64) -> io::Result<()> {
491 // “Fast” path: Try to efficiently zero as much as possible
492 if let Some(gran) = self.inner.zero_granularity() {
493 let end = offset.checked_add(length).ok_or_else(|| {
494 io::Error::new(
495 io::ErrorKind::InvalidInput,
496 format!("Write-zero wrap-around: {offset} + {length}"),
497 )
498 })?;
499 let mut aligned_start = offset - offset % gran;
500 // Could be handled, but don’t bother
501 let mut aligned_end = end.checked_next_multiple_of(gran).ok_or_else(|| {
502 io::Error::new(
503 io::ErrorKind::InvalidInput,
504 "Write-zero wrap-around at cluster granularity",
505 )
506 })?;
507
508 aligned_end = cmp::min(aligned_end, self.size());
509
510 // Whether the whole area could be efficiently zeroed
511 let mut fully_zeroed = true;
512
513 if offset > aligned_start
514 && !self
515 .is_range_zero(aligned_start, offset - aligned_start)
516 .await
517 {
518 // Non-zero head, we cannot zero that cluster. Still try to zero as much as
519 // possible.
520 fully_zeroed = false;
521 aligned_start += gran;
522 }
523 if end < aligned_end && !self.is_range_zero(end, aligned_end - end).await {
524 // Non-zero tail, we cannot zero that cluster. Still try to zero as much as
525 // possible.
526 fully_zeroed = false;
527 aligned_end -= gran;
528 }
529
530 while aligned_start < aligned_end {
531 let res = self
532 .inner
533 .ensure_zero_mapping(aligned_start, aligned_end - aligned_start)
534 .await;
535 if let Ok((zofs, zlen)) = res {
536 if zofs != aligned_start || zlen == 0 {
537 // Produced a gap, so will need to fall back, but still try to zero as
538 // much as possible
539 fully_zeroed = false;
540 if zlen == 0 {
541 // Cannot go on
542 break;
543 }
544 }
545 aligned_start = zofs + zlen;
546 } else {
547 // Ignore errors, just fall back
548 fully_zeroed = false;
549 break;
550 }
551 }
552
553 if fully_zeroed {
554 // Everything zeroed, no need to check
555 return Ok(());
556 }
557 }
558
559 // Slow path: Everything that is not zero in this layer is allocated as data and zeroes are
560 // written. The more we zeroed in the fast path, the quicker this will be.
561 while length > 0 {
562 let (mapping, mlen) = self.inner.get_mapping(offset, length).await?;
563 let mlen = cmp::min(mlen, length);
564
565 let mapping = match mapping {
566 ShallowMapping::Raw {
567 storage,
568 offset,
569 writable,
570 } => writable.then_some((storage, offset)),
571 // For already zero clusters, we don’t need to do anything
572 ShallowMapping::Zero { explicit: true } => {
573 // Nothing to be done
574 offset += mlen;
575 length -= mlen;
576 continue;
577 }
578 // For unallocated clusters, we should establish zero data
579 ShallowMapping::Zero { explicit: false }
580 | ShallowMapping::Indirect {
581 layer: _,
582 offset: _,
583 writable: _,
584 } => None,
585 ShallowMapping::Eof {} => {
586 return Err(io::ErrorKind::UnexpectedEof.into());
587 }
588 ShallowMapping::Special { offset: _ } => None,
589 };
590
591 let (file, mofs, mlen) = if let Some((file, mofs)) = mapping {
592 (file, mofs, mlen)
593 } else {
594 self.ensure_data_mapping(offset, mlen, true).await?
595 };
596
597 write_full_zeroes(file, mofs, mlen).await?;
598 offset += mlen;
599 length -= mlen;
600 }
601
602 Ok(())
603 }
604
605 /// Ensure the given range reads as zeroes.
606 ///
607 /// May use efficient zeroing for a subset of the given range, if supported by the format.
608 /// Will not discard anything, which keeps existing data mappings usable, albeit writing to
609 /// mappings that are now zeroed may have no effect.
610 ///
611 /// Check if [`FormatAccess::discard_to_zero()`] better suits your needs: It may work better on
612 /// a wider range of formats (`write_zeroes()` requires support for preallocated zero clusters,
613 /// which qcow2 does have, but other formats may not), and can actually free up space.
614 /// However, because it can break existing data mappings, it requires a mutable `self`
615 /// reference.
616 pub async fn write_zeroes(&self, mut offset: u64, length: u64) -> io::Result<()> {
617 let max_offset = offset.checked_add(length).ok_or_else(|| {
618 io::Error::new(io::ErrorKind::InvalidInput, "Write-zeroes range overflow")
619 })?;
620
621 while offset < max_offset {
622 let (zofs, zlen) = self
623 .inner
624 .ensure_zero_mapping(offset, max_offset - offset)
625 .await?;
626 if zlen == 0 {
627 break;
628 }
629 // Fill up head, i.e. the range [offset, zofs)
630 self.soft_ensure_zero(offset, zofs - offset).await?;
631 offset = zofs + zlen;
632 }
633
634 // Fill up tail, i.e. the remaining range [offset, max_offset)
635 self.soft_ensure_zero(offset, max_offset - offset).await?;
636 Ok(())
637 }
638
639 /// Discard the given range, ensure it is read back as zeroes.
640 ///
641 /// Effectively the same as [`FormatAccess::write_zeroes()`], but discard as much of the
642 /// existing allocation as possible. This breaks existing data mappings, so needs a mutable
643 /// reference to `self`, which ensures that existing data references (which have the lifetime
644 /// of an immutable `self` reference) cannot be kept.
645 ///
646 /// Areas that cannot be discarded (because of format-inherent alignment restrictions) are
647 /// still overwritten with zeroes, unless discarding is not supported altogether.
648 pub async fn discard_to_zero(&mut self, offset: u64, length: u64) -> io::Result<()> {
649 // Safe: `&mut self` guarantees nobody has concurrent data mappings
650 unsafe { self.discard_to_zero_unsafe(offset, length).await }
651 }
652
653 /// Discard the given range, ensure it is read back as zeroes.
654 ///
655 /// Unsafe variant of [`FormatAccess::discard_to_zero()`], only requiring an immutable `&self`.
656 ///
657 /// # Safety
658 ///
659 /// This function may invalidate existing data mappings. The caller must ensure to invalidate
660 /// all concurrently existing data mappings they have. Note that this includes concurrent
661 /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
662 /// while they run.
663 ///
664 /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
665 /// safe variant [`FormatAccess::discard_to_zero()`].
666 pub async unsafe fn discard_to_zero_unsafe(
667 &self,
668 mut offset: u64,
669 length: u64,
670 ) -> io::Result<()> {
671 let max_offset = offset.checked_add(length).ok_or_else(|| {
672 io::Error::new(
673 io::ErrorKind::InvalidInput,
674 "Discard-to-zero range overflow",
675 )
676 })?;
677
678 while offset < max_offset {
679 // Safe: Caller guarantees this is safe
680 let (zofs, zlen) = unsafe {
681 self.inner
682 .discard_to_zero_unsafe(offset, max_offset - offset)
683 .await?
684 };
685 if zlen == 0 {
686 break;
687 }
688 // Fill up head, i.e. the range [offset, zofs)
689 self.soft_ensure_zero(offset, zofs - offset).await?;
690 offset = zofs + zlen;
691 }
692
693 // Fill up tail, i.e. the remaining range [offset, max_offset)
694 self.soft_ensure_zero(offset, max_offset - offset).await?;
695 Ok(())
696 }
697
698 /// Discard the given range, not guaranteeing specific data on read-back.
699 ///
700 /// Discard as much of the given range as possible, and keep the rest as-is. Does not
701 /// guarantee any specific data on read-back, in contrast to
702 /// [`FormatAccess::discard_to_zero()`].
703 ///
704 /// Discarding being unsupported by this format is still returned as an error
705 /// ([`std::io::ErrorKind::Unsupported`])
706 pub async fn discard_to_any(&mut self, offset: u64, length: u64) -> io::Result<()> {
707 unsafe { self.discard_to_any_unsafe(offset, length).await }
708 }
709
710 /// Discard the given range, not guaranteeing specific data on read-back.
711 ///
712 /// Unsafe variant of [`FormatAccess::discard_to_any()`], only requiring an immutable `&self`.
713 ///
714 /// # Safety
715 ///
716 /// This function may invalidate existing data mappings. The caller must ensure to invalidate
717 /// all concurrently existing data mappings they have. Note that this includes concurrent
718 /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
719 /// while they run.
720 ///
721 /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
722 /// safe variant [`FormatAccess::discard_to_any()`].
723 pub async unsafe fn discard_to_any_unsafe(
724 &self,
725 mut offset: u64,
726 length: u64,
727 ) -> io::Result<()> {
728 let max_offset = offset.checked_add(length).ok_or_else(|| {
729 io::Error::new(io::ErrorKind::InvalidInput, "Discard-to-any range overflow")
730 })?;
731
732 while offset < max_offset {
733 // Safe: Caller guarantees this is safe
734 let (dofs, dlen) = unsafe {
735 self.inner
736 .discard_to_any_unsafe(offset, max_offset - offset)
737 .await?
738 };
739 if dlen == 0 {
740 break;
741 }
742 offset = dofs + dlen;
743 }
744
745 Ok(())
746 }
747
748 /// Discard the given range, such that the backing image becomes visible.
749 ///
750 /// Discard as much of the given range as possible so that a backing image’s data becomes
751 /// visible, and keep the rest as-is. This breaks existing data mappings, so needs a mutable
752 /// reference to `self`, which ensures that existing data references (which have the lifetime
753 /// of an immutable `self` reference) cannot be kept.
754 pub async fn discard_to_backing(&mut self, offset: u64, length: u64) -> io::Result<()> {
755 // Safe: `&mut self` guarantees nobody has concurrent data mappings
756 unsafe { self.discard_to_backing_unsafe(offset, length).await }
757 }
758
759 /// Discard the given range, such that the backing image becomes visible.
760 ///
761 /// Unsafe variant of [`FormatAccess::discard_to_backing()`], only requiring an immutable
762 /// `&self`.
763 ///
764 /// # Safety
765 ///
766 /// This function may invalidate existing data mappings. The caller must ensure to invalidate
767 /// all concurrently existing data mappings they have. Note that this includes concurrent
768 /// accesses through this type ([`FormatAccess`]), which may hold these mappings internally
769 /// while they run.
770 ///
771 /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
772 /// safe variant [`FormatAccess::discard_to_backing()`].
773 pub async unsafe fn discard_to_backing_unsafe(
774 &self,
775 mut offset: u64,
776 length: u64,
777 ) -> io::Result<()> {
778 let max_offset = offset.checked_add(length).ok_or_else(|| {
779 io::Error::new(
780 io::ErrorKind::InvalidInput,
781 "Discard-to-backing range overflow",
782 )
783 })?;
784
785 while offset < max_offset {
786 // Safe: Caller guarantees this is safe
787 let (dofs, dlen) = unsafe {
788 self.inner
789 .discard_to_backing_unsafe(offset, max_offset - offset)
790 .await?
791 };
792 if dlen == 0 {
793 break;
794 }
795 offset = dofs + dlen;
796 }
797
798 Ok(())
799 }
800
801 /// Flush internal buffers. Always call this before drop! (except in sync mode)
802 ///
803 /// Does not necessarily sync those buffers to disk. When using `flush()`, consider whether
804 /// you want to call `sync()` afterwards.
805 ///
806 /// In async mode, because of the current lack of stable `async_drop`, you must manually call
807 /// this before dropping a `FormatAccess` instance! (Not necessarily for read-only images,
808 /// though.) In sync mode, `Drop` calls this automatically.
809 ///
810 /// Note that this will not drop the buffers, so they may still be used to serve later
811 /// accesses. Use [`FormatAccess::invalidate_cache()`] to drop all buffers.
812 pub async fn flush(&self) -> io::Result<()> {
813 self.inner.flush().await
814 }
815
816 /// Sync data already written to the storage hardware.
817 ///
818 /// This does not necessarily include flushing internal buffers, i.e. `flush`. When using
819 /// `sync()`, consider whether you want to call `flush()` before it.
820 pub async fn sync(&self) -> io::Result<()> {
821 self.inner.sync().await
822 }
823
824 /// Drop internal buffers.
825 ///
826 /// This drops all internal buffers, but does not flush them! All cached data is reloaded from
827 /// disk on subsequent accesses.
828 ///
829 /// # Safety
830 /// Not flushing internal buffers may cause image corruption. You must ensure the on-disk
831 /// state is consistent.
832 pub async unsafe fn invalidate_cache(&self) -> io::Result<()> {
833 // Safety ensured by caller
834 unsafe { self.inner.invalidate_cache() }.await
835 }
836
837 /// Resize to the given size.
838 ///
839 /// Set the disk size to `new_size`. If `new_size` is smaller than the current size, ignore
840 /// both preallocation modes and discard the data after `new_size`.
841 ///
842 /// If `new_size` is larger than the current size, `prealloc_mode` determines whether and how
843 /// the new range should be allocated; depending on the image format, is possible some
844 /// preallocation modes are not supported, in which case an [`std::io::ErrorKind::Unsupported`]
845 /// is returned.
846 ///
847 /// This may break existing data mappings, so needs a mutable reference to `self`, which
848 /// ensures that existing data references (which have the lifetime of an immutable `self`
849 /// reference) cannot be kept.
850 ///
851 /// See also [`FormatAccess::resize_grow()`] and [`FormatAccess::resize_shrink()`], whose more
852 /// specialized interface may be useful when you know whether you want to grow or shrink the
853 /// image.
854 pub async fn resize(
855 &mut self,
856 new_size: u64,
857 prealloc_mode: PreallocateMode,
858 ) -> io::Result<()> {
859 match new_size.cmp(&self.size()) {
860 std::cmp::Ordering::Less => self.resize_shrink(new_size).await,
861 std::cmp::Ordering::Equal => Ok(()),
862 std::cmp::Ordering::Greater => self.resize_grow(new_size, prealloc_mode).await,
863 }
864 }
865
866 /// Resize to the given size, which must be greater than the current size.
867 ///
868 /// Set the disk size to `new_size`, preallocating the new space according to `prealloc_mode`.
869 /// Depending on the image format, it is possible some preallocation modes are not supported,
870 /// in which case an [`std::io::ErrorKind::Unsupported`] is returned.
871 ///
872 /// If the current size is already `new_size` or greater, do nothing.
873 pub async fn resize_grow(
874 &self,
875 new_size: u64,
876 prealloc_mode: PreallocateMode,
877 ) -> io::Result<()> {
878 self.inner.resize_grow(new_size, prealloc_mode).await
879 }
880
881 /// Truncate to the given size, which must be smaller than the current size.
882 ///
883 /// Set the disk size to `new_size`, discarding the data after `new_size`.
884 ///
885 /// May break existing data mappings thanks to the mutable `self` reference.
886 ///
887 /// If the current size is already `new_size` or smaller, do nothing.
888 pub async fn resize_shrink(&mut self, new_size: u64) -> io::Result<()> {
889 self.inner.resize_shrink(new_size).await
890 }
891}
892
893impl<S: Storage> Mapping<'_, S> {
894 /// Return `true` if and only if this mapping signifies the end of file.
895 pub fn is_eof(&self) -> bool {
896 matches!(self, Mapping::Eof {})
897 }
898}
899
900impl<S: Storage> Display for FormatAccess<S> {
901 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
902 self.inner.fmt(f)
903 }
904}
905
906impl<S: Storage> Display for Mapping<'_, S> {
907 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
908 match self {
909 Mapping::Raw {
910 storage,
911 offset,
912 writable,
913 } => {
914 let writable = if *writable { "rw" } else { "ro" };
915 write!(f, "{storage}:0x{offset:x}/{writable}")
916 }
917
918 Mapping::Zero { explicit } => {
919 let explicit = if *explicit { "explicit" } else { "unallocated" };
920 write!(f, "<zero:{explicit}>")
921 }
922
923 Mapping::Eof {} => write!(f, "<eof>"),
924
925 Mapping::Special { layer, offset } => {
926 write!(f, "<special:{layer}:0x{offset:x}>")
927 }
928 }
929 }
930}
931
932#[cfg(feature = "sync")]
933impl<S: Storage> Drop for FormatAccess<S> {
934 fn drop(&mut self) {
935 if let Err(err) = self.flush() {
936 let inner = &self.inner;
937 tracing::error!("Failed to flush {inner}: {err}");
938 }
939 }
940}
941
942/*
943#[cfg(feature = "async-drop")]
944impl<S: Storage> std::future::AsyncDrop for FormatAccess<S> {
945 type Dropper<'a> = std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 'a>> where S: 'a;
946
947 fn async_drop(self: std::pin::Pin<&mut Self>) -> Self::Dropper<'_> {
948 Box::pin(async move {
949 if let Err(err) = self.flush().await {
950 let inner = &self.inner;
951 tracing::error!("Failed to flush {inner}: {err}");
952 }
953 })
954 }
955}
956*/