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