blob: 75c445af2b8dcd70c9af79db20a7679f8056eb4e [file] [log] [blame]
// Copyright 2021 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.
mod block_wrapper;
mod fvm;
mod gpt;
mod pkg;
use {
crate::fvm::{get_partition_size, FvmRamdisk},
anyhow::{Context, Error},
fidl_fuchsia_boot::{ArgumentsMarker, BoolPair},
fidl_fuchsia_hardware_block_partition::PartitionMarker,
fidl_fuchsia_io as fio,
fuchsia_fs::directory::{WatchEvent, Watcher},
fuchsia_zircon as zx,
futures::prelude::*,
tracing::{error, info},
};
/// This GUID is used by the installer to identify partitions that contain
/// data that will be installed to disk. The `fx mkinstaller` tool generates
/// images containing partitions with this GUID.
static WORKSTATION_INSTALLER_GPT: [u8; 16] = [
0xce, 0x98, 0xce, 0x4d, 0x7e, 0xe7, 0xc1, 0x45, 0xa8, 0x63, 0xca, 0xf9, 0x2f, 0x13, 0x30, 0xc1,
];
async fn is_live_usb_enabled() -> Result<bool, Error> {
let proxy = fuchsia_component::client::connect_to_protocol::<ArgumentsMarker>()
.context("Connecting to service")?;
let bools = &[BoolPair { key: "boot.usb".to_owned(), defaultval: false }];
let result: Vec<bool> = proxy.get_bools(bools).await.context("Getting boot.usb bool value")?;
Ok(result.iter().all(|f| *f))
}
/// This function is intended for use as an argument to skip_while().
async fn is_sparse_fvm(
dev_class_block: &fio::DirectoryProxy,
filename: &str,
) -> Result<bool, Error> {
let proxy =
fuchsia_component::client::connect_to_named_protocol_at_dir_root::<PartitionMarker>(
dev_class_block,
filename,
)
.context("connecting to block device")?;
let (status, guid) = proxy.get_type_guid().await?;
zx::Status::ok(status).context("getting partition type")?;
let guid = guid.ok_or(anyhow::anyhow!("no guid!"))?;
Ok(guid.value == WORKSTATION_INSTALLER_GPT)
}
/// Waits for a sparse FVM to appear.
/// Returns the path to the partition with the sparse FVM once one is found.
async fn wait_for_sparse_fvm() -> Result<String, Error> {
let dir = fuchsia_fs::directory::open_in_namespace(
"/dev/class/block",
fuchsia_fs::OpenFlags::RIGHT_READABLE,
)
.context("opening block dir")?;
let mut watcher = Watcher::new(&dir).await.context("starting watch")?;
while let Some(message) = watcher.next().await {
let message = message.context("watcher returned an error")?;
if message.event != WatchEvent::ADD_FILE && message.event != WatchEvent::EXISTING {
continue;
}
let filename = message.filename.to_str().unwrap();
if filename == "." {
continue;
}
if is_sparse_fvm(&dir, filename).await.unwrap_or(false) {
return Ok(format!("/dev/class/block/{}", filename));
}
}
Err(anyhow::anyhow!("failed to find sparse fvm"))
}
async fn inner_main() -> Result<(), Error> {
// Find the sparse FVM partition.
let sparse_fvm_partition = wait_for_sparse_fvm().await?;
info!("using {} as sparse partition", sparse_fvm_partition);
// Cap the ramdisk at 1/4 of system ram, unless that's not big enough for the FVM, a misc
// partition and FVM_MINIMUM_PADDING bytes of extra room.
let physmem = zx::system_get_physmem();
let ramdisk_size = std::cmp::max(
physmem / 4,
get_partition_size(&sparse_fvm_partition).await.context("getting FVM size")? as u64
+ gpt::MISC_SIZE
+ gpt::FVM_MINIMUM_PADDING,
);
info!("using {} bytes for ramdisk", ramdisk_size);
let ramdisk = FvmRamdisk::new(ramdisk_size, sparse_fvm_partition)
.await
.context("creating FVM ramdisk")?;
// Write FVM to the ramdisk.
ramdisk.pave_fvm().await.context("paving FVM")?;
pkg::disable_updates().await.context("disabling updates")?;
Ok(())
}
#[fuchsia::main(logging_tags = ["live_usb.cm"])]
async fn main() {
let enable_live_usb = match is_live_usb_enabled().await {
Ok(val) => val,
Err(err) => {
error!(?err, "Failed to check boot arguments");
false
}
};
if !enable_live_usb {
info!("Not booting from a USB!");
return;
}
info!("Doing live USB boot!");
let error = inner_main().await;
if let Err(err) = error {
error!(?err, "Failed to do USB boot");
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::block_wrapper::WrappedBlockDevice,
::gpt::{
disk::LogicalBlockSize,
partition_types::{OperatingSystem, Type},
GptConfig,
},
ramdevice_client::{RamdiskClient, RamdiskClientBuilder},
std::collections::BTreeMap,
};
async fn create_ramdisk_with_partitions(uuids: Vec<&'static str>) -> RamdiskClient {
// 16MB
let ramdisk_size: u64 = 16 * 1024 * 1024;
let builder = RamdiskClientBuilder::new(512, ramdisk_size / 512);
let ramdisk = builder.build().await.expect("creating ramdisk succeeds");
let channel = ramdisk.open().await.expect("opening ramdisk succeeds");
let block_client = remote_block_device::RemoteBlockClientSync::new(channel)
.expect("creating remote block client succeeds");
let cache = remote_block_device::cache::Cache::new(block_client)
.expect("creating remote block client cache succeeds");
let wrapper = Box::new(WrappedBlockDevice::new(cache, 512));
let mut disk = GptConfig::new()
.writable(true)
.initialized(false)
.logical_block_size(LogicalBlockSize::Lb512)
.create_from_device(wrapper, None)
.expect("create gpt succeeds");
disk.update_partitions(BTreeMap::new()).expect("init disk ok");
for (i, uuid) in uuids.into_iter().enumerate() {
let partition_type = Type { guid: uuid, os: OperatingSystem::None };
disk.add_partition(&format!("part{}", i), 1024, partition_type, 0, None)
.expect("adding partition succeeds");
}
disk.write().expect("writing GPT succeeds");
let client_end = ramdisk.open_controller().expect("opening ramdisk OK");
let controller = client_end.into_proxy().unwrap();
controller
.rebind("gpt.cm")
.await
.expect("rebind request OK")
.map_err(zx::Status::from_raw)
.expect("rebind OK");
ramdisk
}
#[fuchsia::test]
async fn test_wait_for_sparse_fvm() {
let uuids = vec![
"1d75395d-f2c6-476b-a8b7-45cc1c97b476", // MISC - 001
"41d0e340-57e3-954e-8c1e-17ecac44cff5", // Real FVM - 002
"4dce98ce-e77e-45c1-a863-caf92f1330c1", // sparse FVM - 003
"00000000-0000-0000-0000-000000000000", // empty guid - 004
];
let _disk = create_ramdisk_with_partitions(uuids).await;
let path = wait_for_sparse_fvm().await.expect("found FVM");
// Don't just assert on the path, as other tests might use the devmgr and cause race
// conditions.
let proxy =
fuchsia_component::client::connect_to_protocol_at_path::<PartitionMarker>(&path)
.unwrap();
let (status, guid) = proxy.get_type_guid().await.expect("send get type guid");
zx::Status::ok(status).expect("get_type_guid ok");
assert_eq!(guid.unwrap().value, WORKSTATION_INSTALLER_GPT);
}
}