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::{Immutable, 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;
50pub const DEFAULT_DATA_VOLUME_SIZE: u64 = DEFAULT_F2FS_MIN_BYTES;
51pub const BLOBFS_MAX_BYTES: u64 = 8765432;
52pub const DEFAULT_DISK_SIZE: u64 = DEFAULT_DATA_VOLUME_SIZE * 2 + BLOBFS_MAX_BYTES;
55
56const KEY_BAG_CONTENTS: &'static str = r#"
61{
62 "version":1,
63 "keys": {
64 "0":{
65 "Aes128GcmSivWrapped": [
66 "7a7c6a718cfde7078f6edec5",
67 "7cc31b765c74db3191e269d2666267022639e758fe3370e8f36c166d888586454fd4de8aeb47aadd81c531b0a0a66f27"
68 ]
69 },
70 "1":{
71 "Aes128GcmSivWrapped": [
72 "b7d7f459cbee4cc536cc4324",
73 "9f6a5d894f526b61c5c091e5e02a7ff94d18e6ad36a0aa439c86081b726eca79e6b60bd86ee5d86a20b3df98f5265a99"
74 ]
75 }
76 }
77}"#;
78
79async fn generate_keymint_file_contents(
80 old_blob: Option<&[u8]>,
81 custom_keymint: Option<Arc<FakeKeymint>>,
82) -> Vec<u8> {
83 let serve_keymint = |keymint_proxy: fidl_fuchsia_security_keymint::SealingKeysProxy| async move {
84 let key_info = b"fuchsia";
85 let key_blob = keymint_proxy.create_sealing_key(&key_info[..]).await.unwrap().unwrap();
86 assert!(!key_blob.is_empty());
87
88 let data_key_sealed =
89 keymint_proxy.seal(&key_info[..], &key_blob, DATA_KEY.deref()).await.unwrap().unwrap();
90 let metadata_key_sealed = keymint_proxy
91 .seal(&key_info[..], &key_blob, METADATA_KEY.deref())
92 .await
93 .unwrap()
94 .unwrap();
95
96 let mut json = json!({
97 "sealing_key_info": key_info,
98 "sealing_key_blob": key_blob,
99 "sealed_keys": {
100 "data.data": data_key_sealed,
101 "data.metadata": metadata_key_sealed,
102 }
103 });
104
105 if let Some(old) = old_blob {
106 json.as_object_mut().unwrap().insert(
107 "old_blob".to_string(),
108 serde_json::Value::Array(
109 old.iter().map(|&b| serde_json::Value::Number(b.into())).collect(),
110 ),
111 );
112 }
113
114 serde_json::to_vec_pretty(&json).unwrap()
115 };
116
117 if let Some(fake_keymint) = custom_keymint {
118 let key_info = b"fuchsia".to_vec();
121
122 let key_blob = fake_keymint.generate_static_sealing_key(&key_info);
125
126 let data_key_sealed =
127 fake_keymint.generate_static_sealed_data(&key_info, &key_blob, DATA_KEY.deref());
128 let metadata_key_sealed =
129 fake_keymint.generate_static_sealed_data(&key_info, &key_blob, METADATA_KEY.deref());
130
131 let mut json = json!({
132 "sealing_key_info": key_info,
133 "sealing_key_blob": key_blob,
134 "sealed_keys": {
135 "data.data": data_key_sealed,
136 "data.metadata": metadata_key_sealed,
137 }
138 });
139
140 if let Some(old) = old_blob {
141 json.as_object_mut().unwrap().insert(
142 "old_blob".to_string(),
143 serde_json::Value::Array(
144 old.iter().map(|&b| serde_json::Value::Number(b.into())).collect(),
145 ),
146 );
147 }
148
149 serde_json::to_vec_pretty(&json).unwrap()
150 } else {
151 with_keymint_service(|proxy, _| async move { Ok(serve_keymint(proxy.into_proxy()).await) })
152 .await
153 .unwrap()
154 }
155}
156
157pub const TEST_BLOB_CONTENTS: [u8; 1000] = [1; 1000];
158
159pub fn test_blob_hash() -> fuchsia_merkle::Hash {
160 fuchsia_merkle::root_from_slice(&TEST_BLOB_CONTENTS)
161}
162
163const DATA_KEY: Aes256Key = Aes256Key::create([
164 0xcf, 0x9e, 0x45, 0x2a, 0x22, 0xa5, 0x70, 0x31, 0x33, 0x3b, 0x4d, 0x6b, 0x6f, 0x78, 0x58, 0x29,
165 0x04, 0x79, 0xc7, 0xd6, 0xa9, 0x4b, 0xce, 0x82, 0x04, 0x56, 0x5e, 0x82, 0xfc, 0xe7, 0x37, 0xa8,
166]);
167
168const METADATA_KEY: Aes256Key = Aes256Key::create([
169 0x0f, 0x4d, 0xca, 0x6b, 0x35, 0x0e, 0x85, 0x6a, 0xb3, 0x8c, 0xdd, 0xe9, 0xda, 0x0e, 0xc8, 0x22,
170 0x8e, 0xea, 0xd8, 0x05, 0xc4, 0xc9, 0x0b, 0xa8, 0xd8, 0x85, 0x87, 0x50, 0x75, 0x40, 0x1c, 0x4c,
171]);
172
173pub const FVM_PART_INSTANCE_GUID: [u8; 16] = [3u8; 16];
174pub const DEFAULT_TEST_TYPE_GUID: [u8; 16] = [
175 0x66, 0x73, 0x68, 0x6F, 0x73, 0x74, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x73,
176];
177
178async fn create_hermetic_crypt_service(
179 data_key: Aes256Key,
180 metadata_key: Aes256Key,
181) -> RealmInstance {
182 let builder = RealmBuilder::new().await.unwrap();
183 let url = "#meta/fxfs-crypt.cm";
184 let crypt = builder.add_child("fxfs-crypt", url, ChildOptions::new().eager()).await.unwrap();
185 builder
186 .add_route(
187 Route::new()
188 .capability(Capability::protocol::<CryptMarker>())
189 .capability(Capability::protocol::<CryptManagementMarker>())
190 .from(&crypt)
191 .to(Ref::parent()),
192 )
193 .await
194 .unwrap();
195 builder
196 .add_route(
197 Route::new()
198 .capability(Capability::protocol::<flogger::LogSinkMarker>())
199 .from(Ref::parent())
200 .to(&crypt),
201 )
202 .await
203 .unwrap();
204 let realm = builder.build().await.expect("realm build failed");
205 let crypt_management: CryptManagementProxy =
206 realm.root.connect_to_protocol_at_exposed_dir().unwrap();
207 let wrapping_key_id_0 = [0; 16];
208 let mut wrapping_key_id_1 = [0; 16];
209 wrapping_key_id_1[0] = 1;
210 crypt_management
211 .add_wrapping_key(&wrapping_key_id_0, data_key.deref())
212 .await
213 .unwrap()
214 .expect("add_wrapping_key failed");
215 crypt_management
216 .add_wrapping_key(&wrapping_key_id_1, metadata_key.deref())
217 .await
218 .unwrap()
219 .expect("add_wrapping_key failed");
220 crypt_management
221 .set_active_key(KeyPurpose::Data, &wrapping_key_id_0)
222 .await
223 .unwrap()
224 .expect("set_active_key failed");
225 crypt_management
226 .set_active_key(KeyPurpose::Metadata, &wrapping_key_id_1)
227 .await
228 .unwrap()
229 .expect("set_active_key failed");
230 realm
231}
232
233pub async fn write_blob(blob_creator: BlobCreatorProxy, data: &[u8]) -> Hash {
235 let hash = fuchsia_merkle::root_from_slice(data);
236 let compressed_data = Type1Blob::generate(&data, CompressionMode::Always);
237
238 let blob_writer_client_end = blob_creator
239 .create(&hash.into(), false)
240 .await
241 .expect("transport error on create")
242 .expect("failed to create blob");
243
244 let writer = blob_writer_client_end.into_proxy();
245 let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
246 .await
247 .expect("failed to create BlobWriter");
248 blob_writer.write(&compressed_data).await.unwrap();
249 hash
250}
251
252#[allow(clippy::large_enum_variant)]
253pub enum Disk {
254 Prebuilt(zx::Vmo, Option<[u8; 16]>),
255 Builder(DiskBuilder),
256}
257
258impl Disk {
259 pub async fn into_vmo_and_type_guid(self) -> (zx::Vmo, Option<[u8; 16]>) {
260 match self {
261 Disk::Prebuilt(vmo, guid) => (vmo, guid),
262 Disk::Builder(builder) => builder.build().await,
263 }
264 }
265
266 pub fn builder(&mut self) -> &mut DiskBuilder {
267 match self {
268 Disk::Prebuilt(..) => panic!("attempted to get builder for prebuilt disk"),
269 Disk::Builder(builder) => builder,
270 }
271 }
272}
273
274#[derive(Debug)]
275pub struct DataSpec {
276 pub format: Option<&'static str>,
277 pub zxcrypt: bool,
278 pub crypt_policy: crypt_policy::Policy,
279}
280
281impl Default for DataSpec {
282 fn default() -> Self {
283 Self {
284 format: Default::default(),
285 zxcrypt: Default::default(),
286 crypt_policy: crypt_policy::Policy::Null,
287 }
288 }
289}
290
291#[derive(Debug)]
292pub struct VolumesSpec {
293 pub fxfs_blob: bool,
294 pub create_data_partition: bool,
295}
296
297enum FxfsType {
298 Fxfs(Box<dyn BlockConnector>),
299 FxBlob(ServingMultiVolumeFilesystem, RealmInstance),
300}
301
302pub struct DiskBuilder {
303 size: u64,
304 uninitialized: bool,
306 blob_hash: Option<Hash>,
307 data_volume_size: u64,
308 fvm_slice_size: u64,
309 data_spec: DataSpec,
310 volumes_spec: VolumesSpec,
311 corrupt_data: bool,
313 gpt: bool,
314 extra_volumes: Vec<&'static str>,
315 extra_gpt_partitions: Vec<(&'static str, u64)>,
316 format_volume_manager: bool,
318 legacy_data_label: bool,
319 fs_switch: Option<String>,
321 type_guid: Option<[u8; 16]>,
323 system_partition_label: &'static str,
324 keymint_old_blob: Option<Vec<u8>>,
328 keymint: Option<std::sync::Arc<FakeKeymint>>,
330}
331
332impl std::fmt::Debug for DiskBuilder {
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 f.debug_struct("DiskBuilder")
335 .field("size", &self.size)
336 .field("uninitialized", &self.uninitialized)
337 .field("blob_hash", &self.blob_hash)
338 .field("data_volume_size", &self.data_volume_size)
339 .field("fvm_slice_size", &self.fvm_slice_size)
340 .field("data_spec", &self.data_spec)
341 .field("volumes_spec", &self.volumes_spec)
342 .field("corrupt_data", &self.corrupt_data)
343 .field("gpt", &self.gpt)
344 .field("format_volume_manager", &self.format_volume_manager)
345 .field("legacy_data_label", &self.legacy_data_label)
346 .field("fs_switch", &self.fs_switch)
347 .field("type_guid", &self.type_guid)
348 .field("system_partition_label", &self.system_partition_label)
349 .field("keymint_old_blob", &self.keymint_old_blob)
350 .finish_non_exhaustive()
351 }
352}
353
354impl DiskBuilder {
355 pub fn uninitialized() -> DiskBuilder {
356 Self { uninitialized: true, type_guid: None, ..Self::new() }
357 }
358
359 pub fn new() -> DiskBuilder {
360 DiskBuilder {
361 size: DEFAULT_DISK_SIZE,
362 uninitialized: false,
363 blob_hash: None,
364 data_volume_size: DEFAULT_DATA_VOLUME_SIZE,
365 fvm_slice_size: FVM_SLICE_SIZE,
366 data_spec: DataSpec::default(),
367 volumes_spec: VolumesSpec { fxfs_blob: false, create_data_partition: true },
368 corrupt_data: false,
369 gpt: false,
370 extra_volumes: Vec::new(),
371 extra_gpt_partitions: Vec::new(),
372 format_volume_manager: true,
373 legacy_data_label: false,
374 fs_switch: None,
375 type_guid: Some(DEFAULT_TEST_TYPE_GUID),
376 system_partition_label: "fvm",
377 keymint_old_blob: None,
378 keymint: None,
379 }
380 }
381
382 pub fn with_crypt_policy(&mut self, policy: crypt_policy::Policy) -> &mut Self {
383 self.data_spec.crypt_policy = policy;
384 self
385 }
386
387 pub fn with_keymint_instance(&mut self, keymint: std::sync::Arc<FakeKeymint>) -> &mut Self {
388 self.keymint = Some(keymint);
389 self
390 }
391
392 pub fn set_uninitialized(&mut self) -> &mut Self {
393 self.uninitialized = true;
394 self
395 }
396
397 pub fn with_keymint_old_blob(&mut self, blob: Vec<u8>) -> &mut Self {
398 self.keymint_old_blob = Some(blob);
399 self
400 }
401
402 pub fn size(&mut self, size: u64) -> &mut Self {
403 self.size = size;
404 self
405 }
406
407 pub fn data_volume_size(&mut self, data_volume_size: u64) -> &mut Self {
408 self.data_volume_size = data_volume_size;
409 self.size = self.size.max(self.data_volume_size + BLOBFS_MAX_BYTES);
413 self
414 }
415
416 pub fn format_volumes(&mut self, volumes_spec: VolumesSpec) -> &mut Self {
417 self.volumes_spec = volumes_spec;
418 self
419 }
420
421 pub fn format_data(&mut self, data_spec: DataSpec) -> &mut Self {
422 log::info!(data_spec:?; "formatting data volume");
423 if !self.volumes_spec.fxfs_blob {
424 assert!(self.format_volume_manager);
425 } else {
426 if let Some(format) = data_spec.format {
427 assert_eq!(format, "fxfs");
428 }
429 }
430 if data_spec.format == Some("f2fs") {
431 self.fvm_slice_size = FVM_F2FS_SLICE_SIZE;
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_TYPE_STORAGE_RAMDISK: u32 = 0x4b534452;
893 const ZBI_FLAGS_VERSION: u32 = 0x00010000;
894 const ZBI_ITEM_MAGIC: u32 = 0xb5781729;
895 const ZBI_FLAGS_STORAGE_COMPRESSED: u32 = 0x00000001;
896
897 #[repr(C)]
898 #[derive(IntoBytes, Immutable)]
899 struct ZbiHeader {
900 type_: u32,
901 length: u32,
902 extra: u32,
903 flags: u32,
904 _reserved0: u32,
905 _reserved1: u32,
906 magic: u32,
907 _crc32: u32,
908 }
909
910 let (ramdisk_vmo, _) = self.build().await;
911 let extra = ramdisk_vmo.get_size().unwrap() as u32;
912 let mut decompressed_buf = vec![0u8; extra as usize];
913 ramdisk_vmo.read(&mut decompressed_buf, 0).unwrap();
914 let compressed_buf = zstd::encode_all(decompressed_buf.as_slice(), 0).unwrap();
915 let length = compressed_buf.len() as u32;
916
917 let header = ZbiHeader {
918 type_: ZBI_TYPE_STORAGE_RAMDISK,
919 length,
920 extra,
921 flags: ZBI_FLAGS_VERSION | ZBI_FLAGS_STORAGE_COMPRESSED,
922 _reserved0: 0,
923 _reserved1: 0,
924 magic: ZBI_ITEM_MAGIC,
925 _crc32: 0,
926 };
927
928 let header_size = std::mem::size_of::<ZbiHeader>() as u64;
929 let zbi_vmo = zx::Vmo::create(header_size + length as u64).unwrap();
930 zbi_vmo.write(header.as_bytes(), 0).unwrap();
931 zbi_vmo.write(&compressed_buf, header_size).unwrap();
932
933 zbi_vmo
934 }
935}
936
937pub async fn list_all_fxfs_volumes(disk: &Disk) -> HashSet<String> {
940 let Disk::Prebuilt(vmo, _) = disk else {
941 panic!("list_all_fxfs_volumes only supports prebuilt disks");
942 };
943 let server = Arc::new(
944 VmoBackedServer::from_vmo(
945 TEST_DISK_BLOCK_SIZE,
946 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
947 )
948 .unwrap(),
949 );
950
951 let partitions_dir = vfs::directory::immutable::simple();
952 let manager = GptManager::new(server.connect(), partitions_dir.clone()).await.unwrap();
953 let dir = vfs::directory::serve(
954 partitions_dir.clone(),
955 vfs::execution_scope::ExecutionScope::new(),
956 fio::PERM_READABLE | fio::PERM_WRITABLE,
957 );
958
959 let partitions = fuchsia_fs::directory::readdir(&dir).await.unwrap().into_iter().map(|entry| {
960 let dir = fuchsia_fs::directory::clone(&dir).unwrap();
961 let name = entry.name;
962 DirBasedBlockConnector::new(dir, format!("{name}/volume"))
963 });
964 let connector = find_block_device(
965 &[
966 BlockDeviceMatcher::TypeGuid(&FVM_TYPE_GUID),
967 BlockDeviceMatcher::InstanceGuid(&FVM_PART_INSTANCE_GUID),
968 ],
969 partitions,
970 )
971 .await
972 .expect("failed to match partition")
973 .expect("did not match fxfs partition");
974
975 let fxfs = Filesystem::from_boxed_config(Box::new(connector), Box::new(Fxfs::default()));
976 let fs = fxfs.serve_multi_volume().await.expect("serve_multi_volume failed");
977 let volumes = fs.list_volumes().await.expect("list_volumes failed");
978 fs.shutdown().await.expect("shutdown failed");
979 manager.shutdown().await;
980 volumes.into_iter().collect()
981}
982
983pub fn expected_fxblob_volumes() -> HashSet<String> {
985 ["blob", "data", "unencrypted"].into_iter().map(str::to_owned).collect()
986}