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 mem_align = self.mem_align();
105        let req_align = self.req_align();
106
107        if is_aligned(&bufv, offset, mem_align, req_align) {
108            // Safe: Alignment checked
109            return unsafe { self.pure_readv(bufv, offset) }.await;
110        }
111
112        trace!(
113            "Unaligned read: 0x{offset:x} + {} (size: {:#x})",
114            bufv.len(),
115            self.size().unwrap()
116        );
117
118        let req_align_mask = req_align as u64 - 1;
119        // Length must be aligned to both memory and request alignments
120        let len_align_mask = req_align_mask | (mem_align as u64 - 1);
121        debug_assert!((len_align_mask + 1).is_multiple_of(req_align as u64));
122
123        let unpadded_end = offset + bufv.len();
124        let padded_offset = offset & !req_align_mask;
125        // This will over-align at the end of file (aligning to exactly the end of file would be
126        // sufficient), but it is easier this way.
127        let padded_end = (unpadded_end + req_align_mask) & !req_align_mask;
128        // Now also align to memory alignment
129        let padded_len = (padded_end - padded_offset + len_align_mask) & !(len_align_mask);
130        let padded_end = padded_offset + padded_len;
131
132        let padded_len: usize = (padded_end - padded_offset)
133            .try_into()
134            .map_err(|e| io::Error::other(format!("Cannot realign read: {e}")))?;
135
136        trace!("Padded read: {padded_offset:#x} + {padded_len}");
137
138        let mut bounce_buf = IoBuffer::new(padded_len, mem_align)?;
139
140        // Safe: Alignment enforced
141        unsafe { self.pure_readv(bounce_buf.as_mut().into(), padded_offset) }.await?;
142
143        let in_buf_ofs = (offset - padded_offset) as usize;
144        // Must fit in `usize` because `padded_len: usize`
145        let in_buf_end = (unpadded_end - padded_offset) as usize;
146
147        bufv.copy_from_slice(bounce_buf.as_ref_range(in_buf_ofs..in_buf_end).into_slice());
148
149        Ok(())
150    }
151
152    async fn writev(&self, bufv: IoVector<'_>, offset: u64) -> io::Result<()> {
153        if bufv.is_empty() {
154            return Ok(());
155        }
156
157        let mem_align = self.mem_align();
158        let req_align = self.req_align();
159
160        if is_aligned(&bufv, offset, mem_align, req_align) {
161            let _sw_guard = self.weak_write_blocker(offset..(offset + bufv.len())).await;
162
163            // Safe: Alignment checked, and weak write blocker set up
164            return unsafe { self.pure_writev(bufv, offset) }.await;
165        }
166
167        trace!(
168            "Unaligned write: {offset:#x} + {} (size: {:#x})",
169            bufv.len(),
170            self.size().unwrap()
171        );
172
173        let req_align_mask = req_align - 1;
174        // Length must be aligned to both memory and request alignments
175        let len_align_mask = req_align_mask | (mem_align - 1);
176        let len_align = req_align_mask + 1;
177        debug_assert!(len_align.is_multiple_of(req_align));
178
179        let unpadded_end = offset + bufv.len();
180        let padded_offset = offset & !(req_align_mask as u64);
181        // This will over-align at the end of file (aligning to exactly the end of file would be
182        // sufficient), but it is easier this way.  Small TODO, as this will indeed increase the
183        // file length (which the over-alignment in `unaligned_readv()` does not).
184        let padded_end = (unpadded_end + req_align_mask as u64) & !(req_align_mask as u64);
185        // Now also align to memory alignment
186        let padded_len =
187            (padded_end - padded_offset + len_align_mask as u64) & !(len_align_mask as u64);
188        let padded_end = padded_offset + padded_len;
189
190        let padded_len: usize = (padded_end - padded_offset)
191            .try_into()
192            .map_err(|e| io::Error::other(format!("Cannot realign write: {e}")))?;
193
194        trace!("Padded write: {padded_offset:#x} + {padded_len}");
195
196        let mut bounce_buf = IoBuffer::new(padded_len, mem_align)?;
197        assert!(padded_len >= len_align && padded_len & len_align_mask == 0);
198
199        // For the strong blocker, just the RMW regions (head and tail) would be enough.  However,
200        // we don’t expect any concurrent writes to the non-RMW (pure write) regions (it is
201        // unlikely that the guest would write to the same area twice concurrently), so we don’t
202        // need to optimize for it.  On the other hand, writes to the RMW regions are likely
203        // (adjacent writes), so those will be blocked either way.
204        // Instating fewer blockers makes them less expensive to check, though.
205        let _sw_guard = self.strong_write_blocker(padded_offset..padded_end).await;
206
207        let in_buf_ofs = (offset - padded_offset) as usize;
208        // Must fit in `usize` because `padded_len: usize`
209        let in_buf_end = (unpadded_end - padded_offset) as usize;
210
211        // RMW part 1: Read
212
213        let head_len = in_buf_ofs;
214        let aligned_head_len = (head_len + len_align_mask) & !len_align_mask;
215
216        let tail_len = padded_len - in_buf_end;
217        let aligned_tail_len = (tail_len + len_align_mask) & !len_align_mask;
218
219        if aligned_head_len + aligned_tail_len == padded_len {
220            // Must read the whole bounce buffer
221            // Safe: Alignment enforced
222            unsafe { self.pure_readv(bounce_buf.as_mut().into(), padded_offset) }.await?;
223        } else {
224            if aligned_head_len > 0 {
225                let head_bufv = bounce_buf.as_mut_range(0..aligned_head_len).into();
226                // Safe: Alignment enforced
227                unsafe { self.pure_readv(head_bufv, padded_offset) }.await?;
228            }
229            if aligned_tail_len > 0 {
230                let tail_start = padded_len - aligned_tail_len;
231                let tail_bufv = bounce_buf.as_mut_range(tail_start..padded_len).into();
232                // Safe: Alignment enforced
233                unsafe { self.pure_readv(tail_bufv, padded_offset + tail_start as u64) }.await?;
234            }
235        }
236
237        // RMW part 2: Modify
238        bufv.copy_into_slice(bounce_buf.as_mut_range(in_buf_ofs..in_buf_end).into_slice());
239
240        // RMW part 3: Write
241        // Safe: Alignment enforced, and strong write blocker set up
242        unsafe { self.pure_writev(bounce_buf.as_ref().into(), padded_offset) }.await
243    }
244
245    async fn read<'a>(&'a self, buf: impl Into<IoVectorMut<'a>>, offset: u64) -> io::Result<()> {
246        self.readv(buf.into(), offset).await
247    }
248
249    async fn write<'a>(&'a self, buf: impl Into<IoVector<'a>>, offset: u64) -> io::Result<()> {
250        self.writev(buf.into(), offset).await
251    }
252
253    async fn write_zeroes(&self, offset: u64, length: u64) -> io::Result<()> {
254        write_efficient_zeroes(self, offset, length, false).await
255    }
256
257    async fn write_allocated_zeroes(&self, offset: u64, length: u64) -> io::Result<()> {
258        write_efficient_zeroes(self, offset, length, true).await
259    }
260
261    async fn discard(&self, offset: u64, length: u64) -> io::Result<()> {
262        let discard_align = self.discard_align();
263        debug_assert!(discard_align.is_power_of_two());
264        let align_mask = discard_align as u64 - 1;
265
266        let unaligned_end = offset
267            .checked_add(length)
268            .ok_or_else(|| io::Error::other("Discard wrap-around"))?;
269        let aligned_offset = (offset + align_mask) & !align_mask;
270        let aligned_end = unaligned_end & !align_mask;
271
272        if aligned_end > aligned_offset {
273            let _sw_guard = self.weak_write_blocker(aligned_offset..aligned_end).await;
274            let aligned_len = aligned_end - aligned_offset;
275            // Safe: Alignment checked, and weak write blocker set up
276            if let Err(err) = unsafe { self.pure_discard(aligned_offset, aligned_len) }.await {
277                // Ignore ENOTSUP errors: Where the fall-back for write-zeroes in case of ENOTSUP
278                // is `write_full_zeroes()`, in case of discard, we don’t need to do anything,
279                // because the state after discard is undefined anyway (so a no-op is OK).  So the
280                // fall-back is just to return `Ok(())`.
281                if err.kind() != io::ErrorKind::Unsupported {
282                    return Err(err);
283                }
284            }
285        }
286
287        // Nothing to do for the unaligned part; discarding is always just advisory.
288
289        Ok(())
290    }
291
292    async fn weak_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_> {
293        self.get_storage_helper().weak_write_blocker(range).await
294    }
295
296    async fn strong_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_> {
297        self.get_storage_helper().strong_write_blocker(range).await
298    }
299}
300
301/// Check whether the given request is aligned.
302fn is_aligned<V: IoVectorTrait>(bufv: &V, offset: u64, mem_align: usize, req_align: usize) -> bool {
303    debug_assert!(mem_align.is_power_of_two() && req_align.is_power_of_two());
304
305    let req_align_mask = req_align as u64 - 1;
306
307    if offset & req_align_mask != 0 {
308        false
309    } else if bufv.len() & req_align_mask == 0 {
310        bufv.is_aligned(mem_align, req_align)
311    } else {
312        false
313    }
314}
315
316/// Write zero data to the given area.
317///
318/// In contrast to `write_zeroes()` functions, this one will actually write zero data, fully
319/// allocated.
320#[maybe_async]
321pub(crate) async fn write_full_zeroes<S: StorageExt>(
322    storage: S,
323    mut offset: u64,
324    mut length: u64,
325) -> io::Result<()> {
326    let req_align = storage.req_align();
327    let req_align_mask = (req_align - 1) as u64;
328
329    let mem_align = storage.mem_align();
330    let max_chunk_len = cmp::max(cmp::max(req_align, mem_align), 1048576) as u64;
331    let buflen = cmp::min(length, max_chunk_len) as usize;
332    let mut buf = IoBuffer::new(buflen, storage.mem_align())?;
333    buf.as_mut().into_slice().fill(0);
334
335    while length > 0 {
336        let mut chunk_length = cmp::min(length, max_chunk_len) as usize;
337        if offset & req_align_mask != 0 {
338            chunk_length = cmp::min(chunk_length, req_align - (offset & req_align_mask) as usize);
339        }
340        storage
341            .write(buf.as_ref_range(0..chunk_length), offset)
342            .await?;
343        offset += chunk_length as u64;
344        length -= chunk_length as u64;
345    }
346
347    Ok(())
348}
349
350/// Write zeroes efficiently to the given area.
351///
352/// This implements `write_zeroes()` and `write_allocated_zeroes()`.
353///
354/// If `allocate` is `true`, use [`Storage::pure_write_allocated_zeroes()`]; else, use
355/// [`Storage::pure_write_zeroes()`].
356///
357/// If the `pure_*` call fails with [`io::ErrorKind::Unsupported`], fall back to
358/// [`write_full_zeroes()`].
359#[maybe_async]
360pub(crate) async fn write_efficient_zeroes<S: StorageExt>(
361    storage: S,
362    offset: u64,
363    length: u64,
364    allocate: bool,
365) -> io::Result<()> {
366    if length == 0 {
367        return Ok(());
368    }
369
370    let zero_align = storage.zero_align();
371    debug_assert!(zero_align.is_power_of_two());
372    let align_mask = zero_align as u64 - 1;
373
374    let unaligned_end = offset
375        .checked_add(length)
376        .ok_or_else(|| io::Error::other("Zero-write wrap-around"))?;
377    let aligned_offset = (offset + align_mask) & !align_mask;
378    let aligned_end = unaligned_end & !align_mask;
379
380    if aligned_end > aligned_offset {
381        let result = {
382            let _sw_guard = storage
383                .weak_write_blocker(aligned_offset..aligned_end)
384                .await;
385            // Safe: Alignment checked, and weak write blocker set up
386            if allocate {
387                unsafe {
388                    storage
389                        .pure_write_allocated_zeroes(aligned_offset, aligned_end - aligned_offset)
390                }
391                .await
392            } else {
393                unsafe { storage.pure_write_zeroes(aligned_offset, aligned_end - aligned_offset) }
394                    .await
395            }
396        };
397        if let Err(err) = result {
398            return if err.kind() == io::ErrorKind::Unsupported {
399                write_full_zeroes(storage, offset, length).await
400            } else {
401                Err(err)
402            };
403        }
404    }
405
406    let zero_buf = if aligned_offset > offset || aligned_end < unaligned_end {
407        let buflen = if aligned_offset >= aligned_end {
408            unaligned_end - offset
409        } else {
410            cmp::max(aligned_offset - offset, unaligned_end - aligned_end)
411        };
412        let mut buf = IoBuffer::new(buflen as usize, storage.mem_align())?;
413        buf.as_mut().into_slice().fill(0);
414        Some(buf)
415    } else {
416        None
417    };
418
419    if aligned_offset >= aligned_end {
420        let buf = zero_buf
421            .as_ref()
422            .unwrap()
423            .as_ref_range(0..((unaligned_end - offset) as usize));
424        storage.write(buf, offset).await?;
425    } else {
426        if aligned_offset > offset {
427            assert!(aligned_offset <= unaligned_end);
428            let buf = zero_buf
429                .as_ref()
430                .unwrap()
431                .as_ref_range(0..((aligned_offset - offset) as usize));
432            storage.write(buf, offset).await?;
433        }
434        if aligned_end < unaligned_end {
435            assert!(aligned_end >= offset);
436            let buf = zero_buf
437                .as_ref()
438                .unwrap()
439                .as_ref_range(0..((unaligned_end - aligned_end) as usize));
440            storage.write(buf, aligned_end).await?;
441        }
442    }
443
444    Ok(())
445}