blob: 87b5d34391c8cb485040dc8a54958a420a578e13 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Functionality for extracting a ramdisk image from the zircon boot items. This ramdisk contains
//! an fvm with blobfs and data volumes, and is intended to be used in conjunction with the
//! ramdisk_image option, in run modes where we need to operate on the real disk and can't run
//! filesystems off it, such as recovery.
use {
anyhow::{ensure, Context, Error},
fuchsia_component::client::connect_to_protocol,
fuchsia_zircon as zx,
zerocopy::{FromBytes, FromZeros, NoCell},
};
/// The following types and constants are defined in sdk/lib/zbi-format/include/lib/zbi-format/zbi.h.
const ZBI_TYPE_STORAGE_RAMDISK: u32 = 0x4b534452;
const ZBI_FLAGS_VERSION: u32 = 0x00010000;
const ZBI_ITEM_MAGIC: u32 = 0xb5781729;
const ZBI_FLAGS_STORAGE_COMPRESSED: u32 = 0x00000001;
#[repr(C)]
#[derive(FromZeros, FromBytes, NoCell)]
struct ZbiHeader {
type_: u32,
length: u32,
extra: u32,
flags: u32,
_reserved0: u32,
_reserved1: u32,
magic: u32,
_crc32: u32,
}
async fn create_ramdisk(zbi_vmo: zx::Vmo) -> Result<String, Error> {
let mut header_buf = [0u8; std::mem::size_of::<ZbiHeader>()];
zbi_vmo.read(&mut header_buf, 0).context("reading zbi item header")?;
// Expect is fine here - we made the buffer ourselves to the exact size of the header so
// something is very wrong if we trip this.
let header = ZbiHeader::read_from(header_buf.as_slice()).expect("buffer was the wrong size");
ensure!(
header.flags & ZBI_FLAGS_VERSION != 0,
"invalid ZBI_TYPE_STORAGE_RAMDISK item header: flags"
);
ensure!(header.magic == ZBI_ITEM_MAGIC, "invalid ZBI_TYPE_STORAGE_RAMDISK item header: magic");
ensure!(
header.type_ == ZBI_TYPE_STORAGE_RAMDISK,
"invalid ZBI_TYPE_STORAGE_RAMDISK item header: type"
);
// TODO(https://fxbug.dev/42109921): The old code ignored uncompressed items too, and silently. Really
// the protocol should be cleaned up so the VMO arrives without the header in it and then it
// could just be used here directly if uncompressed (or maybe bootsvc deals with decompression
// in the first place so the uncompressed VMO is always what we get).
ensure!(
header.flags & ZBI_FLAGS_STORAGE_COMPRESSED != 0,
"ignoring uncompressed RAMDISK item in ZBI"
);
let ramdisk_vmo = zx::Vmo::create(header.extra as u64).context("making output vmo")?;
let mut compressed_buf = vec![0u8; header.length as usize];
zbi_vmo
.read(&mut compressed_buf, std::mem::size_of::<ZbiHeader>() as u64)
.context("reading compressed ramdisk")?;
let decompressed_buf =
zstd::decode_all(compressed_buf.as_slice()).context("zstd decompression failed")?;
ramdisk_vmo.write(&decompressed_buf, 0).context("writing decompressed contents to vmo")?;
let ramdisk = ramdevice_client::RamdiskClientBuilder::new_with_vmo(ramdisk_vmo, None)
.build()
.await
.context("building ramdisk from vmo")?;
let topological_path = ramdisk
.as_controller()
.ok_or_else(|| anyhow::anyhow!("ramdisk instance missing controller"))?
.get_topological_path()
.await
.context("get_topological_path (fidl failure)")?
.map_err(zx::Status::from_raw)
.context("get_topological_path returned an error")?;
tracing::info!(%topological_path, "launched ramdisk filesystem");
// Ensure the boot image remains attached for the system lifetime.
ramdisk.forget().context("detaching/forgetting ramdisk client")?;
Ok(topological_path)
}
/// Set up a ramdisk provided by the boot items service as a vmo. If there is no vmo provided, None
/// is returned. If there is, the ramdisk is decoded and set up, and the topological path is
/// returned.
pub async fn set_up_ramdisk() -> Result<Option<String>, Error> {
let proxy = connect_to_protocol::<fidl_fuchsia_boot::ItemsMarker>()?;
let (maybe_vmo, _length) = proxy
.get(ZBI_TYPE_STORAGE_RAMDISK, 0)
.await
.context("boot items get failed (fidl failure)")?;
if let Some(vmo) = maybe_vmo {
Ok(Some(create_ramdisk(vmo).await?))
} else {
Ok(None)
}
}