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