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 {
12    fidl_fuchsia_fshost as ffshost, fidl_fuchsia_fxfs as ffxfs,
13    fidl_fuchsia_hardware_block_volume as fvolume, fidl_fuchsia_io as fio,
14    fidl_fuchsia_logger as flogger, fidl_fuchsia_process as fprocess,
15    fidl_fuchsia_storage_partitions as fpartitions, fidl_fuchsia_update_verify as ffuv,
16};
17
18pub trait IntoValueSpec {
19    fn into_value_spec(self) -> cm_rust::ConfigValueSpec;
20}
21
22impl IntoValueSpec for bool {
23    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
24        cm_rust::ConfigValueSpec {
25            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(self)),
26        }
27    }
28}
29
30impl IntoValueSpec for u64 {
31    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
32        cm_rust::ConfigValueSpec {
33            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint64(self)),
34        }
35    }
36}
37
38impl IntoValueSpec for String {
39    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
40        cm_rust::ConfigValueSpec {
41            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(self)),
42        }
43    }
44}
45
46impl<'a> IntoValueSpec for &'a str {
47    fn into_value_spec(self) -> cm_rust::ConfigValueSpec {
48        self.to_string().into_value_spec()
49    }
50}
51
52/// Builder for the fshost component. This handles configuring the fshost component to use and
53/// structured config overrides to set, as well as setting up the expected protocols to be routed
54/// between the realm builder root and the fshost child when the test realm is built.
55///
56/// Any desired additional config overrides should be added to this builder. New routes for exposed
57/// capabilities from the fshost component or offered capabilities to the fshost component should
58/// be added to the [`FshostBuilder::build`] function below.
59#[derive(Debug, Clone)]
60pub struct FshostBuilder {
61    component_name: &'static str,
62    config_values: HashMap<&'static str, cm_rust::ConfigValueSpec>,
63    create_starnix_volume_crypt: bool,
64    block_device_config_json: String,
65    crypt_policy: crypt_policy::Policy,
66}
67
68impl FshostBuilder {
69    pub fn new(component_name: &'static str) -> FshostBuilder {
70        FshostBuilder {
71            component_name,
72            config_values: HashMap::new(),
73            create_starnix_volume_crypt: false,
74            block_device_config_json: String::from("[]"),
75            crypt_policy: crypt_policy::Policy::Null,
76        }
77    }
78
79    pub fn create_starnix_volume_crypt(&mut self) -> &mut Self {
80        self.create_starnix_volume_crypt = true;
81        self
82    }
83
84    pub fn set_config_value(&mut self, key: &'static str, value: impl IntoValueSpec) -> &mut Self {
85        assert!(
86            self.config_values.insert(key, value.into_value_spec()).is_none(),
87            "Attempted to insert duplicate config value '{}'!",
88            key
89        );
90        self
91    }
92
93    pub fn set_device_config(
94        &mut self,
95        config: Vec<fshost_assembly_config::BlockDeviceConfig>,
96    ) -> &mut Self {
97        self.block_device_config_json = serde_json::to_string(&config).unwrap();
98        self
99    }
100
101    pub fn set_crypt_policy(&mut self, policy: crypt_policy::Policy) -> &mut Self {
102        self.crypt_policy = policy;
103        self
104    }
105
106    pub async fn build(mut self, realm_builder: &RealmBuilder) -> ChildRef {
107        let fshost_url = format!("#meta/{}.cm", self.component_name);
108        log::info!(fshost_url:%; "building test fshost instance");
109        let fshost = realm_builder
110            .add_child("test-fshost", fshost_url, ChildOptions::new().eager())
111            .await
112            .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            ("mbr", "fuchsia.fshost.Mbr"),
169            ("merge_super_and_userdata", "fuchsia.fshost.MergeSuperAndUserdata"),
170            ("data", "fuchsia.fshost.Data"),
171            ("use_disk_migration", "fuchsia.fshost.UseDiskMigration"),
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            ("storage_host", "fuchsia.fshost.StorageHost"),
182            ("disable_automount", "fuchsia.fshost.DisableAutomount"),
183            ("starnix_volume_name", "fuchsia.fshost.StarnixVolumeName"),
184            ("inline_crypto", "fuchsia.fshost.InlineCrypto"),
185            ("provision_fxfs", "fuchsia.fshost.ProvisionFxfs"),
186            ("watch_deprecated_v1_drivers", "fuchsia.fshost.WatchDeprecatedV1Drivers"),
187        ]);
188
189        if self.create_starnix_volume_crypt {
190            let user_fxfs_crypt = realm_builder
191                .add_child("user_fxfs_crypt", "#meta/fxfs-crypt.cm", ChildOptions::new().eager())
192                .await
193                .unwrap();
194            realm_builder
195                .add_route(
196                    Route::new()
197                        .capability(Capability::protocol::<ffxfs::CryptMarker>())
198                        .capability(Capability::protocol::<ffxfs::CryptManagementMarker>())
199                        .from(&user_fxfs_crypt)
200                        .to(Ref::parent()),
201                )
202                .await
203                .unwrap();
204        }
205
206        // Add the overrides as capabilities and route them.
207        self.config_values.insert("fxfs_crypt_url", "#meta/fxfs-crypt.cm".into_value_spec());
208        for (key, value) in self.config_values {
209            let cap_name = map[key];
210            realm_builder
211                .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
212                    name: cap_name.parse().unwrap(),
213                    value: value.value,
214                }))
215                .await
216                .unwrap();
217            realm_builder
218                .add_route(
219                    Route::new()
220                        .capability(Capability::configuration(cap_name))
221                        .from(Ref::self_())
222                        .to(&fshost),
223                )
224                .await
225                .unwrap();
226            map.remove(key);
227        }
228
229        // Add the remaining keys from the config component.
230        let fshost_config_url = format!("#meta/{}_config.cm", self.component_name);
231        let fshost_config = realm_builder
232            .add_child("test-fshost-config", fshost_config_url, ChildOptions::new().eager())
233            .await
234            .unwrap();
235        for (_, value) in map.iter() {
236            realm_builder
237                .add_route(
238                    Route::new()
239                        .capability(Capability::configuration(*value))
240                        .from(&fshost_config)
241                        .to(&fshost),
242                )
243                .await
244                .unwrap();
245        }
246
247        realm_builder
248            .add_route(
249                Route::new()
250                    .capability(Capability::protocol::<ffshost::AdminMarker>())
251                    .capability(Capability::protocol::<ffshost::RecoveryMarker>())
252                    .capability(Capability::protocol::<ffuv::ComponentOtaHealthCheckMarker>())
253                    .capability(Capability::protocol::<ffshost::StarnixVolumeProviderMarker>())
254                    .capability(Capability::protocol::<fpartitions::PartitionsManagerMarker>())
255                    .capability(Capability::protocol::<BlobCreatorMarker>())
256                    .capability(Capability::protocol::<BlobReaderMarker>())
257                    .capability(Capability::directory("blob").rights(fio::RW_STAR_DIR))
258                    .capability(
259                        Capability::directory("blob-exec")
260                            .rights(fio::RW_STAR_DIR | fio::Operations::EXECUTE),
261                    )
262                    .capability(Capability::directory("block").rights(fio::R_STAR_DIR))
263                    .capability(Capability::directory("debug_block").rights(fio::R_STAR_DIR))
264                    .capability(Capability::directory("data").rights(fio::RW_STAR_DIR))
265                    .capability(Capability::directory("tmp").rights(fio::RW_STAR_DIR))
266                    .capability(Capability::directory("volumes").rights(fio::RW_STAR_DIR))
267                    .capability(Capability::service::<fpartitions::PartitionServiceMarker>())
268                    .capability(Capability::service::<fvolume::ServiceMarker>())
269                    .from(&fshost)
270                    .to(Ref::parent()),
271            )
272            .await
273            .unwrap();
274
275        realm_builder
276            .add_route(
277                Route::new()
278                    .capability(Capability::protocol::<flogger::LogSinkMarker>())
279                    .capability(Capability::protocol::<fprocess::LauncherMarker>())
280                    .from(Ref::parent())
281                    .to(&fshost),
282            )
283            .await
284            .unwrap();
285
286        realm_builder
287            .add_route(
288                Route::new()
289                    .capability(
290                        Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional(),
291                    )
292                    .capability(
293                        Capability::protocol_by_name("fuchsia.tracing.provider.Registry")
294                            .optional(),
295                    )
296                    .capability(
297                        Capability::protocol_by_name("fuchsia.memorypressure.Provider").optional(),
298                    )
299                    .from(Ref::void())
300                    .to(&fshost),
301            )
302            .await
303            .unwrap();
304
305        fshost
306    }
307}