diff --git a/Cargo.lock b/Cargo.lock index d2f1ef24..7af52d84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,6 +87,7 @@ dependencies = [ "openat", "openat-ext", "openssl", + "os-release", "serde", "serde_json", "tempfile", @@ -387,6 +388,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.155" @@ -562,6 +569,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os-release" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f29ae2f71b53ec19cc23385f8e4f3d90975195aa3d09171ba3bef7159bec27" +dependencies = [ + "lazy_static", +] + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -654,9 +670,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -666,9 +682,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index c03a04d3..4f1d4c09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ nix = ">= 0.22.1, < 0.24.0" openat = "0.1.20" openat-ext = ">= 0.2.2, < 0.3.0" openssl = "^0.10" +os-release = "0.1.0" serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" tempfile = "^3.10" diff --git a/src/efi.rs b/src/efi.rs index e621726a..e89fb05b 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -12,6 +12,7 @@ use std::process::Command; use anyhow::{bail, Context, Result}; use fn_error_context::context; use openat_ext::OpenatDirExt; +use os_release::OsRelease; use walkdir::WalkDir; use widestring::U16CString; @@ -137,8 +138,14 @@ impl Efi { log::debug!("Not booted via EFI, skipping firmware update"); return Ok(()); } - clear_efi_current()?; - set_efi_current(device, espdir, vendordir) + // Read /etc/os-release + let release: OsRelease = OsRelease::new()?; + let product_name: &str = &release.name; + log::debug!("Get product name: {product_name}"); + assert!(product_name.len() > 0); + // clear all the boot entries that match the target name + clear_efi_target(product_name)?; + create_efi_boot_entry(device, espdir, vendordir, product_name) } } @@ -455,46 +462,71 @@ fn validate_esp(dir: &openat::Dir) -> Result<()> { Ok(()) } -#[context("Clearing current EFI boot entry")] -pub(crate) fn clear_efi_current() -> Result<()> { - const BOOTCURRENT: &str = "BootCurrent"; - if !crate::efi::is_efi_booted()? { - log::debug!("System is not booted via EFI"); - return Ok(()); +#[derive(Debug, PartialEq)] +struct BootEntry { + id: String, + name: String, +} + +/// Parse boot entries from efibootmgr output +fn parse_boot_entries(output: &str) -> Vec { + let mut entries = Vec::new(); + + for line in output.lines().filter_map(|line| line.strip_prefix("Boot")) { + // Need to consider if output only has "Boot0000* UiApp", without additional info + if line.starts_with('0') { + let parts = if let Some((parts, _)) = line.split_once('\t') { + parts + } else { + line + }; + if let Some((id, name)) = parts.split_once(' ') { + let id = id.trim_end_matches('*').to_string(); + let name = name.trim().to_string(); + entries.push(BootEntry { id, name }); + } + } } + entries +} + +#[context("Clearing EFI boot entries that match target {target}")] +pub(crate) fn clear_efi_target(target: &str) -> Result<()> { + let target = target.to_lowercase(); let output = Command::new(EFIBOOTMGR).output()?; if !output.status.success() { anyhow::bail!("Failed to invoke {EFIBOOTMGR}") } + let output = String::from_utf8(output.stdout)?; - let current = if let Some(current) = output - .lines() - .filter_map(|l| l.split_once(':')) - .filter_map(|(k, v)| (k == BOOTCURRENT).then_some(v.trim())) - .next() - { - current - } else { - log::debug!("No EFI {BOOTCURRENT} found"); - return Ok(()); - }; - log::debug!("EFI current: {current}"); - let output = Command::new(EFIBOOTMGR) - .args(["-b", current, "-B"]) - .output()?; - let st = output.status; - if !st.success() { - std::io::copy( - &mut std::io::Cursor::new(output.stderr), - &mut std::io::stderr().lock(), - )?; - anyhow::bail!("Failed to invoke {EFIBOOTMGR}: {st:?}"); + let boot_entries = parse_boot_entries(&output); + for entry in boot_entries { + if entry.name.to_lowercase() == target { + log::debug!("Deleting matched target {:?}", entry); + let output = Command::new(EFIBOOTMGR) + .args(["-b", entry.id.as_str(), "-B"]) + .output()?; + let st = output.status; + if !st.success() { + std::io::copy( + &mut std::io::Cursor::new(output.stderr), + &mut std::io::stderr().lock(), + )?; + anyhow::bail!("Failed to invoke {EFIBOOTMGR}: {st:?}"); + } + } } + anyhow::Ok(()) } #[context("Adding new EFI boot entry")] -pub(crate) fn set_efi_current(device: &str, espdir: &openat::Dir, vendordir: &str) -> Result<()> { +pub(crate) fn create_efi_boot_entry( + device: &str, + espdir: &openat::Dir, + vendordir: &str, + target: &str, +) -> Result<()> { let fsinfo = crate::filesystem::inspect_filesystem(espdir, ".")?; let source = fsinfo.source; let devname = source @@ -509,6 +541,7 @@ pub(crate) fn set_efi_current(device: &str, espdir: &openat::Dir, vendordir: &st anyhow::bail!("Failed to find {SHIM}"); } let loader = format!("\\EFI\\{}\\{SHIM}", vendordir); + log::debug!("Creating new EFI boot entry using '{target}'"); let st = Command::new(EFIBOOTMGR) .args([ "--create", @@ -519,7 +552,7 @@ pub(crate) fn set_efi_current(device: &str, espdir: &openat::Dir, vendordir: &st "--loader", loader.as_str(), "--label", - vendordir, + target, ]) .status()?; if !st.success() { @@ -546,3 +579,80 @@ fn find_file_recursive>(dir: P, target_file: &str) -> Result Result<()> { + let output = r" +BootCurrent: 0003 +Timeout: 0 seconds +BootOrder: 0003,0001,0000,0002 +Boot0000* UiApp FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(462caa21-7614-4503-836e-8ab6f4662331) +Boot0001* UEFI Misc Device PciRoot(0x0)/Pci(0x3,0x0){auto_created_boot_option} +Boot0002* EFI Internal Shell FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(7c04a583-9e3e-4f1c-ad65-e05268d0b4d1) +Boot0003* Fedora HD(2,GPT,94ff4025-5276-4bec-adea-e98da271b64c,0x1000,0x3f800)/\EFI\fedora\shimx64.efi"; + let entries = parse_boot_entries(output); + assert_eq!( + entries, + [ + BootEntry { + id: "0000".to_string(), + name: "UiApp".to_string() + }, + BootEntry { + id: "0001".to_string(), + name: "UEFI Misc Device".to_string() + }, + BootEntry { + id: "0002".to_string(), + name: "EFI Internal Shell".to_string() + }, + BootEntry { + id: "0003".to_string(), + name: "Fedora".to_string() + } + ] + ); + let output = r" +BootCurrent: 0003 +Timeout: 0 seconds +BootOrder: 0003,0001,0000,0002"; + let entries = parse_boot_entries(output); + assert_eq!(entries, []); + + let output = r" +BootCurrent: 0003 +Timeout: 0 seconds +BootOrder: 0003,0001,0000,0002 +Boot0000* UiApp +Boot0001* UEFI Misc Device +Boot0002* EFI Internal Shell +Boot0003* test"; + let entries = parse_boot_entries(output); + assert_eq!( + entries, + [ + BootEntry { + id: "0000".to_string(), + name: "UiApp".to_string() + }, + BootEntry { + id: "0001".to_string(), + name: "UEFI Misc Device".to_string() + }, + BootEntry { + id: "0002".to_string(), + name: "EFI Internal Shell".to_string() + }, + BootEntry { + id: "0003".to_string(), + name: "test".to_string() + } + ] + ); + Ok(()) + } +}