Skip to main content

imago/format/
sync_wrappers.rs

1//! Synchronous wrapper around [`FormatAccess`].
2
3use super::drivers::FormatDriverInstance;
4use super::PreallocateMode;
5use crate::io_buffers::{IoVector, IoVectorMut};
6use crate::{FormatAccess, FormatReadPlan, Mapping, Storage};
7use std::io;
8
9/// Synchronous wrapper around [`FormatAccess`].
10///
11/// Creates and keeps a tokio runtime in which to run I/O.
12pub struct SyncFormatAccess<S: Storage + 'static> {
13    /// Wrapped asynchronous [`FormatAccess`].
14    inner: FormatAccess<S>,
15
16    /// Tokio runtime in which I/O is run.
17    runtime: tokio::runtime::Runtime,
18}
19
20impl<S: Storage + 'static> SyncFormatAccess<S> {
21    /// Like [`FormatAccess::new()`], but create a synchronous wrapper.
22    pub fn new<D: FormatDriverInstance<Storage = S> + 'static>(inner: D) -> io::Result<Self> {
23        FormatAccess::new(inner).try_into()
24    }
25
26    /// Get a reference to the contained async [`FormatAccess`] object.
27    pub fn inner(&self) -> &FormatAccess<S> {
28        &self.inner
29    }
30
31    /// Return the disk size in bytes.
32    pub fn size(&self) -> u64 {
33        self.inner.size()
34    }
35
36    /// Set the number of simultaneous async requests per read.
37    ///
38    /// When issuing read requests, issue this many async requests in parallel (still in a single
39    /// thread).  The default count is `1`, i.e. no parallel requests.
40    ///
41    /// Note that inside of this synchronous wrapper, we still run async functions, so this setting
42    /// is valid even for [`SyncFormatAccess`].
43    pub fn set_async_read_parallelization(&mut self, count: usize) {
44        self.inner.set_async_read_parallelization(count)
45    }
46
47    /// Set the number of simultaneous async requests per write.
48    ///
49    /// When issuing write requests, issue this many async requests in parallel (still in a single
50    /// thread).  The default count is `1`, i.e. no parallel requests.
51    ///
52    /// Note that inside of this synchronous wrapper, we still run async functions, so this setting
53    /// is valid even for [`SyncFormatAccess`].
54    pub fn set_async_write_parallelization(&mut self, count: usize) {
55        self.inner.set_async_write_parallelization(count)
56    }
57
58    /// Minimal I/O alignment, for both length and offset.
59    ///
60    /// All requests to this image should be aligned to this value, both in length and offset.
61    ///
62    /// Requests that do not match this alignment will be realigned internally, which requires
63    /// creating bounce buffers and read-modify-write cycles for write requests, which is costly,
64    /// so should be avoided.
65    pub fn req_align(&self) -> usize {
66        self.inner.req_align()
67    }
68
69    /// Minimal memory buffer alignment, for both address and length.
70    ///
71    /// All buffers used in requests to this image should be aligned to this value, both their
72    /// address and length.
73    ///
74    /// Request buffers that do not match this alignment will be realigned internally, which
75    /// requires creating bounce buffers, which is costly, so should be avoided.
76    pub fn mem_align(&self) -> usize {
77        self.inner.mem_align()
78    }
79
80    /// Return the mapping at `offset`.
81    ///
82    /// Find what `offset` is mapped to, return that mapping information, and the length of that
83    /// continuous mapping (from `offset`).
84    pub fn get_mapping_sync(
85        &self,
86        offset: u64,
87        max_length: u64,
88    ) -> io::Result<(Mapping<'_, S>, u64)> {
89        self.runtime
90            .block_on(self.inner.get_mapping(offset, max_length))
91    }
92
93    /// Plan a read without issuing storage I/O.
94    ///
95    /// See [`FormatAccess::plan_read()`].
96    pub fn plan_read(&self, offset: u64, length: u64) -> io::Result<FormatReadPlan<'_, S>> {
97        self.runtime.block_on(self.inner.plan_read(offset, length))
98    }
99
100    /// Create a raw data mapping at `offset`.
101    ///
102    /// Ensure that `offset` is directly mapped to some storage object, up to a length of `length`.
103    /// Return the storage object, the corresponding offset there, and the continuous length that
104    /// we were able to map (less than or equal to `length`).
105    ///
106    /// If `overwrite` is true, the contents in the range are supposed to be overwritten and may be
107    /// discarded.  Otherwise, they are kept.
108    pub fn ensure_data_mapping(
109        &self,
110        offset: u64,
111        length: u64,
112        overwrite: bool,
113    ) -> io::Result<(&S, u64, u64)> {
114        self.runtime
115            .block_on(self.inner.ensure_data_mapping(offset, length, overwrite))
116    }
117
118    /// Read data at `offset` into `bufv`.
119    ///
120    /// Reads until `bufv` is filled completely, i.e. will not do short reads.  When reaching the
121    /// end of file, the rest of `bufv` is filled with 0.
122    pub fn readv(&self, bufv: IoVectorMut<'_>, offset: u64) -> io::Result<()> {
123        self.runtime.block_on(self.inner.readv(bufv, offset))
124    }
125
126    /// Read data at `offset` into `buf`.
127    ///
128    /// Reads until `buf` is filled completely, i.e. will not do short reads.  When reaching the
129    /// end of file, the rest of `buf` is filled with 0.
130    pub fn read<'a>(&'a self, buf: impl Into<IoVectorMut<'a>>, offset: u64) -> io::Result<()> {
131        self.readv(buf.into(), offset)
132    }
133
134    /// Write data from `bufv` to `offset`.
135    ///
136    /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes.  Reaching
137    /// the end of file before the end of the buffer results in an error.
138    pub fn writev(&self, bufv: IoVector<'_>, offset: u64) -> io::Result<()> {
139        self.runtime.block_on(self.inner.writev(bufv, offset))
140    }
141
142    /// Write data from `buf` to `offset`.
143    ///
144    /// Writes all data from `bufv` (or returns an error), i.e. will not do short writes.  Reaching
145    /// the end of file before the end of the buffer results in an error.
146    pub fn write<'a>(&'a self, buf: impl Into<IoVector<'a>>, offset: u64) -> io::Result<()> {
147        self.writev(buf.into(), offset)
148    }
149
150    /// Ensure the given range reads as zeroes.
151    ///
152    /// May use efficient zeroing for a subset of the given range, if supported by the format.
153    /// Will not discard anything, which keeps existing data mappings usable, albeit writing to
154    /// mappings that are now zeroed may have no effect.
155    ///
156    /// Check if [`SyncFormatAccess::discard_to_zero()`] better suits your needs: It may work
157    /// better on a wider range of formats (`write_zeroes()` requires support for preallocated zero
158    /// clusters, which qcow2 does have, but other formats may not), and can actually free up
159    /// space.  However, because it can break existing data mappings, it requires a mutable `self`
160    /// reference.
161    pub fn write_zeroes(&self, offset: u64, length: u64) -> io::Result<()> {
162        self.runtime
163            .block_on(self.inner.write_zeroes(offset, length))
164    }
165
166    /// Discard the given range, ensure it is read back as zeroes.
167    ///
168    /// Effectively the same as [`SyncFormatAccess::write_zeroes()`], but discard as much of the
169    /// existing allocation as possible.  This breaks existing data mappings, so needs a mutable
170    /// reference to `self`, which ensures that existing data references (which have the lifetime
171    /// of an immutable `self` reference) cannot be kept.
172    ///
173    /// Areas that cannot be discarded (because of format-inherent alignment restrictions) are
174    /// still overwritten with zeroes, unless discarding is not supported altogether.
175    pub fn discard_to_zero(&mut self, offset: u64, length: u64) -> io::Result<()> {
176        self.runtime
177            .block_on(self.inner.discard_to_zero(offset, length))
178    }
179
180    /// Discard the given range, ensure it is read back as zeroes.
181    ///
182    /// Unsafe variant of [`SyncFormatAccess::discard_to_zero()`], only requiring an immutable
183    /// `&self`.
184    ///
185    /// # Safety
186    ///
187    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
188    /// all concurrently existing data mappings they have.  Note that this includes concurrent
189    /// accesses through this type ([`SyncFormatAccess`]), which may hold these mappings internally
190    /// while they run.
191    ///
192    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
193    /// safe variant [`SyncFormatAccess::discard_to_zero()`].
194    pub unsafe fn discard_to_zero_unsafe(&self, offset: u64, length: u64) -> io::Result<()> {
195        // Safe: Caller guarantees this is safe
196        self.runtime
197            .block_on(unsafe { self.inner.discard_to_zero_unsafe(offset, length) })
198    }
199
200    /// Discard the given range, not guaranteeing specific data on read-back.
201    ///
202    /// Discard as much of the given range as possible, and keep the rest as-is.  Does not
203    /// guarantee any specific data on read-back, in contrast to
204    /// [`SyncFormatAccess::discard_to_zero()`].
205    ///
206    /// Discarding being unsupported by this format is still returned as an error
207    /// ([`std::io::ErrorKind::Unsupported`])
208    pub fn discard_to_any(&mut self, offset: u64, length: u64) -> io::Result<()> {
209        self.runtime
210            .block_on(self.inner.discard_to_any(offset, length))
211    }
212
213    /// Discard the given range, not guaranteeing specific data on read-back.
214    ///
215    /// Unsafe variant of [`SyncFormatAccess::discard_to_any()`], only requiring an immutable
216    /// `&self`.
217    ///
218    /// # Safety
219    ///
220    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
221    /// all concurrently existing data mappings they have.  Note that this includes concurrent
222    /// accesses through this type ([`SyncFormatAccess`]), which may hold these mappings internally
223    /// while they run.
224    ///
225    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
226    /// safe variant [`SyncFormatAccess::discard_to_any()`].
227    pub unsafe fn discard_to_any_unsafe(&self, offset: u64, length: u64) -> io::Result<()> {
228        // Safe: Caller guarantees this is safe
229        self.runtime
230            .block_on(unsafe { self.inner.discard_to_any_unsafe(offset, length) })
231    }
232
233    /// Discard the given range, such that the backing image becomes visible.
234    ///
235    /// Discard as much of the given range as possible so that a backing image’s data becomes
236    /// visible, and keep the rest as-is.  This breaks existing data mappings, so needs a mutable
237    /// reference to `self`, which ensures that existing data references (which have the lifetime
238    /// of an immutable `self` reference) cannot be kept.
239    pub fn discard_to_backing(&mut self, offset: u64, length: u64) -> io::Result<()> {
240        self.runtime
241            .block_on(self.inner.discard_to_backing(offset, length))
242    }
243
244    /// Discard the given range, such that the backing image becomes visible.
245    ///
246    /// Unsafe variant of [`SyncFormatAccess::discard_to_backing()`], only requiring an immutable
247    /// `&self`.
248    ///
249    /// # Safety
250    ///
251    /// This function may invalidate existing data mappings.  The caller must ensure to invalidate
252    /// all concurrently existing data mappings they have.  Note that this includes concurrent
253    /// accesses through this type ([`SyncFormatAccess`]), which may hold these mappings internally
254    /// while they run.
255    ///
256    /// One way to ensure safety is to have a mutable reference to `self`, which allows using the
257    /// safe variant [`SyncFormatAccess::discard_to_backing()`].
258    pub unsafe fn discard_to_backing_unsafe(&self, offset: u64, length: u64) -> io::Result<()> {
259        // Safe: Caller guarantees this is safe
260        self.runtime
261            .block_on(unsafe { self.inner.discard_to_backing_unsafe(offset, length) })
262    }
263
264    /// Flush internal buffers.
265    ///
266    /// Does not necessarily sync those buffers to disk.  When using `flush()`, consider whether
267    /// you want to call `sync()` afterwards.
268    ///
269    /// Note that this will not drop the buffers, so they may still be used to serve later
270    /// accesses.  Use [`SyncFormatAccess::invalidate_cache()`] to drop all buffers.
271    pub fn flush(&self) -> io::Result<()> {
272        self.runtime.block_on(self.inner.flush())
273    }
274
275    /// Sync data already written to the storage hardware.
276    ///
277    /// This does not necessarily include flushing internal buffers, i.e. `flush`.  When using
278    /// `sync()`, consider whether you want to call `flush()` before it.
279    pub fn sync(&self) -> io::Result<()> {
280        self.runtime.block_on(self.inner.sync())
281    }
282
283    /// Drop internal buffers.
284    ///
285    /// This drops all internal buffers, but does not flush them!  All cached data is reloaded from
286    /// disk on subsequent accesses.
287    ///
288    /// # Safety
289    /// Not flushing internal buffers may cause image corruption.  You must ensure the on-disk
290    /// state is consistent.
291    pub unsafe fn invalidate_cache(&self) -> io::Result<()> {
292        // Safety ensured by caller
293        self.runtime
294            .block_on(unsafe { self.inner.invalidate_cache() })
295    }
296
297    /// Resize to the given size.
298    ///
299    /// Set the disk size to `new_size`.  If `new_size` is smaller than the current size, ignore
300    /// both preallocation modes and discard the data after `new_size`.
301    ///
302    /// If `new_size` is larger than the current size, `prealloc_mode` determines whether and how
303    /// the new range should be allocated; depending on the image format, is possible some
304    /// preallocation modes are not supported, in which case an [`std::io::ErrorKind::Unsupported`]
305    /// is returned.
306    ///
307    /// This may break existing data mappings, so needs a mutable reference to `self`, which
308    /// ensures that existing data references (which have the lifetime of an immutable `self`
309    /// reference) cannot be kept.
310    ///
311    /// See also [`SyncFormatAccess::resize_grow()`] and [`SyncFormatAccess::resize_shrink()`],
312    /// whose more specialized interface may be useful when you know whether you want to grow or
313    /// shrink the image.
314    pub fn resize(&mut self, new_size: u64, prealloc_mode: PreallocateMode) -> io::Result<()> {
315        self.runtime
316            .block_on(self.inner.resize(new_size, prealloc_mode))
317    }
318
319    /// Resize to the given size, which must be greater than the current size.
320    ///
321    /// Set the disk size to `new_size`, preallocating the new space according to `prealloc_mode`.
322    /// Depending on the image format, it is possible some preallocation modes are not supported,
323    /// in which case an [`std::io::ErrorKind::Unsupported`] is returned.
324    pub fn resize_grow(&self, new_size: u64, prealloc_mode: PreallocateMode) -> io::Result<()> {
325        self.runtime
326            .block_on(self.inner.resize_grow(new_size, prealloc_mode))
327    }
328
329    /// Truncate to the given size, which must be smaller than the current size.
330    ///
331    /// Set the disk size to `new_size`, discarding the data after `new_size`.
332    ///
333    /// May break existing data mappings thanks to the mutable `self` reference.
334    pub fn resize_shrink(&mut self, new_size: u64) -> io::Result<()> {
335        self.runtime.block_on(self.inner.resize_shrink(new_size))
336    }
337}
338
339impl<S: Storage> TryFrom<FormatAccess<S>> for SyncFormatAccess<S> {
340    type Error = io::Error;
341
342    fn try_from(async_access: FormatAccess<S>) -> io::Result<Self> {
343        let runtime = tokio::runtime::Builder::new_current_thread()
344            .build()
345            .map_err(|err| {
346                io::Error::other(format!(
347                    "Failed to create a tokio runtime for synchronous image access: {err}"
348                ))
349            })?;
350
351        Ok(SyncFormatAccess {
352            inner: async_access,
353            runtime,
354        })
355    }
356}
357
358// #[cfg(not(feature = "async-drop"))]
359impl<S: Storage> Drop for SyncFormatAccess<S> {
360    fn drop(&mut self) {
361        if let Err(err) = self.flush() {
362            let inner = &self.inner;
363            tracing::error!("Failed to flush {inner}: {err}");
364        }
365    }
366}