Skip to content

Commit 6e37e52

Browse files
authored
Merge pull request #665 from HuijingHei/sync-anaconda
efi: change `--update-firmware` to match current Anaconda logic
2 parents 19610be + 482d159 commit 6e37e52

File tree

3 files changed

+163
-36
lines changed

3 files changed

+163
-36
lines changed

Cargo.lock

Lines changed: 20 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ nix = ">= 0.22.1, < 0.24.0"
3434
openat = "0.1.20"
3535
openat-ext = ">= 0.2.2, < 0.3.0"
3636
openssl = "^0.10"
37+
os-release = "0.1.0"
3738
serde = { version = "^1.0", features = ["derive"] }
3839
serde_json = "^1.0"
3940
tempfile = "^3.10"

src/efi.rs

Lines changed: 142 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::process::Command;
1212
use anyhow::{bail, Context, Result};
1313
use fn_error_context::context;
1414
use openat_ext::OpenatDirExt;
15+
use os_release::OsRelease;
1516
use walkdir::WalkDir;
1617
use widestring::U16CString;
1718

@@ -137,8 +138,14 @@ impl Efi {
137138
log::debug!("Not booted via EFI, skipping firmware update");
138139
return Ok(());
139140
}
140-
clear_efi_current()?;
141-
set_efi_current(device, espdir, vendordir)
141+
// Read /etc/os-release
142+
let release: OsRelease = OsRelease::new()?;
143+
let product_name: &str = &release.name;
144+
log::debug!("Get product name: {product_name}");
145+
assert!(product_name.len() > 0);
146+
// clear all the boot entries that match the target name
147+
clear_efi_target(product_name)?;
148+
create_efi_boot_entry(device, espdir, vendordir, product_name)
142149
}
143150
}
144151

@@ -455,46 +462,71 @@ fn validate_esp(dir: &openat::Dir) -> Result<()> {
455462
Ok(())
456463
}
457464

