1use 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#[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 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 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 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}