Skip to main content

imago/storage/
ext.rs

1//! Provides the `StorageExt` struct for more convenient access.
2//!
3//! `Storage` is provided by the driver, so is supposed to be simple and only contain what’s
4//! necessary.  `StorageExt` builds on that to provide more convenient access, e.g. allows
5//! unaligned requests and provides write serialization.
6
7use super::drivers::RangeBlockedGuard;
8use crate::io_buffers::{IoBuffer, IoVector, IoVectorMut, IoVectorTrait};
9use crate::Storage;
10use maybe_async::maybe_async;
11use std::ops::Range;
12use std::{cmp, io};
13use tracing::trace;
14
15/// Helper methods for storage objects.
16///
17/// Provides some more convenient methods for accessing storage objects.
18#[maybe_async(AFIT)]
19pub trait StorageExt: Storage {
20    /// Read data at `offset` into `bufv`.
21    ///
22    /// Reads until `bufv` is filled completely, i.e. will not do short reads.  When reaching the
23    /// end of file, the rest of `bufv` is filled with 0.
24    ///
25    /// Checks alignment.  If anything does not meet the requirements, enforces it (using ephemeral
26    /// bounce buffers).
27    #[allow(async_fn_in_trait)] // No need for Send
28    async fn readv(&self, bufv: IoVectorMut<'_>, offset: u64) -> io::Result<()>;
29
30    /// Write data from `bufv` to `offset`.
31    ///
32    /// Writes all data from `bufv`, i.e. will not do short writes.  When reaching the end of file,
33    /// it is grown as necessary so that the new end of file will be at `offset + bufv.len()`.
34    ///
35    /// If growing is not possible, expect writes beyond the end of file (even if only partially)
36    /// to fail.
37    ///
38    /// Checks alignment.  If anything does not meet the requirements, enforces it using bounce
39    /// buffers and a read-modify-write cycle that blocks concurrent writes to the affected area.
40    #[allow(async_fn_in_trait)] // No need for Send
41    async fn writev(&self, bufv: IoVector<'_>, offset: u64) -> io::Result<()>;
42
43    /// Read data at `offset` into `buf`.
44    ///
45    /// Reads until `buf` is filled completely, i.e. will not do short reads.  When reaching the
46    /// end of file, the rest of `buf` is filled with 0.
47    ///
48    /// Checks alignment.  If anything does not meet the requirements, enforces it (using ephemeral
49    /// bounce buffers).
50    #[allow(async_fn_in_trait)] // No need for Send
51    async fn read<'a>(&'a self, buf: impl Into<IoVectorMut<'a>>, offset: u64) -> io::Result<()>;
52
53    /// Write data from `buf` to `offset`.
54    ///
55    /// Writes all data from `buf`, i.e. will not do short writes.  When reaching the end of file,
56    /// it is grown as necessary so that the new end of file will be at `offset + buf.len()`.
57    ///
58    /// If growing is not possible, expect writes beyond the end of file (even if only partially)
59    /// to fail.
60    ///
61    /// Checks alignment.  If anything does not meet the requirements, enforces it using bounce
62    /// buffers and a read-modify-write cycle that blocks concurrent writes to the affected area.
63    #[allow(async_fn_in_trait)] // No need for Send
64    async fn write<'a>(&'a self, buf: impl Into<IoVector<'a>>, offset: u64) -> io::Result<()>;
65
66    /// Ensure the given range reads back as zeroes.
67    #[allow(async_fn_in_trait)] // No need for Send
68    async fn write_zeroes(&self, offset: u64, length: u64) -> io::Result<()>;
69
70    /// Ensure the given range is allocated and reads back as zeroes.
71    #[allow(async_fn_in_trait)] // No need for Send
72    async fn write_allocated_zeroes(&self, offset: u64, length: u64) -> io::Result<()>;
73
74    /// Discard the given range, with undefined contents when read back.
75    ///
76    /// Tell the storage layer this range is no longer needed and need not be backed by actual
77    /// storage.  When read back, the data read will be undefined, i.e. not necessarily zeroes.
78    #[allow(async_fn_in_trait)] // No need for Send
79    async fn discard(&self, offset: u64, length: u64) -> io::Result<()>;
80
81    /// Await concurrent strong write blockers for the given range.
82    ///
83    /// Strong write blockers are set up for writes that must not be intersected by any other
84    /// write.  Await such intersecting concurrent write requests, and return a guard that will
85    /// delay such new writes until the guard is dropped.
86    #[allow(async_fn_in_trait)] // No need for Send
87    async fn weak_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_>;
88
89    /// Await any concurrent write request for the given range.
90    ///
91    /// Block the given range for any concurrent write requests until the returned guard object is
92    /// dropped.  Existing requests are awaited, and new ones will be delayed.
93    #[allow(async_fn_in_trait)] // No need for Send
94    async fn strong_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_>;
95}
96
97#[maybe_async(AFIT)]
98impl<S: Storage> StorageExt for S {
99    async fn readv(&self, mut bufv: IoVectorMut<'_>, offset: u64) -> io::Result<()> {
100        if bufv.is_empty() {
101            return Ok(());
102        }
103
104        let unpadded_end = offset
105            .checked_add(bufv.len())
106            .ok_or_else(|| io::Error::other("Read wrap-around"))?;
107
108        let mem_align = self.mem_align();
109        let req_align = self.req_align();
110
111        if is_aligned(&bufv, offset, mem_align, req_align) {
112            // Safe: Alignment checked
113            return unsafe { self.pure_readv(bufv, offset) }.await;
114        }
115
116        trace!(
117            "Unaligned read: 0x{offset:x} + {} (size: {:#x})",
118            bufv.len(),
119            self.size().unwrap()
120        );
121
122        let req_align_mask = req_align as u64 - 1;
123        // Length must be aligned to both memory and request alignments
124        let len_align_mask = req_align_mask | (mem_align as u64 - 1);
125        debug_assert!((len_align_mask + 1).is_multiple_of(req_align as u64));
126
127        let padded_offset = offset & !req_align_mask;
128        // This will over-align at the end of file (aligning to exactly the end of file would be
129        // sufficient), but it is easier this way.
130        let padded_end = unpadded_end
131            .checked_add(req_align_mask)
132            .ok_or_else(|| io::Error::other("Read wrap-around"))?
133            & !req_align_mask;
134        // Now also align to memory alignment
135        let padded_len = (padded_end - padded_offset + len_align_mask) & !(len_align_mask);
136        let padded_end = padded_offset
137            .checked_add(padded_len)
138            .ok_or_else(|| io::Error::other("Read wrap-around"))?;
139
140        let padded_len: usize = (padded_end - padded_offset)
141            .try_into()
142            .map_err(|e| io::Error::other(format!("Cannot realign read: {e}")))?;
143
144        trace!("Padded read: {padded_offset:#x} + {padded_len}");
145
146        let mut bounce_buf = IoBuffer::new(padded_len, mem_align)?;
147
148        // Safe: Alignment enforced
149        unsafe { self.pure_readv(bounce_buf.as_mut().into(), padded_offset) }.await?;
150
151        let in_buf_ofs = (offset - padded_offset) as usize;
152        // Must fit in `usize` because `padded_len: usize`
153        let in_buf_end = (unpadded_end - padded_offset) as usize;
154
155        bufv.copy_from_slice(bounce_buf.as_ref_range(in_buf_ofs..in_buf_end).into_slice());
156
157        Ok(())
158    }
159
160    async fn writev(&self, bufv: IoVector<'_>, offset: u64) -> io::Result<()> {
161        if bufv.is_empty() {
162            return Ok(());
163        }
164
165        let unpadded_end = offset
166            .checked_add(bufv.len())
167            .ok_or_else(|| io::Error::other("Write wrap-around"))?;
168
169        let mem_align = self.mem_align();
170        let req_align = self.req_align();
171
172        if is_aligned(&bufv, offset, mem_align, req_align) {
173            let _sw_guard = self.weak_write_blocker(offset..unpadded_end).await;
174
175            // Safe: Alignment checked, and weak write blocker set up
176            return unsafe { self.pure_writev(bufv, offset) }.await;
177        }
178
179        trace!(
180            "Unaligned write: {offset:#x} + {} (size: {:#x})",
181            bufv.len(),
182            self.size().unwrap()
183        );
184
185        let req_align_mask = req_align - 1;
186        // Length must be aligned to both memory and request alignments
187        let len_align_mask = req_align_mask | (mem_align - 1);
188        let len_align = req_align_mask + 1;
189        debug_assert!(len_align.is_multiple_of(req_align));
190
191        let padded_offset = offset & !(req_align_mask as u64);
192        // This will over-align at the end of file (aligning to exactly the end of file would be
193        // sufficient), but it is easier this way.  Small TODO, as this will indeed increase the
194        // file length (which the over-alignment in `unaligned_readv()` does not).
195        let padded_end = unpadded_end
196            .checked_add(req_align_mask as u64)
197            .ok_or_else(|| io::Error::other("Write wrap-around"))?
198            & !(req_align_mask as u64);
199        // Now also align to memory alignment
200        let padded_len =
201            (padded_end - padded_offset + len_align_mask as u64) & !(len_align_mask as u64);
202        let padded_end = padded_offset
203            .checked_add(padded_len)
204            .ok_or_else(|| io::Error::other("Write wrap-around"))?;
205
206        let padded_len: usize = (padded_end - padded_offset)
207            .try_into()
208            .map_err(|e| io::Error::other(format!("Cannot realign write: {e}")))?;
209
210        trace!("Padded write: {padded_offset:#x} + {padded_len}");
211
212        let mut bounce_buf = IoBuffer::new(padded_len, mem_align)?;
213        assert!(padded_len >= len_align && padded_len & len_align_mask == 0);
214
215        // For the strong blocker, just the RMW regions (head and tail) would be enough.  However,
216        // we don’t expect any concurrent writes to the non-RMW (pure write) regions (it is
217        // unlikely that the guest would write to the same area twice concurrently), so we don’t
218        // need to optimize for it.  On the other hand, writes to the RMW regions are likely
219        // (adjacent writes), so those will be blocked either way.
220        // Instating fewer blockers makes them less expensive to check, though.
221        let _sw_guard = self.strong_write_blocker(padded_offset..padded_end).await;
222
223        let in_buf_ofs = (offset - padded_offset) as usize;
224        // Must fit in `usize` because `padded_len: usize`
225        let in_buf_end = (unpadded_end - padded_offset) as usize;
226
227        // RMW part 1: Read
228
229        let head_len = in_buf_ofs;
230        let aligned_head_len = (head_len + len_align_mask) & !len_align_mask;
231
232        let tail_len = padded_len - in_buf_end;
233        let aligned_tail_len = (tail_len + len_align_mask) & !len_align_mask;
234
235        if aligned_head_len + aligned_tail_len == padded_len {
236            // Must read the whole bounce buffer
237            // Safe: Alignment enforced
238            unsafe { self.pure_readv(bounce_buf.as_mut().into(), padded_offset) }.await?;
239        } else {
240            if aligned_head_len > 0 {
241                let head_bufv = bounce_buf.as_mut_range(0..aligned_head_len).into();
242                // Safe: Alignment enforced
243                unsafe { self.pure_readv(head_bufv, padded_offset) }.await?;
244            }
245            if aligned_tail_len > 0 {
246                let tail_start = padded_len - aligned_tail_len;
247                let tail_bufv = bounce_buf.as_mut_range(tail_start..padded_len).into();
248                // Safe: Alignment enforced
249                unsafe { self.pure_readv(tail_bufv, padded_offset + tail_start as u64) }.await?;
250            }
251        }
252
253        // RMW part 2: Modify
254        bufv.copy_into_slice(bounce_buf.as_mut_range(in_buf_ofs..in_buf_end).into_slice());
255
256        // RMW part 3: Write
257        // Safe: Alignment enforced, and strong write blocker set up
258        unsafe { self.pure_writev(bounce_buf.as_ref().into(), padded_offset) }.await
259    }
260
261    async fn read<'a>(&'a self, buf: impl Into<IoVectorMut<'a>>, offset: u64) -> io::Result<()> {
262        self.readv(buf.into(), offset).await
263    }
264
265    async fn write<'a>(&'a self, buf: impl Into<IoVector<'a>>, offset: u64) -> io::Result<()> {
266        self.writev(buf.into(), offset).await
267    }
268
269    async fn write_zeroes(&self, offset: u64, length: u64) -> io::Result<()> {
270        write_efficient_zeroes(self, offset, length, false).await
271    }
272
273    async fn write_allocated_zeroes(&self, offset: u64, length: u64) -> io::Result<()> {
274        write_efficient_zeroes(self, offset, length, true).await
275    }
276
277    async fn discard(&self, offset: u64, length: u64) -> io::Result<()> {
278        let discard_align = self.discard_align();
279        debug_assert!(discard_align.is_power_of_two());
280        let align_mask = discard_align as u64 - 1;
281
282        let unaligned_end = offset
283            .checked_add(length)
284            .ok_or_else(|| io::Error::other("Discard wrap-around"))?;
285        let aligned_offset = offset
286            .checked_add(align_mask)
287            .ok_or_else(|| io::Error::other("Discard wrap-around"))?
288            & !align_mask;
289        let aligned_end = unaligned_end & !align_mask;
290
291        if aligned_end > aligned_offset {
292            let _sw_guard = self.weak_write_blocker(aligned_offset..aligned_end).await;
293            let aligned_len = aligned_end - aligned_offset;
294            // Safe: Alignment checked, and weak write blocker set up
295            if let Err(err) = unsafe { self.pure_discard(aligned_offset, aligned_len) }.await {
296                // Ignore ENOTSUP errors: Where the fall-back for write-zeroes in case of ENOTSUP
297                // is `write_full_zeroes()`, in case of discard, we don’t need to do anything,
298                // because the state after discard is undefined anyway (so a no-op is OK).  So the
299                // fall-back is just to return `Ok(())`.
300                if err.kind() != io::ErrorKind::Unsupported {
301                    return Err(err);
302                }
303            }
304        }
305
306        // Nothing to do for the unaligned part; discarding is always just advisory.
307
308        Ok(())
309    }
310
311    async fn weak_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_> {
312        self.get_storage_helper().weak_write_blocker(range).await
313    }
314
315    async fn strong_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_> {
316        self.get_storage_helper().strong_write_blocker(range).await
317    }
318}
319
320/// Check whether the given request is aligned.
321fn is_aligned<V: IoVectorTrait>(bufv: &V, offset: u64, mem_align: usize, req_align: usize) -> bool {
322    debug_assert!(mem_align.is_power_of_two() && req_align.is_power_of_two());
323
324    let req_align_mask = req_align as u64 - 1;
325
326    if offset & req_align_mask != 0 {
327        false
328    } else if bufv.len() & req_align_mask == 0 {
329        bufv.is_aligned(mem_align, req_align)
330    } else {
331        false
332    }
333}
334
335/// Write zero data to the given area.
336///
337/// In contrast to `write_zeroes()` functions, this one will actually write zero data, fully
338/// allocated.
339#[maybe_async]
340pub(crate) async fn write_full_zeroes<S: StorageExt>(
341    storage: S,
342    mut offset: u64,
343    mut length: u64,
344) -> io::Result<()> {
345    let req_align = storage.req_align();
346    let req_align_mask = (req_align - 1) as u64;
347
348    let mem_align = storage.mem_align();
349    let max_chunk_len = cmp::max(cmp::max(req_align, mem_align), 1048576) as u64;
350    let buflen = cmp::min(length, max_chunk_len) as usize;
351    let mut buf = IoBuffer::new(buflen, storage.mem_align())?;
352    buf.as_mut().into_slice().fill(0);
353
354    while length > 0 {
355        let mut chunk_length = cmp::min(length, max_chunk_len) as usize;
356        if offset & req_align_mask != 0 {
357            chunk_length = cmp::min(chunk_length, req_align - (offset & req_align_mask) as usize);
358        }
359        storage
360            .write(buf.as_ref_range(0..chunk_length), offset)
361            .await?;
362        offset += chunk_length as u64;
363        length -= chunk_length as u64;
364    }
365
366    Ok(())
367}
368
369/// Write zeroes efficiently to the given area.
370///
371/// This implements `write_zeroes()` and `write_allocated_zeroes()`.
372///
373/// If `allocate` is `true`, use [`Storage::pure_write_allocated_zeroes()`]; else, use
374/// [`Storage::pure_write_zeroes()`].
375///
376/// If the `pure_*` call fails with [`io::ErrorKind::Unsupported`], fall back to
377/// [`write_full_zeroes()`].
378#[maybe_async]
379pub(crate) async fn write_efficient_zeroes<S: StorageExt>(
380    storage: S,
381    offset: u64,
382    length: u64,
383    allocate: bool,
384) -> io::Result<()> {
385    if length == 0 {
386        return Ok(());
387    }
388
389    let zero_align = storage.zero_align();
390    debug_assert!(zero_align.is_power_of_two());
391    let align_mask = zero_align as u64 - 1;
392
393    let unaligned_end = offset
394        .checked_add(length)
395        .ok_or_else(|| io::Error::other("Zero-write wrap-around"))?;
396    let aligned_offset = offset
397        .checked_add(align_mask)
398        .ok_or_else(|| io::Error::other("Zero-write wrap-around"))?
399        & !align_mask;
400    let aligned_end = unaligned_end & !align_mask;
401
402    if aligned_end > aligned_offset {
403        let result = {
404            let _sw_guard = storage
405                .weak_write_blocker(aligned_offset..aligned_end)
406                .await;
407            // Safe: Alignment checked, and weak write blocker set up
408            if allocate {
409                unsafe {
410                    storage
411                        .pure_write_allocated_zeroes(aligned_offset, aligned_end - aligned_offset)
412                }
413                .await
414            } else {
415                unsafe { storage.pure_write_zeroes(aligned_offset, aligned_end - aligned_offset) }
416                    .await
417            }
418        };
419        if let Err(err) = result {
420            return if err.kind() == io::ErrorKind::Unsupported {
421                write_full_zeroes(storage, offset, length).await
422            } else {
423                Err(err)
424            };
425        }
426    }
427
428    let zero_buf = if aligned_offset > offset || aligned_end < unaligned_end {
429        let buflen = if aligned_offset >= aligned_end {
430            unaligned_end - offset
431        } else {
432            cmp::max(aligned_offset - offset, unaligned_end - aligned_end)
433        };
434        let mut buf = IoBuffer::new(buflen as usize, storage.mem_align())?;
435        buf.as_mut().into_slice().fill(0);
436        Some(buf)
437    } else {
438        None
439    };
440
441    if aligned_offset >= aligned_end {
442        let buf = zero_buf
443            .as_ref()
444            .unwrap()
445            .as_ref_range(0..((unaligned_end - offset) as usize));
446        storage.write(buf, offset).await?;
447    } else {
448        if aligned_offset > offset {
449            assert!(aligned_offset <= unaligned_end);
450            let buf = zero_buf
451                .as_ref()
452                .unwrap()
453                .as_ref_range(0..((aligned_offset - offset) as usize));
454            storage.write(buf, offset).await?;
455        }
456        if aligned_end < unaligned_end {
457            assert!(aligned_end >= offset);
458            let buf = zero_buf
459                .as_ref()
460                .unwrap()
461                .as_ref_range(0..((unaligned_end - aligned_end) as usize));
462            storage.write(buf, aligned_end).await?;
463        }
464    }
465
466    Ok(())
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472    use crate::test::mem_storage::{MemStorage, MemStorageState};
473    use std::sync::Arc;
474
475    /// Requests whose end, or padded end, is beyond `u64::MAX` must return an error.
476    #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
477    async fn readv_writev_overflow() {
478        let state = Arc::new(MemStorageState::default());
479        let mut buf = vec![0u8; 8192];
480
481        // (`.await` must not be used inside of macros, or `maybe_async` will not remove it)
482
483        // Aligned: end overflows
484        let mem = MemStorage::new(Arc::clone(&state), false);
485        let offset = u64::MAX - 4095;
486        let result = mem.read(&mut buf[..], offset).await;
487        assert!(result.is_err());
488        let result = mem.write(&buf[..], offset).await;
489        assert!(result.is_err());
490
491        // Unaligned (request alignment 4096): end overflows
492        let mem = MemStorage::new(state, false).with_align(4096);
493        let offset = u64::MAX - 10;
494        let result = mem.read(&mut buf[..100], offset).await;
495        assert!(result.is_err());
496        let result = mem.write(&buf[..100], offset).await;
497        assert!(result.is_err());
498
499        // Unaligned: end does not overflow, but padded end does
500        let offset = u64::MAX - 100;
501        let result = mem.read(&mut buf[..50], offset).await;
502        assert!(result.is_err());
503        let result = mem.write(&buf[..50], offset).await;
504        assert!(result.is_err());
505    }
506
507    /// Discard and write-zeroes requests whose start, aligned up, is beyond `u64::MAX` must return
508    /// an error.
509    #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
510    async fn discard_write_zeroes_overflow() {
511        // Discard and zero alignment default to the request alignment
512        let mem = MemStorage::new(Arc::new(MemStorageState::default()), false).with_align(4096);
513
514        // End does not overflow, but the start aligned up does
515        let offset = u64::MAX - 10;
516        let result = mem.discard(offset, 5).await;
517        assert!(result.is_err());
518        let result = mem.write_zeroes(offset, 5).await;
519        assert!(result.is_err());
520        let result = mem.write_allocated_zeroes(offset, 5).await;
521        assert!(result.is_err());
522    }
523}