458-
#[context("Clearing current EFI boot entry")]
459-
pub(crate) fn clear_efi_current() -> Result<()> {
460-
const BOOTCURRENT: &str = "BootCurrent";
461-
if !crate::efi::is_efi_booted()? {
462-
log::debug!("System is not booted via EFI");
463-
return Ok(());
465+
#[derive(Debug, PartialEq)]
466+
struct BootEntry {
467+
id: String,
468+
name: String,
469+
}
470+
471+
/// Parse boot entries from efibootmgr output
472+
fn parse_boot_entries(output: &str) -> Vec<BootEntry> {
473+
let mut entries = Vec::new();
474+
475+
for line in output.lines().filter_map(|line| line.strip_prefix("Boot")) {
476+
// Need to consider if output only has "Boot0000* UiApp", without additional info
477+
if line.starts_with('0') {
478+
let parts = if let Some((parts, _)) = line.split_once('\t') {
479+
parts
480+
} else {
481+
line
482+
};
483+
if let Some((id, name)) = parts.split_once(' ') {
484+
let id = id.trim_end_matches('*').to_string();
485+
let name = name.trim().to_string();
486+
entries.push(BootEntry { id, name });
487+
}
488+
}
464489
}
490+
entries
491+
}
492+
493+
#[context("Clearing EFI boot entries that match target {target}")]
494+
pub(crate) fn clear_efi_target(target: &str) -> Result<()> {
495+
let target = target.to_lowercase();
465496
let output = Command::new(EFIBOOTMGR).output()?;
466497
if !output.status.success() {
467498
anyhow::bail!("Failed to invoke {EFIBOOTMGR}")
468499
}
500+
469501
let output = String::from_utf8(output.stdout)?;
470-
let current = if let Some(current) = output
471-
.lines()
472-
.filter_map(|l| l.split_once(':'))
473-
.filter_map(|(k, v)| (k == BOOTCURRENT).then_some(v.trim()))
474-
.next()
475-
{
476-
current
477-
} else {
478-
log::debug!("No EFI {BOOTCURRENT} found");
479-
return Ok(());
480-
};
481-
log::debug!("EFI current: {current}");
482-
let output = Command::new(EFIBOOTMGR)
483-
.args(["-b", current, "-B"])
484-
.output()?;
485-
let st = output.status;
486-
if !st.success() {
487-
std::io::copy(
488-
&mut std::io::Cursor::new(output.stderr),
489-
&mut std::io::stderr().lock(),
490-
)?;
491-
anyhow::bail!("Failed to invoke {EFIBOOTMGR}: {st:?}");
502+
let boot_entries = parse_boot_entries(&output);
503+
for entry in boot_entries {
504+
if entry.name.to_lowercase() == target {
505+
log::debug!("Deleting matched target {:?}", entry);
506+
let output = Command::new(EFIBOOTMGR)
507+
.args(["-b", entry.id.as_str(), "-B"])
508+
.output()?;
509+
let st = output.status;
510+
if !st.success() {
511+
std::io::copy(
512+
&mut std::io::Cursor::new(output.stderr),
513+
&mut std::io::stderr().lock(),
514+
)?;
515+
anyhow::bail!("Failed to invoke {EFIBOOTMGR}: {st:?}");
516+
}
517+
}
492518
}
519+
493520
anyhow::Ok(())
494521
}
495522

496523
#[context("Adding new EFI boot entry")]
497-
pub(crate) fn set_efi_current(device: &str, espdir: &openat::Dir, vendordir: &str) -> Result<()> {
524+
pub(crate) fn create_efi_boot_entry(
525+
device: &str,
526+
espdir: &openat::Dir,
527+
vendordir: &str,
528+
target: &str,
529+
) -> Result<()> {
498530
let fsinfo = crate::filesystem::inspect_filesystem(espdir, ".")?;
499531
let source = fsinfo.source;
500532
let devname = source
@@ -509,6 +541,7 @@ pub(crate) fn set_efi_current(device: &str, espdir: &openat::Dir, vendordir: &st
509541
anyhow::bail!("Failed to find {SHIM}");
510542
}
511543
let loader = format!("\\EFI\\{}\\{SHIM}", vendordir);
544+
log::debug!("Creating new EFI boot entry using '{target}'");
512545
let st = Command::new(EFIBOOTMGR)
513546
.args([
514547
"--create",
@@ -519,7 +552,7 @@ pub(crate) fn set_efi_current(device: &str, espdir: &openat::Dir, vendordir: &st
519552
"--loader",
520553
loader.as_str(),
521554
"--label",
522-
vendordir,
555+
target,
523556
])
524557
.status()?;
525558
if !st.success() {
@@ -546,3 +579,80 @@ fn find_file_recursive<P: AsRef<Path>>(dir: P, target_file: &str) -> Result<Vec<
546579

547580
Ok(result)
548581
}
582+
583+
#[cfg(test)]
584+
mod tests {
585+
use super::*;
586+
587+
#[test]
588+
fn test_parse_boot_entries() -> Result<()> {
589+
let output = r"
590+
BootCurrent: 0003
591+
Timeout: 0 seconds
592+
BootOrder: 0003,0001,0000,0002
593+
Boot0000* UiApp FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(462caa21-7614-4503-836e-8ab6f4662331)
594+
Boot0001* UEFI Misc Device PciRoot(0x0)/Pci(0x3,0x0){auto_created_boot_option}
595+
Boot0002* EFI Internal Shell FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(7c04a583-9e3e-4f1c-ad65-e05268d0b4d1)
596+
Boot0003* Fedora HD(2,GPT,94ff4025-5276-4bec-adea-e98da271b64c,0x1000,0x3f800)/\EFI\fedora\shimx64.efi";
597+
let entries = parse_boot_entries(output);
598+
assert_eq!(
599+
entries,
600+
[
601+
BootEntry {
602+
id: "0000".to_string(),
603+
name: "UiApp".to_string()
604+
},
605+
BootEntry {
606+
id: "0001".to_string(),
607+
name: "UEFI Misc Device".to_string()
608+
},
609+
BootEntry {
610+
id: "0002".to_string(),
611+
name: "EFI Internal Shell".to_string()
612+
},
613+
BootEntry {
614+
id: "0003".to_string(),
615+
name: "Fedora".to_string()
616+
}
617+
]
618+
);
619+
let output = r"
620+
BootCurrent: 0003
621+
Timeout: 0 seconds
622+
BootOrder: 0003,0001,0000,0002";
623+
let entries = parse_boot_entries(output);
624+
assert_eq!(entries, []);
625+
626+
let output = r"
627+
BootCurrent: 0003
628+
Timeout: 0 seconds
629+
BootOrder: 0003,0001,0000,0002
630+
Boot0000* UiApp
631+
Boot0001* UEFI Misc Device
632+
Boot0002* EFI Internal Shell
633+
Boot0003* test";
634+
let entries = parse_boot_entries(output);
635+
assert_eq!(
636+
entries,
637+
[
638+
BootEntry {
639+
id: "0000".to_string(),
640+
name: "UiApp".to_string()
641+
},
642+
BootEntry {
643+
id: "0001".to_string(),
644+
name: "UEFI Misc Device".to_string()
645+
},
646+
BootEntry {
647+
id: "0002".to_string(),
648+
name: "EFI Internal Shell".to_string()
649+
},
650+
BootEntry {
651+
id: "0003".to_string(),
652+
name: "test".to_string()
653+
}
654+
]
655+
);
656+
Ok(())
657+
}
658+
}

0 commit comments

Comments
 (0)