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            fkeymint::AdminMarker::PROTOCOL_NAME => vfs::service::host(move |stream| {
56                run_keymint_admin(stream)
57            }),
58            fboot::ItemsMarker::PROTOCOL_NAME => vfs::service::host(move |stream| {
59                let vmo_clone = vmo.clone();
60                run_boot_items(stream, vmo_clone)
61            }),
62            ffeedback::CrashReporterMarker::PROTOCOL_NAME => vfs::service::host(move |stream| {
63                run_crash_reporter(stream, crash_reports_sink.clone())
64            }),
65            ffxfsprovisioner::FxfsProvisionerMarker::PROTOCOL_NAME => vfs::service::host(
66                move |stream| { run_fxfs_provisioner(stream, force_fxfs_provisioner_failure) }
67            ),
68        },
69    };
70
71    let scope = ExecutionScope::new();
72    vfs::directory::serve_on(export, fio::PERM_READABLE, scope.clone(), handles.outgoing_dir);
73    scope.wait().await;
74
75    Ok(())
76}
77
78/// fshost uses exactly one boot item - it checks to see if there is an item of type
79/// ZBI_TYPE_STORAGE_RAMDISK. If it's there, it's a vmo that represents a ramdisk version of the
80/// fvm, and fshost creates a ramdisk from the vmo so it can go through the normal device matching.
81async fn run_boot_items(mut stream: fboot::ItemsRequestStream, vmo: Option<Arc<zx::Vmo>>) {
82    while let Some(request) = stream.next().await {
83        match request.unwrap() {
84            fboot::ItemsRequest::Get { type_, extra, responder } => {
85                assert_eq!(type_, ZBI_TYPE_STORAGE_RAMDISK);
86                assert_eq!(extra, 0);
87                let response_vmo = vmo.as_ref().map(|vmo| {
88                    vmo.create_child(zx::VmoChildOptions::SLICE, 0, vmo.get_size().unwrap())
89                        .unwrap()
90                });
91                responder.send(response_vmo, 0).unwrap();
92            }
93            fboot::ItemsRequest::Get2 { type_, extra, responder } => {
94                assert_eq!(type_, ZBI_TYPE_STORAGE_RAMDISK);
95                assert_eq!((*extra.unwrap()).n, 0);
96                responder.send(Ok(Vec::new())).unwrap();
97            }
98            fboot::ItemsRequest::GetBootloaderFile { .. } => {
99                panic!(
100                    "unexpectedly called GetBootloaderFile on {}",
101                    fboot::ItemsMarker::PROTOCOL_NAME
102                );
103            }
104        }
105    }
106}
107
108async fn run_crash_reporter(
109    mut stream: ffeedback::CrashReporterRequestStream,
110    mut crash_reports_sink: mpsc::Sender<ffeedback::CrashReport>,
111) {
112    while let Some(request) = stream.next().await {
113        match request.unwrap() {
114            ffeedback::CrashReporterRequest::FileReport { report, responder } => {
115                crash_reports_sink.send(report).await.unwrap();
116                responder.send(Ok(&FileReportResults::default())).unwrap();
117            }
118        }
119    }
120}
121
122async fn run_fxfs_provisioner(
123    mut stream: ffxfsprovisioner::FxfsProvisionerRequestStream,
124    force_failure: bool,
125) {
126    while let Some(request) = stream.next().await {
127        match request.unwrap() {
128            ffxfsprovisioner::FxfsProvisionerRequest::Provision {
129                partition_service,
130                responder,
131            } => {
132                if force_failure {
133                    responder.send(Err(zx::Status::INTERNAL.into_raw())).unwrap();
134                    return;
135                }
136                let partition_service = partition_service.into_proxy();
137
138                let overlay = connect_to_named_protocol_at_dir_root::<
139                    fpartitions::OverlayPartitionProxy,
140                >(&partition_service, "overlay")
141                .expect("failed to connect to OverlayPartition protocol");
142                let partitions_info = overlay
143                    .get_partitions()
144                    .await
145                    .expect("get_partitions FIDL call failed")
146                    .expect("get_partitions failed");
147                assert_eq!(partitions_info.len(), 2);
148                assert!(partitions_info.iter().any(|info| info.name == "super"));
149                assert!(partitions_info.iter().any(|info| info.name == "userdata"));
150
151                let connector =
152                    Box::new(DirBasedBlockConnector::new(partition_service, "/volume".to_string()));
153
154                let mut disk_builder = DiskBuilder::new();
155                disk_builder
156                    .format_volumes(VolumesSpec { fxfs_blob: true, create_data_partition: true })
157                    .format_data(DataSpec { format: Some("fxfs"), ..Default::default() });
158                disk_builder.build_fxfs_as_volume_manager(connector).await;
159
160                responder.send(Ok(())).unwrap();
161            }
162            _ => {
163                unreachable!()
164            }
165        }
166    }
167}
168
169static KEYMINT: LazyLock<FakeKeymint> = LazyLock::new(FakeKeymint::default);
170
171async fn run_keymint(stream: fkeymint::SealingKeysRequestStream) {
172    let keymint = &*KEYMINT;
173    keymint.run_sealing_keys_service(stream).await.unwrap();
174}
175
176async fn run_keymint_admin(stream: fkeymint::AdminRequestStream) {
177    let keymint = &*KEYMINT;
178    keymint.run_admin_service(stream).await.unwrap();
179}