Skip to main content

fxfs/
virtual_device.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Implementation of a virtual storage devices backed by a [`ReadObjectHandle`]. Allows using
6//! files within an existing fxfs as a virtual storage device (e.g. to mount an inner filesystem).
7
8use crate::errors::FxfsError;
9use crate::object_handle::ReadObjectHandle;
10use anyhow::{Context as _, Error, bail};
11use async_trait::async_trait;
12use std::ops::Range;
13use storage_device::buffer::MutableBufferRef;
14use storage_device::buffer_allocator::BufferFuture;
15use storage_device::{Device, ReadOptions};
16
17/// Allows using anything that implements [`ReadObjectHandle`] as a read-only storage [`Device`].
18pub struct ReadOnlyDevice<H: ReadObjectHandle> {
19    handle: H,
20}
21
22impl<H: ReadObjectHandle> ReadOnlyDevice<H> {
23    pub fn new(handle: H) -> Result<Self, Error> {
24        let device = Self { handle };
25        // Prevent division by zero when calculating the block count.
26        if device.block_size() == 0 {
27            bail!("Expected non-zero block size.");
28        }
29        Ok(device)
30    }
31}
32
33#[async_trait]
34impl<H: ReadObjectHandle> Device for ReadOnlyDevice<H> {
35    fn allocate_buffer(&self, size: usize) -> BufferFuture<'_> {
36        self.handle.allocate_buffer(size)
37    }
38
39    fn block_size(&self) -> u32 {
40        self.handle.block_size().get() as u32
41    }
42
43    fn block_count(&self) -> u64 {
44        self.handle.get_size() / self.handle.block_size()
45    }
46
47    async fn read_with_opts(
48        &self,
49        offset: u64,
50        buffer: MutableBufferRef<'_>,
51        _read_opts: ReadOptions,
52    ) -> Result<(), Error> {
53        let len = buffer.len();
54        let amount = self.handle.read(offset, buffer).await?;
55        if amount != len {
56            return Err(FxfsError::OutOfRange).context("short read from underlying object");
57        }
58        Ok(())
59    }
60
61    async fn close(&self) -> Result<(), Error> {
62        Ok(())
63    }
64
65    fn is_read_only(&self) -> bool {
66        true
67    }
68
69    fn supports_trim(&self) -> bool {
70        false
71    }
72
73    async fn write_with_opts(
74        &self,
75        _offset: u64,
76        _buffer: storage_device::buffer::BufferRef<'_>,
77        _write_opts: storage_device::WriteOptions,
78    ) -> Result<(), Error> {
79        unreachable!()
80    }
81
82    async fn flush(&self) -> Result<(), Error> {
83        unreachable!()
84    }
85
86    async fn trim(&self, _range: Range<u64>) -> Result<(), Error> {
87        unreachable!()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::filesystem::FxFilesystem;
95    use crate::object_handle::ObjectHandle as _;
96    use crate::object_store::transaction::{LockKey, lock_keys};
97    use crate::object_store::volume::root_volume;
98    use crate::object_store::{DataObjectHandle, Directory, NewChildStoreOptions, ObjectStore};
99    use std::sync::Arc;
100    use storage_device::DeviceHolder;
101    use storage_device::fake_device::FakeDevice;
102
103    /// Helper function that creates a test file filled with a known byte pattern.
104    /// Each block in the file will be filled with the block offset mod 0xFF.
105    async fn create_test_file(
106        fs: &Arc<FxFilesystem>,
107        num_blocks: usize,
108    ) -> DataObjectHandle<ObjectStore> {
109        let root_vol = root_volume(fs.clone()).await.unwrap();
110        let store = root_vol.new_volume("test", NewChildStoreOptions::default()).await.unwrap();
111        let test_vol_root =
112            Directory::open(&store, store.root_directory_object_id()).await.unwrap();
113
114        let mut transaction = fs
115            .root_store()
116            .new_transaction(
117                lock_keys![LockKey::object(
118                    store.store_object_id(),
119                    store.root_directory_object_id()
120                )],
121                Default::default(),
122            )
123            .await
124            .unwrap();
125
126        let object = test_vol_root.create_child_file(&mut transaction, "test_file").await.unwrap();
127        transaction.commit().await.unwrap();
128
129        {
130            let mut transaction = object.new_transaction().await.unwrap();
131            let block_size = object.block_size().get() as usize;
132            let mut buffer = object.allocate_buffer(block_size * num_blocks).await;
133            for i in 0..num_blocks {
134                let buff_range = (i * block_size)..((i + 1) * block_size);
135                buffer.subslice_mut(buff_range).fill((i % 0xFF) as u8);
136            }
137            object.txn_write(&mut transaction, 0, buffer.as_ref()).await.unwrap();
138
139            transaction.commit().await.unwrap();
140        }
141
142        object
143    }
144
145    #[fuchsia::test]
146    async fn test_read_only_virtual_device() {
147        const BLOCK_SIZE: usize = 4096;
148        const TEST_FILE_BLOCK_COUNT: usize = 64;
149        let device = DeviceHolder::new(FakeDevice::new(512, BLOCK_SIZE as u32));
150        let fs = FxFilesystem::new_empty(device).await.unwrap();
151        let handle = create_test_file(&fs, TEST_FILE_BLOCK_COUNT).await;
152        let handle_as_device = ReadOnlyDevice::new(handle).unwrap();
153        assert_eq!(handle_as_device.block_size(), BLOCK_SIZE as u32);
154        assert_eq!(handle_as_device.block_count(), TEST_FILE_BLOCK_COUNT as u64);
155
156        // We should be able to read the whole file through our virtual read-only device.
157        let mut buffer = handle_as_device.allocate_buffer(BLOCK_SIZE * TEST_FILE_BLOCK_COUNT).await;
158        handle_as_device.read(0, buffer.as_mut()).await.unwrap();
159        let data = buffer.to_vec();
160        for i in 0..TEST_FILE_BLOCK_COUNT {
161            let buff_range = (i * BLOCK_SIZE)..((i + 1) * BLOCK_SIZE);
162            assert_eq!(data[buff_range], [(i % 0xFF) as u8; BLOCK_SIZE]);
163        }
164
165        // Test reading from an offset.
166        let mut buffer = handle_as_device.allocate_buffer(BLOCK_SIZE).await;
167        handle_as_device.read((BLOCK_SIZE * 4) as u64, buffer.as_mut()).await.unwrap();
168        assert_eq!(buffer.to_vec(), [4u8; BLOCK_SIZE]);
169    }
170}