Skip to main content

fuchsia_storage_benchmarks/filesystems/
mod.rs

1// Copyright 2023 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
5use async_trait::async_trait;
6use blob_writer::BlobWriter;
7use delivery_blob::{CompressionMode, Type1Blob};
8use fidl::endpoints::ClientEnd;
9use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
10use fidl_fuchsia_fxfs::{BlobCreatorProxy, BlobReaderProxy, CryptMarker};
11use fidl_fuchsia_io as fio;
12use fs_management::FSConfig;
13use fs_management::filesystem::{
14    Filesystem as FsManagementFilesystem, ServingMultiVolumeFilesystem,
15    ServingSingleVolumeFilesystem, ServingVolume,
16};
17use fuchsia_merkle::Hash;
18use std::path::Path;
19use std::sync::Arc;
20use storage_benchmarks::block_device::BlockDevice;
21use storage_benchmarks::{CacheClearableFilesystem, Filesystem};
22
23mod blobfs;
24mod f2fs;
25mod fxblob;
26pub mod fxfs;
27mod memfs;
28mod minfs;
29mod pkgdir;
30#[cfg(test)]
31mod testing;
32
33pub use blobfs::Blobfs;
34pub use f2fs::F2fs;
35pub use fxblob::Fxblob;
36pub use fxfs::Fxfs;
37pub use memfs::Memfs;
38pub use minfs::Minfs;
39pub use pkgdir::{PkgDirInstance, PkgDirTest};
40
41const MOUNT_PATH: &str = "/benchmark";
42
43/// Struct for holding the name of a blob and its contents in the delivery blob format.
44pub struct DeliveryBlob {
45    pub data: Vec<u8>,
46    pub name: Hash,
47}
48
49impl DeliveryBlob {
50    pub fn new(data: Vec<u8>, mode: CompressionMode) -> Self {
51        let name = fuchsia_merkle::root_from_slice(&data);
52        Self { data: Type1Blob::generate(&data, mode), name }
53    }
54}
55
56/// A trait for filesystems that support reading and writing blobs.
57#[async_trait]
58pub trait BlobFilesystem: CacheClearableFilesystem {
59    /// Writes a blob to the filesystem.
60    async fn write_blob(&self, blob: &DeliveryBlob) {
61        let writer_client_end = self
62            .blob_creator()
63            .create(&blob.name.into(), false)
64            .await
65            .expect("transport error on BlobCreator.Create")
66            .expect("failed to create blob");
67        let writer = writer_client_end.into_proxy();
68        let mut blob_writer = BlobWriter::create(writer, blob.data.len() as u64)
69            .await
70            .expect("failed to create BlobWriter");
71        blob_writer.write(&blob.data).await.unwrap();
72    }
73
74    /// Gets a VMO backing a blob.
75    async fn get_vmo(&self, name: &Hash) -> zx::Vmo {
76        self.blob_reader()
77            .get_vmo(&*name)
78            .await
79            .expect("transport error on BlobReader.GetVmo")
80            .expect("failed to get vmo")
81    }
82
83    async fn remove_blob(&self, name: &Hash) {
84        let root = fuchsia_fs::directory::open_directory(
85            self.exposed_dir(),
86            "root",
87            fio::PERM_READABLE | fio::PERM_WRITABLE,
88        )
89        .await
90        .expect("failed to open blob directory");
91        root.unlink(&name.to_string(), &fio::UnlinkOptions::default())
92            .await
93            .expect("transport error on Directory.Unlink")
94            .expect("failed to unlink blob");
95        root.sync()
96            .await
97            .expect("transport error on Directory.Sync")
98            .expect("failed to sync blob directory");
99    }
100
101    /// Direct access to the BlobCreator protocol.
102    fn blob_creator(&self) -> &BlobCreatorProxy;
103
104    /// Direct access to the BlobReader protocol.
105    fn blob_reader(&self) -> &BlobReaderProxy;
106
107    /// Returns the exposed dir of the blob volume.
108    fn exposed_dir(&self) -> &fio::DirectoryProxy;
109}
110
111enum FsType {
112    SingleVolume(ServingSingleVolumeFilesystem),
113    MultiVolume(ServingMultiVolumeFilesystem, ServingVolume),
114}
115
116pub type CryptClientFn = Arc<dyn Fn() -> ClientEnd<CryptMarker> + Send + Sync>;
117
118pub struct FsManagementFilesystemInstance {
119    config_creator: Box<dyn Fn() -> Box<dyn FSConfig> + Send + Sync>,
120    crypt_client_fn: Option<CryptClientFn>,
121    serving_filesystem: Option<FsType>,
122    as_blob: bool,
123    // Keep the underlying block device alive for as long as we are using the filesystem.
124    block_device: Box<dyn BlockDevice>,
125}
126
127impl FsManagementFilesystemInstance {
128    pub async fn new<FSC: FSConfig>(
129        config_creator: impl (Fn() -> FSC) + Send + Sync + 'static,
130        block_device: Box<dyn BlockDevice>,
131        crypt_client_fn: Option<CryptClientFn>,
132        as_blob: bool,
133    ) -> Self {
134        let config_creator = Box::new(move || Box::new(config_creator()) as Box<dyn FSConfig>);
135        let mut fs =
136            FsManagementFilesystem::from_boxed_config(block_device.connector(), config_creator());
137        fs.format().await.expect("Failed to format the filesystem");
138        let serving_filesystem = if fs.config().is_multi_volume() {
139            let serving_filesystem =
140                fs.serve_multi_volume().await.expect("Failed to start the filesystem");
141            let mut vol = serving_filesystem
142                .create_volume(
143                    "default",
144                    CreateOptions::default(),
145                    MountOptions {
146                        crypt: crypt_client_fn.as_ref().map(|f| f()),
147                        as_blob: Some(as_blob),
148                        ..MountOptions::default()
149                    },
150                )
151                .await
152                .expect("Failed to create volume");
153            vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
154            FsType::MultiVolume(serving_filesystem, vol)
155        } else {
156            let mut serving_filesystem = fs.serve().await.expect("Failed to start the filesystem");
157            serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
158            FsType::SingleVolume(serving_filesystem)
159        };
160        Self {
161            config_creator,
162            crypt_client_fn,
163            serving_filesystem: Some(serving_filesystem),
164            as_blob,
165            block_device,
166        }
167    }
168
169    fn exposed_dir(&self) -> &fio::DirectoryProxy {
170        let fs = self.serving_filesystem.as_ref().unwrap();
171        match fs {
172            FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
173            FsType::MultiVolume(_, serving_volume) => serving_volume.exposed_dir(),
174        }
175    }
176
177    // Instead of returning the directory that is the data root for this filesystem, return the root
178    // that would be used for accessing top level service protocols.
179    fn exposed_services_dir(&self) -> &fio::DirectoryProxy {
180        let fs = self.serving_filesystem.as_ref().unwrap();
181        match fs {
182            FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
183            FsType::MultiVolume(serving_filesystem, _) => serving_filesystem.exposed_dir(),
184        }
185    }
186
187    fn fs(&self) -> FsManagementFilesystem {
188        FsManagementFilesystem::from_boxed_config(
189            self.block_device.connector(),
190            (self.config_creator)(),
191        )
192    }
193}
194
195#[async_trait]
196impl Filesystem for FsManagementFilesystemInstance {
197    async fn shutdown(mut self) {
198        if let Some(fs) = self.serving_filesystem.take() {
199            match fs {
200                FsType::SingleVolume(fs) => fs.shutdown().await.expect("Failed to stop filesystem"),
201                FsType::MultiVolume(fs, vol) => {
202                    vol.shutdown().await.expect("Failed to stop volume");
203                    fs.shutdown().await.expect("Failed to stop filesystem")
204                }
205            }
206        }
207    }
208
209    fn benchmark_dir(&self) -> &Path {
210        Path::new(MOUNT_PATH)
211    }
212}
213
214#[async_trait]
215impl CacheClearableFilesystem for FsManagementFilesystemInstance {
216    async fn clear_cache(&mut self) {
217        // Remount the filesystem to guarantee that all cached data from reads and write is cleared.
218        let serving_filesystem = self.serving_filesystem.take().unwrap();
219        let serving_filesystem = match serving_filesystem {
220            FsType::SingleVolume(serving_filesystem) => {
221                serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
222                let mut serving_filesystem =
223                    self.fs().serve().await.expect("Failed to start the filesystem");
224                serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
225                FsType::SingleVolume(serving_filesystem)
226            }
227            FsType::MultiVolume(serving_filesystem, volume) => {
228                volume.shutdown().await.expect("Failed to stop the volume");
229                serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
230                let serving_filesystem =
231                    self.fs().serve_multi_volume().await.expect("Failed to start the filesystem");
232                let mut vol = serving_filesystem
233                    .open_volume(
234                        "default",
235                        MountOptions {
236                            crypt: self.crypt_client_fn.as_ref().map(|f| f()),
237                            as_blob: Some(self.as_blob),
238                            ..MountOptions::default()
239                        },
240                    )
241                    .await
242                    .expect("Failed to create volume");
243                vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
244                FsType::MultiVolume(serving_filesystem, vol)
245            }
246        };
247        self.serving_filesystem = Some(serving_filesystem);
248    }
249}