Skip to main content

fuchsia_storage_benchmarks/
block_devices.rs

1// Copyright 2022 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 fidl::HandleBased as _;
7use fidl::endpoints::{
8    DiscoverableProtocolMarker as _, Proxy, create_proxy, create_request_stream,
9};
10use fidl_fuchsia_device::ControllerMarker;
11use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_storage_block::{
14    ALLOCATE_PARTITION_FLAG_INACTIVE, BlockMarker, BlockSynchronousProxy, VolumeManagerMarker,
15    VolumeManagerProxy,
16};
17use fs_management::filesystem::{
18    BlockConnector, DirBasedBlockConnector, ServingMultiVolumeFilesystem,
19};
20use fs_management::format::DiskFormat;
21use fs_management::format::constants::{BENCHMARK_FVM_TYPE_GUID, BENCHMARK_FVM_VOLUME_NAME};
22use fs_management::{BLOBFS_TYPE_GUID, Fvm};
23use fuchsia_component::client::{
24    Service, connect_to_named_protocol_at_dir_root, connect_to_protocol,
25    connect_to_protocol_at_dir_root, connect_to_protocol_at_path,
26};
27
28use std::path::PathBuf;
29use std::sync::Arc;
30use storage_benchmarks::block_device::BlockDevice;
31use storage_benchmarks::{BlockDeviceConfig, BlockDeviceFactory};
32use storage_isolated_driver_manager::{
33    BlockDeviceMatcher, Guid, create_random_guid, find_block_device, find_block_device_devfs, fvm,
34    into_guid, wait_for_block_device_devfs, zxcrypt,
35};
36use {
37    fidl_fuchsia_device as fdevice, fidl_fuchsia_storage_partitions as fpartitions,
38    fuchsia_async as fasync,
39};
40
41const BLOBFS_VOLUME_NAME: &str = "blobfs";
42
43const BENCHMARK_FVM_SIZE_BYTES: u64 = 160 * 1024 * 1024;
44// 8MiB is the default slice size; use it so the test FVM partition matches the performance of the
45// system FVM partition (so they are interchangeable).
46// Note that this only affects the performance of minfs and blobfs, since these two filesystems are
47// the only ones that dynamically allocate from FVM.
48const BENCHMARK_FVM_SLICE_SIZE_BYTES: usize = 8 * 1024 * 1024;
49
50// On systems which don't have FVM (i.e. Fxblob), we create an FVM partition the test can use, with
51// this GUID.  See connect_to_test_fvm for details.
52
53const BENCHMARK_TYPE_GUID: &Guid = &[
54    0x67, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
55];
56const BENCHMARK_VOLUME_NAME: &str = "benchmark";
57
58/// Returns the exposed directory of the volume, as well as the task running Crypt for the volume
59/// (if configured in `config`).
60pub async fn create_fvm_volume(
61    fvm: &fidl_fuchsia_fs_startup::VolumesProxy,
62    instance_guid: [u8; 16],
63    config: &BlockDeviceConfig,
64) -> (fio::DirectoryProxy, Option<fasync::Task<()>>) {
65    let (crypt, crypt_task) = if config.use_zxcrypt {
66        let (crypt, stream) = create_request_stream::<fidl_fuchsia_fxfs::CryptMarker>();
67        let task = fasync::Task::spawn(async {
68            if let Err(err) =
69                zxcrypt_crypt::run_crypt_service(crypt_policy::Policy::Null, stream).await
70            {
71                log::error!(err:?; "Crypt service failure");
72            }
73        });
74        (Some(crypt), Some(task))
75    } else {
76        (None, None)
77    };
78    let (volume_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
79    fvm.create(
80        BENCHMARK_VOLUME_NAME,
81        server_end,
82        CreateOptions {
83            initial_size: config.volume_size,
84            type_guid: Some(BENCHMARK_TYPE_GUID.clone()),
85            guid: Some(instance_guid),
86            ..Default::default()
87        },
88        MountOptions { crypt, ..Default::default() },
89    )
90    .await
91    .expect("FIDL error")
92    .map_err(zx::Status::from_raw)
93    .expect("Failed to create volume");
94
95    (volume_dir, crypt_task)
96}
97
98pub enum SystemFvm {
99    /// The FVM is running in devfs.
100    Devfs(VolumeManagerProxy),
101    /// The FVM is running as a component.
102    Component(
103        Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
104        Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
105    ),
106}
107
108pub enum SystemGpt {
109    /// The GPT is running in devfs.
110    Devfs(fdevice::ControllerProxy),
111    /// The GPT is running as a component.
112    Component(Arc<fpartitions::PartitionServiceProxy>),
113}
114
115/// A factory for volumes which the benchmarks run in.  If the system has an FVM, benchmarks run in
116/// a volume in the system FVM.  Otherwise, they run out of a partition in the system GPT (and might
117/// still include a hermetic FVM instance, if the benchmark needs it to run).
118pub enum BenchmarkVolumeFactory {
119    SystemFvm(SystemFvm),
120    SystemGpt(SystemGpt),
121}
122
123struct RawBlockDeviceInDevfs(fdevice::ControllerProxy);
124
125impl BlockDevice for RawBlockDeviceInDevfs {
126    fn connector(&self) -> Box<dyn BlockConnector> {
127        Box::new(self.0.clone())
128    }
129}
130
131struct RawBlockDeviceInGpt(Arc<fpartitions::PartitionServiceProxy>);
132
133impl BlockDevice for RawBlockDeviceInGpt {
134    fn connector(&self) -> Box<dyn BlockConnector> {
135        Box::new(self.0.clone())
136    }
137}
138
139#[async_trait]
140impl BlockDeviceFactory for BenchmarkVolumeFactory {
141    async fn create_block_device(&self, config: &BlockDeviceConfig) -> Box<dyn BlockDevice> {
142        let instance_guid = create_random_guid();
143        match self {
144            Self::SystemFvm(SystemFvm::Devfs(volume_manager)) => Box::new(
145                Self::create_fvm_volume_in_devfs(volume_manager, instance_guid, config).await,
146            ),
147            Self::SystemFvm(SystemFvm::Component(volumes_connector, _)) => {
148                let volumes = volumes_connector();
149                Box::new(Self::create_fvm_volume(volumes, instance_guid, config).await)
150            }
151            Self::SystemGpt(SystemGpt::Devfs(controller)) => {
152                if config.requires_fvm {
153                    Box::new(
154                        Self::create_fvm_instance_and_volume_in_devfs(
155                            controller,
156                            instance_guid,
157                            config,
158                        )
159                        .await,
160                    )
161                } else {
162                    Box::new(RawBlockDeviceInDevfs(controller.clone()))
163                }
164            }
165            Self::SystemGpt(SystemGpt::Component(partition_service)) => {
166                if config.requires_fvm {
167                    Box::new(
168                        Self::create_fvm_instance_and_volume(
169                            partition_service.clone(),
170                            instance_guid,
171                            config,
172                        )
173                        .await,
174                    )
175                } else {
176                    Box::new(RawBlockDeviceInGpt(partition_service.clone()))
177                }
178            }
179        }
180    }
181}
182
183impl BenchmarkVolumeFactory {
184    /// Creates a factory for volumes in which benchmarks should run in based on the provided
185    /// configuration.  Uses various capabilities in the incoming namespace of the process.
186    pub async fn from_config(storage_host: bool, fxfs_blob: bool) -> BenchmarkVolumeFactory {
187        if storage_host {
188            let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
189            let manager = connect_to_protocol::<fpartitions::PartitionsManagerMarker>().unwrap();
190            if fxfs_blob {
191                let instance =
192                    BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager).await;
193                assert!(
194                    instance.is_some(),
195                    "Failed to open or create testing FVM in GPT.  \
196                    Perhaps the system doesn't have a GPT-formatted block device?"
197                );
198                instance.unwrap()
199            } else {
200                let volumes_connector = Box::new(move || {
201                    connect_to_protocol::<fidl_fuchsia_fs_startup::VolumesMarker>().unwrap()
202                });
203                let volumes_dir_connector = {
204                    Box::new(move || {
205                        fuchsia_fs::directory::open_in_namespace("volumes", fio::PERM_READABLE)
206                            .unwrap()
207                    })
208                };
209                BenchmarkVolumeFactory::connect_to_system_fvm(
210                    volumes_connector,
211                    volumes_dir_connector,
212                )
213                .unwrap()
214            }
215        } else {
216            if fxfs_blob {
217                let instance = BenchmarkVolumeFactory::connect_to_test_partition_devfs().await;
218                assert!(
219                    instance.is_some(),
220                    "Failed to open or create testing volume in GPT.  \
221                    Perhaps the system doesn't have a GPT-formatted block device?"
222                );
223                instance.unwrap()
224            } else {
225                let instance = BenchmarkVolumeFactory::connect_to_system_fvm_devfs().await;
226                assert!(
227                    instance.is_some(),
228                    "Failed to open or create volume in FVM.  \
229                    Perhaps the system doesn't have an FVM-formatted block device?"
230                );
231                instance.unwrap()
232            }
233        }
234    }
235
236    /// Connects to the system FVM component.
237    pub fn connect_to_system_fvm(
238        volumes_connector: Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
239        volumes_dir_connector: Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
240    ) -> Option<BenchmarkVolumeFactory> {
241        Some(BenchmarkVolumeFactory::SystemFvm(SystemFvm::Component(
242            volumes_connector,
243            volumes_dir_connector,
244        )))
245    }
246
247    /// Connects to the system FVM running in devfs.
248    pub async fn connect_to_system_fvm_devfs() -> Option<BenchmarkVolumeFactory> {
249        // The FVM won't always have a label or GUID we can search for (e.g. on Astro where it is
250        // the top-level partition exposed by the FTL).  Search for blobfs and work backwards.
251        let blobfs_dev_path = find_block_device_devfs(&[
252            BlockDeviceMatcher::Name(BLOBFS_VOLUME_NAME),
253            BlockDeviceMatcher::TypeGuid(&BLOBFS_TYPE_GUID),
254        ])
255        .await
256        .ok()?;
257
258        let controller_path = format!("{}/device_controller", blobfs_dev_path.to_str().unwrap());
259        let blobfs_controller = connect_to_protocol_at_path::<ControllerMarker>(&controller_path)
260            .unwrap_or_else(|_| panic!("Failed to connect to Controller at {:?}", controller_path));
261        let path = blobfs_controller
262            .get_topological_path()
263            .await
264            .expect("FIDL error")
265            .expect("get_topological_path failed");
266
267        let mut path = PathBuf::from(path);
268        if !path.pop() || !path.pop() {
269            panic!("Unexpected topological path for Blobfs {}", path.display());
270        }
271
272        match path.file_name() {
273            Some(p) => assert!(p == "fvm", "Unexpected FVM path: {}", path.display()),
274            None => panic!("Unexpected FVM path: {}", path.display()),
275        }
276        Some(BenchmarkVolumeFactory::SystemFvm(SystemFvm::Devfs(
277            connect_to_protocol_at_path::<VolumeManagerMarker>(path.to_str().unwrap())
278                .unwrap_or_else(|_| panic!("Failed to connect to VolumeManager at {:?}", path)),
279        )))
280    }
281
282    // Creates and connects to the partition reserved for benchmarks, or adds it to the GPT if
283    // absent.  The partition will be unformatted and should be reformatted explicitly before being
284    // used for a benchmark.
285    pub async fn connect_to_test_partition(
286        service: Service<fpartitions::PartitionServiceMarker>,
287        manager: fpartitions::PartitionsManagerProxy,
288    ) -> Option<BenchmarkVolumeFactory> {
289        let service_instances =
290            service.clone().enumerate().await.expect("Failed to enumerate partitions");
291        let connector = if let Some(connector) = find_block_device(
292            &[
293                BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
294                BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
295            ],
296            service_instances.into_iter(),
297        )
298        .await
299        .expect("Failed to find block device")
300        {
301            // If the test FVM already exists, just use it.
302            connector
303        } else {
304            // Otherwise, create it in the GPT.
305            let info =
306                manager.get_block_info().await.expect("FIDL error").expect("get_block_info failed");
307            let transaction = manager
308                .create_transaction()
309                .await
310                .expect("FIDL error")
311                .map_err(zx::Status::from_raw)
312                .expect("create_transaction failed");
313            let request = fpartitions::PartitionsManagerAddPartitionRequest {
314                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
315                name: Some(BENCHMARK_FVM_VOLUME_NAME.to_string()),
316                type_guid: Some(fidl_fuchsia_storage_block::Guid {
317                    value: BENCHMARK_FVM_TYPE_GUID.clone(),
318                }),
319                num_blocks: Some(BENCHMARK_FVM_SIZE_BYTES / info.1 as u64),
320                ..Default::default()
321            };
322            manager
323                .add_partition(request)
324                .await
325                .expect("FIDL error")
326                .map_err(zx::Status::from_raw)
327                .expect("add_partition failed");
328            manager
329                .commit_transaction(transaction)
330                .await
331                .expect("FIDL error")
332                .map_err(zx::Status::from_raw)
333                .expect("add_partition failed");
334            let service_instances =
335                service.enumerate().await.expect("Failed to enumerate partitions");
336            log::info!("len {}", service_instances.len());
337            find_block_device(
338                &[
339                    BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
340                    BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
341                ],
342                service_instances.into_iter(),
343            )
344            .await
345            .expect("Failed to find block device")?
346        };
347
348        Some(BenchmarkVolumeFactory::SystemGpt(SystemGpt::Component(Arc::new(connector))))
349    }
350
351    // Creates and connects to the partition reserved for benchmarks, or adds it to the GPT if
352    // absent.  The partition will be unformatted and should be reformatted explicitly before being
353    // used for a benchmark.
354    // TODO(https://fxbug.dev/372555079): Remove.
355    pub async fn connect_to_test_partition_devfs() -> Option<BenchmarkVolumeFactory> {
356        let mut fvm_path = if let Ok(path) = find_block_device_devfs(&[
357            BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
358            BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
359        ])
360        .await
361        {
362            // If the test FVM already exists, just use it.
363            path
364        } else {
365            // Otherwise, create it in the GPT.
366            let mut gpt_block_path =
367                find_block_device_devfs(&[BlockDeviceMatcher::ContentsMatch(DiskFormat::Gpt)])
368                    .await
369                    .ok()?;
370            gpt_block_path.push("device_controller");
371            let gpt_block_controller =
372                connect_to_protocol_at_path::<ControllerMarker>(gpt_block_path.to_str().unwrap())
373                    .expect("Failed to connect to GPT controller");
374
375            let mut gpt_path = gpt_block_controller
376                .get_topological_path()
377                .await
378                .expect("FIDL error")
379                .expect("get_topological_path failed");
380            gpt_path.push_str("/gpt/device_controller");
381
382            let gpt_controller = connect_to_protocol_at_path::<ControllerMarker>(&gpt_path)
383                .expect("Failed to connect to GPT controller");
384
385            let (volume_manager, server) = create_proxy::<VolumeManagerMarker>();
386            gpt_controller
387                .connect_to_device_fidl(server.into_channel())
388                .expect("Failed to connect to device FIDL");
389            let slice_size = {
390                let (status, info) = volume_manager.get_info().await.expect("FIDL error");
391                zx::ok(status).expect("Failed to get VolumeManager info");
392                info.unwrap().slice_size
393            };
394            let slice_count = BENCHMARK_FVM_SIZE_BYTES / slice_size;
395            let instance_guid = into_guid(create_random_guid());
396            let status = volume_manager
397                .allocate_partition(
398                    slice_count,
399                    &into_guid(BENCHMARK_FVM_TYPE_GUID.clone()),
400                    &instance_guid,
401                    BENCHMARK_FVM_VOLUME_NAME,
402                    0,
403                )
404                .await
405                .expect("FIDL error");
406            zx::ok(status).expect("Failed to allocate benchmark FVM");
407
408            wait_for_block_device_devfs(&[
409                BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
410                BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
411            ])
412            .await
413            .expect("Failed to wait for newly created benchmark FVM to appear")
414        };
415        fvm_path.push("device_controller");
416        let fvm_controller =
417            connect_to_protocol_at_path::<ControllerMarker>(fvm_path.to_str().unwrap())
418                .expect("failed to connect to controller");
419
420        Some(BenchmarkVolumeFactory::SystemGpt(SystemGpt::Devfs(fvm_controller)))
421    }
422
423    #[cfg(test)]
424    pub async fn contains_fvm_volume(&self, name: &str) -> bool {
425        match self {
426            Self::SystemFvm(SystemFvm::Devfs(_)) => {
427                find_block_device_devfs(&[BlockDeviceMatcher::Name(name)]).await.is_ok()
428            }
429            Self::SystemFvm(SystemFvm::Component(_, volumes_dir_connector)) => {
430                let dir = volumes_dir_connector();
431                fuchsia_fs::directory::dir_contains(&dir, name).await.unwrap()
432            }
433            // If we're using a system GPT, the FVM instance is created on the fly, so volumes are
434            // too.
435            _ => false,
436        }
437    }
438
439    async fn create_fvm_volume_in_devfs(
440        volume_manager: &VolumeManagerProxy,
441        instance_guid: [u8; 16],
442        config: &BlockDeviceConfig,
443    ) -> FvmVolume {
444        fvm::create_fvm_volume(
445            volume_manager,
446            BENCHMARK_VOLUME_NAME,
447            BENCHMARK_TYPE_GUID,
448            &instance_guid,
449            config.volume_size,
450            ALLOCATE_PARTITION_FLAG_INACTIVE,
451        )
452        .await
453        .expect("Failed to create FVM volume");
454
455        let device_path = wait_for_block_device_devfs(&[
456            BlockDeviceMatcher::TypeGuid(BENCHMARK_TYPE_GUID),
457            BlockDeviceMatcher::InstanceGuid(&instance_guid),
458            BlockDeviceMatcher::Name(BENCHMARK_VOLUME_NAME),
459        ])
460        .await
461        .expect("Failed to find the FVM volume");
462
463        // We need to connect to the volume's DirectoryProxy via its topological path in order to
464        // allow the caller to access its zxcrypt child. Hence, we use the controller to get access
465        // to the topological path and then call open().
466        // Connect to the controller and get the device's topological path.
467        let controller = connect_to_protocol_at_path::<ControllerMarker>(&format!(
468            "{}/device_controller",
469            device_path.to_str().unwrap()
470        ))
471        .expect("failed to connect to controller");
472        let topo_path = controller
473            .get_topological_path()
474            .await
475            .expect("transport error on get_topological_path")
476            .expect("get_topological_path failed");
477        let volume_dir =
478            fuchsia_fs::directory::open_in_namespace(&topo_path, fuchsia_fs::Flags::empty())
479                .expect("failed to open device");
480        // TODO(https://fxbug.dev/42063787): In order to allow multiplexing to be removed, use
481        // connect_to_device_fidl to connect to the BlockProxy instead of connect_to_.._dir_root.
482        // Requires downstream work, i.e. set_up_fvm_volume() and set_up_insecure_zxcrypt should
483        // return controllers.
484        let block = connect_to_named_protocol_at_dir_root::<BlockMarker>(&volume_dir, ".").unwrap();
485        let volume = BlockSynchronousProxy::new(block.into_channel().unwrap().into());
486        let volume_dir = if config.use_zxcrypt {
487            zxcrypt::set_up_insecure_zxcrypt(&volume_dir).await.expect("Failed to set up zxcrypt")
488        } else {
489            volume_dir
490        };
491
492        FvmVolume {
493            destroy_fn: Some(Box::new(move || {
494                zx::ok(volume.destroy(zx::MonotonicInstant::INFINITE).unwrap())
495            })),
496            fvm_instance: None,
497            volume_dir: Some(volume_dir),
498            block_path: ".".to_string(),
499            crypt_task: None,
500        }
501    }
502
503    async fn create_fvm_volume(
504        volumes: fidl_fuchsia_fs_startup::VolumesProxy,
505        instance_guid: [u8; 16],
506        config: &BlockDeviceConfig,
507    ) -> FvmVolume {
508        let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
509        let volumes = volumes.into_client_end().unwrap().into_sync_proxy();
510        FvmVolume {
511            destroy_fn: Some(Box::new(move || {
512                volumes
513                    .remove(BENCHMARK_VOLUME_NAME, zx::MonotonicInstant::INFINITE)
514                    .unwrap()
515                    .map_err(zx::Status::from_raw)
516            })),
517            volume_dir: Some(volume_dir),
518            fvm_instance: None,
519            block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
520            crypt_task,
521        }
522    }
523
524    async fn create_fvm_instance_and_volume(
525        partition: Arc<fpartitions::PartitionServiceProxy>,
526        instance_guid: [u8; 16],
527        config: &BlockDeviceConfig,
528    ) -> FvmVolume {
529        let block_device =
530            partition.connect_block().expect("Failed to connect to block").into_proxy();
531        fvm::format_for_fvm(&block_device, BENCHMARK_FVM_SLICE_SIZE_BYTES)
532            .expect("Failed to format FVM");
533
534        let fs = fs_management::filesystem::Filesystem::from_boxed_config(
535            Box::new(partition),
536            Box::new(Fvm::default()),
537        );
538        let fvm_instance = fs.serve_multi_volume().await.expect("Failed to serve FVM");
539        let volumes = connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
540            fvm_instance.exposed_dir(),
541        )
542        .unwrap();
543
544        let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
545        FvmVolume {
546            destroy_fn: None,
547            volume_dir: Some(volume_dir),
548            fvm_instance: Some(fvm_instance),
549            block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
550            crypt_task,
551        }
552    }
553
554    async fn create_fvm_instance_and_volume_in_devfs(
555        fvm_controller: &fdevice::ControllerProxy,
556        instance_guid: [u8; 16],
557        config: &BlockDeviceConfig,
558    ) -> FvmVolume {
559        // Unbind in case anything was using the partition.
560        fvm_controller
561            .unbind_children()
562            .await
563            .expect("FIDL error")
564            .expect("failed to unbind children");
565
566        let (block_device, server_end) = create_proxy::<BlockMarker>();
567        fvm_controller.connect_to_device_fidl(server_end.into_channel()).unwrap();
568        fvm::format_for_fvm(&block_device, BENCHMARK_FVM_SLICE_SIZE_BYTES)
569            .expect("Failed to format FVM");
570
571        let topo_path = fvm_controller
572            .get_topological_path()
573            .await
574            .expect("transport error on get_topological_path")
575            .expect("get_topological_path failed");
576        let dir = fuchsia_fs::directory::open_in_namespace(&topo_path, fio::PERM_READABLE).unwrap();
577        let volume_manager =
578            fvm::start_fvm_driver(&fvm_controller, &dir).await.expect("Failed to start FVM");
579
580        Self::create_fvm_volume_in_devfs(&volume_manager, instance_guid, config).await
581    }
582}
583
584/// A block device created on top of an FVM instance.
585pub struct FvmVolume {
586    destroy_fn: Option<Box<dyn Send + Sync + FnOnce() -> Result<(), zx::Status>>>,
587    fvm_instance: Option<ServingMultiVolumeFilesystem>,
588    volume_dir: Option<fio::DirectoryProxy>,
589    crypt_task: Option<fasync::Task<()>>,
590    // The path in `volume_dir` to connect to when opening a new Block connection.
591    block_path: String,
592}
593
594impl BlockDevice for FvmVolume {
595    fn connector(&self) -> Box<dyn BlockConnector> {
596        let volume_dir = fuchsia_fs::directory::clone(self.volume_dir.as_ref().unwrap()).unwrap();
597        Box::new(DirBasedBlockConnector::new(volume_dir, self.block_path.clone()))
598    }
599}
600
601impl Drop for FvmVolume {
602    fn drop(&mut self) {
603        self.volume_dir = None;
604        self.fvm_instance = None;
605        self.crypt_task = None;
606        if let Some(destroy_fn) = self.destroy_fn.take() {
607            destroy_fn().expect("Failed to destroy FVM volume");
608        }
609    }
610}
611
612#[cfg(test)]
613mod tests {
614    use super::*;
615    use crate::testing::{RAMDISK_FVM_SLICE_SIZE, RamdiskFactory};
616    use block_client::RemoteBlockClient;
617    use fidl_fuchsia_fs_startup::VolumesMarker;
618    use fs_management::Gpt;
619    use ramdevice_client::{RamdiskClient, RamdiskClientBuilder};
620    use std::sync::Arc;
621    use vmo_backed_block_server::{VmoBackedServer, VmoBackedServerTestingExt as _};
622
623    const BLOCK_SIZE: u64 = 4 * 1024;
624    const BLOCK_COUNT: u64 = 1024;
625    // We need more blocks for the GPT version of the test, since the library will by default
626    // allocate 128MiB for the embedded FVM.  This is big enough for a 192MiB device.
627    const GPT_BLOCK_COUNT: u64 = 49152;
628
629    #[fuchsia::test]
630    async fn ramdisk_create_block_device_with_zxcrypt() {
631        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
632        let _ = ramdisk_factory
633            .create_block_device(&BlockDeviceConfig {
634                requires_fvm: true,
635                use_zxcrypt: true,
636                volume_size: None,
637            })
638            .await;
639    }
640
641    #[fuchsia::test]
642    async fn ramdisk_create_block_device_without_zxcrypt() {
643        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
644        let _ = ramdisk_factory
645            .create_block_device(&BlockDeviceConfig {
646                requires_fvm: true,
647                use_zxcrypt: false,
648                volume_size: None,
649            })
650            .await;
651    }
652
653    #[fuchsia::test]
654    async fn ramdisk_create_block_device_without_volume_size() {
655        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
656        let ramdisk = ramdisk_factory
657            .create_block_device(&BlockDeviceConfig {
658                requires_fvm: true,
659                use_zxcrypt: false,
660                volume_size: None,
661            })
662            .await;
663        let volume_info = ramdisk
664            .connector()
665            .connect_block()
666            .unwrap()
667            .into_proxy()
668            .get_volume_info()
669            .await
670            .unwrap();
671        zx::ok(volume_info.0).unwrap();
672        let volume_info = volume_info.2.unwrap();
673        assert_eq!(volume_info.partition_slice_count, 1);
674    }
675
676    #[fuchsia::test]
677    async fn ramdisk_create_block_device_with_volume_size() {
678        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
679        let ramdisk = ramdisk_factory
680            .create_block_device(&BlockDeviceConfig {
681                requires_fvm: false,
682                use_zxcrypt: false,
683                volume_size: Some(RAMDISK_FVM_SLICE_SIZE as u64 * 3),
684            })
685            .await;
686        let volume_info = ramdisk
687            .connector()
688            .connect_block()
689            .unwrap()
690            .into_proxy()
691            .get_volume_info()
692            .await
693            .unwrap();
694        zx::ok(volume_info.0).unwrap();
695        let volume_info = volume_info.2.unwrap();
696        assert_eq!(volume_info.partition_slice_count, 3);
697    }
698
699    async fn init_gpt(block_size: u32, block_count: u64) -> zx::Vmo {
700        let vmo = zx::Vmo::create(block_size as u64 * block_count).unwrap();
701        let server = Arc::new(VmoBackedServer::from_vmo(
702            block_size,
703            vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0).unwrap(),
704        ));
705        let (client, server_end) =
706            fidl::endpoints::create_proxy::<fidl_fuchsia_storage_block::BlockMarker>();
707
708        let _task =
709            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
710        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
711        gpt::Gpt::format(client.clone(), vec![gpt::PartitionInfo::nil(); 128])
712            .await
713            .expect("format failed");
714        vmo
715    }
716
717    struct FvmTestConfig {
718        fxblob_enabled: bool,
719        storage_host_enabled: bool,
720    }
721
722    // Retains test state.
723    enum TestState {
724        // The drivers are running in an isolated devmgr instance.
725        Devfs(#[allow(dead_code)] RamdiskClient),
726        // The ramdisk is running in an isolated devmgr instance, but the volume managers are
727        // running in child components.
728        StorageHost(
729            #[allow(dead_code)] RamdiskClient,
730            #[allow(dead_code)] ServingMultiVolumeFilesystem,
731        ),
732    }
733
734    async fn initialize(config: FvmTestConfig) -> (TestState, BenchmarkVolumeFactory) {
735        if config.fxblob_enabled {
736            // Initialize a new GPT.
737            let vmo = init_gpt(BLOCK_SIZE as u32, GPT_BLOCK_COUNT).await;
738            let ramdisk_builder = RamdiskClientBuilder::new_with_vmo(vmo, Some(BLOCK_SIZE));
739            let ramdisk = if config.storage_host_enabled {
740                ramdisk_builder.use_v2()
741            } else {
742                ramdisk_builder
743            }
744            .build()
745            .await
746            .expect("Failed to create ramdisk");
747
748            if config.storage_host_enabled {
749                let gpt = fs_management::filesystem::Filesystem::from_boxed_config(
750                    ramdisk.connector().unwrap(),
751                    Box::new(Gpt::dynamic_child()),
752                )
753                .serve_multi_volume()
754                .await
755                .expect("Failed to serve GPT");
756                let partitions =
757                    Service::open_from_dir(gpt.exposed_dir(), fpartitions::PartitionServiceMarker)
758                        .unwrap();
759                let manager =
760                    connect_to_protocol_at_dir_root::<fpartitions::PartitionsManagerMarker>(
761                        gpt.exposed_dir(),
762                    )
763                    .unwrap();
764                let fvm = BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager)
765                    .await
766                    .expect("Failed to connect to FVM");
767                (TestState::StorageHost(ramdisk, gpt), fvm)
768            } else {
769                ramdisk
770                    .as_controller()
771                    .expect("invalid controller")
772                    .bind("gpt.cm")
773                    .await
774                    .expect("FIDL error calling bind()")
775                    .map_err(zx::Status::from_raw)
776                    .expect("bind() returned non-Ok status");
777                wait_for_block_device_devfs(&[BlockDeviceMatcher::ContentsMatch(DiskFormat::Gpt)])
778                    .await
779                    .expect("Failed to wait for GPT to appear");
780                let fvm = BenchmarkVolumeFactory::connect_to_test_partition_devfs()
781                    .await
782                    .expect("Failed to connect to FVM");
783                (TestState::Devfs(ramdisk), fvm)
784            }
785        } else {
786            // Initialize a new FVM.
787            let ramdisk_builder = RamdiskClientBuilder::new(BLOCK_SIZE, BLOCK_COUNT);
788            let ramdisk = if config.storage_host_enabled {
789                ramdisk_builder.use_v2()
790            } else {
791                ramdisk_builder
792            }
793            .build()
794            .await
795            .expect("Failed to create ramdisk");
796            fvm::format_for_fvm(&ramdisk.open().unwrap().into_proxy(), RAMDISK_FVM_SLICE_SIZE)
797                .expect("Failed to format FVM");
798            if config.storage_host_enabled {
799                let fvm_component = match fs_management::filesystem::Filesystem::from_boxed_config(
800                    ramdisk.connector().unwrap(),
801                    Box::new(Fvm::dynamic_child()),
802                )
803                .serve_multi_volume()
804                .await
805                {
806                    Ok(fvm_component) => fvm_component,
807                    Err(_) => loop {},
808                };
809                let volumes_connector = {
810                    let exposed_dir =
811                        fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
812                    Box::new(move || {
813                        connect_to_protocol_at_dir_root::<VolumesMarker>(&exposed_dir).unwrap()
814                    })
815                };
816                let volumes_dir_connector = {
817                    let exposed_dir =
818                        fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
819                    Box::new(move || {
820                        fuchsia_fs::directory::open_directory_async(
821                            &exposed_dir,
822                            "volumes",
823                            fio::PERM_READABLE,
824                        )
825                        .unwrap()
826                    })
827                };
828                let fvm = BenchmarkVolumeFactory::connect_to_system_fvm(
829                    volumes_connector,
830                    volumes_dir_connector,
831                );
832                (TestState::StorageHost(ramdisk, fvm_component), fvm.unwrap())
833            } else {
834                // Add a blob volume, since that is how we identify the system FVM partition.
835                let block_controller = ramdisk.open_controller().unwrap();
836                let fvm_path = block_controller
837                    .get_topological_path()
838                    .await
839                    .expect("FIDL error")
840                    .expect("Failed to get topo path");
841                let dir = fuchsia_fs::directory::open_in_namespace(&fvm_path, fio::PERM_READABLE)
842                    .unwrap();
843                let volume_manager = fvm::start_fvm_driver(&block_controller, &dir)
844                    .await
845                    .expect("Failed to start FVM");
846                let type_guid = fidl_fuchsia_storage_block::Guid { value: BLOBFS_TYPE_GUID };
847                let instance_guid =
848                    fidl_fuchsia_storage_block::Guid { value: create_random_guid() };
849                zx::ok(
850                    volume_manager
851                        .allocate_partition(1, &type_guid, &instance_guid, BLOBFS_VOLUME_NAME, 0)
852                        .await
853                        .expect("FIDL error"),
854                )
855                .expect("failed to allocate blobfs partition");
856                wait_for_block_device_devfs(&[
857                    BlockDeviceMatcher::Name(BLOBFS_VOLUME_NAME),
858                    BlockDeviceMatcher::TypeGuid(&BLOBFS_TYPE_GUID),
859                ])
860                .await
861                .expect("Failed to wait for blobfs to appear");
862                let fvm = BenchmarkVolumeFactory::connect_to_system_fvm_devfs()
863                    .await
864                    .expect("Failed to connect to FVM");
865                (TestState::Devfs(ramdisk), fvm)
866            }
867        }
868    }
869
870    async fn benchmark_volume_factory_can_find_fvm_instance(config: FvmTestConfig) {
871        let (_state, volume_factory) = initialize(config).await;
872
873        // Verify that a volume can be created.
874        volume_factory
875            .create_block_device(&BlockDeviceConfig {
876                requires_fvm: true,
877                use_zxcrypt: false,
878                volume_size: None,
879            })
880            .await;
881    }
882
883    #[fuchsia::test]
884    async fn benchmark_volume_factory_can_find_fvm_instance_fvm_non_storage_host() {
885        benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig {
886            fxblob_enabled: false,
887            storage_host_enabled: false,
888        })
889        .await;
890    }
891
892    #[fuchsia::test]
893    async fn benchmark_volume_factory_can_find_fvm_instance_gpt_non_storage_host() {
894        benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig {
895            fxblob_enabled: true,
896            storage_host_enabled: false,
897        })
898        .await;
899    }
900
901    #[fuchsia::test]
902    async fn benchmark_volume_factory_can_find_fvm_instance_fvm() {
903        benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig {
904            fxblob_enabled: false,
905            storage_host_enabled: true,
906        })
907        .await;
908    }
909
910    #[fuchsia::test]
911    async fn benchmark_volume_factory_can_find_fvm_instance_gpt() {
912        benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig {
913            fxblob_enabled: true,
914            storage_host_enabled: true,
915        })
916        .await;
917    }
918
919    async fn dropping_an_fvm_volume_removes_the_volume(config: FvmTestConfig) {
920        let (_state, volume_factory) = initialize(config).await;
921        {
922            let _volume = volume_factory
923                .create_block_device(&BlockDeviceConfig {
924                    requires_fvm: true,
925                    use_zxcrypt: false,
926                    volume_size: None,
927                })
928                .await;
929            assert!(volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
930        };
931        assert!(!volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
932    }
933
934    #[fuchsia::test]
935    async fn dropping_an_fvm_volume_removes_the_volume_fvm_non_storage_host() {
936        dropping_an_fvm_volume_removes_the_volume(FvmTestConfig {
937            fxblob_enabled: false,
938            storage_host_enabled: false,
939        })
940        .await;
941    }
942
943    #[fuchsia::test]
944    async fn dropping_an_fvm_volume_removes_the_volume_fvm() {
945        dropping_an_fvm_volume_removes_the_volume(FvmTestConfig {
946            fxblob_enabled: false,
947            storage_host_enabled: true,
948        })
949        .await;
950    }
951
952    async fn benchmark_volume_factory_create_block_device_with_zxcrypt(config: FvmTestConfig) {
953        let (_state, volume_factory) = initialize(config).await;
954        let _ = volume_factory
955            .create_block_device(&BlockDeviceConfig {
956                requires_fvm: true,
957                use_zxcrypt: true,
958                volume_size: None,
959            })
960            .await;
961    }
962
963    #[fuchsia::test]
964    async fn benchmark_volume_factory_create_block_device_with_zxcrypt_fvm_non_storage_host() {
965        benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
966            fxblob_enabled: false,
967            storage_host_enabled: false,
968        })
969        .await;
970    }
971
972    #[fuchsia::test]
973    async fn benchmark_volume_factory_create_block_device_with_zxcrypt_gpt_non_storage_host() {
974        benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
975            fxblob_enabled: true,
976            storage_host_enabled: false,
977        })
978        .await;
979    }
980
981    #[fuchsia::test]
982    async fn benchmark_volume_factory_create_block_device_with_zxcrypt_fvm() {
983        benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
984            fxblob_enabled: false,
985            storage_host_enabled: true,
986        })
987        .await;
988    }
989
990    #[fuchsia::test]
991    async fn benchmark_volume_factory_create_block_device_with_zxcrypt_gpt() {
992        benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
993            fxblob_enabled: true,
994            storage_host_enabled: true,
995        })
996        .await;
997    }
998}