fshost_test_fixture/
mocks.rs

1// Copyright 2021 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 crate::disk_builder::{DataSpec, DiskBuilder, VolumesSpec};
6use anyhow::Error;
7use fake_keymint::FakeKeymint;
8use ffeedback::FileReportResults;
9use fidl::prelude::*;
10use fs_management::filesystem::DirBasedBlockConnector;
11use fuchsia_component::client::connect::connect_to_named_protocol_at_dir_root;
12use fuchsia_component_test::LocalComponentHandles;
13use futures::channel::mpsc::{self};
14use futures::future::BoxFuture;
15use futures::{FutureExt as _, SinkExt as _, StreamExt as _};
16use std::sync::{Arc, LazyLock};
17use vfs::execution_scope::ExecutionScope;
18
19use {
20    fidl_fuchsia_boot as fboot, fidl_fuchsia_feedback as ffeedback,
21    fidl_fuchsia_fshost_fxfsprovisioner as ffxfsprovisioner, fidl_fuchsia_io as fio,
22    fidl_fuchsia_security_keymint as fkeymint, fidl_fuchsia_storage_partitions as fpartitions,
23};
24
25/// Identifier for ramdisk storage. Defined in sdk/lib/zbi-format/include/lib/zbi-format/zbi.h.
26const ZBI_TYPE_STORAGE_RAMDISK: u32 = 0x4b534452;
27
28pub fn new_mocks(
29    vmo: Option<zx::Vmo>,
30    crash_reports_sink: mpsc::Sender<ffeedback::CrashReport>,
31    force_fxfs_provisioner_failure: bool,
32) -> impl Fn(LocalComponentHandles) -> BoxFuture<'static, Result<(), Error>> + Sync + Send + 'static
33{
34    let vmo = vmo.map(Arc::new);
35    let mock = move |handles: LocalComponentHandles| {
36        let vmo_clone = vmo.clone();
37        run_mocks(handles, vmo_clone, crash_reports_sink.clone(), force_fxfs_provisioner_failure)
38            .boxed()
39    };
40
41    mock
42}
43
44async fn run_mocks(
45    handles: LocalComponentHandles,
46    vmo: Option<Arc<zx::Vmo>>,
47    crash_reports_sink: mpsc::Sender<ffeedback::CrashReport>,
48    force_fxfs_provisioner_failure: bool,
49) -> Result<(), Error> {
50    let export = vfs::pseudo_directory! {
51        "svc" => vfs::pseudo_directory! {
52            fkeymint::SealingKeysMarker::PROTOCOL_NAME => vfs::service::host(move |stream| {
53                run_keymint(stream)
54            }),
55            fboot::ItemsMarker::PROTOCOL_NAME => vfs::service::host(move |stream| {
56                let vmo_clone = vmo.clone();
57                run_boot_items(stream, vmo_clone)
58            }),
59            ffeedback::CrashReporterMarker::PROTOCOL_NAME => vfs::service::host(move |stream| {
60                run_crash_reporter(stream, crash_reports_sink.clone())
61            }),
62            ffxfsprovisioner::FxfsProvisionerMarker::PROTOCOL_NAME => vfs::service::host(
63                move |stream| { run_fxfs_provisioner(stream, force_fxfs_provisioner_failure) }
64            ),
65        },
66    };
67
68    let scope = ExecutionScope::new();
69    vfs::directory::serve_on(export, fio::PERM_READABLE, scope.clone(), handles.outgoing_dir);
70    scope.wait().await;
71
72    Ok(())
73}
74
75/// fshost uses exactly one boot item - it checks to see if there is an item of type
76/// ZBI_TYPE_STORAGE_RAMDISK. If it's there, it's a vmo that represents a ramdisk version of the
77/// fvm, and fshost creates a ramdisk from the vmo so it can go through the normal device matching.
78async fn run_boot_items(mut stream: fboot::ItemsRequestStream, vmo: Option<Arc<zx::Vmo>>) {
79    while let Some(request) = stream.next().await {
80        match request.unwrap() {
81            fboot::ItemsRequest::Get { type_, extra, responder } => {
82                assert_eq!(type_, ZBI_TYPE_STORAGE_RAMDISK);
83                assert_eq!(extra, 0);
84                let response_vmo = vmo.as_ref().map(|vmo| {
85                    vmo.create_child(zx::VmoChildOptions::SLICE, 0, vmo.get_size().unwrap())
86                        .unwrap()
87                });
88                responder.send(response_vmo, 0).unwrap();
89            }
90            fboot::ItemsRequest::Get2 { type_, extra, responder } => {
91                assert_eq!(type_, ZBI_TYPE_STORAGE_RAMDISK);
92                assert_eq!((*extra.unwrap()).n, 0);
93                responder.send(Ok(Vec::new())).unwrap();
94            }
95            fboot::ItemsRequest::GetBootloaderFile { .. } => {
96                panic!(
97                    "unexpectedly called GetBootloaderFile on {}",
98                    fboot::ItemsMarker::PROTOCOL_NAME
99                );
100            }
101        }
102    }
103}
104
105async fn run_crash_reporter(
106    mut stream: ffeedback::CrashReporterRequestStream,
107    mut crash_reports_sink: mpsc::Sender<ffeedback::CrashReport>,
108) {
109    while let Some(request) = stream.next().await {
110        match request.unwrap() {
111            ffeedback::CrashReporterRequest::FileReport { report, responder } => {
112                crash_reports_sink.send(report).await.unwrap();
113                responder.send(Ok(&FileReportResults::default())).unwrap();
114            }
115        }
116    }
117}
118
119async fn run_fxfs_provisioner(
120    mut stream: ffxfsprovisioner::FxfsProvisionerRequestStream,
121    force_failure: bool,
122) {
123    while let Some(request) = stream.next().await {
124        match request.unwrap() {
125            ffxfsprovisioner::FxfsProvisionerRequest::Provision {
126                partition_service,
127                responder,
128            } => {
129                if force_failure {
130                    responder.send(Err(zx::Status::INTERNAL.into_raw())).unwrap();
131                    return;
132                }
133                let partition_service = partition_service.into_proxy();
134
135                let overlay = connect_to_named_protocol_at_dir_root::<
136                    fpartitions::OverlayPartitionProxy,
137                >(&partition_service, "overlay")
138                .expect("failed to connect to OverlayPartition protocol");
139                let partitions_info = overlay
140                    .get_partitions()
141                    .await
142                    .expect("get_partitions FIDL call failed")
143                    .expect("get_partitions failed");
144                assert_eq!(partitions_info.len(), 2);
145                assert!(partitions_info.iter().any(|info| info.name == "super"));
146                assert!(partitions_info.iter().any(|info| info.name == "userdata"));
147
148                let connector =
149                    Box::new(DirBasedBlockConnector::new(partition_service, "/volume".to_string()));
150
151                let mut disk_builder = DiskBuilder::new();
152                disk_builder
153                    .format_volumes(VolumesSpec { fxfs_blob: true, create_data_partition: true })
154                    .format_data(DataSpec { format: Some("fxfs"), ..Default::default() });
155                disk_builder.build_fxfs_as_volume_manager(connector).await;
156
157                responder.send(Ok(())).unwrap();
158            }
159            _ => {
160                unreachable!()
161            }
162        }
163    }
164}
165
166async fn run_keymint(stream: fkeymint::SealingKeysRequestStream) {
167    // We have to use a singleton because the Keymint service is stateful.
168    static KEYMINT: LazyLock<FakeKeymint> = LazyLock::new(FakeKeymint::default);
169    let keymint = &*KEYMINT;
170    keymint.run_sealing_keys_service(stream).await.unwrap();
171}