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