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