Skip to main content

disklayer_ram/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! RAM-backed disk layer implementation.
5
6#![forbid(unsafe_code)]
7
8pub mod resolver;
9
10use anyhow::Context;
11use disk_backend::Disk;
12use disk_backend::DiskError;
13use disk_backend::UnmapBehavior;
14use disk_layered::DiskLayer;
15use disk_layered::LayerAttach;
16use disk_layered::LayerConfiguration;
17use disk_layered::LayerIo;
18use disk_layered::LayeredDisk;
19use disk_layered::SectorMarker;
20use disk_layered::WriteNoOverwrite;
21use guestmem::MemoryRead;
22use guestmem::MemoryWrite;
23use inspect::Inspect;
24use parking_lot::RwLock;
25use scsi_buffers::RequestBuffers;
26use std::collections::BTreeMap;
27use std::collections::btree_map::Entry;
28use std::fmt;
29use std::fmt::Debug;
30use std::sync::atomic::AtomicU64;
31use std::sync::atomic::Ordering;
32use thiserror::Error;
33
34/// The default sector size in bytes.
35pub const DEFAULT_SECTOR_SIZE: u32 = 512;
36
37/// A RAM-backed disk layer that infers unspecified topology parameters from
38/// the lower layer at attach time.
39pub struct LazyRamDiskLayer {
40    len: Option<u64>,
41    sector_size: Option<u32>,
42}
43
44impl LazyRamDiskLayer {
45    /// Create a new lazy RAM-backed disk layer.
46    ///
47    /// By default, both size and sector size are inferred from the lower layer.
48    /// Use [`with_len`](Self::with_len) and
49    /// [`with_sector_size`](Self::with_sector_size) to override.
50    pub fn new() -> Self {
51        Self {
52            len: None,
53            sector_size: None,
54        }
55    }
56
57    /// Set the layer size in bytes.
58    ///
59    /// If not set, the size is inferred from the lower layer at attach time.
60    pub fn with_len(mut self, len: u64) -> Self {
61        self.len = Some(len);
62        self
63    }
64
65    /// Set the sector size in bytes.
66    ///
67    /// If not set, the sector size is inferred from the lower layer at attach
68    /// time, defaulting to 512 if there is no lower layer.
69    pub fn with_sector_size(mut self, sector_size: u32) -> Self {
70        self.sector_size = Some(sector_size);
71        self
72    }
73}
74
75/// A disk layer backed entirely by RAM.
76#[derive(Inspect)]
77#[inspect(extra = "Self::inspect_extra")]
78pub struct RamDiskLayer {
79    #[inspect(flatten)]
80    state: RwLock<RamState>,
81    #[inspect(skip)]
82    sector_count: AtomicU64,
83    #[inspect(skip)]
84    resize_event: event_listener::Event,
85    sector_size: u32,
86    #[inspect(skip)]
87    sector_shift: u32,
88}
89
90#[derive(Inspect)]
91struct RamState {
92    #[inspect(skip)]
93    data: BTreeMap<u64, Box<[u8]>>,
94    #[inspect(skip)] // handled in inspect_extra()
95    sector_count: u64,
96    zero_after: u64,
97}
98
99impl RamDiskLayer {
100    fn inspect_extra(&self, resp: &mut inspect::Response<'_>) {
101        resp.field_with("committed_size", || {
102            self.state.read().data.len() * self.sector_size as usize
103        })
104        .field_mut_with("sector_count", |new_count| {
105            if let Some(new_count) = new_count {
106                self.resize(new_count.parse().context("invalid sector count")?)?;
107            }
108            anyhow::Ok(self.sector_count())
109        });
110    }
111}
112
113impl Debug for RamDiskLayer {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        f.debug_struct("RamDiskLayer")
116            .field("sector_count", &self.sector_count)
117            .finish()
118    }
119}
120
121/// An error creating a RAM disk.
122#[derive(Error, Debug)]
123pub enum Error {
124    /// The disk size is not a multiple of the sector size.
125    #[error("disk size {disk_size:#x} is not a multiple of the sector size {sector_size}")]
126    NotSectorMultiple {
127        /// The disk size.
128        disk_size: u64,
129        /// The sector size.
130        sector_size: u32,
131    },
132    /// The disk has no sectors.
133    #[error("disk has no sectors")]
134    EmptyDisk,
135    /// The sector size is invalid.
136    #[error("sector size {0} is not a power-of-two >= 512")]
137    InvalidSectorSize(u32),
138}
139
140impl RamDiskLayer {
141    /// Makes a new RAM disk layer of `size` bytes with 512-byte sectors.
142    pub fn new(size: u64) -> Result<Self, Error> {
143        Self::new_with_sector_size(size, DEFAULT_SECTOR_SIZE)
144    }
145
146    /// Makes a new RAM disk layer of `size` bytes with the given sector size.
147    ///
148    /// `sector_size` must be a power-of-two >= 512.
149    pub fn new_with_sector_size(size: u64, sector_size: u32) -> Result<Self, Error> {
150        if !sector_size.is_power_of_two() || sector_size < DEFAULT_SECTOR_SIZE {
151            return Err(Error::InvalidSectorSize(sector_size));
152        }
153        let sector_shift = sector_size.trailing_zeros();
154        let sector_count = {
155            if size == 0 {
156                return Err(Error::EmptyDisk);
157            }
158            if !size.is_multiple_of(sector_size as u64) {
159                return Err(Error::NotSectorMultiple {
160                    disk_size: size,
161                    sector_size,
162                });
163            }
164            size >> sector_shift
165        };
166        Ok(Self {
167            state: RwLock::new(RamState {
168                data: BTreeMap::new(),
169                sector_count,
170                zero_after: sector_count,
171            }),
172            sector_count: sector_count.into(),
173            resize_event: Default::default(),
174            sector_size,
175            sector_shift,
176        })
177    }
178
179    fn resize(&self, new_sector_count: u64) -> anyhow::Result<()> {
180        if new_sector_count == 0 {
181            anyhow::bail!("invalid sector count");
182        }
183        // Remove any truncated data and update the sector count under the lock.
184        let _removed = {
185            let mut state = self.state.write();
186            // Remember that any non-present sectors after this point need to be zeroed.
187            state.zero_after = new_sector_count.min(state.zero_after);
188            state.sector_count = new_sector_count;
189            // Cache the sector count in an atomic for the fast path.
190            //
191            // FUTURE: remove uses of .sector_count() in the IO path,
192            // eliminating the need for this.
193            self.sector_count.store(new_sector_count, Ordering::Relaxed);
194            state.data.split_off(&new_sector_count)
195        };
196        self.resize_event.notify(usize::MAX);
197        Ok(())
198    }
199
200    fn write_maybe_overwrite(
201        &self,
202        buffers: &RequestBuffers<'_>,
203        sector: u64,
204        overwrite: bool,
205    ) -> Result<(), DiskError> {
206        let sector_bytes = self.sector_size as usize;
207        let count = buffers.len() >> self.sector_shift;
208        tracing::trace!(sector, count, "write");
209        let mut state = self.state.write();
210        if sector + count as u64 > state.sector_count {
211            return Err(DiskError::IllegalBlock);
212        }
213        for i in 0..count {
214            let cur = sector + i as u64;
215            let buf = buffers.subrange(i << self.sector_shift, sector_bytes);
216            let mut reader = buf.reader();
217            match state.data.entry(cur) {
218                Entry::Vacant(entry) => {
219                    let mut data = vec![0u8; sector_bytes].into_boxed_slice();
220                    reader.read(&mut data)?;
221                    entry.insert(data);
222                }
223                Entry::Occupied(mut entry) => {
224                    if overwrite {
225                        reader.read(entry.get_mut())?;
226                    }
227                }
228            }
229        }
230        Ok(())
231    }
232}
233
234impl LayerAttach for LazyRamDiskLayer {
235    type Error = Error;
236    type Layer = RamDiskLayer;
237
238    async fn attach(
239        self,
240        lower_layer_metadata: Option<disk_layered::DiskLayerMetadata>,
241    ) -> Result<Self::Layer, Self::Error> {
242        let sector_size = self
243            .sector_size
244            .or(lower_layer_metadata.as_ref().map(|meta| meta.sector_size))
245            .unwrap_or(DEFAULT_SECTOR_SIZE);
246        let total_size = match self.len {
247            Some(len) => len,
248            None => {
249                let meta = lower_layer_metadata.ok_or(Error::EmptyDisk)?;
250                meta.sector_count * meta.sector_size as u64
251            }
252        };
253        RamDiskLayer::new_with_sector_size(total_size, sector_size)
254    }
255}
256
257impl LayerIo for RamDiskLayer {
258    fn layer_type(&self) -> &str {
259        "ram"
260    }
261
262    fn sector_count(&self) -> u64 {
263        self.sector_count.load(Ordering::Relaxed)
264    }
265
266    fn sector_size(&self) -> u32 {
267        self.sector_size
268    }
269
270    fn is_logically_read_only(&self) -> bool {
271        false
272    }
273
274    fn disk_id(&self) -> Option<[u8; 16]> {
275        None
276    }
277
278    fn physical_sector_size(&self) -> u32 {
279        self.sector_size
280    }
281
282    fn is_fua_respected(&self) -> bool {
283        true
284    }
285
286    async fn read(
287        &self,
288        buffers: &RequestBuffers<'_>,
289        sector: u64,
290        mut marker: SectorMarker<'_>,
291    ) -> Result<(), DiskError> {
292        let sector_bytes = self.sector_size as usize;
293        let count = (buffers.len() >> self.sector_shift) as u64;
294        let end = sector + count;
295        tracing::trace!(sector, count, "read");
296        let state = self.state.read();
297        if end > state.sector_count {
298            return Err(DiskError::IllegalBlock);
299        }
300        let mut range = state.data.range(sector..end);
301        let mut last = sector;
302        while last < end {
303            let r = range.next();
304            let next = r.map(|(&s, _)| s).unwrap_or(end);
305            if next > last && next > state.zero_after {
306                // Some non-present sectors need to be zeroed, since they are
307                // after the zero-after point (due to a resize).
308                let zero_start = last.max(state.zero_after);
309                let zero_count = next - zero_start;
310                let offset = ((zero_start - sector) as usize) << self.sector_shift;
311                let len = (zero_count as usize) << self.sector_shift;
312                buffers.subrange(offset, len).writer().zero(len)?;
313                marker.set_range(zero_start..next);
314            }
315            if let Some((&s, buf)) = r {
316                let offset = ((s - sector) as usize) << self.sector_shift;
317                buffers.subrange(offset, sector_bytes).writer().write(buf)?;
318
319                marker.set(s);
320            }
321            last = next;
322        }
323        Ok(())
324    }
325
326    async fn write(
327        &self,
328        buffers: &RequestBuffers<'_>,
329        sector: u64,
330        _fua: bool,
331    ) -> Result<(), DiskError> {
332        self.write_maybe_overwrite(buffers, sector, true)
333    }
334
335    fn write_no_overwrite(&self) -> Option<impl WriteNoOverwrite> {
336        Some(self)
337    }
338
339    async fn sync_cache(&self) -> Result<(), DiskError> {
340        tracing::trace!("sync_cache");
341        Ok(())
342    }
343
344    async fn wait_resize(&self, sector_count: u64) -> u64 {
345        loop {
346            let listen = self.resize_event.listen();
347            let current = self.sector_count();
348            if current != sector_count {
349                break current;
350            }
351            listen.await;
352        }
353    }
354
355    async fn unmap(
356        &self,
357        sector_offset: u64,
358        sector_count: u64,
359        _block_level_only: bool,
360        next_is_zero: bool,
361    ) -> Result<(), DiskError> {
362        tracing::trace!(sector_offset, sector_count, "unmap");
363        let mut state = self.state.write();
364        if sector_offset + sector_count > state.sector_count {
365            return Err(DiskError::IllegalBlock);
366        }
367        if !next_is_zero {
368            // This would create a hole of zeroes, which we cannot represent in
369            // the tree. Ignore the unmap.
370            if sector_offset + sector_count < state.zero_after {
371                return Ok(());
372            }
373            // The unmap is within or will extend the not-present-is-zero
374            // region, so allow it.
375            state.zero_after = state.zero_after.min(sector_offset);
376        }
377        let end = sector_offset + sector_count;
378        if sector_offset < end {
379            state
380                .data
381                .extract_if(sector_offset..end, |_, _| true)
382                .for_each(drop);
383        }
384        Ok(())
385    }
386
387    fn unmap_behavior(&self) -> UnmapBehavior {
388        // This layer zeroes if the lower layer is zero, but otherwise does
389        // nothing, so we must report unspecified.
390        UnmapBehavior::Unspecified
391    }
392
393    fn optimal_unmap_sectors(&self) -> u32 {
394        1
395    }
396}
397
398impl WriteNoOverwrite for RamDiskLayer {
399    async fn write_no_overwrite(
400        &self,
401        buffers: &RequestBuffers<'_>,
402        sector: u64,
403    ) -> Result<(), DiskError> {
404        self.write_maybe_overwrite(buffers, sector, false)
405    }
406}
407
408/// Create a RAM disk of `size` bytes.
409///
410/// This is a convenience function for creating a layered disk with a single RAM
411/// layer. It is useful since non-layered RAM disks are used all over the place,
412/// especially in tests.
413pub fn ram_disk(size: u64, read_only: bool) -> anyhow::Result<Disk> {
414    ram_disk_with_sector_size(size, read_only, DEFAULT_SECTOR_SIZE)
415}
416
417/// Create a RAM disk of `size` bytes with the given sector size.
418///
419/// This is a convenience function for creating a layered disk with a single RAM
420/// layer and a custom sector size.
421pub fn ram_disk_with_sector_size(
422    size: u64,
423    read_only: bool,
424    sector_size: u32,
425) -> anyhow::Result<Disk> {
426    use futures::future::FutureExt;
427
428    let disk = Disk::new(
429        LayeredDisk::new(
430            read_only,
431            vec![LayerConfiguration {
432                layer: DiskLayer::new(RamDiskLayer::new_with_sector_size(size, sector_size)?),
433                write_through: false,
434                read_cache: false,
435            }],
436        )
437        .now_or_never()
438        .expect("RamDiskLayer won't block")?,
439    )?;
440
441    Ok(disk)
442}
443
444#[cfg(test)]
445mod tests {
446    use super::RamDiskLayer;
447    use disk_backend::DiskIo;
448    use disk_layered::DiskLayer;
449    use disk_layered::LayerConfiguration;
450    use disk_layered::LayerIo;
451    use disk_layered::LayeredDisk;
452    use guestmem::GuestMemory;
453    use pal_async::async_test;
454    use scsi_buffers::OwnedRequestBuffers;
455    use test_with_tracing::test;
456    use zerocopy::IntoBytes;
457
458    const SECTOR_SIZE: u32 = 512;
459    const SECTOR_U64: u64 = SECTOR_SIZE as u64;
460    const SECTOR_USIZE: usize = SECTOR_SIZE as usize;
461
462    const CONFORMANCE_DISK_SIZE: u64 = 1024 * 1024;
463
464    #[async_test]
465    async fn sector_range_conformance() {
466        let disk = crate::ram_disk(CONFORMANCE_DISK_SIZE, false).unwrap();
467        storage_tests::sector_range::test_disk_sector_range_conformance(&disk).await;
468    }
469
470    /// The representability guarantee is not specific to 512-byte sectors.
471    #[async_test]
472    async fn sector_range_conformance_4k() {
473        let disk = crate::ram_disk_with_sector_size(CONFORMANCE_DISK_SIZE, false, 4096).unwrap();
474        storage_tests::sector_range::test_disk_representability(&disk).await;
475    }
476
477    /// `LayeredDisk` does not reach `write_no_overwrite` or both values of
478    /// `unmap`'s `next_is_zero`, so the layer is also tested directly.
479    #[async_test]
480    async fn layer_sector_range_conformance() {
481        let layer = RamDiskLayer::new(CONFORMANCE_DISK_SIZE).unwrap();
482        storage_tests::sector_range::test_layer_sector_range(&layer).await;
483    }
484
485    fn check(mem: &GuestMemory, sector: u64, start: usize, count: usize, high: u8) {
486        let mut buf = vec![0u32; count * SECTOR_USIZE / 4];
487        mem.read_at(start as u64 * SECTOR_U64, buf.as_mut_bytes())
488            .unwrap();
489        for (i, &b) in buf.iter().enumerate() {
490            let offset = sector * SECTOR_U64 + i as u64 * 4;
491            let expected = (offset as u32 / 4) | ((high as u32) << 24);
492            assert!(
493                b == expected,
494                "at sector {}, word {}, got {:#x}, expected {:#x}",
495                offset / SECTOR_U64,
496                (offset % SECTOR_U64) / 4,
497                b,
498                expected
499            );
500        }
501    }
502
503    async fn read(mem: &GuestMemory, disk: &mut impl DiskIo, sector: u64, count: usize) {
504        disk.read_vectored(
505            &OwnedRequestBuffers::linear(0, count * SECTOR_USIZE, true).buffer(mem),
506            sector,
507        )
508        .await
509        .unwrap();
510    }
511
512    async fn write_layer(
513        mem: &GuestMemory,
514        disk: &mut impl LayerIo,
515        sector: u64,
516        count: usize,
517        high: u8,
518    ) {
519        let buf: Vec<_> = (sector * SECTOR_U64 / 4..(sector + count as u64) * SECTOR_U64 / 4)
520            .map(|x| x as u32 | ((high as u32) << 24))
521            .collect();
522        let len = SECTOR_USIZE * count;
523        mem.write_at(0, &buf.as_bytes()[..len]).unwrap();
524
525        disk.write(
526            &OwnedRequestBuffers::linear(0, len, false).buffer(mem),
527            sector,
528            false,
529        )
530        .await
531        .unwrap();
532    }
533
534    async fn write(mem: &GuestMemory, disk: &mut impl DiskIo, sector: u64, count: usize, high: u8) {
535        let buf: Vec<_> = (sector * SECTOR_U64 / 4..(sector + count as u64) * SECTOR_U64 / 4)
536            .map(|x| x as u32 | ((high as u32) << 24))
537            .collect();
538        let len = SECTOR_USIZE * count;
539        mem.write_at(0, &buf.as_bytes()[..len]).unwrap();
540
541        disk.write_vectored(
542            &OwnedRequestBuffers::linear(0, len, false).buffer(mem),
543            sector,
544            false,
545        )
546        .await
547        .unwrap();
548    }
549
550    async fn prep_disk(size: usize) -> (GuestMemory, LayeredDisk) {
551        let guest_mem = GuestMemory::allocate(size);
552        let mut lower = RamDiskLayer::new(size as u64).unwrap();
553        write_layer(&guest_mem, &mut lower, 0, size / SECTOR_USIZE, 0).await;
554        let upper = RamDiskLayer::new(size as u64).unwrap();
555        let upper = LayeredDisk::new(
556            false,
557            Vec::from_iter([upper, lower].map(|layer| LayerConfiguration {
558                layer: DiskLayer::new(layer),
559                write_through: false,
560                read_cache: false,
561            })),
562        )
563        .await
564        .unwrap();
565        (guest_mem, upper)
566    }
567
568    #[async_test]
569    async fn diff() {
570        const SIZE: usize = 1024 * 1024;
571
572        let (guest_mem, mut upper) = prep_disk(SIZE).await;
573        read(&guest_mem, &mut upper, 10, 2).await;
574        check(&guest_mem, 10, 0, 2, 0);
575        write(&guest_mem, &mut upper, 10, 2, 1).await;
576        write(&guest_mem, &mut upper, 11, 1, 2).await;
577        read(&guest_mem, &mut upper, 9, 5).await;
578        check(&guest_mem, 9, 0, 1, 0);
579        check(&guest_mem, 10, 1, 1, 1);
580        check(&guest_mem, 11, 2, 1, 2);
581        check(&guest_mem, 12, 3, 1, 0);
582    }
583
584    async fn resize(disk: &LayeredDisk, new_size: u64) {
585        let inspect::ValueKind::Unsigned(v) =
586            inspect::update("layers/0/backing/sector_count", &new_size.to_string(), disk)
587                .await
588                .unwrap()
589                .kind
590        else {
591            panic!("bad inspect value")
592        };
593        assert_eq!(new_size, v);
594    }
595
596    #[async_test]
597    async fn test_resize() {
598        const SIZE: usize = 1024 * 1024;
599        const SECTORS: usize = SIZE / SECTOR_USIZE;
600
601        let (guest_mem, mut upper) = prep_disk(SIZE).await;
602        check(&guest_mem, 0, 0, SECTORS, 0);
603        resize(&upper, SECTORS as u64 / 2).await;
604        resize(&upper, SECTORS as u64).await;
605        read(&guest_mem, &mut upper, 0, SECTORS).await;
606        check(&guest_mem, 0, 0, SECTORS / 2, 0);
607        for s in SECTORS / 2..SECTORS {
608            let mut buf = [0u8; SECTOR_USIZE];
609            guest_mem.read_at(s as u64 * SECTOR_U64, &mut buf).unwrap();
610            assert_eq!(buf, [0u8; SECTOR_USIZE]);
611        }
612    }
613
614    #[async_test]
615    async fn test_unmap() {
616        const SIZE: usize = 1024 * 1024;
617        const SECTORS: usize = SIZE / SECTOR_USIZE;
618
619        let (guest_mem, mut upper) = prep_disk(SIZE).await;
620        upper.unmap(0, SECTORS as u64 - 1, false).await.unwrap();
621        read(&guest_mem, &mut upper, 0, SECTORS).await;
622        check(&guest_mem, 0, 0, SECTORS, 0);
623        upper
624            .unmap(SECTORS as u64 / 2, SECTORS as u64 / 2, false)
625            .await
626            .unwrap();
627        read(&guest_mem, &mut upper, 0, SECTORS).await;
628        check(&guest_mem, 0, 0, SECTORS / 2, 0);
629        for s in SECTORS / 2..SECTORS {
630            let mut buf = [0u8; SECTOR_USIZE];
631            guest_mem.read_at(s as u64 * SECTOR_U64, &mut buf).unwrap();
632            assert_eq!(buf, [0u8; SECTOR_USIZE]);
633        }
634    }
635
636    #[async_test]
637    async fn test_unmap_sparse_range() {
638        let guest_mem = GuestMemory::allocate(SECTOR_USIZE);
639        for (offset, count, expected) in [
640            (1, 0, &[0, 2, 4, 7][..]),
641            (1, 1, &[0, 2, 4, 7][..]),
642            (2, 2, &[0, 4, 7][..]),
643            (1, 6, &[0, 7][..]),
644            (0, 8, &[][..]),
645            (8, 0, &[0, 2, 4, 7][..]),
646        ] {
647            let mut layer = RamDiskLayer::new(8 * SECTOR_U64).unwrap();
648            for sector in [0, 2, 4, 7] {
649                write_layer(&guest_mem, &mut layer, sector, 1, 0).await;
650            }
651
652            layer.unmap(offset, count, false, true).await.unwrap();
653
654            assert_eq!(
655                layer.state.read().data.keys().copied().collect::<Vec<_>>(),
656                expected,
657                "unmap {offset}..{}",
658                offset + count,
659            );
660        }
661    }
662
663    #[async_test]
664    async fn test_4096_sector_write_read() {
665        const SECTOR_4K: usize = 4096;
666        const DISK_SIZE: u64 = 1024 * 1024; // 1 MiB
667        const SECTOR_COUNT: u64 = DISK_SIZE / SECTOR_4K as u64;
668
669        let layer = RamDiskLayer::new_with_sector_size(DISK_SIZE, 4096).unwrap();
670        assert_eq!(layer.sector_size(), 4096);
671        assert_eq!(layer.physical_sector_size(), 4096);
672        assert_eq!(layer.sector_count(), SECTOR_COUNT);
673
674        let guest_mem = GuestMemory::allocate(SECTOR_4K * 2);
675
676        // Write pattern to sector 0
677        let pattern: Vec<u8> = (0..SECTOR_4K).map(|i| (i % 251) as u8).collect();
678        guest_mem.write_at(0, &pattern).unwrap();
679        let bufs = OwnedRequestBuffers::linear(0, SECTOR_4K, false);
680        layer
681            .write(&bufs.buffer(&guest_mem), 0, false)
682            .await
683            .unwrap();
684
685        // Build a single-layer disk to test read-back through DiskIo
686        let disk = LayeredDisk::new(
687            false,
688            vec![LayerConfiguration {
689                layer: DiskLayer::new(layer),
690                write_through: false,
691                read_cache: false,
692            }],
693        )
694        .await
695        .unwrap();
696
697        // Clear guest memory and read back
698        guest_mem.write_at(0, &vec![0u8; SECTOR_4K]).unwrap();
699        disk.read_vectored(
700            &OwnedRequestBuffers::linear(0, SECTOR_4K, true).buffer(&guest_mem),
701            0,
702        )
703        .await
704        .unwrap();
705
706        let mut readback = vec![0u8; SECTOR_4K];
707        guest_mem.read_at(0, &mut readback).unwrap();
708        assert_eq!(readback, pattern);
709    }
710
711    #[test]
712    fn test_sector_count_4096() {
713        let layer = RamDiskLayer::new_with_sector_size(1024 * 1024, 4096).unwrap();
714        assert_eq!(layer.sector_count(), 256); // 1 MiB / 4096 = 256
715    }
716
717    #[test]
718    fn test_invalid_sector_size_not_power_of_two() {
719        let err = RamDiskLayer::new_with_sector_size(4096, 1000).unwrap_err();
720        assert!(matches!(err, super::Error::InvalidSectorSize(1000)));
721    }
722
723    #[test]
724    fn test_invalid_sector_size_too_small() {
725        let err = RamDiskLayer::new_with_sector_size(4096, 256).unwrap_err();
726        assert!(matches!(err, super::Error::InvalidSectorSize(256)));
727    }
728
729    #[test]
730    fn test_invalid_disk_size_not_multiple() {
731        let err = RamDiskLayer::new_with_sector_size(5000, 4096).unwrap_err();
732        assert!(matches!(
733            err,
734            super::Error::NotSectorMultiple {
735                disk_size: 5000,
736                sector_size: 4096
737            }
738        ));
739    }
740
741    #[async_test]
742    async fn test_lazy_inherits_sector_size() {
743        let lower = RamDiskLayer::new_with_sector_size(1024 * 1024, 4096).unwrap();
744        let upper = LayeredDisk::new(
745            false,
746            vec![
747                LayerConfiguration {
748                    layer: DiskLayer::new(super::LazyRamDiskLayer::new()),
749                    write_through: false,
750                    read_cache: false,
751                },
752                LayerConfiguration {
753                    layer: DiskLayer::new(lower),
754                    write_through: false,
755                    read_cache: false,
756                },
757            ],
758        )
759        .await
760        .unwrap();
761
762        assert_eq!(upper.sector_size(), 4096);
763        assert_eq!(upper.sector_count(), 256);
764    }
765
766    #[async_test]
767    async fn test_mismatched_sector_sizes_rejected() {
768        const DISK_SIZE: u64 = 1024 * 1024;
769
770        // 4K upper on 512B lower should be rejected.
771        let result = LayeredDisk::new(
772            false,
773            vec![
774                LayerConfiguration {
775                    layer: DiskLayer::new(
776                        RamDiskLayer::new_with_sector_size(DISK_SIZE, 4096).unwrap(),
777                    ),
778                    write_through: false,
779                    read_cache: false,
780                },
781                LayerConfiguration {
782                    layer: DiskLayer::new(RamDiskLayer::new(DISK_SIZE).unwrap()),
783                    write_through: false,
784                    read_cache: false,
785                },
786            ],
787        )
788        .await;
789        assert!(result.is_err());
790
791        // 512B upper on 4K lower should be rejected.
792        let result = LayeredDisk::new(
793            false,
794            vec![
795                LayerConfiguration {
796                    layer: DiskLayer::new(RamDiskLayer::new(DISK_SIZE).unwrap()),
797                    write_through: false,
798                    read_cache: false,
799                },
800                LayerConfiguration {
801                    layer: DiskLayer::new(
802                        RamDiskLayer::new_with_sector_size(DISK_SIZE, 4096).unwrap(),
803                    ),
804                    write_through: false,
805                    read_cache: false,
806                },
807            ],
808        )
809        .await;
810        assert!(result.is_err());
811    }
812}