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