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