Skip to main content

fshost_testing/
lib.rs

1// Copyright 2025 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 fidl_fuchsia_fxfs::{BlobCreatorMarker, BlobReaderMarker};
6use fshost_assembly_config;
7use fuchsia_component_test::{Capability, ChildOptions, ChildRef, RealmBuilder, Ref, Route};
8use futures::future::FutureExt as _;
9use std::collections::HashMap;
10
11use fidl_fuchsia_fshost as ffshost;
12use fidl_fuchsia_fxfs as ffxfs;
13use fidl_fuchsia_hardware_block_volume as fvolume;
14use fidl_fuchsia_io as fio;
15use fidl_fuchsia_logger as flogger;
16use fidl_fuchsia_process as fprocess;
17use fidl_fuchsia_storage_partitions as fpartitions;
18use fidl_fuchsia_update_verify as ffuv;
19
20pub trait IntoValueSpec {
21    fn into_value_spec(self) -> cm_rust::ConfigValueSpec;
22}
23
24impl IntoValueSpec for bool {
25    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
26        cm_rust::ConfigValueSpec {
27            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(self)),
28        }
29    }
30}
31
32impl IntoValueSpec for u64 {
33    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
34        cm_rust::ConfigValueSpec {
35            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint64(self)),
36        }
37    }
38}
39
40impl IntoValueSpec for String {
41    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
42        cm_rust::ConfigValueSpec {
43            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(self.into())),
44        }
45    }
46}
47
48impl<'a> IntoValueSpec for &'a str {
49    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
50        self.to_string().into_value_spec()
51    }
52}
53
54/// Builder for the fshost component. This handles configuring the fshost component to use and
55/// structured config overrides to set, as well as setting up the expected protocols to be routed
56/// between the realm builder root and the fshost child when the test realm is built.
57///
58/// Any desired additional config overrides should be added to this builder. New routes for exposed
59/// capabilities from the fshost component or offered capabilities to the fshost component should
60/// be added to the [`FshostBuilder::build`] function below.
61#[derive(Debug, Clone)]
62pub struct FshostBuilder {
63    component_name: &'static str,
64    config_values: HashMap<&'static str, cm_rust::ConfigValueSpec>,
65    create_starnix_volume_crypt: bool,
66    block_device_config_json: String,
67    crypt_policy: crypt_policy::Policy,
68}
69
70impl FshostBuilder {
71    pub fn new(component_name: &'static str) -> FshostBuilder {
72        FshostBuilder {
73            component_name,
74            config_values: HashMap::new(),
75            create_starnix_volume_crypt: false,
76            block_device_config_json: String::from("[]"),
77            crypt_policy: crypt_policy::Policy::Null,
78        }
79    }
80
81    pub fn create_starnix_volume_crypt(&mut self) -> &mut Self {
82        self.create_starnix_volume_crypt = true;
83        self
84    }
85
86    pub fn set_config_value(&mut self, key: &'static str, value: impl IntoValueSpec) -> &mut Self {
87        assert!(
88            self.config_values.insert(key, value.into_value_spec()).is_none(),
89            "Attempted to insert duplicate config value '{}'!",
90            key
91        );
92        self
93    }
94
95    pub fn set_device_config(
96        &mut self,
97        config: Vec<fshost_assembly_config::BlockDeviceConfig>,
98    ) -> &mut Self {
99        self.block_device_config_json = serde_json::to_string(&config).unwrap();
100        self
101    }
102
103    pub fn set_crypt_policy(&mut self, policy: crypt_policy::Policy) -> &mut Self {
104        self.crypt_policy = policy;
105        self
106    }
107
108    pub async fn build(mut self, realm_builder: &RealmBuilder) -> ChildRef {
109        let fshost_url = format!("#meta/{}.cm", self.component_name);
110        log::info!(fshost_url:%; "building test fshost instance");
111        let fshost =
112            realm_builder.add_child("test-fshost", fshost_url, ChildOptions::new()).await.unwrap();
113
114        let bootfs = vfs::pseudo_directory! {
115            "boot" => vfs::pseudo_directory! {
116                "config" => vfs::pseudo_directory! {
117                    "fshost" => vfs::file::read_only(&self.block_device_config_json),
118                    "zxcrypt" => vfs::file::read_only(&format!("{}", self.crypt_policy)),
119                },
120            },
121        };
122        let bootfs = realm_builder
123            .add_local_child(
124                "bootfs",
125                move |handles| {
126                    let bootfs = bootfs.clone();
127                    async move {
128                        let scope = vfs::ExecutionScope::new();
129                        vfs::directory::serve_on(
130                            bootfs,
131                            fio::PERM_READABLE,
132                            scope.clone(),
133                            handles.outgoing_dir,
134                        );
135                        scope.wait().await;
136                        Ok(())
137                    }
138                    .boxed()
139                },
140                ChildOptions::new(),
141            )
142            .await
143            .unwrap();
144        realm_builder
145            .add_route(
146                Route::new()
147                    .capability(Capability::directory("boot").rights(fio::R_STAR_DIR).path("/boot"))
148                    .from(&bootfs)
149                    .to(&fshost),
150            )
151            .await
152            .unwrap();
153
154        // This is a map from config keys to configuration capability names.
155        let mut map = HashMap::from([
156            ("no_zxcrypt", "fuchsia.fshost.NoZxcrypt"),
157            ("ramdisk_image", "fuchsia.fshost.RamdiskImage"),
158            ("gpt_all", "fuchsia.fshost.GptAll"),
159            ("check_filesystems", "fuchsia.fshost.CheckFilesystems"),
160            ("blob_max_bytes", "fuchsia.fshost.BlobMaxBytes"),
161            ("data_max_bytes", "fuchsia.fshost.DataMaxBytes"),
162            ("format_data_on_corruption", "fuchsia.fshost.FormatDataOnCorruption"),
163            ("data_filesystem_format", "fuchsia.fshost.DataFilesystemFormat"),
164            ("blobfs", "fuchsia.fshost.Blobfs"),
165            ("factory", "fuchsia.fshost.Factory"),
166            ("fvm", "fuchsia.fshost.Fvm"),
167            ("gpt", "fuchsia.fshost.Gpt"),
168            ("merge_super_and_userdata", "fuchsia.fshost.MergeSuperAndUserdata"),
169            ("data", "fuchsia.fshost.Data"),
170            ("disable_block_watcher", "fuchsia.fshost.DisableBlockWatcher"),
171            ("fvm_slice_size", "fuchsia.fshost.FvmSliceSize"),
172            ("blobfs_initial_inodes", "fuchsia.fshost.BlobfsInitialInodes"),
173            (
174                "blobfs_use_deprecated_padded_format",
175                "fuchsia.fshost.BlobfsUseDeprecatedPaddedFormat",
176            ),
177            ("fxfs_blob", "fuchsia.fshost.FxfsBlob"),
178            ("fxfs_crypt_url", "fuchsia.fshost.FxfsCryptUrl"),
179            ("disable_automount", "fuchsia.fshost.DisableAutomount"),
180            ("starnix_volume_name", "fuchsia.fshost.StarnixVolumeName"),
181            ("inline_crypto", "fuchsia.fshost.InlineCrypto"),
182            ("provision_fxfs", "fuchsia.fshost.ProvisionFxfs"),
183            ("watch_deprecated_v1_drivers", "fuchsia.fshost.WatchDeprecatedV1Drivers"),
184        ]);
185
186        if self.create_starnix_volume_crypt {
187            let user_fxfs_crypt = realm_builder
188                .add_child("user_fxfs_crypt", "#meta/fxfs-crypt.cm", ChildOptions::new().eager())
189                .await
190                .unwrap();
191            realm_builder
192                .add_route(
193                    Route::new()
194                        .capability(Capability::protocol::<ffxfs::CryptMarker>())
195                        .capability(Capability::protocol::<ffxfs::CryptManagementMarker>())
196                        .from(&user_fxfs_crypt)
197                        .to(Ref::parent()),
198                )
199                .await
200                .unwrap();
201        }
202
203        // Add the overrides as capabilities and route them.
204        self.config_values.insert("fxfs_crypt_url", "#meta/fxfs-crypt.cm".into_value_spec());
205        for (key, value) in self.config_values {
206            let cap_name = map[key];
207            realm_builder
208                .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
209                    name: cap_name.parse().unwrap(),
210                    value: value.value,
211                }))
212                .await
213                .unwrap();
214            realm_builder
215                .add_route(
216                    Route::new()
217                        .capability(Capability::configuration(cap_name))
218                        .from(Ref::self_())
219                        .to(&fshost),
220                )
221                .await
222                .unwrap();
223            map.remove(key);
224        }
225
226        // Add the remaining keys from the config component.
227        let fshost_config_url = format!("#meta/{}_config.cm", self.component_name);
228        let fshost_config = realm_builder
229            .add_child("test-fshost-config", fshost_config_url, ChildOptions::new().eager())
230            .await
231            .unwrap();
232        for (_, value) in map.iter() {
233            realm_builder
234                .add_route(
235                    Route::new()
236                        .capability(Capability::configuration(*value))
237                        .from(&fshost_config)
238                        .to(&fshost),
239                )
240                .await
241                .unwrap();
242        }
243
244        realm_builder
245            .add_route(
246                Route::new()
247                    .capability(Capability::protocol::<ffshost::AdminMarker>())
248                    .capability(Capability::protocol::<ffshost::RecoveryMarker>())
249                    .capability(Capability::protocol::<ffuv::ComponentOtaHealthCheckMarker>())
250                    .capability(Capability::protocol::<ffshost::StarnixVolumeProviderMarker>())
251                    .capability(Capability::protocol::<fpartitions::PartitionsManagerMarker>())
252                    .capability(Capability::protocol::<BlobCreatorMarker>())
253                    .capability(Capability::protocol::<BlobReaderMarker>())
254                    .capability(Capability::directory("blob").rights(fio::RW_STAR_DIR))
255                    .capability(
256                        Capability::directory("blob-exec")
257                            .rights(fio::RW_STAR_DIR | fio::Operations::EXECUTE),
258                    )
259                    .capability(Capability::directory("block").rights(fio::R_STAR_DIR))
260                    .capability(Capability::directory("debug_block").rights(fio::R_STAR_DIR))
261                    .capability(Capability::directory("data").rights(fio::RW_STAR_DIR))
262                    .capability(Capability::directory("tmp").rights(fio::RW_STAR_DIR))
263                    .capability(Capability::directory("volumes").rights(fio::RW_STAR_DIR))
264                    .capability(Capability::service::<fpartitions::PartitionServiceMarker>())
265                    .capability(Capability::service::<fvolume::ServiceMarker>())
266                    .from(&fshost)
267                    .to(Ref::parent()),
268            )
269            .await
270            .unwrap();
271
272        realm_builder
273            .add_route(
274                Route::new()
275                    .capability(Capability::protocol::<flogger::LogSinkMarker>())
276                    .capability(Capability::protocol::<fprocess::LauncherMarker>())
277                    .from(Ref::parent())
278                    .to(&fshost),
279            )
280            .await
281            .unwrap();
282
283        realm_builder
284            .add_route(
285                Route::new()
286                    .capability(
287                        Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional(),
288                    )
289                    .capability(
290                        Capability::protocol_by_name("fuchsia.tracing.provider.Registry")
291                            .optional(),
292                    )
293                    .capability(
294                        Capability::protocol_by_name("fuchsia.memorypressure.Provider").optional(),
295                    )
296                    .from(Ref::void())
297                    .to(&fshost),
298            )
299            .await
300            .unwrap();
301
302        fshost
303    }
304}