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}