1use async_trait::async_trait;
6use block_matcher::{Guid, create_random_guid};
7use fidl::endpoints::{
8 DiscoverableProtocolMarker as _, Proxy, create_proxy, create_request_stream,
9};
10use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_storage_block::BlockMarker;
13use fidl_fuchsia_storage_partitions as fpartitions;
14use fs_management::Fvm;
15use fs_management::filesystem::{
16 BlockConnector, DirBasedBlockConnector, ServingMultiVolumeFilesystem,
17};
18use fuchsia_async as fasync;
19use fuchsia_component::client::{Service, connect_to_protocol, connect_to_protocol_at_dir_root};
20use std::sync::Arc;
21use storage_benchmarks::block_device::BlockDevice;
22use storage_benchmarks::{BlockDeviceConfig, BlockDeviceFactory};
23
24const BENCHMARK_FVM_SIZE_BYTES: u64 = 160 * 1024 * 1024;
25const BENCHMARK_FVM_SLICE_SIZE_BYTES: u64 = 8 * 1024 * 1024;
30
31const BENCHMARK_TYPE_GUID: &Guid = &[
35 0x67, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
36];
37const BENCHMARK_VOLUME_NAME: &str = "benchmark";
38
39pub async fn create_fvm_volume(
42 fvm: &fidl_fuchsia_fs_startup::VolumesProxy,
43 instance_guid: [u8; 16],
44 config: &BlockDeviceConfig,
45) -> (fio::DirectoryProxy, Option<fasync::Task<()>>) {
46 loop {
47 let (crypt, crypt_task) = if config.use_zxcrypt {
48 let (crypt, stream) = create_request_stream::<fidl_fuchsia_fxfs::CryptMarker>();
49 let task = fasync::Task::spawn(async {
50 if let Err(err) =
51 zxcrypt_crypt::run_crypt_service(crypt_policy::Policy::Null, stream).await
52 {
53 log::error!(err:?; "Crypt service failure");
54 }
55 });
56 (Some(crypt), Some(task))
57 } else {
58 (None, None)
59 };
60
61 let (volume_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
62 let res = fvm
63 .create(
64 BENCHMARK_VOLUME_NAME,
65 server_end,
66 CreateOptions {
67 initial_size: config.volume_size,
68 type_guid: Some(BENCHMARK_TYPE_GUID.clone()),
69 guid: Some(instance_guid),
70 ..Default::default()
71 },
72 MountOptions { crypt, ..Default::default() },
73 )
74 .await
75 .expect("FIDL error")
76 .map_err(zx::Status::err_from_raw);
77
78 if res == Err(zx::Status::ALREADY_EXISTS) {
80 fvm.remove(BENCHMARK_VOLUME_NAME)
81 .await
82 .expect("FIDL error")
83 .map_err(zx::Status::err_from_raw)
84 .expect("Failed to remove volume");
85 continue;
86 }
87
88 res.expect("Failed to create volume");
89 return (volume_dir, crypt_task);
90 }
91}
92
93pub enum BenchmarkVolumeFactory {
97 SystemFvm(
98 Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
99 Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
100 ),
101 SystemGpt(Arc<fpartitions::PartitionServiceProxy>),
102}
103
104struct RawBlockDeviceInGpt(Arc<fpartitions::PartitionServiceProxy>);
105
106impl BlockDevice for RawBlockDeviceInGpt {
107 fn connector(&self) -> Box<dyn BlockConnector> {
108 Box::new(self.0.clone())
109 }
110}
111
112#[async_trait]
113impl BlockDeviceFactory for BenchmarkVolumeFactory {
114 async fn create_block_device(&self, config: &BlockDeviceConfig) -> Box<dyn BlockDevice> {
115 let instance_guid = create_random_guid();
116 match self {
117 Self::SystemFvm(volumes_connector, _) => {
118 let volumes = volumes_connector();
119 Box::new(Self::create_fvm_volume(volumes, instance_guid, config).await)
120 }
121 Self::SystemGpt(partition_service) => {
122 if config.requires_fvm {
123 Box::new(
124 Self::create_fvm_instance_and_volume(
125 partition_service.clone(),
126 instance_guid,
127 config,
128 )
129 .await,
130 )
131 } else {
132 Box::new(RawBlockDeviceInGpt(partition_service.clone()))
133 }
134 }
135 }
136 }
137}
138
139impl BenchmarkVolumeFactory {
140 pub async fn from_config(fxfs_blob: bool) -> BenchmarkVolumeFactory {
143 if fxfs_blob {
144 let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
145 let manager = connect_to_protocol::<fpartitions::PartitionsManagerMarker>().unwrap();
146 let instance =
147 BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager).await;
148 assert!(
149 instance.is_some(),
150 "Failed to open or create testing FVM in GPT. \
151 Perhaps the system doesn't have a GPT-formatted block device?"
152 );
153 instance.unwrap()
154 } else {
155 let volumes_connector = Box::new(move || {
156 connect_to_protocol::<fidl_fuchsia_fs_startup::VolumesMarker>().unwrap()
157 });
158 let volumes_dir_connector = {
159 Box::new(move || {
160 fuchsia_fs::directory::open_in_namespace("volumes", fio::PERM_READABLE).unwrap()
161 })
162 };
163 BenchmarkVolumeFactory::connect_to_system_fvm(volumes_connector, volumes_dir_connector)
164 .unwrap()
165 }
166 }
167
168 pub fn connect_to_system_fvm(
170 volumes_connector: Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
171 volumes_dir_connector: Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
172 ) -> Option<BenchmarkVolumeFactory> {
173 Some(BenchmarkVolumeFactory::SystemFvm(volumes_connector, volumes_dir_connector))
174 }
175
176 pub async fn connect_to_test_partition(
180 service: Service<fpartitions::PartitionServiceMarker>,
181 manager: fpartitions::PartitionsManagerProxy,
182 ) -> Option<BenchmarkVolumeFactory> {
183 let connector = block_matcher::find_or_create_test_partition(
184 service,
185 manager,
186 BENCHMARK_FVM_SIZE_BYTES,
187 )
188 .await
189 .expect("Failed to find or create test partition");
190
191 Some(BenchmarkVolumeFactory::SystemGpt(Arc::new(connector)))
192 }
193
194 #[cfg(test)]
195 pub async fn contains_fvm_volume(&self, name: &str) -> bool {
196 match self {
197 Self::SystemFvm(_, volumes_dir_connector) => {
198 let dir = volumes_dir_connector();
199 fuchsia_fs::directory::dir_contains(&dir, name).await.unwrap()
200 }
201 _ => false,
204 }
205 }
206
207 async fn create_fvm_volume(
208 volumes: fidl_fuchsia_fs_startup::VolumesProxy,
209 instance_guid: [u8; 16],
210 config: &BlockDeviceConfig,
211 ) -> FvmVolume {
212 let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
213 let volumes = volumes.into_client_end().unwrap().into_sync_proxy();
214 FvmVolume {
215 destroy_fn: Some(Box::new(move || {
216 volumes
217 .remove(BENCHMARK_VOLUME_NAME, zx::MonotonicInstant::INFINITE)
218 .unwrap()
219 .map_err(zx::Status::err_from_raw)
220 })),
221 volume_dir: Some(volume_dir),
222 fvm_instance: None,
223 block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
224 crypt_task,
225 }
226 }
227
228 async fn create_fvm_instance_and_volume(
229 partition: Arc<fpartitions::PartitionServiceProxy>,
230 instance_guid: [u8; 16],
231 config: &BlockDeviceConfig,
232 ) -> FvmVolume {
233 let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
234 Box::new(partition),
235 Box::new(Fvm { slice_size: BENCHMARK_FVM_SLICE_SIZE_BYTES, ..Fvm::default() }),
236 );
237 fs.format().await.expect("Failed to format FVM");
238 let fvm_instance = fs.serve_multi_volume().await.expect("Failed to serve FVM");
239 let volumes = connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
240 fvm_instance.exposed_dir(),
241 )
242 .unwrap();
243
244 let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
245 FvmVolume {
246 destroy_fn: None,
247 volume_dir: Some(volume_dir),
248 fvm_instance: Some(fvm_instance),
249 block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
250 crypt_task,
251 }
252 }
253}
254
255pub struct FvmVolume {
257 destroy_fn: Option<Box<dyn Send + Sync + FnOnce() -> Result<(), zx::Status>>>,
258 fvm_instance: Option<ServingMultiVolumeFilesystem>,
259 volume_dir: Option<fio::DirectoryProxy>,
260 crypt_task: Option<fasync::Task<()>>,
261 block_path: String,
263}
264
265impl BlockDevice for FvmVolume {
266 fn connector(&self) -> Box<dyn BlockConnector> {
267 let volume_dir = fuchsia_fs::directory::clone(self.volume_dir.as_ref().unwrap()).unwrap();
268 Box::new(DirBasedBlockConnector::new(volume_dir, self.block_path.clone()))
269 }
270}
271
272impl Drop for FvmVolume {
273 fn drop(&mut self) {
274 self.volume_dir = None;
275 self.fvm_instance = None;
276 self.crypt_task = None;
277 if let Some(destroy_fn) = self.destroy_fn.take() {
278 destroy_fn().expect("Failed to destroy FVM volume");
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::testing::{RAMDISK_FVM_SLICE_SIZE, RamdiskFactory};
287 use block_client::RemoteBlockClient;
288 use fidl_fuchsia_fs_startup::VolumesMarker;
289 use fs_management::Gpt;
290 use ramdevice_client::{RamdiskClient, RamdiskClientBuilder};
291 use std::sync::Arc;
292 use vmo_backed_block_server::VmoBackedServer;
293
294 const BLOCK_SIZE: u64 = 4 * 1024;
295 const BLOCK_COUNT: u64 = 1024;
296 const GPT_BLOCK_COUNT: u64 = 49152;
299
300 #[fuchsia::test]
301 async fn ramdisk_create_block_device_with_zxcrypt() {
302 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
303 let _ = ramdisk_factory
304 .create_block_device(&BlockDeviceConfig {
305 requires_fvm: true,
306 use_zxcrypt: true,
307 volume_size: None,
308 })
309 .await;
310 }
311
312 #[fuchsia::test]
313 async fn ramdisk_create_block_device_without_zxcrypt() {
314 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
315 let _ = ramdisk_factory
316 .create_block_device(&BlockDeviceConfig {
317 requires_fvm: true,
318 use_zxcrypt: false,
319 volume_size: None,
320 })
321 .await;
322 }
323
324 #[fuchsia::test]
325 async fn ramdisk_create_block_device_without_volume_size() {
326 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
327 let ramdisk = ramdisk_factory
328 .create_block_device(&BlockDeviceConfig {
329 requires_fvm: true,
330 use_zxcrypt: false,
331 volume_size: None,
332 })
333 .await;
334 let volume_info = ramdisk
335 .connector()
336 .connect_block()
337 .unwrap()
338 .into_proxy()
339 .get_volume_info()
340 .await
341 .unwrap();
342 zx::ok(volume_info.0).unwrap();
343 let volume_info = volume_info.2.unwrap();
344 assert_eq!(volume_info.partition_slice_count, 1);
345 }
346
347 #[fuchsia::test]
348 async fn ramdisk_create_block_device_with_volume_size() {
349 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
350 let ramdisk = ramdisk_factory
351 .create_block_device(&BlockDeviceConfig {
352 requires_fvm: false,
353 use_zxcrypt: false,
354 volume_size: Some(RAMDISK_FVM_SLICE_SIZE as u64 * 3),
355 })
356 .await;
357 let volume_info = ramdisk
358 .connector()
359 .connect_block()
360 .unwrap()
361 .into_proxy()
362 .get_volume_info()
363 .await
364 .unwrap();
365 zx::ok(volume_info.0).unwrap();
366 let volume_info = volume_info.2.unwrap();
367 assert_eq!(volume_info.partition_slice_count, 3);
368 }
369
370 async fn init_gpt(block_size: u32, block_count: u64) -> zx::Vmo {
371 let vmo = zx::Vmo::create(block_size as u64 * block_count).unwrap();
372 let server = VmoBackedServer::from_vmo(
373 block_size,
374 vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0).unwrap(),
375 )
376 .expect("Failed to create VmoBackedServer");
377 let (client, server_end) =
378 fidl::endpoints::create_proxy::<fidl_fuchsia_storage_block::BlockMarker>();
379
380 let _task =
381 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
382 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
383 gpt::Gpt::format(client.clone(), vec![gpt::PartitionInfo::nil(); 128])
384 .await
385 .expect("format failed");
386 vmo
387 }
388
389 struct FvmTestConfig {
390 fxblob_enabled: bool,
391 }
392
393 struct TestState(
398 #[allow(dead_code)] RamdiskClient,
399 #[allow(dead_code)] ServingMultiVolumeFilesystem,
400 );
401
402 async fn initialize(config: FvmTestConfig) -> (TestState, BenchmarkVolumeFactory) {
403 if config.fxblob_enabled {
404 let vmo = init_gpt(BLOCK_SIZE as u32, GPT_BLOCK_COUNT).await;
406 let ramdisk = RamdiskClientBuilder::new_with_vmo(vmo, Some(BLOCK_SIZE))
407 .build()
408 .await
409 .expect("Failed to create ramdisk");
410
411 let gpt = fs_management::filesystem::Filesystem::from_boxed_config(
412 ramdisk.connector().unwrap(),
413 Box::new(Gpt::dynamic_child()),
414 )
415 .serve_multi_volume()
416 .await
417 .expect("Failed to serve GPT");
418 let partitions =
419 Service::open_from_dir(gpt.exposed_dir(), fpartitions::PartitionServiceMarker)
420 .unwrap();
421 let manager = connect_to_protocol_at_dir_root::<fpartitions::PartitionsManagerMarker>(
422 gpt.exposed_dir(),
423 )
424 .unwrap();
425 let fvm = BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager)
426 .await
427 .expect("Failed to connect to FVM");
428 (TestState(ramdisk, gpt), fvm)
429 } else {
430 let ramdisk = RamdiskClientBuilder::new(BLOCK_SIZE, BLOCK_COUNT)
432 .build()
433 .await
434 .expect("Failed to create ramdisk");
435 let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
436 ramdisk.connector().unwrap(),
437 Box::new(Fvm { slice_size: RAMDISK_FVM_SLICE_SIZE, ..Fvm::dynamic_child() }),
438 );
439 fs.format().await.expect("Failed to format FVM");
440 let fvm_component = match fs.serve_multi_volume().await {
441 Ok(fvm_component) => fvm_component,
442 Err(_) => loop {},
443 };
444 let volumes_connector = {
445 let exposed_dir =
446 fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
447 Box::new(move || {
448 connect_to_protocol_at_dir_root::<VolumesMarker>(&exposed_dir).unwrap()
449 })
450 };
451 let volumes_dir_connector = {
452 let exposed_dir =
453 fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
454 Box::new(move || {
455 fuchsia_fs::directory::open_directory_async(
456 &exposed_dir,
457 "volumes",
458 fio::PERM_READABLE,
459 )
460 .unwrap()
461 })
462 };
463 let fvm = BenchmarkVolumeFactory::connect_to_system_fvm(
464 volumes_connector,
465 volumes_dir_connector,
466 );
467 (TestState(ramdisk, fvm_component), fvm.unwrap())
468 }
469 }
470
471 async fn benchmark_volume_factory_can_find_fvm_instance(config: FvmTestConfig) {
472 let (_state, volume_factory) = initialize(config).await;
473
474 volume_factory
476 .create_block_device(&BlockDeviceConfig {
477 requires_fvm: true,
478 use_zxcrypt: false,
479 volume_size: None,
480 })
481 .await;
482 }
483
484 #[fuchsia::test]
485 async fn benchmark_volume_factory_can_find_fvm_instance_fvm() {
486 benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: false })
487 .await;
488 }
489
490 #[fuchsia::test]
491 async fn benchmark_volume_factory_can_find_fvm_instance_gpt() {
492 benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: true })
493 .await;
494 }
495
496 async fn dropping_an_fvm_volume_removes_the_volume(config: FvmTestConfig) {
497 let (_state, volume_factory) = initialize(config).await;
498 {
499 let _volume = volume_factory
500 .create_block_device(&BlockDeviceConfig {
501 requires_fvm: true,
502 use_zxcrypt: false,
503 volume_size: None,
504 })
505 .await;
506 assert!(volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
507 };
508 assert!(!volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
509 }
510
511 #[fuchsia::test]
512 async fn dropping_an_fvm_volume_removes_the_volume_fvm() {
513 dropping_an_fvm_volume_removes_the_volume(FvmTestConfig { fxblob_enabled: false }).await;
514 }
515
516 async fn benchmark_volume_factory_create_block_device_with_zxcrypt(config: FvmTestConfig) {
517 let (_state, volume_factory) = initialize(config).await;
518 let _ = volume_factory
519 .create_block_device(&BlockDeviceConfig {
520 requires_fvm: true,
521 use_zxcrypt: true,
522 volume_size: None,
523 })
524 .await;
525 }
526
527 #[fuchsia::test]
528 async fn benchmark_volume_factory_create_block_device_with_zxcrypt_fvm() {
529 benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
530 fxblob_enabled: false,
531 })
532 .await;
533 }
534
535 #[fuchsia::test]
536 async fn benchmark_volume_factory_create_block_device_with_zxcrypt_gpt() {
537 benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
538 fxblob_enabled: true,
539 })
540 .await;
541 }
542}