1use blob_writer::BlobWriter;
6use block_client::{BlockClient as _, RemoteBlockClient};
7use delivery_blob::{CompressionMode, Type1Blob};
8use fake_keymint::{FakeKeymint, with_keymint_service};
9use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
10use fidl_fuchsia_fxfs::{
11 BlobCreatorProxy, CryptManagementMarker, CryptManagementProxy, CryptMarker, KeyPurpose,
12};
13use fidl_fuchsia_io as fio;
14use fidl_fuchsia_logger as flogger;
15use fidl_fuchsia_storage_block as fblock;
16use fs_management::filesystem::{
17 BlockConnector, DirBasedBlockConnector, Filesystem, ServingMultiVolumeFilesystem,
18};
19use fs_management::format::constants::{F2FS_MAGIC, FXFS_MAGIC, MINFS_MAGIC};
20use fs_management::{BLOBFS_TYPE_GUID, DATA_TYPE_GUID, FVM_TYPE_GUID, Fvm, Fxfs};
21use fuchsia_async as fasync;
22use fuchsia_component::client::connect_to_protocol_at_dir_svc;
23use fuchsia_component_test::{Capability, ChildOptions, RealmBuilder, RealmInstance, Ref, Route};
24use fuchsia_hash::Hash;
25use gpt_component::gpt::GptManager;
26use key_bag::Aes256Key;
27use serde_json::json;
28use std::collections::HashSet;
29use std::ops::Deref;
30use std::sync::Arc;
31use storage_isolated_driver_manager::{BlockDeviceMatcher, find_block_device};
32use uuid::Uuid;
33use vmo_backed_block_server::{VmoBackedServer, VmoBackedServerTestingExt as _};
34use zerocopy::{Immutable, IntoBytes};
35use zx::{self as zx, HandleBased};
36
37pub const TEST_DISK_BLOCK_SIZE: u32 = 512;
38pub const FVM_SLICE_SIZE: u64 = 32 * 1024;
39pub const FVM_F2FS_SLICE_SIZE: u64 = 2 * 1024 * 1024;
40
41pub const DEFAULT_F2FS_MIN_BYTES: u64 = 50 * 1024 * 1024;
51pub const DEFAULT_DATA_VOLUME_SIZE: u64 = DEFAULT_F2FS_MIN_BYTES;
52pub const BLOBFS_MAX_BYTES: u64 = 8765432;
53pub const DEFAULT_DISK_SIZE: u64 = DEFAULT_DATA_VOLUME_SIZE * 2 + BLOBFS_MAX_BYTES;
56
57const KEY_BAG_CONTENTS: &'static str = r#"
62{
63 "version":1,
64 "keys": {
65 "0":{
66 "Aes128GcmSivWrapped": [
67 "7a7c6a718cfde7078f6edec5",
68 "7cc31b765c74db3191e269d2666267022639e758fe3370e8f36c166d888586454fd4de8aeb47aadd81c531b0a0a66f27"
69 ]
70 },
71 "1":{
72 "Aes128GcmSivWrapped": [
73 "b7d7f459cbee4cc536cc4324",
74 "9f6a5d894f526b61c5c091e5e02a7ff94d18e6ad36a0aa439c86081b726eca79e6b60bd86ee5d86a20b3df98f5265a99"
75 ]
76 }
77 }
78}"#;
79
80async fn generate_keymint_file_contents(
81 old_blob: Option<&[u8]>,
82 custom_keymint: Option<Arc<FakeKeymint>>,
83) -> Vec<u8> {
84 let serve_keymint = |keymint_proxy: fidl_fuchsia_security_keymint::SealingKeysProxy| async move {
85 let key_info = b"fuchsia";
86 let key_blob = keymint_proxy.create_sealing_key(&key_info[..]).await.unwrap().unwrap();
87 assert!(!key_blob.is_empty());
88
89 let data_key_sealed =
90 keymint_proxy.seal(&key_info[..], &key_blob, DATA_KEY.deref()).await.unwrap().unwrap();
91 let metadata_key_sealed = keymint_proxy
92 .seal(&key_info[..], &key_blob, METADATA_KEY.deref())
93 .await
94 .unwrap()
95 .unwrap();
96
97 let mut json = json!({
98 "sealing_key_info": key_info,
99 "sealing_key_blob": key_blob,
100 "sealed_keys": {
101 "data.data": data_key_sealed,
102 "data.metadata": metadata_key_sealed,
103 }
104 });
105
106 if let Some(old) = old_blob {
107 json.as_object_mut().unwrap().insert(
108 "old_blob".to_string(),
109 serde_json::Value::Array(
110 old.iter().map(|&b| serde_json::Value::Number(b.into())).collect(),
111 ),
112 );
113 }
114
115 serde_json::to_vec_pretty(&json).unwrap()
116 };
117
118 if let Some(fake_keymint) = custom_keymint {
119 let key_info = b"fuchsia".to_vec();
122
123 let key_blob = fake_keymint.generate_static_sealing_key(&key_info);
126
127 let data_key_sealed =
128 fake_keymint.generate_static_sealed_data(&key_info, &key_blob, DATA_KEY.deref());
129 let metadata_key_sealed =
130 fake_keymint.generate_static_sealed_data(&key_info, &key_blob, METADATA_KEY.deref());
131
132 let mut json = json!({
133 "sealing_key_info": key_info,
134 "sealing_key_blob": key_blob,
135 "sealed_keys": {
136 "data.data": data_key_sealed,
137 "data.metadata": metadata_key_sealed,
138 }
139 });
140
141 if let Some(old) = old_blob {
142 json.as_object_mut().unwrap().insert(
143 "old_blob".to_string(),
144 serde_json::Value::Array(
145 old.iter().map(|&b| serde_json::Value::Number(b.into())).collect(),
146 ),
147 );
148 }
149
150 serde_json::to_vec_pretty(&json).unwrap()
151 } else {
152 with_keymint_service(|proxy, _| async move { Ok(serve_keymint(proxy.into_proxy()).await) })
153 .await
154 .unwrap()
155 }
156}
157
158pub const TEST_BLOB_CONTENTS: [u8; 1000] = [1; 1000];
159
160pub fn test_blob_hash() -> fuchsia_merkle::Hash {
161 fuchsia_merkle::root_from_slice(&TEST_BLOB_CONTENTS)
162}
163
164const DATA_KEY: Aes256Key = Aes256Key::create([
165 0xcf, 0x9e, 0x45, 0x2a, 0x22, 0xa5, 0x70, 0x31, 0x33, 0x3b, 0x4d, 0x6b, 0x6f, 0x78, 0x58, 0x29,
166 0x04, 0x79, 0xc7, 0xd6, 0xa9, 0x4b, 0xce, 0x82, 0x04, 0x56, 0x5e, 0x82, 0xfc, 0xe7, 0x37, 0xa8,
167]);
168
169const METADATA_KEY: Aes256Key = Aes256Key::create([
170 0x0f, 0x4d, 0xca, 0x6b, 0x35, 0x0e, 0x85, 0x6a, 0xb3, 0x8c, 0xdd, 0xe9, 0xda, 0x0e, 0xc8, 0x22,
171 0x8e, 0xea, 0xd8, 0x05, 0xc4, 0xc9, 0x0b, 0xa8, 0xd8, 0x85, 0x87, 0x50, 0x75, 0x40, 0x1c, 0x4c,
172]);
173
174pub const FVM_PART_INSTANCE_GUID: [u8; 16] = [3u8; 16];
175pub const DEFAULT_TEST_TYPE_GUID: [u8; 16] = [
176 0x66, 0x73, 0x68, 0x6F, 0x73, 0x74, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x73,
177];
178
179async fn create_hermetic_crypt_service(
180 data_key: Aes256Key,
181 metadata_key: Aes256Key,
182) -> RealmInstance {
183 let builder = RealmBuilder::new().await.unwrap();
184 let url = "#meta/fxfs-crypt.cm";
185 let crypt = builder.add_child("fxfs-crypt", url, ChildOptions::new().eager()).await.unwrap();
186 builder
187 .add_route(
188 Route::new()
189 .capability(Capability::protocol::<CryptMarker>())
190 .capability(Capability::protocol::<CryptManagementMarker>())
191 .from(&crypt)
192 .to(Ref::parent()),
193 )
194 .await
195 .unwrap();
196 builder
197 .add_route(
198 Route::new()
199 .capability(Capability::protocol::<flogger::LogSinkMarker>())
200 .from(Ref::parent())
201 .to(&crypt),
202 )
203 .await
204 .unwrap();
205 let realm = builder.build().await.expect("realm build failed");
206 let crypt_management: CryptManagementProxy =
207 realm.root.connect_to_protocol_at_exposed_dir().unwrap();
208 let wrapping_key_id_0 = [0; 16];
209 let mut wrapping_key_id_1 = [0; 16];
210 wrapping_key_id_1[0] = 1;
211 crypt_management
212 .add_wrapping_key(&wrapping_key_id_0, data_key.deref())
213 .await
214 .unwrap()
215 .expect("add_wrapping_key failed");
216 crypt_management
217 .add_wrapping_key(&wrapping_key_id_1, metadata_key.deref())
218 .await
219 .unwrap()
220 .expect("add_wrapping_key failed");
221 crypt_management
222 .set_active_key(KeyPurpose::Data, &wrapping_key_id_0)
223 .await
224 .unwrap()
225 .expect("set_active_key failed");
226 crypt_management
227 .set_active_key(KeyPurpose::Metadata, &wrapping_key_id_1)
228 .await
229 .unwrap()
230 .expect("set_active_key failed");
231 realm
232}
233
234pub async fn write_blob(blob_creator: BlobCreatorProxy, data: &[u8]) -> Hash {
236 let hash = fuchsia_merkle::root_from_slice(data);
237 let compressed_data = Type1Blob::generate(&data, CompressionMode::Always);
238
239 let blob_writer_client_end = blob_creator
240 .create(&hash.into(), false)
241 .await
242 .expect("transport error on create")
243 .expect("failed to create blob");
244
245 let writer = blob_writer_client_end.into_proxy();
246 let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
247 .await
248 .expect("failed to create BlobWriter");
249 blob_writer.write(&compressed_data).await.unwrap();
250 hash
251}
252
253#[allow(clippy::large_enum_variant)]
254pub enum Disk {
255 Prebuilt(zx::Vmo, Option<[u8; 16]>),
256 Builder(DiskBuilder),
257}
258
259impl Disk {
260 pub async fn into_vmo_and_type_guid(self) -> (zx::Vmo, Option<[u8; 16]>) {
261 match self {
262 Disk::Prebuilt(vmo, guid) => (vmo, guid),
263 Disk::Builder(builder) => builder.build().await,
264 }
265 }
266
267 pub fn builder(&mut self) -> &mut DiskBuilder {
268 match self {
269 Disk::Prebuilt(..) => panic!("attempted to get builder for prebuilt disk"),
270 Disk::Builder(builder) => builder,
271 }
272 }
273}
274
275#[derive(Debug)]
276pub struct DataSpec {
277 pub format: Option<&'static str>,
278 pub zxcrypt: bool,
279 pub crypt_policy: crypt_policy::Policy,
280}
281
282impl Default for DataSpec {
283 fn default() -> Self {
284 Self {
285 format: Default::default(),
286 zxcrypt: Default::default(),
287 crypt_policy: crypt_policy::Policy::Null,
288 }
289 }
290}
291
292#[derive(Debug)]
293pub struct VolumesSpec {
294 pub fxfs_blob: bool,
295 pub create_data_partition: bool,
296}
297
298enum FxfsType {
299 Fxfs(Box<dyn BlockConnector>),
300 FxBlob(ServingMultiVolumeFilesystem, RealmInstance),
301}
302
303pub struct DiskBuilder {
304 size: u64,
305 uninitialized: bool,
307 blob_hash: Option<Hash>,
308 data_volume_size: u64,
309 fvm_slice_size: u64,
310 data_spec: DataSpec,
311 volumes_spec: VolumesSpec,
312 corrupt_data: bool,
314 gpt: bool,
315 extra_volumes: Vec<&'static str>,
316 extra_gpt_partitions: Vec<(&'static str, u64)>,
317 format_volume_manager: bool,
319 legacy_data_label: bool,
320 fs_switch: Option<String>,
322 type_guid: Option<[u8; 16]>,
324 system_partition_label: &'static str,
325 keymint_old_blob: Option<Vec<u8>>,
329 keymint: Option<std::sync::Arc<FakeKeymint>>,
331}
332
333impl std::fmt::Debug for DiskBuilder {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 f.debug_struct("DiskBuilder")
336 .field("size", &self.size)
337 .field("uninitialized", &self.uninitialized)
338 .field("blob_hash", &self.blob_hash)
339 .field("data_volume_size", &self.data_volume_size)
340 .field("fvm_slice_size", &self.fvm_slice_size)
341 .field("data_spec", &self.data_spec)
342 .field("volumes_spec", &self.volumes_spec)
343 .field("corrupt_data", &self.corrupt_data)
344 .field("gpt", &self.gpt)
345 .field("format_volume_manager", &self.format_volume_manager)
346 .field("legacy_data_label", &self.legacy_data_label)
347 .field("fs_switch", &self.fs_switch)
348 .field("type_guid", &self.type_guid)
349 .field("system_partition_label", &self.system_partition_label)
350 .field("keymint_old_blob", &self.keymint_old_blob)
351 .finish_non_exhaustive()
352 }
353}
354
355impl DiskBuilder {
356 pub fn uninitialized() -> DiskBuilder {
357 Self { uninitialized: true, type_guid: None, ..Self::new() }
358 }
359
360 pub fn new() -> DiskBuilder {
361 DiskBuilder {
362 size: DEFAULT_DISK_SIZE,
363 uninitialized: false,
364 blob_hash: None,
365 data_volume_size: DEFAULT_DATA_VOLUME_SIZE,
366 fvm_slice_size: FVM_SLICE_SIZE,
367 data_spec: DataSpec::default(),
368 volumes_spec: VolumesSpec { fxfs_blob: false, create_data_partition: true },
369 corrupt_data: false,
370 gpt: false,
371 extra_volumes: Vec::new(),
372 extra_gpt_partitions: Vec::new(),
373 format_volume_manager: true,
374 legacy_data_label: false,
375 fs_switch: None,
376 type_guid: Some(DEFAULT_TEST_TYPE_GUID),
377 system_partition_label: "fvm",
378 keymint_old_blob: None,
379 keymint: None,
380 }
381 }
382
383 pub fn with_crypt_policy(&mut self, policy: crypt_policy::Policy) -> &mut Self {
384 self.data_spec.crypt_policy = policy;
385 self
386 }
387
388 pub fn with_keymint_instance(&mut self, keymint: std::sync::Arc<FakeKeymint>) -> &mut Self {
389 self.keymint = Some(keymint);
390 self
391 }
392
393 pub fn set_uninitialized(&mut self) -> &mut Self {
394 self.uninitialized = true;
395 self
396 }
397
398 pub fn with_keymint_old_blob(&mut self, blob: Vec<u8>) -> &mut Self {
399 self.keymint_old_blob = Some(blob);
400 self
401 }
402
403 pub fn size(&mut self, size: u64) -> &mut Self {
404 self.size = size;
405 self
406 }
407
408 pub fn data_volume_size(&mut self, data_volume_size: u64) -> &mut Self {
409 self.data_volume_size = data_volume_size;
410 self.size = self.size.max(self.data_volume_size + BLOBFS_MAX_BYTES);
414 self
415 }
416
417 pub fn format_volumes(&mut self, volumes_spec: VolumesSpec) -> &mut Self {
418 self.volumes_spec = volumes_spec;
419 self
420 }
421
422 pub fn format_data(&mut self, data_spec: DataSpec) -> &mut Self {
423 log::info!(data_spec:?; "formatting data volume");
424 if !self.volumes_spec.fxfs_blob {
425 assert!(self.format_volume_manager);
426 } else {
427 if let Some(format) = data_spec.format {
428 assert_eq!(format, "fxfs");
429 }
430 }
431 if data_spec.format == Some("f2fs") {
432 self.fvm_slice_size = FVM_F2FS_SLICE_SIZE;
433 }
434 self.data_spec = data_spec;
435 self
436 }
437
438 pub fn set_fs_switch(&mut self, content: &str) -> &mut Self {
439 self.fs_switch = Some(content.to_string());
440 self
441 }
442
443 pub fn corrupt_data(&mut self) -> &mut Self {
444 self.corrupt_data = true;
445 self
446 }
447
448 pub fn with_gpt(&mut self) -> &mut Self {
449 self.gpt = true;
450 self.type_guid = None;
453 self
454 }
455
456 pub fn with_system_partition_label(&mut self, label: &'static str) -> &mut Self {
457 self.system_partition_label = label;
458 self
459 }
460
461 pub fn with_extra_gpt_partition(
464 &mut self,
465 volume_name: &'static str,
466 num_blocks: u64,
467 ) -> &mut Self {
468 self.extra_gpt_partitions.push((volume_name, num_blocks));
469 self
470 }
471
472 pub fn with_extra_volume(&mut self, volume_name: &'static str) -> &mut Self {
473 self.extra_volumes.push(volume_name);
474 self
475 }
476
477 pub fn with_unformatted_volume_manager(&mut self) -> &mut Self {
478 assert!(self.data_spec.format.is_none());
479 self.format_volume_manager = false;
480 self
481 }
482
483 pub fn with_legacy_data_label(&mut self) -> &mut Self {
484 self.legacy_data_label = true;
485 self
486 }
487
488 pub async fn build(mut self) -> (zx::Vmo, Option<[u8; 16]>) {
489 log::info!("building disk: {:?}", self);
490 assert_eq!(
491 self.data_volume_size % self.fvm_slice_size,
492 0,
493 "data_volume_size {} needs to be a multiple of fvm slice size {}",
494 self.data_volume_size,
495 self.fvm_slice_size
496 );
497 if self.data_spec.format == Some("f2fs") {
498 assert!(self.data_volume_size >= DEFAULT_F2FS_MIN_BYTES);
499 }
500
501 let vmo = zx::Vmo::create(self.size).unwrap();
502
503 if self.uninitialized {
504 return (vmo, self.type_guid);
505 }
506
507 let server = Arc::new(VmoBackedServer::from_vmo(
508 TEST_DISK_BLOCK_SIZE,
509 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
510 ));
511
512 if self.gpt {
513 let client = Arc::new(
515 RemoteBlockClient::new(server.connect::<fblock::BlockProxy>()).await.unwrap(),
516 );
517 assert!(self.extra_gpt_partitions.len() < 10);
518 let fvm_num_blocks = self.size / TEST_DISK_BLOCK_SIZE as u64 - 138;
519 let mut start_block = 64;
520 let mut partitions = vec![gpt::PartitionInfo {
521 label: self.system_partition_label.to_string(),
522 type_guid: gpt::Guid::from_bytes(FVM_TYPE_GUID),
523 instance_guid: gpt::Guid::from_bytes(FVM_PART_INSTANCE_GUID),
524 start_block,
525 num_blocks: fvm_num_blocks,
526 flags: 0,
527 }];
528 start_block = start_block + fvm_num_blocks;
529 for (extra_partition, num_blocks) in &self.extra_gpt_partitions {
530 partitions.push(gpt::PartitionInfo {
531 label: extra_partition.to_string(),
532 type_guid: gpt::Guid::from_bytes(DEFAULT_TEST_TYPE_GUID),
533 instance_guid: gpt::Guid::from_bytes(FVM_PART_INSTANCE_GUID),
534 start_block,
535 num_blocks: *num_blocks,
536 flags: 0,
537 });
538 start_block += num_blocks;
539 }
540 let _ = gpt::Gpt::format(client, partitions).await.expect("gpt format failed");
541 }
542
543 if !self.format_volume_manager {
544 return (vmo, self.type_guid);
545 }
546
547 let mut gpt = None;
548 let connector: Box<dyn BlockConnector> = if self.gpt {
549 let partitions_dir = vfs::directory::immutable::simple();
551 let manager = GptManager::new(server.connect(), partitions_dir.clone()).await.unwrap();
552 let dir =
553 vfs::directory::serve(partitions_dir, fio::PERM_READABLE | fio::PERM_WRITABLE);
554 gpt = Some((manager, fuchsia_fs::directory::clone(&dir).unwrap()));
555 Box::new(DirBasedBlockConnector::new(dir, "part-000/volume".to_string()))
556 } else {
557 Box::new(move |server_end| Ok(server.connect_server(server_end)))
559 };
560
561 if self.volumes_spec.fxfs_blob {
562 self.build_fxfs_as_volume_manager(connector).await;
563 } else {
564 self.build_fvm_as_volume_manager(connector).await;
565 }
566 if let Some((gpt, partitions_dir)) = gpt {
567 partitions_dir.close().await.unwrap().unwrap();
568 gpt.shutdown().await;
569 }
570 (vmo, self.type_guid)
571 }
572
573 pub(crate) async fn build_fxfs_as_volume_manager(
574 &mut self,
575 connector: Box<dyn BlockConnector>,
576 ) {
577 let crypt_realm = create_hermetic_crypt_service(DATA_KEY, METADATA_KEY).await;
578 let mut fxfs = Filesystem::from_boxed_config(connector, Box::new(Fxfs::default()));
579 fxfs.format().await.expect("format failed");
581 let fs = fxfs.serve_multi_volume().await.expect("serve_multi_volume failed");
582 let blob_volume = fs
583 .create_volume(
584 "blob",
585 CreateOptions::default(),
586 MountOptions { as_blob: Some(true), ..MountOptions::default() },
587 )
588 .await
589 .expect("failed to create blob volume");
590 let blob_creator = connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
591 blob_volume.exposed_dir(),
592 )
593 .expect("failed to connect to the Blob service");
594 self.blob_hash = Some(write_blob(blob_creator, &TEST_BLOB_CONTENTS).await);
595
596 for volume in &self.extra_volumes {
597 fs.create_volume(volume, CreateOptions::default(), MountOptions::default())
598 .await
599 .expect("failed to make extra fxfs volume");
600 }
601
602 if self.data_spec.format.is_some() {
603 self.init_data_fxfs(FxfsType::FxBlob(fs, crypt_realm), self.data_spec.crypt_policy)
604 .await;
605 } else {
606 fs.shutdown().await.expect("shutdown failed");
607 }
608 }
609
610 async fn build_fvm_as_volume_manager(&mut self, connector: Box<dyn BlockConnector>) {
611 let fvm_slice_size = self.fvm_slice_size;
612 let mut fvm_fs = Filesystem::from_boxed_config(
613 connector,
614 Box::new(Fvm { slice_size: fvm_slice_size, ..Fvm::dynamic_child() }),
615 );
616 fvm_fs.format().await.unwrap();
617 let fvm = fvm_fs.serve_multi_volume().await.unwrap();
618
619 {
620 let blob_volume = fvm
621 .create_volume(
622 "blobfs",
623 CreateOptions {
624 type_guid: Some(BLOBFS_TYPE_GUID),
625 guid: Some(Uuid::new_v4().into_bytes()),
626 ..Default::default()
627 },
628 MountOptions {
629 uri: Some(String::from("#meta/blobfs.cm")),
630 ..Default::default()
631 },
632 )
633 .await
634 .expect("failed to make fvm blobfs volume");
635 let blob_creator =
636 connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
637 blob_volume.exposed_dir(),
638 )
639 .expect("failed to connect to the Blob service");
640 self.blob_hash = Some(write_blob(blob_creator, &TEST_BLOB_CONTENTS).await);
641
642 blob_volume.shutdown().await.unwrap();
643 }
644
645 if self.volumes_spec.create_data_partition {
646 let data_label = if self.legacy_data_label { "minfs" } else { "data" };
647
648 let _crypt_service;
649 let crypt = if self.data_spec.format != Some("fxfs") && self.data_spec.zxcrypt {
650 let (crypt, stream) = fidl::endpoints::create_request_stream();
651 _crypt_service = fasync::Task::spawn(zxcrypt_crypt::run_crypt_service(
652 crypt_policy::Policy::Null,
653 stream,
654 ));
655 Some(crypt)
656 } else {
657 None
658 };
659 let uri = match (&self.data_spec.format, self.corrupt_data) {
660 (None, _) => None,
661 (_, true) => None,
662 (Some("fxfs"), false) => None,
663 (Some("minfs"), false) => Some(String::from("#meta/minfs.cm")),
664 (Some("f2fs"), false) => Some(String::from("#meta/f2fs.cm")),
665 (Some(format), _) => panic!("unsupported data volume format '{}'", format),
666 };
667
668 let data_volume = fvm
669 .create_volume(
670 data_label,
671 CreateOptions {
672 initial_size: Some(self.data_volume_size),
673 type_guid: Some(DATA_TYPE_GUID),
674 guid: Some(Uuid::new_v4().into_bytes()),
675 ..Default::default()
676 },
677 MountOptions { crypt, uri, ..Default::default() },
678 )
679 .await
680 .unwrap();
681
682 if self.corrupt_data {
683 let volume_proxy = connect_to_protocol_at_dir_svc::<
684 fidl_fuchsia_storage_block::BlockMarker,
685 >(data_volume.exposed_dir())
686 .unwrap();
687 match self.data_spec.format {
688 Some("fxfs") => self.write_magic(volume_proxy, FXFS_MAGIC, 0).await,
689 Some("minfs") => self.write_magic(volume_proxy, MINFS_MAGIC, 0).await,
690 Some("f2fs") => self.write_magic(volume_proxy, F2FS_MAGIC, 1024).await,
691 _ => (),
692 }
693 } else if self.data_spec.format == Some("fxfs") {
694 let dir = fuchsia_fs::directory::clone(data_volume.exposed_dir()).unwrap();
695 self.init_data_fxfs(
696 FxfsType::Fxfs(Box::new(DirBasedBlockConnector::new(
697 dir,
698 String::from("svc/fuchsia.storage.block.Block"),
699 ))),
700 self.data_spec.crypt_policy,
701 )
702 .await
703 } else if self.data_spec.format.is_some() {
704 self.write_test_data(data_volume.root()).await;
705 data_volume.shutdown().await.unwrap();
706 }
707 }
708
709 for volume in &self.extra_volumes {
710 fvm.create_volume(
711 volume,
712 CreateOptions {
713 type_guid: Some(DATA_TYPE_GUID),
714 guid: Some(Uuid::new_v4().into_bytes()),
715 ..Default::default()
716 },
717 MountOptions::default(),
718 )
719 .await
720 .expect("failed to make extra fvm volume");
721 }
722
723 fvm.shutdown().await.expect("fvm shutdown failed");
724 }
725
726 async fn init_data_fxfs(&self, fxfs: FxfsType, crypt_policy: crypt_policy::Policy) {
727 let mut fxblob = false;
728 let (fs, crypt_realm) = match fxfs {
729 FxfsType::Fxfs(connector) => {
730 let crypt_realm = create_hermetic_crypt_service(DATA_KEY, METADATA_KEY).await;
731 let mut fxfs =
732 Filesystem::from_boxed_config(connector, Box::new(Fxfs::dynamic_child()));
733 fxfs.format().await.expect("format failed");
734 (fxfs.serve_multi_volume().await.expect("serve_multi_volume failed"), crypt_realm)
735 }
736 FxfsType::FxBlob(fs, crypt_realm) => {
737 fxblob = true;
738 (fs, crypt_realm)
739 }
740 };
741
742 let vol = {
743 let vol = fs
744 .create_volume("unencrypted", CreateOptions::default(), MountOptions::default())
745 .await
746 .expect("create_volume failed");
747 let keys_dir = fuchsia_fs::directory::create_directory(
748 vol.root(),
749 "keys",
750 fio::PERM_READABLE | fio::PERM_WRITABLE,
751 )
752 .await
753 .unwrap();
754 if let crypt_policy::Policy::Keymint = crypt_policy {
755 let keymint_file = fuchsia_fs::directory::open_file(
756 &keys_dir,
757 "keymint.0",
758 fio::Flags::FLAG_MAYBE_CREATE
759 | fio::Flags::PROTOCOL_FILE
760 | fio::PERM_READABLE
761 | fio::PERM_WRITABLE,
762 )
763 .await
764 .unwrap();
765 let contents = generate_keymint_file_contents(
766 self.keymint_old_blob.as_deref(),
767 self.keymint.clone(),
768 )
769 .await;
770 let mut contents_ref = contents.as_slice();
771 if self.corrupt_data && fxblob {
772 contents_ref = &TEST_BLOB_CONTENTS;
773 }
774 fuchsia_fs::file::write(&keymint_file, contents_ref).await.unwrap();
775 fuchsia_fs::file::close(keymint_file).await.unwrap();
776 } else {
777 let keys_file = fuchsia_fs::directory::open_file(
778 &keys_dir,
779 "fxfs-data",
780 fio::Flags::FLAG_MAYBE_CREATE
781 | fio::Flags::PROTOCOL_FILE
782 | fio::PERM_READABLE
783 | fio::PERM_WRITABLE,
784 )
785 .await
786 .unwrap();
787 let mut key_bag = KEY_BAG_CONTENTS.as_bytes();
788 if self.corrupt_data && fxblob {
789 key_bag = &TEST_BLOB_CONTENTS;
790 }
791 fuchsia_fs::file::write(&keys_file, key_bag).await.unwrap();
792 fuchsia_fs::file::close(keys_file).await.unwrap();
793 }
794 fuchsia_fs::directory::close(keys_dir).await.unwrap();
795
796 let crypt = Some(
797 crypt_realm
798 .root
799 .connect_to_protocol_at_exposed_dir()
800 .expect("Unable to connect to Crypt service"),
801 );
802 fs.create_volume(
803 "data",
804 CreateOptions::default(),
805 MountOptions { crypt, ..MountOptions::default() },
806 )
807 .await
808 .expect("create_volume failed")
809 };
810 self.write_test_data(&vol.root()).await;
811 fs.shutdown().await.expect("shutdown failed");
812 }
813
814 async fn write_test_data(&self, root: &fio::DirectoryProxy) {
822 fuchsia_fs::directory::open_file(
823 root,
824 ".testdata",
825 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE,
826 )
827 .await
828 .unwrap();
829
830 let ssh_dir = fuchsia_fs::directory::create_directory(
831 root,
832 "ssh",
833 fio::PERM_READABLE | fio::PERM_WRITABLE,
834 )
835 .await
836 .unwrap();
837 let authorized_keys = fuchsia_fs::directory::open_file(
838 &ssh_dir,
839 "authorized_keys",
840 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
841 )
842 .await
843 .unwrap();
844 fuchsia_fs::file::write(&authorized_keys, "public key!").await.unwrap();
845 fuchsia_fs::directory::create_directory(&ssh_dir, "config", fio::PERM_READABLE)
846 .await
847 .unwrap();
848
849 fuchsia_fs::directory::create_directory(&root, "problems", fio::PERM_READABLE)
850 .await
851 .unwrap();
852
853 if let Some(content) = &self.fs_switch {
854 let fs_switch = fuchsia_fs::directory::open_file(
855 &root,
856 "fs_switch",
857 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
858 )
859 .await
860 .unwrap();
861 fuchsia_fs::file::write(&fs_switch, content).await.unwrap();
862 }
863 }
864
865 async fn write_magic<const N: usize>(
866 &self,
867 volume_proxy: fblock::BlockProxy,
868 value: [u8; N],
869 offset: u64,
870 ) {
871 let client = block_client::RemoteBlockClient::new(volume_proxy)
872 .await
873 .expect("Failed to create client");
874 let block_size = client.block_size() as usize;
875 assert!(value.len() <= block_size);
876 let mut data = vec![0xffu8; block_size];
877 data[..value.len()].copy_from_slice(&value);
878 let buffer = block_client::BufferSlice::Memory(&data[..]);
879 client.write_at(buffer, offset).await.expect("write failed");
880 }
881
882 pub(crate) async fn build_as_zbi_ramdisk(self) -> zx::Vmo {
885 const ZBI_TYPE_STORAGE_RAMDISK: u32 = 0x4b534452;
888 const ZBI_FLAGS_VERSION: u32 = 0x00010000;
889 const ZBI_ITEM_MAGIC: u32 = 0xb5781729;
890 const ZBI_FLAGS_STORAGE_COMPRESSED: u32 = 0x00000001;
891
892 #[repr(C)]
893 #[derive(IntoBytes, Immutable)]
894 struct ZbiHeader {
895 type_: u32,
896 length: u32,
897 extra: u32,
898 flags: u32,
899 _reserved0: u32,
900 _reserved1: u32,
901 magic: u32,
902 _crc32: u32,
903 }
904
905 let (ramdisk_vmo, _) = self.build().await;
906 let extra = ramdisk_vmo.get_size().unwrap() as u32;
907 let mut decompressed_buf = vec![0u8; extra as usize];
908 ramdisk_vmo.read(&mut decompressed_buf, 0).unwrap();
909 let compressed_buf = zstd::encode_all(decompressed_buf.as_slice(), 0).unwrap();
910 let length = compressed_buf.len() as u32;
911
912 let header = ZbiHeader {
913 type_: ZBI_TYPE_STORAGE_RAMDISK,
914 length,
915 extra,
916 flags: ZBI_FLAGS_VERSION | ZBI_FLAGS_STORAGE_COMPRESSED,
917 _reserved0: 0,
918 _reserved1: 0,
919 magic: ZBI_ITEM_MAGIC,
920 _crc32: 0,
921 };
922
923 let header_size = std::mem::size_of::<ZbiHeader>() as u64;
924 let zbi_vmo = zx::Vmo::create(header_size + length as u64).unwrap();
925 zbi_vmo.write(header.as_bytes(), 0).unwrap();
926 zbi_vmo.write(&compressed_buf, header_size).unwrap();
927
928 zbi_vmo
929 }
930}
931
932pub async fn list_all_fxfs_volumes(disk: &Disk) -> HashSet<String> {
935 let Disk::Prebuilt(vmo, _) = disk else {
936 panic!("list_all_fxfs_volumes only supports prebuilt disks");
937 };
938 let server = Arc::new(VmoBackedServer::from_vmo(
939 TEST_DISK_BLOCK_SIZE,
940 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
941 ));
942
943 let partitions_dir = vfs::directory::immutable::simple();
944 let manager = GptManager::new(server.connect(), partitions_dir.clone()).await.unwrap();
945 let dir =
946 vfs::directory::serve(partitions_dir.clone(), fio::PERM_READABLE | fio::PERM_WRITABLE);
947
948 let partitions = fuchsia_fs::directory::readdir(&dir).await.unwrap().into_iter().map(|entry| {
949 let dir = fuchsia_fs::directory::clone(&dir).unwrap();
950 let name = entry.name;
951 DirBasedBlockConnector::new(dir, format!("{name}/volume"))
952 });
953 let connector = find_block_device(
954 &[
955 BlockDeviceMatcher::TypeGuid(&FVM_TYPE_GUID),
956 BlockDeviceMatcher::InstanceGuid(&FVM_PART_INSTANCE_GUID),
957 ],
958 partitions,
959 )
960 .await
961 .expect("failed to match partition")
962 .expect("did not match fxfs partition");
963
964 let fxfs = Filesystem::from_boxed_config(Box::new(connector), Box::new(Fxfs::default()));
965 let fs = fxfs.serve_multi_volume().await.expect("serve_multi_volume failed");
966 let volumes = fs.list_volumes().await.expect("list_volumes failed");
967 fs.shutdown().await.expect("shutdown failed");
968 manager.shutdown().await;
969 volumes.into_iter().collect()
970}
971
972pub fn expected_fxblob_volumes() -> HashSet<String> {
974 ["blob", "data", "unencrypted"].into_iter().map(str::to_owned).collect()
975}