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_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 fs_management::format::constants::{
19 BENCHMARK_FVM_TYPE_GUID, BENCHMARK_FVM_VOLUME_NAME, PAD_RW_PARTITION_LABEL,
20};
21use fuchsia_async as fasync;
22use fuchsia_component::client::{Service, connect_to_protocol, connect_to_protocol_at_dir_root};
23use std::sync::Arc;
24use storage_benchmarks::block_device::BlockDevice;
25use storage_benchmarks::{BlockDeviceConfig, BlockDeviceFactory};
26use storage_isolated_driver_manager::{
27 BlockDeviceMatcher, Guid, create_random_guid, find_block_device,
28};
29
30const BENCHMARK_FVM_SIZE_BYTES: u64 = 160 * 1024 * 1024;
31const BENCHMARK_FVM_SLICE_SIZE_BYTES: u64 = 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 mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
284 Box::new(partition),
285 Box::new(Fvm { slice_size: BENCHMARK_FVM_SLICE_SIZE_BYTES, ..Fvm::default() }),
286 );
287 fs.format().await.expect("Failed to format FVM");
288 let fvm_instance = fs.serve_multi_volume().await.expect("Failed to serve FVM");
289 let volumes = connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
290 fvm_instance.exposed_dir(),
291 )
292 .unwrap();
293
294 let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
295 FvmVolume {
296 destroy_fn: None,
297 volume_dir: Some(volume_dir),
298 fvm_instance: Some(fvm_instance),
299 block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
300 crypt_task,
301 }
302 }
303}
304
305pub struct FvmVolume {
307 destroy_fn: Option<Box<dyn Send + Sync + FnOnce() -> Result<(), zx::Status>>>,
308 fvm_instance: Option<ServingMultiVolumeFilesystem>,
309 volume_dir: Option<fio::DirectoryProxy>,
310 crypt_task: Option<fasync::Task<()>>,
311 block_path: String,
313}
314
315impl BlockDevice for FvmVolume {
316 fn connector(&self) -> Box<dyn BlockConnector> {
317 let volume_dir = fuchsia_fs::directory::clone(self.volume_dir.as_ref().unwrap()).unwrap();
318 Box::new(DirBasedBlockConnector::new(volume_dir, self.block_path.clone()))
319 }
320}
321
322impl Drop for FvmVolume {
323 fn drop(&mut self) {
324 self.volume_dir = None;
325 self.fvm_instance = None;
326 self.crypt_task = None;
327 if let Some(destroy_fn) = self.destroy_fn.take() {
328 destroy_fn().expect("Failed to destroy FVM volume");
329 }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::testing::{RAMDISK_FVM_SLICE_SIZE, RamdiskFactory};
337 use block_client::RemoteBlockClient;
338 use fidl_fuchsia_fs_startup::VolumesMarker;
339 use fs_management::Gpt;
340 use ramdevice_client::{RamdiskClient, RamdiskClientBuilder};
341 use std::sync::Arc;
342 use vmo_backed_block_server::{VmoBackedServer, VmoBackedServerTestingExt as _};
343
344 const BLOCK_SIZE: u64 = 4 * 1024;
345 const BLOCK_COUNT: u64 = 1024;
346 const GPT_BLOCK_COUNT: u64 = 49152;
349
350 #[fuchsia::test]
351 async fn ramdisk_create_block_device_with_zxcrypt() {
352 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
353 let _ = ramdisk_factory
354 .create_block_device(&BlockDeviceConfig {
355 requires_fvm: true,
356 use_zxcrypt: true,
357 volume_size: None,
358 })
359 .await;
360 }
361
362 #[fuchsia::test]
363 async fn ramdisk_create_block_device_without_zxcrypt() {
364 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
365 let _ = ramdisk_factory
366 .create_block_device(&BlockDeviceConfig {
367 requires_fvm: true,
368 use_zxcrypt: false,
369 volume_size: None,
370 })
371 .await;
372 }
373
374 #[fuchsia::test]
375 async fn ramdisk_create_block_device_without_volume_size() {
376 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
377 let ramdisk = ramdisk_factory
378 .create_block_device(&BlockDeviceConfig {
379 requires_fvm: true,
380 use_zxcrypt: false,
381 volume_size: None,
382 })
383 .await;
384 let volume_info = ramdisk
385 .connector()
386 .connect_block()
387 .unwrap()
388 .into_proxy()
389 .get_volume_info()
390 .await
391 .unwrap();
392 zx::ok(volume_info.0).unwrap();
393 let volume_info = volume_info.2.unwrap();
394 assert_eq!(volume_info.partition_slice_count, 1);
395 }
396
397 #[fuchsia::test]
398 async fn ramdisk_create_block_device_with_volume_size() {
399 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
400 let ramdisk = ramdisk_factory
401 .create_block_device(&BlockDeviceConfig {
402 requires_fvm: false,
403 use_zxcrypt: false,
404 volume_size: Some(RAMDISK_FVM_SLICE_SIZE as u64 * 3),
405 })
406 .await;
407 let volume_info = ramdisk
408 .connector()
409 .connect_block()
410 .unwrap()
411 .into_proxy()
412 .get_volume_info()
413 .await
414 .unwrap();
415 zx::ok(volume_info.0).unwrap();
416 let volume_info = volume_info.2.unwrap();
417 assert_eq!(volume_info.partition_slice_count, 3);
418 }
419
420 async fn init_gpt(block_size: u32, block_count: u64) -> zx::Vmo {
421 let vmo = zx::Vmo::create(block_size as u64 * block_count).unwrap();
422 let server = Arc::new(VmoBackedServer::from_vmo(
423 block_size,
424 vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0).unwrap(),
425 ));
426 let (client, server_end) =
427 fidl::endpoints::create_proxy::<fidl_fuchsia_storage_block::BlockMarker>();
428
429 let _task =
430 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
431 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
432 gpt::Gpt::format(client.clone(), vec![gpt::PartitionInfo::nil(); 128])
433 .await
434 .expect("format failed");
435 vmo
436 }
437
438 struct FvmTestConfig {
439 fxblob_enabled: bool,
440 }
441
442 struct TestState(
447 #[allow(dead_code)] RamdiskClient,
448 #[allow(dead_code)] ServingMultiVolumeFilesystem,
449 );
450
451 async fn initialize(config: FvmTestConfig) -> (TestState, BenchmarkVolumeFactory) {
452 if config.fxblob_enabled {
453 let vmo = init_gpt(BLOCK_SIZE as u32, GPT_BLOCK_COUNT).await;
455 let ramdisk = RamdiskClientBuilder::new_with_vmo(vmo, Some(BLOCK_SIZE))
456 .build()
457 .await
458 .expect("Failed to create ramdisk");
459
460 let gpt = fs_management::filesystem::Filesystem::from_boxed_config(
461 ramdisk.connector().unwrap(),
462 Box::new(Gpt::dynamic_child()),
463 )
464 .serve_multi_volume()
465 .await
466 .expect("Failed to serve GPT");
467 let partitions =
468 Service::open_from_dir(gpt.exposed_dir(), fpartitions::PartitionServiceMarker)
469 .unwrap();
470 let manager = connect_to_protocol_at_dir_root::<fpartitions::PartitionsManagerMarker>(
471 gpt.exposed_dir(),
472 )
473 .unwrap();
474 let fvm = BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager)
475 .await
476 .expect("Failed to connect to FVM");
477 (TestState(ramdisk, gpt), fvm)
478 } else {
479 let ramdisk = RamdiskClientBuilder::new(BLOCK_SIZE, BLOCK_COUNT)
481 .build()
482 .await
483 .expect("Failed to create ramdisk");
484 let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
485 ramdisk.connector().unwrap(),
486 Box::new(Fvm { slice_size: RAMDISK_FVM_SLICE_SIZE, ..Fvm::dynamic_child() }),
487 );
488 fs.format().await.expect("Failed to format FVM");
489 let fvm_component = match fs.serve_multi_volume().await {
490 Ok(fvm_component) => fvm_component,
491 Err(_) => loop {},
492 };
493 let volumes_connector = {
494 let exposed_dir =
495 fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
496 Box::new(move || {
497 connect_to_protocol_at_dir_root::<VolumesMarker>(&exposed_dir).unwrap()
498 })
499 };
500 let volumes_dir_connector = {
501 let exposed_dir =
502 fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
503 Box::new(move || {
504 fuchsia_fs::directory::open_directory_async(
505 &exposed_dir,
506 "volumes",
507 fio::PERM_READABLE,
508 )
509 .unwrap()
510 })
511 };
512 let fvm = BenchmarkVolumeFactory::connect_to_system_fvm(
513 volumes_connector,
514 volumes_dir_connector,
515 );
516 (TestState(ramdisk, fvm_component), fvm.unwrap())
517 }
518 }
519
520 async fn benchmark_volume_factory_can_find_fvm_instance(config: FvmTestConfig) {
521 let (_state, volume_factory) = initialize(config).await;
522
523 volume_factory
525 .create_block_device(&BlockDeviceConfig {
526 requires_fvm: true,
527 use_zxcrypt: false,
528 volume_size: None,
529 })
530 .await;
531 }
532
533 #[fuchsia::test]
534 async fn benchmark_volume_factory_can_find_fvm_instance_fvm() {
535 benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: false })
536 .await;
537 }
538
539 #[fuchsia::test]
540 async fn benchmark_volume_factory_can_find_fvm_instance_gpt() {
541 benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: true })
542 .await;
543 }
544
545 async fn dropping_an_fvm_volume_removes_the_volume(config: FvmTestConfig) {
546 let (_state, volume_factory) = initialize(config).await;
547 {
548 let _volume = volume_factory
549 .create_block_device(&BlockDeviceConfig {
550 requires_fvm: true,
551 use_zxcrypt: false,
552 volume_size: None,
553 })
554 .await;
555 assert!(volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
556 };
557 assert!(!volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
558 }
559
560 #[fuchsia::test]
561 async fn dropping_an_fvm_volume_removes_the_volume_fvm() {
562 dropping_an_fvm_volume_removes_the_volume(FvmTestConfig { fxblob_enabled: false }).await;
563 }
564
565 async fn benchmark_volume_factory_create_block_device_with_zxcrypt(config: FvmTestConfig) {
566 let (_state, volume_factory) = initialize(config).await;
567 let _ = volume_factory
568 .create_block_device(&BlockDeviceConfig {
569 requires_fvm: true,
570 use_zxcrypt: true,
571 volume_size: None,
572 })
573 .await;
574 }
575
576 #[fuchsia::test]
577 async fn benchmark_volume_factory_create_block_device_with_zxcrypt_fvm() {
578 benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
579 fxblob_enabled: false,
580 })
581 .await;
582 }
583
584 #[fuchsia::test]
585 async fn benchmark_volume_factory_create_block_device_with_zxcrypt_gpt() {
586 benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
587 fxblob_enabled: true,
588 })
589 .await;
590 }
591}