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 = 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 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 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 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}