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 = realm_builder
112            .add_child("test-fshost", fshost_url, ChildOptions::new().eager())
113            .await
114            .unwrap();
115
116        let bootfs = vfs::pseudo_directory! {
117            "boot" => vfs::pseudo_directory! {
118                "config" => vfs::pseudo_directory! {
119                    "fshost" => vfs::file::read_only(&self.block_device_config_json),
120                    "zxcrypt" => vfs::file::read_only(&format!("{}", self.crypt_policy)),
121                },
122            },
123        };
124        let bootfs = realm_builder
125            .add_local_child(
126                "bootfs",
127                move |handles| {
128                    let bootfs = bootfs.clone();
129                    async move {
130                        let scope = vfs::ExecutionScope::new();
131                        vfs::directory::serve_on(
132                            bootfs,
133                            fio::PERM_READABLE,
134                            scope.clone(),
135                            handles.outgoing_dir,
136                        );
137                        scope.wait().await;
138                        Ok(())
139                    }
140                    .boxed()
141                },
142                ChildOptions::new(),
143            )
144            .await
145            .unwrap();
146        realm_builder
147            .add_route(
148                Route::new()
149                    .capability(Capability::directory("boot").rights(fio::R_STAR_DIR).path("/boot"))
150                    .from(&bootfs)
151                    .to(&fshost),
152            )
153            .await
154            .unwrap();
155
156        // This is a map from config keys to configuration capability names.
157        let mut map = HashMap::from([
158            ("no_zxcrypt", "fuchsia.fshost.NoZxcrypt"),
159            ("ramdisk_image", "fuchsia.fshost.RamdiskImage"),
160            ("gpt_all", "fuchsia.fshost.GptAll"),
161            ("check_filesystems", "fuchsia.fshost.CheckFilesystems"),
162            ("blob_max_bytes", "fuchsia.fshost.BlobMaxBytes"),
163            ("data_max_bytes", "fuchsia.fshost.DataMaxBytes"),
164            ("format_data_on_corruption", "fuchsia.fshost.FormatDataOnCorruption"),
165            ("data_filesystem_format", "fuchsia.fshost.DataFilesystemFormat"),
166            ("blobfs", "fuchsia.fshost.Blobfs"),
167            ("factory", "fuchsia.fshost.Factory"),
168            ("fvm", "fuchsia.fshost.Fvm"),
169            ("gpt", "fuchsia.fshost.Gpt"),
170            ("merge_super_and_userdata", "fuchsia.fshost.MergeSuperAndUserdata"),
171            ("data", "fuchsia.fshost.Data"),
172            ("disable_block_watcher", "fuchsia.fshost.DisableBlockWatcher"),
173            ("fvm_slice_size", "fuchsia.fshost.FvmSliceSize"),
174            ("blobfs_initial_inodes", "fuchsia.fshost.BlobfsInitialInodes"),
175            (
176                "blobfs_use_deprecated_padded_format",
177                "fuchsia.fshost.BlobfsUseDeprecatedPaddedFormat",
178            ),
179            ("fxfs_blob", "fuchsia.fshost.FxfsBlob"),
180            ("fxfs_crypt_url", "fuchsia.fshost.FxfsCryptUrl"),
181            ("disable_automount", "fuchsia.fshost.DisableAutomount"),
182            ("starnix_volume_name", "fuchsia.fshost.StarnixVolumeName"),
183            ("inline_crypto", "fuchsia.fshost.InlineCrypto"),
184            ("provision_fxfs", "fuchsia.fshost.ProvisionFxfs"),
185            ("watch_deprecated_v1_drivers", "fuchsia.fshost.WatchDeprecatedV1Drivers"),
186        ]);
187
188        if self.create_starnix_volume_crypt {
189            let user_fxfs_crypt = realm_builder
190                .add_child("user_fxfs_crypt", "#meta/fxfs-crypt.cm", ChildOptions::new().eager())
191                .await
192                .unwrap();
193            realm_builder
194                .add_route(
195                    Route::new()
196                        .capability(Capability::protocol::<ffxfs::CryptMarker>())
197                        .capability(Capability::protocol::<ffxfs::CryptManagementMarker>())
198                        .from(&user_fxfs_crypt)
199                        .to(Ref::parent()),
200                )
201                .await
202                .unwrap();
203        }
204
205        // Add the overrides as capabilities and route them.
206        self.config_values.insert("fxfs_crypt_url", "#meta/fxfs-crypt.cm".into_value_spec());
207        for (key, value) in self.config_values {
208            let cap_name = map[key];
209            realm_builder
210                .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
211                    name: cap_name.parse().unwrap(),
212                    value: value.value,
213                }))
214                .await
215                .unwrap();
216            realm_builder
217                .add_route(
218                    Route::new()
219                        .capability(Capability::configuration(cap_name))
220                        .from(Ref::self_())
221                        .to(&fshost),
222                )
223                .await
224                .unwrap();
225            map.remove(key);
226        }
227
228        // Add the remaining keys from the config component.
229        let fshost_config_url = format!("#meta/{}_config.cm", self.component_name);
230        let fshost_config = realm_builder
231            .add_child("test-fshost-config", fshost_config_url, ChildOptions::new().eager())
232            .await
233            .unwrap();
234        for (_, value) in map.iter() {
235            realm_builder
236                .add_route(
237                    Route::new()
238                        .capability(Capability::configuration(*value))
239                        .from(&fshost_config)
240                        .to(&fshost),
241                )
242                .await
243                .unwrap();
244        }
245
246        realm_builder
247            .add_route(
248                Route::new()
249                    .capability(Capability::protocol::<ffshost::AdminMarker>())
250                    .capability(Capability::protocol::<ffshost::RecoveryMarker>())
251                    .capability(Capability::protocol::<ffuv::ComponentOtaHealthCheckMarker>())
252                    .capability(Capability::protocol::<ffshost::StarnixVolumeProviderMarker>())
253                    .capability(Capability::protocol::<fpartitions::PartitionsManagerMarker>())
254                    .capability(Capability::protocol::<BlobCreatorMarker>())
255                    .capability(Capability::protocol::<BlobReaderMarker>())
256                    .capability(Capability::directory("blob").rights(fio::RW_STAR_DIR))
257                    .capability(
258                        Capability::directory("blob-exec")
259                            .rights(fio::RW_STAR_DIR | fio::Operations::EXECUTE),
260                    )
261                    .capability(Capability::directory("block").rights(fio::R_STAR_DIR))
262                    .capability(Capability::directory("debug_block").rights(fio::R_STAR_DIR))
263                    .capability(Capability::directory("data").rights(fio::RW_STAR_DIR))
264                    .capability(Capability::directory("tmp").rights(fio::RW_STAR_DIR))
265                    .capability(Capability::directory("volumes").rights(fio::RW_STAR_DIR))
266                    .capability(Capability::service::<fpartitions::PartitionServiceMarker>())
267                    .capability(Capability::service::<fvolume::ServiceMarker>())
268                    .from(&fshost)
269                    .to(Ref::parent()),
270            )
271            .await
272            .unwrap();
273
274        realm_builder
275            .add_route(
276                Route::new()
277                    .capability(Capability::protocol::<flogger::LogSinkMarker>())
278                    .capability(Capability::protocol::<fprocess::LauncherMarker>())
279                    .from(Ref::parent())
280                    .to(&fshost),
281            )
282            .await
283            .unwrap();
284
285        realm_builder
286            .add_route(
287                Route::new()
288                    .capability(
289                        Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional(),
290                    )
291                    .capability(
292                        Capability::protocol_by_name("fuchsia.tracing.provider.Registry")
293                            .optional(),
294                    )
295                    .capability(
296                        Capability::protocol_by_name("fuchsia.memorypressure.Provider").optional(),
297                    )
298                    .from(Ref::void())
299                    .to(&fshost),
300            )
301            .await
302            .unwrap();
303
304        fshost
305    }
306}