fshost_test_fixture/
disk_builder.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use blob_writer::BlobWriter;
6use block_client::{BlockClient as _, RemoteBlockClient};
7use delivery_blob::{CompressionMode, Type1Blob};
8use fake_block_server::FakeServer;
9use fidl::endpoints::Proxy as _;
10use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
11use fidl_fuchsia_fxfs::{BlobCreatorProxy, CryptManagementMarker, CryptMarker, KeyPurpose};
12use fs_management::filesystem::{
13    BlockConnector, DirBasedBlockConnector, Filesystem, ServingMultiVolumeFilesystem,
14};
15use fs_management::format::constants::{F2FS_MAGIC, FXFS_MAGIC, MINFS_MAGIC};
16use fs_management::{Fvm, Fxfs, BLOBFS_TYPE_GUID, DATA_TYPE_GUID, FVM_TYPE_GUID};
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::ops::Deref;
23use std::sync::Arc;
24use storage_isolated_driver_manager::fvm::format_for_fvm;
25use uuid::Uuid;
26use zerocopy::{Immutable, IntoBytes};
27use zx::{self as zx, HandleBased};
28use {fidl_fuchsia_io as fio, fidl_fuchsia_logger as flogger, fuchsia_async as fasync};
29
30pub const TEST_DISK_BLOCK_SIZE: u32 = 512;
31pub const FVM_SLICE_SIZE: u64 = 32 * 1024;
32
33// The default disk size is about 55MiB, with about 51MiB dedicated to the data volume. This size
34// is chosen because the data volume has to be big enough to support f2fs (>= DEFAULT_F2FS_MIN_BYTES
35// defined in //src/storage/fshost/device/constants.rs), which has a relatively large minimum size
36// requirement to be formatted.
37//
38// Only the data volume is actually created with a specific size, the other volumes aren't passed
39// any sizes. Blobfs can resize itself on the fvm, and the other two potential volumes are only
40// used in specific circumstances and are never formatted. The remaining volume size is just used
41// for calculation.
42pub const DEFAULT_F2FS_MIN_BYTES: u64 = 50 * 1024 * 1024;
43pub const DEFAULT_DATA_VOLUME_SIZE: u64 = DEFAULT_F2FS_MIN_BYTES + 1024 * 1024;
44pub const DEFAULT_REMAINING_VOLUME_SIZE: u64 = 4 * 1024 * 1024;
45// For migration tests, we make sure that the default disk size is twice the data volume size to
46// allow a second full data partition.
47pub const DEFAULT_DISK_SIZE: u64 = DEFAULT_DATA_VOLUME_SIZE + DEFAULT_REMAINING_VOLUME_SIZE;
48
49// We use a static key-bag so that the crypt instance can be shared across test executions safely.
50// These keys match the DATA_KEY and METADATA_KEY respectively, when wrapped with the "zxcrypt"
51// static key used by fshost.
52// Note this isn't used in the legacy crypto format.
53const KEY_BAG_CONTENTS: &'static str = "\
54{
55    \"version\":1,
56    \"keys\": {
57        \"0\":{
58            \"Aes128GcmSivWrapped\": [
59                \"7a7c6a718cfde7078f6edec5\",
60                \"7cc31b765c74db3191e269d2666267022639e758fe3370e8f36c166d888586454fd4de8aeb47aadd81c531b0a0a66f27\"
61            ]
62        },
63        \"1\":{
64            \"Aes128GcmSivWrapped\": [
65                \"b7d7f459cbee4cc536cc4324\",
66                \"9f6a5d894f526b61c5c091e5e02a7ff94d18e6ad36a0aa439c86081b726eca79e6b60bd86ee5d86a20b3df98f5265a99\"
67            ]
68        }
69    }
70}";
71
72pub const BLOB_CONTENTS: [u8; 1000] = [1; 1000];
73
74const DATA_KEY: Aes256Key = Aes256Key::create([
75    0xcf, 0x9e, 0x45, 0x2a, 0x22, 0xa5, 0x70, 0x31, 0x33, 0x3b, 0x4d, 0x6b, 0x6f, 0x78, 0x58, 0x29,
76    0x04, 0x79, 0xc7, 0xd6, 0xa9, 0x4b, 0xce, 0x82, 0x04, 0x56, 0x5e, 0x82, 0xfc, 0xe7, 0x37, 0xa8,
77]);
78
79const METADATA_KEY: Aes256Key = Aes256Key::create([
80    0x0f, 0x4d, 0xca, 0x6b, 0x35, 0x0e, 0x85, 0x6a, 0xb3, 0x8c, 0xdd, 0xe9, 0xda, 0x0e, 0xc8, 0x22,
81    0x8e, 0xea, 0xd8, 0x05, 0xc4, 0xc9, 0x0b, 0xa8, 0xd8, 0x85, 0x87, 0x50, 0x75, 0x40, 0x1c, 0x4c,
82]);
83
84const FVM_PART_INSTANCE_GUID: [u8; 16] = [3u8; 16];
85const DEFAULT_TEST_TYPE_GUID: [u8; 16] = [
86    0x66, 0x73, 0x68, 0x6F, 0x73, 0x74, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x73,
87];
88
89async fn create_hermetic_crypt_service(
90    data_key: Aes256Key,
91    metadata_key: Aes256Key,
92) -> RealmInstance {
93    let builder = RealmBuilder::new().await.unwrap();
94    let url = "#meta/fxfs-crypt.cm";
95    let crypt = builder.add_child("fxfs-crypt", url, ChildOptions::new().eager()).await.unwrap();
96    builder
97        .add_route(
98            Route::new()
99                .capability(Capability::protocol::<CryptMarker>())
100                .capability(Capability::protocol::<CryptManagementMarker>())
101                .from(&crypt)
102                .to(Ref::parent()),
103        )
104        .await
105        .unwrap();
106    builder
107        .add_route(
108            Route::new()
109                .capability(Capability::protocol::<flogger::LogSinkMarker>())
110                .from(Ref::parent())
111                .to(&crypt),
112        )
113        .await
114        .unwrap();
115    let realm = builder.build().await.expect("realm build failed");
116    let crypt_management =
117        realm.root.connect_to_protocol_at_exposed_dir::<CryptManagementMarker>().unwrap();
118    let wrapping_key_id_0 = [0; 16];
119    let mut wrapping_key_id_1 = [0; 16];
120    wrapping_key_id_1[0] = 1;
121    crypt_management
122        .add_wrapping_key(&wrapping_key_id_0, data_key.deref())
123        .await
124        .unwrap()
125        .expect("add_wrapping_key failed");
126    crypt_management
127        .add_wrapping_key(&wrapping_key_id_1, metadata_key.deref())
128        .await
129        .unwrap()
130        .expect("add_wrapping_key failed");
131    crypt_management
132        .set_active_key(KeyPurpose::Data, &wrapping_key_id_0)
133        .await
134        .unwrap()
135        .expect("set_active_key failed");
136    crypt_management
137        .set_active_key(KeyPurpose::Metadata, &wrapping_key_id_1)
138        .await
139        .unwrap()
140        .expect("set_active_key failed");
141    realm
142}
143
144/// Write a blob to the blob volume to ensure that on format, the blob volume does not get wiped.
145pub async fn write_test_blob(blob_creator: BlobCreatorProxy, data: &[u8]) -> Hash {
146    let hash = fuchsia_merkle::from_slice(data).root();
147    let compressed_data = Type1Blob::generate(&data, CompressionMode::Always);
148
149    let blob_writer_client_end = blob_creator
150        .create(&hash.into(), false)
151        .await
152        .expect("transport error on create")
153        .expect("failed to create blob");
154
155    let writer = blob_writer_client_end.into_proxy();
156    let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
157        .await
158        .expect("failed to create BlobWriter");
159    blob_writer.write(&compressed_data).await.unwrap();
160    hash
161}
162
163pub enum Disk {
164    Prebuilt(zx::Vmo, Option<[u8; 16]>),
165    Builder(DiskBuilder),
166}
167
168impl Disk {
169    pub async fn into_vmo_and_type_guid(self) -> (zx::Vmo, Option<[u8; 16]>) {
170        match self {
171            Disk::Prebuilt(vmo, guid) => (vmo, guid),
172            Disk::Builder(builder) => builder.build().await,
173        }
174    }
175
176    pub fn builder(&mut self) -> &mut DiskBuilder {
177        match self {
178            Disk::Prebuilt(..) => panic!("attempted to get builder for prebuilt disk"),
179            Disk::Builder(builder) => builder,
180        }
181    }
182}
183
184#[derive(Debug, Default)]
185pub struct DataSpec {
186    pub format: Option<&'static str>,
187    pub zxcrypt: bool,
188}
189
190#[derive(Debug)]
191pub struct VolumesSpec {
192    pub fxfs_blob: bool,
193    pub create_data_partition: bool,
194}
195
196enum FxfsType {
197    Fxfs(Box<dyn BlockConnector>),
198    FxBlob(ServingMultiVolumeFilesystem, RealmInstance),
199}
200
201#[derive(Debug)]
202pub struct DiskBuilder {
203    size: u64,
204    // Overrides all other options.  The disk will be unformatted.
205    uninitialized: bool,
206    blob_hash: Option<Hash>,
207    data_volume_size: u64,
208    data_spec: DataSpec,
209    volumes_spec: VolumesSpec,
210    // Only used if `format` is Some.
211    corrupt_data: bool,
212    gpt: bool,
213    extra_volumes: Vec<&'static str>,
214    // Note: fvm also means fxfs acting as the volume manager when using fxblob.
215    format_volume_manager: bool,
216    legacy_data_label: bool,
217    // Only used if 'fs_switch' set.
218    fs_switch: Option<String>,
219    // The type guid of the ramdisk when it's created for the test fshost.
220    type_guid: Option<[u8; 16]>,
221}
222
223impl DiskBuilder {
224    pub fn uninitialized() -> DiskBuilder {
225        Self { uninitialized: true, type_guid: None, ..Self::new() }
226    }
227
228    pub fn new() -> DiskBuilder {
229        DiskBuilder {
230            size: DEFAULT_DISK_SIZE,
231            uninitialized: false,
232            blob_hash: None,
233            data_volume_size: DEFAULT_DATA_VOLUME_SIZE,
234            data_spec: DataSpec { format: None, zxcrypt: false },
235            volumes_spec: VolumesSpec { fxfs_blob: false, create_data_partition: true },
236            corrupt_data: false,
237            gpt: false,
238            extra_volumes: Vec::new(),
239            format_volume_manager: true,
240            legacy_data_label: false,
241            fs_switch: None,
242            type_guid: Some(DEFAULT_TEST_TYPE_GUID),
243        }
244    }
245
246    pub fn set_uninitialized(&mut self) -> &mut Self {
247        self.uninitialized = true;
248        self
249    }
250
251    pub fn size(&mut self, size: u64) -> &mut Self {
252        self.size = size;
253        self
254    }
255
256    pub fn data_volume_size(&mut self, data_volume_size: u64) -> &mut Self {
257        assert_eq!(
258            data_volume_size % FVM_SLICE_SIZE,
259            0,
260            "data_volume_size {} needs to be a multiple of fvm slice size {}",
261            data_volume_size,
262            FVM_SLICE_SIZE
263        );
264        self.data_volume_size = data_volume_size;
265        // Increase the size of the disk if required. NB: We don't decrease the size of the disk
266        // because some tests set a lower initial size and expect to be able to resize to a larger
267        // one.
268        self.size = self.size.max(self.data_volume_size + DEFAULT_REMAINING_VOLUME_SIZE);
269        self
270    }
271
272    pub fn format_volumes(&mut self, volumes_spec: VolumesSpec) -> &mut Self {
273        self.volumes_spec = volumes_spec;
274        self
275    }
276
277    pub fn format_data(&mut self, data_spec: DataSpec) -> &mut Self {
278        log::info!(data_spec:?; "formatting data volume");
279        if !self.volumes_spec.fxfs_blob {
280            assert!(self.format_volume_manager);
281        } else {
282            if let Some(format) = data_spec.format {
283                assert_eq!(format, "fxfs");
284            }
285        }
286        self.data_spec = data_spec;
287        self
288    }
289
290    pub fn set_fs_switch(&mut self, content: &str) -> &mut Self {
291        self.fs_switch = Some(content.to_string());
292        self
293    }
294
295    pub fn corrupt_data(&mut self) -> &mut Self {
296        self.corrupt_data = true;
297        self
298    }
299
300    pub fn with_gpt(&mut self) -> &mut Self {
301        self.gpt = true;
302        // The system partition matcher expects the type guid to be either None or completely zero,
303        // so if we are formatting with gpt we clear any type guid.
304        self.type_guid = None;
305        self
306    }
307
308    pub fn with_extra_volume(&mut self, volume_name: &'static str) -> &mut Self {
309        self.extra_volumes.push(volume_name);
310        self
311    }
312
313    pub fn with_unformatted_volume_manager(&mut self) -> &mut Self {
314        assert!(self.data_spec.format.is_none());
315        self.format_volume_manager = false;
316        self
317    }
318
319    pub fn with_legacy_data_label(&mut self) -> &mut Self {
320        self.legacy_data_label = true;
321        self
322    }
323
324    pub async fn build(mut self) -> (zx::Vmo, Option<[u8; 16]>) {
325        log::info!("building disk: {:?}", self);
326        if self.data_spec.format == Some("f2fs") {
327            assert!(self.data_volume_size >= DEFAULT_F2FS_MIN_BYTES);
328        }
329
330        let vmo = zx::Vmo::create(self.size).unwrap();
331
332        if self.uninitialized {
333            return (vmo, self.type_guid);
334        }
335
336        let server = Arc::new(FakeServer::from_vmo(
337            TEST_DISK_BLOCK_SIZE,
338            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
339        ));
340
341        if self.gpt {
342            // Format the disk with gpt, with a single empty partition named "fvm".
343            let client = Arc::new(RemoteBlockClient::new(server.block_proxy()).await.unwrap());
344            let _ = gpt::Gpt::format(
345                client,
346                vec![gpt::PartitionInfo {
347                    label: "fvm".to_string(),
348                    type_guid: gpt::Guid::from_bytes(FVM_TYPE_GUID),
349                    instance_guid: gpt::Guid::from_bytes(FVM_PART_INSTANCE_GUID),
350                    start_block: 64,
351                    num_blocks: self.size / TEST_DISK_BLOCK_SIZE as u64 - 128,
352                    flags: 0,
353                }],
354            )
355            .await
356            .expect("gpt format failed");
357        }
358
359        if !self.format_volume_manager {
360            return (vmo, self.type_guid);
361        }
362
363        let mut gpt = None;
364        let connector: Box<dyn BlockConnector> = if self.gpt {
365            // Format the volume manager in the gpt partition named "fvm".
366            let partitions_dir = vfs::directory::immutable::simple();
367            let manager =
368                GptManager::new(server.block_proxy(), partitions_dir.clone()).await.unwrap();
369            let dir =
370                vfs::directory::serve(partitions_dir, fio::PERM_READABLE | fio::PERM_WRITABLE);
371            gpt = Some(manager);
372            Box::new(DirBasedBlockConnector::new(dir, String::from("part-000/volume")))
373        } else {
374            // Format the volume manager onto the disk directly.
375            Box::new(move |server_end| Ok(server.connect(server_end)))
376        };
377
378        if self.volumes_spec.fxfs_blob {
379            self.build_fxfs_as_volume_manager(connector).await;
380        } else {
381            self.build_fvm_as_volume_manager(connector).await;
382        }
383        if let Some(gpt) = gpt {
384            gpt.shutdown().await;
385        }
386        (vmo, self.type_guid)
387    }
388
389    async fn build_fxfs_as_volume_manager(&mut self, connector: Box<dyn BlockConnector>) {
390        let crypt_realm = create_hermetic_crypt_service(DATA_KEY, METADATA_KEY).await;
391        let mut fxfs = Filesystem::from_boxed_config(connector, Box::new(Fxfs::default()));
392        // Wipes the device
393        fxfs.format().await.expect("format failed");
394        let mut fs = fxfs.serve_multi_volume().await.expect("serve_multi_volume failed");
395        let blob_volume = fs
396            .create_volume(
397                "blob",
398                CreateOptions::default(),
399                MountOptions { as_blob: Some(true), ..MountOptions::default() },
400            )
401            .await
402            .expect("failed to create blob volume");
403        let blob_creator = connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
404            blob_volume.exposed_dir(),
405        )
406        .expect("failed to connect to the Blob service");
407        self.blob_hash = Some(write_test_blob(blob_creator, &BLOB_CONTENTS).await);
408
409        for volume in &self.extra_volumes {
410            fs.create_volume(volume, CreateOptions::default(), MountOptions::default())
411                .await
412                .expect("failed to make extra fxfs volume");
413        }
414
415        if self.data_spec.format.is_some() {
416            self.init_data_fxfs(FxfsType::FxBlob(fs, crypt_realm)).await;
417        } else {
418            fs.shutdown().await.expect("shutdown failed");
419        }
420    }
421
422    async fn build_fvm_as_volume_manager(&mut self, connector: Box<dyn BlockConnector>) {
423        let block_device = connector.connect_block().unwrap().into_proxy();
424        fasync::unblock(move || format_for_fvm(&block_device, FVM_SLICE_SIZE as usize))
425            .await
426            .unwrap();
427        let mut fvm_fs = Filesystem::from_boxed_config(connector, Box::new(Fvm::dynamic_child()));
428        let mut fvm = fvm_fs.serve_multi_volume().await.unwrap();
429
430        {
431            let blob_volume = fvm
432                .create_volume(
433                    "blobfs",
434                    CreateOptions {
435                        type_guid: Some(BLOBFS_TYPE_GUID),
436                        guid: Some(Uuid::new_v4().into_bytes()),
437                        ..Default::default()
438                    },
439                    MountOptions {
440                        uri: Some(String::from("#meta/blobfs.cm")),
441                        ..Default::default()
442                    },
443                )
444                .await
445                .expect("failed to make fvm blobfs volume");
446            let blob_creator =
447                connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
448                    blob_volume.exposed_dir(),
449                )
450                .expect("failed to connect to the Blob service");
451            self.blob_hash = Some(write_test_blob(blob_creator, &BLOB_CONTENTS).await);
452        }
453        fvm.shutdown_volume("blobfs").await.unwrap();
454
455        if self.volumes_spec.create_data_partition {
456            let data_label = if self.legacy_data_label { "minfs" } else { "data" };
457
458            let _crypt_service;
459            let crypt = if self.data_spec.format != Some("fxfs") && self.data_spec.zxcrypt {
460                let (crypt, stream) = fidl::endpoints::create_request_stream();
461                _crypt_service = fasync::Task::spawn(zxcrypt_crypt::run_crypt_service(
462                    crypt_policy::Policy::Null,
463                    stream,
464                ));
465                Some(crypt)
466            } else {
467                None
468            };
469            let uri = match (&self.data_spec.format, self.corrupt_data) {
470                (None, _) => None,
471                (_, true) => None,
472                (Some("fxfs"), false) => None,
473                (Some("minfs"), false) => Some(String::from("#meta/minfs.cm")),
474                (Some("f2fs"), false) => Some(String::from("#meta/f2fs.cm")),
475                (Some(format), _) => panic!("unsupported data volume format '{}'", format),
476            };
477
478            let data_volume = fvm
479                .create_volume(
480                    data_label,
481                    CreateOptions {
482                        initial_size: Some(self.data_volume_size),
483                        type_guid: Some(DATA_TYPE_GUID),
484                        guid: Some(Uuid::new_v4().into_bytes()),
485                        ..Default::default()
486                    },
487                    MountOptions { crypt, uri, ..Default::default() },
488                )
489                .await
490                .unwrap();
491
492            if self.corrupt_data {
493                let volume_proxy = connect_to_protocol_at_dir_svc::<
494                    fidl_fuchsia_hardware_block_volume::VolumeMarker,
495                >(data_volume.exposed_dir())
496                .unwrap();
497                match self.data_spec.format {
498                    Some("fxfs") => self.write_magic(volume_proxy, FXFS_MAGIC, 0).await,
499                    Some("minfs") => self.write_magic(volume_proxy, MINFS_MAGIC, 0).await,
500                    Some("f2fs") => self.write_magic(volume_proxy, F2FS_MAGIC, 1024).await,
501                    _ => (),
502                }
503            } else if self.data_spec.format == Some("fxfs") {
504                let dir = fuchsia_fs::directory::clone(data_volume.exposed_dir()).unwrap();
505                self.init_data_fxfs(FxfsType::Fxfs(Box::new(DirBasedBlockConnector::new(
506                    dir,
507                    String::from("svc/fuchsia.hardware.block.volume.Volume"),
508                ))))
509                .await
510            } else if self.data_spec.format.is_some() {
511                self.write_test_data(data_volume.root()).await;
512                fvm.shutdown_volume(data_label).await.unwrap();
513            }
514        }
515
516        for volume in &self.extra_volumes {
517            fvm.create_volume(
518                volume,
519                CreateOptions {
520                    type_guid: Some(DATA_TYPE_GUID),
521                    guid: Some(Uuid::new_v4().into_bytes()),
522                    ..Default::default()
523                },
524                MountOptions::default(),
525            )
526            .await
527            .expect("failed to make extra fvm volume");
528        }
529
530        fvm.shutdown().await.expect("fvm shutdown failed");
531    }
532
533    async fn init_data_fxfs(&self, fxfs: FxfsType) {
534        let mut fxblob = false;
535        let (mut fs, crypt_realm) = match fxfs {
536            FxfsType::Fxfs(connector) => {
537                let crypt_realm = create_hermetic_crypt_service(DATA_KEY, METADATA_KEY).await;
538                let mut fxfs =
539                    Filesystem::from_boxed_config(connector, Box::new(Fxfs::dynamic_child()));
540                fxfs.format().await.expect("format failed");
541                (fxfs.serve_multi_volume().await.expect("serve_multi_volume failed"), crypt_realm)
542            }
543            FxfsType::FxBlob(fs, crypt_realm) => {
544                fxblob = true;
545                (fs, crypt_realm)
546            }
547        };
548
549        let vol = {
550            let vol = fs
551                .create_volume("unencrypted", CreateOptions::default(), MountOptions::default())
552                .await
553                .expect("create_volume failed");
554            let keys_dir = fuchsia_fs::directory::create_directory(
555                vol.root(),
556                "keys",
557                fio::PERM_READABLE | fio::PERM_WRITABLE,
558            )
559            .await
560            .unwrap();
561            let keys_file = fuchsia_fs::directory::open_file(
562                &keys_dir,
563                "fxfs-data",
564                fio::Flags::FLAG_MAYBE_CREATE
565                    | fio::Flags::PROTOCOL_FILE
566                    | fio::PERM_READABLE
567                    | fio::PERM_WRITABLE,
568            )
569            .await
570            .unwrap();
571            let mut key_bag = KEY_BAG_CONTENTS.as_bytes();
572            if self.corrupt_data && fxblob {
573                key_bag = &BLOB_CONTENTS;
574            }
575            fuchsia_fs::file::write(&keys_file, key_bag).await.unwrap();
576            fuchsia_fs::file::close(keys_file).await.unwrap();
577            fuchsia_fs::directory::close(keys_dir).await.unwrap();
578
579            let crypt_service = Some(
580                crypt_realm
581                    .root
582                    .connect_to_protocol_at_exposed_dir::<CryptMarker>()
583                    .expect("Unable to connect to Crypt service")
584                    .into_channel()
585                    .unwrap()
586                    .into_zx_channel()
587                    .into(),
588            );
589            fs.create_volume(
590                "data",
591                CreateOptions::default(),
592                MountOptions { crypt: crypt_service, ..MountOptions::default() },
593            )
594            .await
595            .expect("create_volume failed")
596        };
597        self.write_test_data(&vol.root()).await;
598        fs.shutdown().await.expect("shutdown failed");
599    }
600
601    /// Create a small set of known files to test for presence. The test tree is
602    ///  root
603    ///   |- .testdata (file, empty)
604    ///   |- ssh (directory, non-empty)
605    ///   |   |- authorized_keys (file, non-empty)
606    ///   |   |- config (directory, empty)
607    ///   |- problems (directory, empty (no problems))
608    async fn write_test_data(&self, root: &fio::DirectoryProxy) {
609        fuchsia_fs::directory::open_file(
610            root,
611            ".testdata",
612            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE,
613        )
614        .await
615        .unwrap();
616
617        let ssh_dir = fuchsia_fs::directory::create_directory(
618            root,
619            "ssh",
620            fio::PERM_READABLE | fio::PERM_WRITABLE,
621        )
622        .await
623        .unwrap();
624        let authorized_keys = fuchsia_fs::directory::open_file(
625            &ssh_dir,
626            "authorized_keys",
627            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
628        )
629        .await
630        .unwrap();
631        fuchsia_fs::file::write(&authorized_keys, "public key!").await.unwrap();
632        fuchsia_fs::directory::create_directory(&ssh_dir, "config", fio::PERM_READABLE)
633            .await
634            .unwrap();
635
636        fuchsia_fs::directory::create_directory(&root, "problems", fio::PERM_READABLE)
637            .await
638            .unwrap();
639
640        if let Some(content) = &self.fs_switch {
641            let fs_switch = fuchsia_fs::directory::open_file(
642                &root,
643                "fs_switch",
644                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
645            )
646            .await
647            .unwrap();
648            fuchsia_fs::file::write(&fs_switch, content).await.unwrap();
649        }
650    }
651
652    async fn write_magic<const N: usize>(
653        &self,
654        volume_proxy: fidl_fuchsia_hardware_block_volume::VolumeProxy,
655        value: [u8; N],
656        offset: u64,
657    ) {
658        let client = block_client::RemoteBlockClient::new(volume_proxy)
659            .await
660            .expect("Failed to create client");
661        let block_size = client.block_size() as usize;
662        assert!(value.len() <= block_size);
663        let mut data = vec![0xffu8; block_size];
664        data[..value.len()].copy_from_slice(&value);
665        let buffer = block_client::BufferSlice::Memory(&data[..]);
666        client.write_at(buffer, offset).await.expect("write failed");
667    }
668
669    /// Create a vmo artifact with the format of a compressed zbi boot item containing this
670    /// filesystem.
671    pub(crate) async fn build_as_zbi_ramdisk(self) -> zx::Vmo {
672        /// The following types and constants are defined in
673        /// sdk/lib/zbi-format/include/lib/zbi-format/zbi.h.
674        const ZBI_TYPE_STORAGE_RAMDISK: u32 = 0x4b534452;
675        const ZBI_FLAGS_VERSION: u32 = 0x00010000;
676        const ZBI_ITEM_MAGIC: u32 = 0xb5781729;
677        const ZBI_FLAGS_STORAGE_COMPRESSED: u32 = 0x00000001;
678
679        #[repr(C)]
680        #[derive(IntoBytes, Immutable)]
681        struct ZbiHeader {
682            type_: u32,
683            length: u32,
684            extra: u32,
685            flags: u32,
686            _reserved0: u32,
687            _reserved1: u32,
688            magic: u32,
689            _crc32: u32,
690        }
691
692        let (ramdisk_vmo, _) = self.build().await;
693        let extra = ramdisk_vmo.get_size().unwrap() as u32;
694        let mut decompressed_buf = vec![0u8; extra as usize];
695        ramdisk_vmo.read(&mut decompressed_buf, 0).unwrap();
696        let compressed_buf = zstd::encode_all(decompressed_buf.as_slice(), 0).unwrap();
697        let length = compressed_buf.len() as u32;
698
699        let header = ZbiHeader {
700            type_: ZBI_TYPE_STORAGE_RAMDISK,
701            length,
702            extra,
703            flags: ZBI_FLAGS_VERSION | ZBI_FLAGS_STORAGE_COMPRESSED,
704            _reserved0: 0,
705            _reserved1: 0,
706            magic: ZBI_ITEM_MAGIC,
707            _crc32: 0,
708        };
709
710        let header_size = std::mem::size_of::<ZbiHeader>() as u64;
711        let zbi_vmo = zx::Vmo::create(header_size + length as u64).unwrap();
712        zbi_vmo.write(header.as_bytes(), 0).unwrap();
713        zbi_vmo.write(&compressed_buf, header_size).unwrap();
714
715        zbi_vmo
716    }
717}