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