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