1pub mod partition;
12
13use anyhow::{Context, Error, anyhow};
14use fidl::endpoints::{Proxy, ServerEnd};
15use fidl_fuchsia_hardware_power_statecontrol::{
16 AdminMarker, ShutdownAction, ShutdownOptions, ShutdownReason,
17};
18use fidl_fuchsia_paver::{
19 BootManagerMarker, Configuration, DynamicDataSinkProxy, PaverMarker, PaverProxy,
20};
21use fidl_fuchsia_sysinfo::SysInfoMarker;
22use fuchsia_component::client;
23
24use fuchsia_sync::Mutex;
25use partition::Partition;
26use recovery_util_block::BlockDevice;
27
28#[derive(Clone, Copy, Debug, PartialEq)]
29pub enum BootloaderType {
30 Efi,
31 Coreboot,
32}
33
34#[derive(Clone, Debug, PartialEq)]
35pub struct InstallationPaths {
36 pub install_source: Option<BlockDevice>,
37 pub install_target: Option<BlockDevice>,
38 pub bootloader_type: Option<BootloaderType>,
39 pub install_destinations: Vec<BlockDevice>,
40 pub available_disks: Vec<BlockDevice>,
41}
42
43impl InstallationPaths {
44 pub fn new() -> InstallationPaths {
45 InstallationPaths {
46 install_source: None,
47 install_target: None,
48 bootloader_type: None,
49 install_destinations: Vec::new(),
50 available_disks: Vec::new(),
51 }
52 }
53}
54
55pub async fn find_install_source(
56 block_devices: &Vec<BlockDevice>,
57 bootloader: BootloaderType,
58) -> Result<&BlockDevice, Error> {
59 let mut candidate = Err(anyhow!("Could not find the installer disk. Is it plugged in?"));
60 for device in block_devices.iter().filter(|d| d.is_disk()) {
61 let partitions = Partition::get_partitions(device, block_devices, bootloader).await?;
64 if !partitions.is_empty() {
65 if candidate.is_err() {
66 candidate = Ok(device);
67 } else {
68 return Err(anyhow!(
69 "Please check you only have one installation disk plugged in!"
70 ));
71 }
72 }
73 }
74 candidate
75}
76
77fn paver_connect() -> Result<(PaverProxy, DynamicDataSinkProxy), Error> {
78 let (data_sink_chan, data_remote) = zx::Channel::create();
79 let paver: PaverProxy =
80 client::connect_to_protocol::<PaverMarker>().context("Could not connect to paver")?;
81 paver.find_partition_table_manager(ServerEnd::from(data_remote))?;
82
83 let data_sink =
84 DynamicDataSinkProxy::from_channel(fidl::AsyncChannel::from_channel(data_sink_chan));
85 Ok((paver, data_sink))
86}
87
88pub async fn get_bootloader_type() -> Result<BootloaderType, Error> {
89 let proxy = fuchsia_component::client::connect_to_protocol::<SysInfoMarker>()
90 .context("Could not connect to 'fuchsia.sysinfo.SysInfo' service")?;
91 let (status, bootloader) =
92 proxy.get_bootloader_vendor().await.context("Getting bootloader vendor")?;
93 if let Some(bootloader) = bootloader {
94 log::info!("Bootloader vendor = {}", bootloader);
95 if bootloader == "coreboot" {
96 Ok(BootloaderType::Coreboot)
97 } else {
98 Ok(BootloaderType::Efi)
103 }
104 } else {
105 Err(Error::new(zx::Status::from_raw(status)))
106 }
107}
108
109pub async fn restart() {
111 let proxy = fuchsia_component::client::connect_to_protocol::<AdminMarker>()
112 .expect("Could not connect to 'fuchsia.hardware.power.statecontrol.Admin' service");
113
114 proxy
115 .shutdown(&ShutdownOptions {
116 action: Some(ShutdownAction::Reboot),
117 reasons: Some(vec![ShutdownReason::DeveloperRequest]),
118 ..Default::default()
119 })
120 .await
121 .expect("Failed to reboot")
122 .expect("Failed to reboot");
123}
124
125async fn set_active_configuration(paver: &PaverProxy) -> Result<(), Error> {
128 let (boot_manager, server) = fidl::endpoints::create_proxy::<BootManagerMarker>();
129
130 paver.find_boot_manager(server).context("Could not find boot manager")?;
131
132 zx::Status::ok(
133 boot_manager
134 .set_configuration_active(Configuration::A)
135 .await
136 .context("Sending set configuration active")?,
137 )
138 .context("Setting active configuration")?;
139
140 zx::Status::ok(boot_manager.flush().await.context("Sending boot manager flush")?)
141 .context("Flushing active configuration")
142}
143
144pub async fn do_install<F>(
145 installation_paths: InstallationPaths,
146 progress_callback: &F,
147) -> Result<(), Error>
148where
149 F: Send + Sync + Fn(String),
150{
151 if installation_paths.install_target.is_some() {
152 log::warn!("Ignoring install target; this will be removed in the future.");
153 }
154 let install_source =
155 installation_paths.install_source.ok_or_else(|| anyhow!("No installation source?"))?;
156 let bootloader_type = installation_paths.bootloader_type.unwrap();
157
158 let (paver, data_sink) = paver_connect().context("Could not contact paver")?;
159
160 log::info!("Initializing Fuchsia partition tables...");
161 progress_callback(String::from("Initializing Fuchsia partition tables..."));
162 data_sink.initialize_partition_tables().await?;
163
164 log::info!("Getting source partitions");
165 progress_callback(String::from("Getting source partitions"));
166 let to_install = Partition::get_partitions(
167 &install_source,
168 &installation_paths.available_disks,
169 bootloader_type,
170 )
171 .await
172 .context("Getting source partitions")?;
173
174 let num_partitions = to_install.len();
175 let mut current_partition = 1;
176 for part in to_install {
177 progress_callback(String::from(format!(
178 "paving partition {} of {}",
179 current_partition, num_partitions
180 )));
181
182 let prev_percent = Mutex::new(0 as i64);
183
184 let pave_progress_callback = |data_read, data_total| {
185 if data_total == 0 {
186 return;
187 }
188 let cur_percent: i64 =
189 unsafe { (((data_read as f64) / (data_total as f64)) * 100.0).to_int_unchecked() };
190 let mut prev = prev_percent.lock();
191 if cur_percent == *prev {
192 return;
193 }
194 *prev = cur_percent;
195
196 progress_callback(String::from(format!(
197 "paving partition {} of {}: {}%",
198 current_partition, num_partitions, cur_percent
199 )));
200 };
201
202 log::info!("Paving partition: {:?}", part);
203 part.pave(&data_sink, &pave_progress_callback).await?;
204 if part.is_ab() {
205 log::info!("Paving partition: {:?} [-B]", part);
206 part.pave_b(&data_sink).await?
207 }
208
209 current_partition += 1;
210 }
211
212 progress_callback(String::from("Flushing Partitions"));
213 zx::Status::ok(data_sink.flush().await.context("Sending flush")?)
214 .context("Flushing partitions")?;
215
216 progress_callback(String::from("Setting active configuration for the new system"));
217 set_active_configuration(&paver)
218 .await
219 .context("Setting active configuration for the new system")?;
220
221 Ok(())
222}