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