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