@@ -12,6 +12,7 @@ use std::process::Command;
1212use anyhow:: { bail, Context , Result } ;
1313use fn_error_context:: context;
1414use openat_ext:: OpenatDirExt ;
15+ use os_release:: OsRelease ;
1516use walkdir:: WalkDir ;
1617use 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