diff options
| -rwxr-xr-x | installer/archangel | 24 | ||||
| -rw-r--r-- | installer/lib/common.sh | 24 | ||||
| -rw-r--r-- | tests/unit/test_common.bats | 78 |
3 files changed, 116 insertions, 10 deletions
diff --git a/installer/archangel b/installer/archangel index aa8eeaa..b3c232f 100755 --- a/installer/archangel +++ b/installer/archangel @@ -1159,16 +1159,20 @@ configure_zfsbootmenu() { --quiet done - # Get the boot entry number and set as first in boot order - local bootnum - bootnum=$(efibootmgr | grep "ZFSBootMenu" | head -1 | grep -oP 'Boot\K[0-9A-F]+') - if [[ -n "$bootnum" ]]; then - # Get current boot order, prepend our entry - local current_order - current_order=$(efibootmgr | grep "BootOrder" | cut -d: -f2 | tr -d ' ') - efibootmgr --bootorder "$bootnum,$current_order" --quiet - info "ZFSBootMenu set as primary boot option" - fi + # Look up our boot entry and the current boot order, then prepend + # our entry. Capture efibootmgr output once and parse via helpers + # so a missing entry or a malformed BootOrder line fails fast with + # an actionable message instead of silently leaving the boot order + # unset (which produces a non-booting machine after reboot). + local efibootmgr_output bootnum current_order + efibootmgr_output=$(efibootmgr) \ + || error "efibootmgr command failed; cannot configure boot order" + bootnum=$(parse_efibootmgr_entry "ZFSBootMenu" <<< "$efibootmgr_output") \ + || error "Could not find ZFSBootMenu entry in efibootmgr output; boot order not set" + current_order=$(parse_efibootmgr_bootorder <<< "$efibootmgr_output") \ + || error "Could not parse BootOrder line from efibootmgr output; boot order not set" + efibootmgr --bootorder "$bootnum,$current_order" --quiet + info "ZFSBootMenu set as primary boot option" info "ZFSBootMenu configuration complete." } diff --git a/installer/lib/common.sh b/installer/lib/common.sh index 8193b19..98220fa 100644 --- a/installer/lib/common.sh +++ b/installer/lib/common.sh @@ -236,6 +236,30 @@ install_dropin() { cat > "${dir}/${dropin_name}.conf" } +# Read efibootmgr output from stdin and echo the boot number of the +# first entry whose label contains $1. Returns 1 (with empty output) +# if the label is empty, no entry matches, or the matched line has no +# Boot[0-9A-F]+ prefix. The empty-label guard is important: an empty +# string would match every line, and a line like "BootCurrent: 0001" +# would falsely satisfy the Boot[hex]+ regex (capturing "C"). +parse_efibootmgr_entry() { + local label="$1" + [[ -z "$label" ]] && return 1 + local line + line=$(grep -F -m 1 "$label") || return 1 + [[ "$line" =~ Boot([0-9A-Fa-f]+) ]] || return 1 + echo "${BASH_REMATCH[1]}" +} + +# Read efibootmgr output from stdin and echo the comma-separated boot +# numbers from the BootOrder line, with whitespace stripped. Returns 1 +# (with empty output) if no BootOrder line is present. +parse_efibootmgr_bootorder() { + local line + line=$(grep "^BootOrder:") || return 1 + echo "${line#BootOrder:}" | tr -d ' ' +} + # List available disks (not in use) list_available_disks() { local disks=() diff --git a/tests/unit/test_common.bats b/tests/unit/test_common.bats index c81d2e3..0bb76a1 100644 --- a/tests/unit/test_common.bats +++ b/tests/unit/test_common.bats @@ -246,3 +246,81 @@ Environment="FOO=bar baz"' [[ "$output" == *'"FOO=bar baz"'* ]] teardown_dropin_tmp } + +############################# +# parse_efibootmgr_entry +############################# + +@test "parse_efibootmgr_entry returns boot number for matching label" { + local sample="BootCurrent: 0001 +Boot0000* Windows Boot Manager +Boot0001* ZFSBootMenu +Boot0002* PXE Boot" + run parse_efibootmgr_entry "ZFSBootMenu" <<< "$sample" + [ "$status" -eq 0 ] + [ "$output" = "0001" ] +} + +@test "parse_efibootmgr_entry returns first match when multiple labels match" { + local sample="Boot0001* ZFSBootMenu +Boot0002* ZFSBootMenu-disk2" + run parse_efibootmgr_entry "ZFSBootMenu" <<< "$sample" + [ "$status" -eq 0 ] + [ "$output" = "0001" ] +} + +@test "parse_efibootmgr_entry handles hex characters in boot number" { + local sample="Boot00FE* ZFSBootMenu" + run parse_efibootmgr_entry "ZFSBootMenu" <<< "$sample" + [ "$status" -eq 0 ] + [ "$output" = "00FE" ] +} + +@test "parse_efibootmgr_entry returns 1 with empty output when label absent" { + local sample="Boot0001* Windows Boot Manager" + run parse_efibootmgr_entry "ZFSBootMenu" <<< "$sample" + [ "$status" -eq 1 ] + [ -z "$output" ] +} + +@test "parse_efibootmgr_entry returns 1 with empty output for empty input" { + run parse_efibootmgr_entry "ZFSBootMenu" < /dev/null + [ "$status" -eq 1 ] + [ -z "$output" ] +} + +@test "parse_efibootmgr_entry returns 1 for empty label without false-matching BootCurrent" { + local sample="BootCurrent: 0001 +Boot0001* ZFSBootMenu" + run parse_efibootmgr_entry "" <<< "$sample" + [ "$status" -eq 1 ] + [ -z "$output" ] +} + +############################# +# parse_efibootmgr_bootorder +############################# + +@test "parse_efibootmgr_bootorder extracts comma-separated boot numbers" { + local sample="BootCurrent: 0001 +BootOrder: 0001,0002,0003 +Boot0001* ZFSBootMenu" + run parse_efibootmgr_bootorder <<< "$sample" + [ "$status" -eq 0 ] + [ "$output" = "0001,0002,0003" ] +} + +@test "parse_efibootmgr_bootorder strips whitespace from boot order" { + local sample="BootOrder: 0001, 0002 , 0003" + run parse_efibootmgr_bootorder <<< "$sample" + [ "$status" -eq 0 ] + [ "$output" = "0001,0002,0003" ] +} + +@test "parse_efibootmgr_bootorder returns 1 when BootOrder line absent" { + local sample="BootCurrent: 0001 +Boot0001* ZFSBootMenu" + run parse_efibootmgr_bootorder <<< "$sample" + [ "$status" -eq 1 ] + [ -z "$output" ] +} |
