From c0fbd86e637208e4c347ba00866bf393dd2287ed Mon Sep 17 00:00:00 2001 From: Lleyton Gray Date: Tue, 31 Dec 2024 17:49:34 -0800 Subject: [PATCH 1/2] feat: show stage on progress bar --- Cargo.lock | 194 +++++++++++++++++++++++++++- Cargo.toml | 1 + src/backend/custom.rs | 6 +- src/{ => backend}/install.rs | 41 +++++- src/backend/mod.rs | 3 +- src/backend/postinstall/efi_stub.rs | 6 +- src/cfg.rs | 5 +- src/consts.rs | 2 +- src/main.rs | 16 ++- src/pages/destination.rs | 2 +- src/pages/installation.rs | 40 ++++-- src/util/macros.rs | 24 +--- 12 files changed, 291 insertions(+), 49 deletions(-) rename src/{ => backend}/install.rs (91%) diff --git a/Cargo.lock b/Cargo.lock index f2ac8a4..8b6becd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -158,6 +167,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytesize" version = "1.3.0" @@ -662,6 +677,22 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-test" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961fb6311645f46e2cdc2964a8bfae6743fd72315eaec181a71ae3eb2467113" +dependencies = [ + "futures-core", + "futures-executor", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "futures-util", + "pin-project", +] + [[package]] name = "futures-util" version = "0.3.31" @@ -1198,7 +1229,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -1244,6 +1275,27 @@ dependencies = [ "serde", ] +[[package]] +name = "ipc-channel" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8251fb7bcd9ccd3725ed8deae9fe7db8e586495c9eb5b0c52e6233e5e75ea" +dependencies = [ + "bincode", + "crossbeam-channel", + "fnv", + "futures", + "futures-test", + "lazy_static", + "libc", + "mio", + "rand 0.8.5", + "serde", + "tempfile", + "uuid", + "windows", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1473,6 +1525,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -1675,6 +1739,26 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -1699,6 +1783,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro-crate" version = "2.0.0" @@ -1797,6 +1890,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1812,6 +1926,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1857,6 +1980,7 @@ dependencies = [ "glob", "gnome-desktop", "gtk4", + "ipc-channel", "itertools 0.13.0", "libhelium", "lsblk", @@ -2342,7 +2466,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ - "rand", + "rand 0.4.6", "remove_dir_all", ] @@ -2653,6 +2777,7 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ + "getrandom", "serde", ] @@ -2777,6 +2902,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -2786,6 +2921,60 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2910,6 +3099,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] diff --git a/Cargo.toml b/Cargo.toml index 1dce66f..e5d6aed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ scopeguard = "1.2.0" tracing-test = "0.2.5" serde-systemd-unit = { path = "./serde-systemd-unit" } format-bytes = "0.3.0" +ipc-channel = { version = "0.19.0", features = ["async"] } [dependencies.os-detect] git = "https://github.com/FyraLabs/distinst" diff --git a/src/backend/custom.rs b/src/backend/custom.rs index 6a11be9..c9e3b64 100644 --- a/src/backend/custom.rs +++ b/src/backend/custom.rs @@ -2,6 +2,8 @@ use std::path::Component; use std::path::Path; use std::path::PathBuf; +use super::install::InstallationState; + #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct MountTarget { #[doc(hidden)] @@ -82,7 +84,7 @@ impl MountTargets { // 2. copy stuff // 3. funny setup_system() pub fn install_custom( - state: &crate::install::InstallationState, + state: &InstallationState, mounttags: &mut MountTargets, ) -> color_eyre::Result<()> { let destroot = Path::new("/mnt/custom"); @@ -96,7 +98,7 @@ pub fn install_custom( } }; - let copy_source = PathBuf::from(crate::install::InstallationState::determine_copy_source()); + let copy_source = PathBuf::from(InstallationState::determine_copy_source()); if copy_source.is_file() { // TODO: impl callback status progress super::mksys::unsquash_copy(©_source, destroot, |_, _| {})?; diff --git a/src/install.rs b/src/backend/install.rs similarity index 91% rename from src/install.rs rename to src/backend/install.rs index 429d86d..3525d05 100644 --- a/src/install.rs +++ b/src/backend/install.rs @@ -2,11 +2,16 @@ use color_eyre::eyre::bail; use color_eyre::eyre::eyre; use color_eyre::eyre::Context; use color_eyre::{Result, Section}; +use ipc_channel::ipc::IpcError; +use ipc_channel::ipc::IpcOneShotServer; +use ipc_channel::ipc::IpcSender; use serde::{Deserialize, Serialize}; use std::io::BufRead; use std::io::BufReader; use std::process::Command; use std::process::Stdio; +use std::sync::Mutex; +use std::sync::OnceLock; use std::{ io::Write, path::{Path, PathBuf}, @@ -23,7 +28,8 @@ use crate::{ stage, util, }; -#[allow(clippy::unsafe_derive_deserialize)] +pub static IPC_CHANNEL: OnceLock>> = OnceLock::new(); + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum InstallationType { @@ -60,14 +66,26 @@ impl Default for InstallationState { } } +/// IPC installation message for non-interactive mode +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub enum InstallationMessage { + Status(String), +} + impl InstallationState { // todo: move methods from installationstate to here! #[allow(clippy::unwrap_in_result)] - pub fn install_using_subprocess(&self) -> Result<()> { + pub fn install_using_subprocess( + &self, + sender: relm4::Sender, + ) -> Result<()> { let mut command = Command::new("pkexec"); command.arg(std::env::current_exe()?); command.arg("--non-interactive"); + let (server, channel_id) = IpcOneShotServer::new()?; + command.arg(channel_id); + if let Ok(value) = std::env::var("REPART_COPY_SOURCE") { command.arg(format!("REPART_COPY_SOURCE={value}")); } @@ -97,7 +115,6 @@ impl InstallationState { let tee_stderr = TeeReader::new(stderr_reader, &mut stderr_logs, false); command - // .arg(std::env::current_exe()?) .stdin(std::process::Stdio::piped()) .stdout(stdout_writer) .stderr(stderr_writer); @@ -123,6 +140,24 @@ impl InstallationState { .lines() .for_each(|line| eprintln!("| {}", line.unwrap())); }); + s.spawn(|| -> Result<()> { + let (receiver, _) = server.accept()?; + + loop { + match receiver.recv() { + Ok(msg) => sender.emit(msg), + Err(IpcError::Disconnected) => { + break; + } + Err(e) => { + tracing::error!("Failed to receive message from subprocess: {:?}", e); + break; + } + } + } + + Ok(()) + }); let result = res.wait_with_output(); drop(command); diff --git a/src/backend/mod.rs b/src/backend/mod.rs index e84fb44..b0b07c6 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,5 +1,6 @@ pub mod custom; +pub mod install; pub mod mksys; pub mod postinstall; pub mod repart_output; -pub mod repartcfg; \ No newline at end of file +pub mod repartcfg; diff --git a/src/backend/postinstall/efi_stub.rs b/src/backend/postinstall/efi_stub.rs index 2054524..e58155b 100644 --- a/src/backend/postinstall/efi_stub.rs +++ b/src/backend/postinstall/efi_stub.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use std::process::Command; use crate::{ - consts::{get_shim_path, OS_NAME}, + consts::{shim_path, OS_NAME}, util::fs::{get_whole_disk, partition_number}, }; @@ -35,7 +35,7 @@ impl PostInstallModule for EfiStub { disk = esp_disk, part = partition_number, label = OS_NAME, - shim_path = get_shim_path(), + shim_path = shim_path(), "Creating EFI boot entry" ); @@ -48,7 +48,7 @@ impl PostInstallModule for EfiStub { .arg("--label") .arg(OS_NAME) .arg("--loader") - .arg(get_shim_path()) + .arg(shim_path()) .status()?; if !status.success() { diff --git a/src/cfg.rs b/src/cfg.rs index 947a82c..954ace4 100644 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -5,6 +5,7 @@ use serde::Deserialize; use serde_valid::toml::FromTomlStr; use serde_valid::Validate; +use crate::backend::install::InstallationType; use crate::backend::postinstall::Module; const DEFAULT_CFG_PATH: &str = "/etc/readymade.toml"; @@ -12,7 +13,7 @@ const DEFAULT_CFG_PATH: &str = "/etc/readymade.toml"; #[derive(Deserialize, Validate, Default, Debug, Clone, PartialEq, Eq)] pub struct Install { #[validate(min_items = 1)] - pub allowed_installtypes: Vec, + pub allowed_installtypes: Vec, } #[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq)] @@ -93,7 +94,7 @@ mod tests { icon: "fedora-logo-icon".into(), }, install: Install { - allowed_installtypes: vec![crate::install::InstallationType::ChromebookInstall], + allowed_installtypes: vec![InstallationType::ChromebookInstall], }, postinstall: vec![ crate::backend::postinstall::grub2::GRUB2.into(), diff --git a/src/consts.rs b/src/consts.rs index eb24160..10cd54c 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -10,7 +10,7 @@ pub fn repart_dir() -> PathBuf { PathBuf::from(std::env::var("READYMADE_REPART_DIR").unwrap_or_else(|_| REPART_DIR.into())) } -pub const fn get_shim_path() -> &'static str { +pub const fn shim_path() -> &'static str { if cfg!(target_arch = "x86_64") { EFI_SHIM_X86_64 } else { diff --git a/src/main.rs b/src/main.rs index a2d8540..3e09572 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,15 +3,18 @@ mod backend; pub mod cfg; mod consts; mod disks; -mod install; mod pages; pub mod prelude; mod util; +use std::sync::Mutex; + use crate::prelude::*; +use backend::install::{InstallationState, InstallationType, IPC_CHANNEL}; +use color_eyre::eyre::ContextCompat; use color_eyre::Result; use gtk::glib::translate::FromGlibPtrNone; -use install::{InstallationState, InstallationType}; +use ipc_channel::ipc::IpcSender; use pages::installation::InstallationPageMsg; use relm4::{ Component, ComponentController, ComponentParts, ComponentSender, RelmApp, SharedState, @@ -200,10 +203,17 @@ impl SimpleComponent for AppModel { #[allow(clippy::missing_panics_doc)] fn main() -> Result<()> { let _guard = setup_hooks(); - if std::env::args().any(|arg| arg == "--non-interactive") { + if let Some((i, _)) = std::env::args().find_position(|arg| arg == "--non-interactive") { tracing::info!("Running in non-interactive mode"); // Get installation state from stdin json instead + let channel = IpcSender::connect( + std::env::args() + .nth(i + 1) + .context("No IPC channel ID passed")?, + )?; + + IPC_CHANNEL.set(Mutex::new(channel)).unwrap(); let install_state: InstallationState = serde_json::from_reader(std::io::stdin())?; return install_state.install(); diff --git a/src/pages/destination.rs b/src/pages/destination.rs index 1b2a9fa..e007bdd 100644 --- a/src/pages/destination.rs +++ b/src/pages/destination.rs @@ -131,7 +131,7 @@ impl SimpleComponent for DestinationPage { connect_clicked => DestinationPageMsg::Navigate(NavigationAction::GoTo( if let [x] = crate::CONFIG.read().install.allowed_installtypes[..] { #[allow(clippy::enum_glob_use)] - use crate::{install::InstallationType::*, Page::*}; + use crate::{backend::install::InstallationType::*, Page::*}; match x { ChromebookInstall | WholeDisk => Confirmation, DualBoot(_) => InstallDual, diff --git a/src/pages/installation.rs b/src/pages/installation.rs index 6b59f7c..1be5770 100644 --- a/src/pages/installation.rs +++ b/src/pages/installation.rs @@ -1,6 +1,8 @@ +use crate::backend::install::InstallationMessage; use crate::prelude::*; use crate::{NavigationAction, INSTALLATION_STATE}; use color_eyre::Result; +use relm4::tokio::sync::broadcast::Receiver; use relm4::{Component, ComponentParts, ComponentSender}; use std::time::Duration; @@ -73,11 +75,14 @@ pub enum InstallationPageMsg { Update, #[doc(hidden)] Throb, + #[doc(hidden)] + SubprocessMessage(InstallationMessage), } #[derive(Debug)] pub enum InstallationPageCommandMsg { FinishInstallation(Result<()>), + None, } #[derive(Debug)] @@ -188,17 +193,9 @@ impl Component for InstallationPage { // }, }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - - gtk::Label { - set_halign: gtk::Align::Start, - #[watch] - set_label: &*gettext("Installing base system...") - }, - - #[local_ref] - progress_bar -> gtk::ProgressBar {} + #[local_ref] + progress_bar -> gtk::ProgressBar { + set_show_text: true } } } @@ -211,6 +208,7 @@ impl Component for InstallationPage { ) -> ComponentParts { let model = Self::default(); let progress_bar = &model.progress_bar; + progress_bar.set_text(Some(&gettext("Installing base system..."))); let widgets = view_output!(); @@ -233,10 +231,24 @@ impl Component for InstallationPage { |_| {}, ), InstallationPageMsg::StartInstallation => { + let sender2 = sender.clone(); + let (s, r) = relm4::channel(); + sender.oneshot_command(async move { + r.forward(sender2.input_sender().clone(), |msg| { + InstallationPageMsg::SubprocessMessage(msg) + }) + .await; + + InstallationPageCommandMsg::None + }); + sender.spawn_oneshot_command(|| { let state = INSTALLATION_STATE.read(); tracing::debug!(?state, "Starting installation..."); - InstallationPageCommandMsg::FinishInstallation(state.install_using_subprocess()) + + InstallationPageCommandMsg::FinishInstallation( + state.install_using_subprocess(s), + ) }); } InstallationPageMsg::Navigate(action) => sender @@ -244,6 +256,9 @@ impl Component for InstallationPage { .unwrap(), InstallationPageMsg::Update => {} InstallationPageMsg::Throb => self.progress_bar.pulse(), + InstallationPageMsg::SubprocessMessage(InstallationMessage::Status(status)) => { + self.progress_bar.set_text(Some(&status)); + } } } @@ -274,6 +289,7 @@ impl Component for InstallationPage { .unwrap(); } } + InstallationPageCommandMsg::None => {} } } } diff --git a/src/util/macros.rs b/src/util/macros.rs index ca24622..701efc8 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -1,30 +1,16 @@ -/// IPC installation message for non-interactive mode -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub enum InstallMessage { - Status(String), -} - -impl InstallMessage { - pub fn new(s: &str) -> Self { - Self::Status(s.to_owned()) - } - - pub fn into_json(self) -> String { - serde_json::to_string(&self).unwrap() - } -} - #[macro_export] macro_rules! stage { // todo: Export text to global progress text ($s:literal $body:block) => {{ let s = tracing::info_span!($s); - if std::env::var("NON_INTERACTIVE_INSTALL").is_ok_and(|v| v == "1") { + if let Some(m) = $crate::backend::install::IPC_CHANNEL.get() { + let sender = m.lock().unwrap(); // Then we are in a non-interactive install, which means we export IPC // to stdout - let install_status = $crate::util::macros::InstallMessage::new($s); - println!("{}", install_status.into_json()); + let install_status = + $crate::backend::install::InstallationMessage::Status($s.to_owned()); + sender.send(install_status).unwrap(); } { From 57bd82ac8b5b853367ac312303ac63f6bcbe2b86 Mon Sep 17 00:00:00 2001 From: Lleyton Gray Date: Tue, 31 Dec 2024 17:52:53 -0800 Subject: [PATCH 2/2] fix: fmt --- src/backend/postinstall/grub2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/postinstall/grub2.rs b/src/backend/postinstall/grub2.rs index faa639b..cc5f00b 100644 --- a/src/backend/postinstall/grub2.rs +++ b/src/backend/postinstall/grub2.rs @@ -112,7 +112,7 @@ fn grub2_install_bios>(disk: P) -> Result<()> { .arg("--force") .arg(disk.as_ref()) .status()?; - + if !status.success() { bail!("Failed to install GRUB2 on disk {_disk:?}") }