pub mod partition;
use anyhow::{anyhow, Context, Error};
use fidl::endpoints::{Proxy, ServerEnd};
use fidl_fuchsia_hardware_power_statecontrol::{AdminMarker, RebootReason};
use fidl_fuchsia_paver::{
BootManagerMarker, Configuration, DynamicDataSinkProxy, PaverMarker, PaverProxy,
};
use fidl_fuchsia_sysinfo::SysInfoMarker;
use fuchsia_component::client;
use partition::Partition;
use recovery_util_block::BlockDevice;
use std::sync::Mutex;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BootloaderType {
Efi,
Coreboot,
}
#[derive(Clone, Debug, PartialEq)]
pub struct InstallationPaths {
pub install_source: Option<BlockDevice>,
pub install_target: Option<BlockDevice>,
pub bootloader_type: Option<BootloaderType>,
pub install_destinations: Vec<BlockDevice>,
pub available_disks: Vec<BlockDevice>,
}
impl InstallationPaths {
pub fn new() -> InstallationPaths {
InstallationPaths {
install_source: None,
install_target: None,
bootloader_type: None,
install_destinations: Vec::new(),
available_disks: Vec::new(),
}
}
}
pub async fn find_install_source(
block_devices: &Vec<BlockDevice>,
bootloader: BootloaderType,
) -> Result<&BlockDevice, Error> {
let mut candidate = Err(anyhow!("Could not find the installer disk. Is it plugged in?"));
for device in block_devices.iter().filter(|d| d.is_disk()) {
let partitions = Partition::get_partitions(device, block_devices, bootloader).await?;
if !partitions.is_empty() {
if candidate.is_err() {
candidate = Ok(device);
} else {
return Err(anyhow!(
"Please check you only have one installation disk plugged in!"
));
}
}
}
candidate
}
fn paver_connect() -> Result<(PaverProxy, DynamicDataSinkProxy), Error> {
let (data_sink_chan, data_remote) = zx::Channel::create();
let paver: PaverProxy =
client::connect_to_protocol::<PaverMarker>().context("Could not connect to paver")?;
paver.find_partition_table_manager(ServerEnd::from(data_remote))?;
let data_sink =
DynamicDataSinkProxy::from_channel(fidl::AsyncChannel::from_channel(data_sink_chan));
Ok((paver, data_sink))
}
pub async fn get_bootloader_type() -> Result<BootloaderType, Error> {
let proxy = fuchsia_component::client::connect_to_protocol::<SysInfoMarker>()
.context("Could not connect to 'fuchsia.sysinfo.SysInfo' service")?;
let (status, bootloader) =
proxy.get_bootloader_vendor().await.context("Getting bootloader vendor")?;
if let Some(bootloader) = bootloader {
tracing::info!("Bootloader vendor = {}", bootloader);
if bootloader == "coreboot" {
Ok(BootloaderType::Coreboot)
} else {
Ok(BootloaderType::Efi)
}
} else {
Err(Error::new(zx::Status::from_raw(status)))
}
}
pub async fn restart() {
let proxy = fuchsia_component::client::connect_to_protocol::<AdminMarker>()
.expect("Could not connect to 'fuchsia.hardware.power.statecontrol.Admin' service");
proxy
.reboot(RebootReason::UserRequest)
.await
.expect("Failed to reboot")
.expect("Failed to reboot");
}
async fn set_active_configuration(paver: &PaverProxy) -> Result<(), Error> {
let (boot_manager, server) = fidl::endpoints::create_proxy::<BootManagerMarker>();
paver.find_boot_manager(server).context("Could not find boot manager")?;
zx::Status::ok(
boot_manager
.set_configuration_active(Configuration::A)
.await
.context("Sending set configuration active")?,
)
.context("Setting active configuration")?;
zx::Status::ok(boot_manager.flush().await.context("Sending boot manager flush")?)
.context("Flushing active configuration")
}
pub async fn do_install<F>(
installation_paths: InstallationPaths,
progress_callback: &F,
) -> Result<(), Error>
where
F: Send + Sync + Fn(String),
{
if installation_paths.install_target.is_some() {
tracing::warn!("Ignoring install target; this will be removed in the future.");
}
let install_source =
installation_paths.install_source.ok_or_else(|| anyhow!("No installation source?"))?;
let bootloader_type = installation_paths.bootloader_type.unwrap();
let (paver, data_sink) = paver_connect().context("Could not contact paver")?;
tracing::info!("Initializing Fuchsia partition tables...");
progress_callback(String::from("Initializing Fuchsia partition tables..."));
data_sink.initialize_partition_tables().await?;
tracing::info!("Getting source partitions");
progress_callback(String::from("Getting source partitions"));
let to_install = Partition::get_partitions(
&install_source,
&installation_paths.available_disks,
bootloader_type,
)
.await
.context("Getting source partitions")?;
let num_partitions = to_install.len();
let mut current_partition = 1;
for part in to_install {
progress_callback(String::from(format!(
"paving partition {} of {}",
current_partition, num_partitions
)));
let prev_percent = Mutex::new(0 as i64);
let pave_progress_callback = |data_read, data_total| {
if data_total == 0 {
return;
}
let cur_percent: i64 =
unsafe { (((data_read as f64) / (data_total as f64)) * 100.0).to_int_unchecked() };
let mut prev = prev_percent.lock().unwrap();
if cur_percent == *prev {
return;
}
*prev = cur_percent;
progress_callback(String::from(format!(
"paving partition {} of {}: {}%",
current_partition, num_partitions, cur_percent
)));
};
tracing::info!("Paving partition: {:?}", part);
part.pave(&data_sink, &pave_progress_callback).await?;
if part.is_ab() {
tracing::info!("Paving partition: {:?} [-B]", part);
part.pave_b(&data_sink).await?
}
current_partition += 1;
}
progress_callback(String::from("Flushing Partitions"));
zx::Status::ok(data_sink.flush().await.context("Sending flush")?)
.context("Flushing partitions")?;
progress_callback(String::from("Setting active configuration for the new system"));
set_active_configuration(&paver)
.await
.context("Setting active configuration for the new system")?;
Ok(())
}