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            ("use_driver_blob_paging", "fuchsia.pkgcache.UseDriverBlobPaging"),
184            ("startup_profiling_seconds", "fuchsia.fshost.StartupProfilingSeconds"),
185        ]);
186
187        if self.create_starnix_volume_crypt {
188            let user_fxfs_crypt = realm_builder
189                .add_child("user_fxfs_crypt", "#meta/fxfs-crypt.cm", ChildOptions::new().eager())
190                .await
191                .unwrap();
192            realm_builder
193                .add_route(
194                    Route::new()
195                        .capability(Capability::protocol::<ffxfs::CryptMarker>())
196                        .capability(Capability::protocol::<ffxfs::CryptManagementMarker>())
197                        .from(&user_fxfs_crypt)
198                        .to(Ref::parent()),
199                )
200                .await
201                .unwrap();
202        }
203
204        // Add the overrides as capabilities and route them.
205        self.config_values.insert("fxfs_crypt_url", "#meta/fxfs-crypt.cm".into_value_spec());
206        for (key, value) in self.config_values {
207            let cap_name = map[key];
208            realm_builder
209                .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
210                    name: cap_name.parse().unwrap(),
211                    value: value.value,
212                }))
213                .await
214                .unwrap();
215            realm_builder
216                .add_route(
217                    Route::new()
218                        .capability(Capability::configuration(cap_name))
219                        .from(Ref::self_())
220                        .to(&fshost),
221                )
222                .await
223                .unwrap();
224            map.remove(key);
225        }
226
227        // Add the remaining keys from the config component.
228        let fshost_config_url = format!("#meta/{}_config.cm", self.component_name);
229        let fshost_config = realm_builder
230            .add_child("test-fshost-config", fshost_config_url, ChildOptions::new().eager())
231            .await
232            .unwrap();
233        for (_, value) in map.iter() {
234            realm_builder
235                .add_route(
236                    Route::new()
237                        .capability(Capability::configuration(*value))
238                        .from(&fshost_config)
239                        .to(&fshost),
240                )
241                .await
242                .unwrap();
243        }
244
245        realm_builder
246            .add_route(
247                Route::new()
248                    .capability(Capability::protocol::<ffshost::AdminMarker>())
249                    .capability(Capability::protocol::<ffshost::RecoveryMarker>())
250                    .capability(Capability::protocol::<ffuv::ComponentOtaHealthCheckMarker>())
251                    .capability(Capability::protocol::<ffshost::StarnixVolumeProviderMarker>())
252                    .capability(Capability::protocol::<fpartitions::PartitionsManagerMarker>())
253                    .capability(Capability::protocol::<BlobCreatorMarker>())
254                    .capability(Capability::protocol::<BlobReaderMarker>())
255                    .capability(Capability::directory("blob").rights(fio::RW_STAR_DIR))
256                    .capability(
257                        Capability::directory("blob-exec")
258                            .rights(fio::RW_STAR_DIR | fio::Operations::EXECUTE),
259                    )
260                    .capability(Capability::directory("block").rights(fio::R_STAR_DIR))
261                    .capability(Capability::directory("debug_block").rights(fio::R_STAR_DIR))
262                    .capability(Capability::directory("data").rights(fio::RW_STAR_DIR))
263                    .capability(Capability::directory("tmp").rights(fio::RW_STAR_DIR))
264                    .capability(Capability::directory("volumes").rights(fio::RW_STAR_DIR))
265                    .capability(Capability::service::<fpartitions::PartitionServiceMarker>())
266                    .capability(Capability::service::<fvolume::ServiceMarker>())
267                    .from(&fshost)
268                    .to(Ref::parent()),
269            )
270            .await
271            .unwrap();
272
273        realm_builder
274            .add_route(
275                Route::new()
276                    .capability(Capability::protocol::<flogger::LogSinkMarker>())
277                    .capability(Capability::protocol::<fprocess::LauncherMarker>())
278                    .from(Ref::parent())
279                    .to(&fshost),
280            )
281            .await
282            .unwrap();
283
284        realm_builder
285            .add_route(
286                Route::new()
287                    .capability(
288                        Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional(),
289                    )
290                    .capability(
291                        Capability::protocol_by_name("fuchsia.tracing.provider.Registry")
292                            .optional(),
293                    )
294                    .capability(
295                        Capability::protocol_by_name("fuchsia.memorypressure.Provider").optional(),
296                    )
297                    .from(Ref::void())
298                    .to(&fshost),
299            )
300            .await
301            .unwrap();
302
303        fshost
304    }
305}