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