diff --git a/.circleci/config.yml b/.circleci/config.yml index 191855454..348084718 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -65,6 +65,19 @@ commands: jobs: + cfr_settings_tests: + docker: + # cimg/base:2026.08 provides Bash and ShellCheck for this host-only job. + - image: cimg/base@sha256:e8f07526f593ac5dee29362b7f98c6fec94c412722d6bbece731e4cc885abccb + steps: + - checkout + - run: + name: CFR settings host fixture + command: ./tests/cfr/test_cfr_settings.sh + - run: + name: CFR settings shellcheck + command: shellcheck initrd/bin/cfr-settings.sh tests/cfr/test_cfr_settings.sh + # ═══════════════════════════════════════════════════════════════════════════ # Glossary (see doc/circleci.md for full cache model) # ═══════════════════════════════════════════════════════════════════════════ @@ -415,6 +428,7 @@ workflows: build_and_test: max_auto_reruns: 3 jobs: + - cfr_settings_tests - create_hashes: name: create_hashes [cache keys] diff --git a/Makefile b/Makefile index 49d1c9eac..526cd24fb 100644 --- a/Makefile +++ b/Makefile @@ -877,10 +877,19 @@ endif # --- TOOLS.CPIO --- # tools.cpio is built from all binaries, libraries, and config staged in initrd_tools_dir +initrd_feature_markers := +ifeq ($(CONFIG_HEADS_CFR),y) +initrd_feature_markers += $(initrd_tools_dir)/etc/heads-cfr-enabled +$(initrd_tools_dir)/etc/heads-cfr-enabled: $(CONFIG) + @mkdir -p "$(dir $@)" + @printf 'enabled\n' > "$@" +endif + $(build)/$(initrd_dir)/tools.cpio: \ $(initrd_bins) \ $(initrd_libs) \ $(initrd_tools_dir)/etc/config \ + $(initrd_feature_markers) \ FORCE $(call do-cpio,$@,$(initrd_tools_dir)) @$(RM) -rf "$(initrd_tools_dir)" diff --git a/doc/cfr.md b/doc/cfr.md new file mode 100644 index 000000000..377988bf4 --- /dev/null +++ b/doc/cfr.md @@ -0,0 +1,37 @@ +# Coreboot Firmware Settings + +Heads can optionally show the standard Linux firmware-attributes interface +published by the coreboot CFR driver. The feature is disabled unless a board +sets `CONFIG_HEADS_CFR=y` in its board configuration. Its build-generated +`/etc/heads-cfr-enabled` marker is immutable at runtime, so `config.user` +cannot enable or disable the feature. + +The UI reads only: + +```text +/sys/class/firmware-attributes/coreboot-cfr/attributes +``` + +It does not parse the coreboot table, access EFI variables or SMMSTORE, or +invoke an SMI. A board must therefore provide a kernel and firmware stack that +implements this standard interface before enabling the feature. + +The board configuration must select kernel and coreboot revisions that provide +the required CFR firmware-attributes ABI. Source selection and board enablement +are intentionally outside this generic UI change. + +The settings menu is available under Options only when both the immutable +feature marker and the firmware-attributes class/device exist. Missing, +malformed, disappearing, or unwritable attributes are handled as unavailable +or read-only. Every selected write requires an explicit confirmation showing +the display name, current value, requested value, and pending-reboot status; +cancelling that confirmation does not write. + +The Linux driver currently exposes writability through the `current_value` mode +and does not publish a `flags` attribute. The UI works with no `flags` files. +It tolerates an optional flags file for forward compatibility, hiding +`inactive`/`suppressed` entries and treating `readonly` or `volatile` as +non-writable, but that file is not part of the required ABI. Enumeration values +use the standard semicolon delimiter; labels containing spaces are preserved. A literal +semicolon cannot be represented unambiguously by that sysfs ABI and is rejected +rather than guessed. diff --git a/doc/index.md b/doc/index.md index 0be106bb1..c36670f2a 100644 --- a/doc/index.md +++ b/doc/index.md @@ -9,6 +9,7 @@ Quick reference: read the relevant doc when working on a topic. | `build-artifacts.md` | ROM filenames, update-package zip layout, LVFS conventions | | `build-freshness.md` | Why rebuilds produce stale artifacts and how to force a full rebuild | | `circleci.md` | CI pipeline: job dependency graph, cache layers, workspace persistence | +| `cfr.md` | Optional coreboot firmware-settings UI | | `docker.md` | Docker-based build environment with pinned, reproducible images | | `modules.md` | Module system: toolchain and bin modules, inclusion rules, sentinel chain | | `patches.md` | Creating and maintaining source patches for upstream packages | diff --git a/initrd/bin/cfr-settings.sh b/initrd/bin/cfr-settings.sh new file mode 100755 index 000000000..af8bbaa86 --- /dev/null +++ b/initrd/bin/cfr-settings.sh @@ -0,0 +1,338 @@ +#!/bin/bash +# shellcheck disable=SC1091 + +set -e -o pipefail + +cfr_root=/sys/class/firmware-attributes/coreboot-cfr/attributes +test_root= + +if [ "${1:-}" = "--test-root" ]; then + [ "$#" -eq 2 ] || exit 2 + test_root=$2 + cfr_root="$test_root/sys/class/firmware-attributes/coreboot-cfr/attributes" + . "$test_root/functions.sh" + . "$test_root/gui_functions.sh" +else + [ "$#" -eq 0 ] || exit 2 + [ -r /etc/functions.sh ] && . /etc/functions.sh + [ -r /etc/gui_functions.sh ] && . /etc/gui_functions.sh +fi + +if [ -n "$test_root" ]; then + dialog_output="$test_root/dialog_output" +else + dialog_output=/tmp/cfr-settings-whiptail.$$ +fi +trap 'rm -f "$dialog_output"' EXIT + +cfr_error() { + local message=$1 + if declare -F whiptail_error >/dev/null 2>&1; then + whiptail_error --title 'Firmware Settings Error' --msgbox "$message" 0 80 || true + else + printf '%s\n' "$message" >&2 + fi +} + +cfr_info() { + local message=$1 + if declare -F whiptail_type >/dev/null 2>&1; then + whiptail_type normal --title 'Firmware Settings' --msgbox "$message" 0 80 || true + else + printf '%s\n' "$message" >&2 + fi +} + +cfr_read_line() { + local file=$1 allow_empty=$2 value extra fd + [ -r "$file" ] || return 1 + exec {fd}<"$file" || return 1 + if ! IFS= read -r value <&"$fd" && [ -z "$value" ]; then + exec {fd}<&- + return 1 + fi + if IFS= read -r extra <&"$fd" || [ -n "$extra" ]; then + exec {fd}<&- + return 1 + fi + exec {fd}<&- + case "$value" in + *$'\n'*|*$'\r'*) return 1 ;; + esac + [ "$allow_empty" = true ] || [ -n "$value" ] || return 1 + printf '%s' "$value" +} + +cfr_read_single_line() { + cfr_read_line "$1" false +} + +cfr_read_optional_line() { + cfr_read_line "$1" true +} + +cfr_number_valid() { + local value=$1 minimum=$2 maximum=$3 step=$4 number + local candidate + for candidate in "$value" "$minimum" "$maximum" "$step"; do + [[ "$candidate" =~ ^[0-9]{1,10}$ ]] || return 1 + (( 10#$candidate <= 4294967295 )) || return 1 + done + number=$((10#$value)) || return 1 + minimum=$((10#$minimum)) || return 1 + maximum=$((10#$maximum)) || return 1 + step=$((10#$step)) || return 1 + (( number >= minimum && number <= maximum )) || return 1 + (( step == 0 || (number - minimum) % step == 0 )) +} + +cfr_enum_values() { + local file=$1 raw value + local -a values + raw=$(cfr_read_single_line "$file") || return 1 + case "$raw" in + ';'*|*';'|*';;'*) return 1 ;; + esac + IFS=';' read -r -a values <<< "$raw" + [ "${#values[@]}" -gt 0 ] || return 1 + for value in "${values[@]}"; do + [ -n "$value" ] || return 1 + case "$value" in + *$'\n'*|*$'\r'*) return 1 ;; + esac + printf '%s\n' "$value" + done +} + +cfr_setting_type() { + local setting=$1 type + type=$(cfr_read_single_line "$setting/type") || return 1 + case "$type" in + enumeration|integer) printf '%s' "$type" ;; + *) return 1 ;; + esac +} + +cfr_setting_flags_valid() { + local setting=$1 raw flag + [ -e "$setting/flags" ] || return 0 + raw=$(cfr_read_optional_line "$setting/flags") || return 1 + raw=${raw//,/ } + for flag in $raw; do + case "$flag" in + readonly|inactive|suppressed|volatile) ;; + *) return 1 ;; + esac + done +} + +cfr_setting_has_flag() { + local setting=$1 wanted=$2 raw flag + [ -e "$setting/flags" ] || return 1 + raw=$(cfr_read_optional_line "$setting/flags") || return 1 + raw=${raw//,/ } + for flag in $raw; do + [ "$flag" = "$wanted" ] && return 0 + done + return 1 +} + +cfr_setting_writable() { + local setting=$1 mode + mode=$(stat -c '%a' "$setting/current_value") || return 1 + [[ "$mode" =~ ^[0-7]+$ ]] || return 1 + (( (8#$mode & 0222) != 0 )) +} + +cfr_setting_valid() { + local setting=$1 type current default minimum maximum step value raw_values + local current_found default_found + local -a values + type=$(cfr_setting_type "$setting") || return 1 + cfr_setting_flags_valid "$setting" || return 1 + cfr_read_single_line "$setting/display_name" >/dev/null || return 1 + current=$(cfr_read_single_line "$setting/current_value") || return 1 + default=$(cfr_read_single_line "$setting/default_value") || return 1 + if [ "$type" = integer ]; then + minimum=$(cfr_read_single_line "$setting/min_value") || return 1 + maximum=$(cfr_read_single_line "$setting/max_value") || return 1 + step=$(cfr_read_single_line "$setting/scalar_increment") || return 1 + cfr_number_valid "$current" "$minimum" "$maximum" "$step" || return 1 + cfr_number_valid "$default" "$minimum" "$maximum" "$step" || return 1 + else + raw_values=$(cfr_enum_values "$setting/possible_values") || return 1 + mapfile -t values <<< "$raw_values" + [ "${#values[@]}" -gt 0 ] || return 1 + current_found=1 + default_found=1 + for value in "${values[@]}"; do + [ "$value" = "$current" ] && current_found=0 + [ "$value" = "$default" ] && default_found=0 + done + [ "$current_found" -eq 0 ] || return 1 + [ "$default_found" -eq 0 ] || return 1 + fi +} + +cfr_item() { + local setting=$1 name display current writable + name=${setting##*/} + display=$(cfr_read_single_line "$setting/display_name") || return 1 + current=$(cfr_read_single_line "$setting/current_value") || return 1 + if cfr_setting_writable "$setting" && + ! cfr_setting_has_flag "$setting" readonly && + ! cfr_setting_has_flag "$setting" volatile; then + writable='' + else + writable=' [read-only]' + fi + cfr_setting_has_flag "$setting" volatile && writable="$writable [volatile]" + printf '%s\t%s%s (current: %s)' "$name" "$display" "$writable" "$current" +} + +cfr_choose_setting() { + local setting item name + local -a menu + menu=() + for setting in "$cfr_root"/*; do + [ -d "$setting" ] || continue + cfr_setting_valid "$setting" || continue + cfr_setting_has_flag "$setting" inactive && continue + cfr_setting_has_flag "$setting" suppressed && continue + item=$(cfr_item "$setting") || continue + name=${item%%$'\t'*} + item=${item#*$'\t'} + menu+=("$name" "$item") + done + + [ "${#menu[@]}" -gt 0 ] || { + cfr_info 'No usable coreboot firmware settings are available.' + return 1 + } + + if ! whiptail_type normal --title 'Coreboot Firmware Settings' \ + --menu 'Select a setting. Values are read again after every write.' \ + 0 100 12 "${menu[@]}" > /dev/null 2>"$dialog_output"; then + return 1 + fi + + [ -r "$dialog_output" ] || return 1 + cat -- "$dialog_output" +} + +cfr_edit_setting() { + local name=$1 setting="$cfr_root/$1" type current default minimum maximum step + local selected value display pending pending_text confirmation + + [ -d "$setting" ] || { + cfr_error 'The selected setting disappeared.' + return 0 + } + if cfr_setting_has_flag "$setting" inactive || + cfr_setting_has_flag "$setting" suppressed; then + cfr_error 'The selected setting is inactive.' + return 0 + fi + cfr_setting_valid "$setting" || { + cfr_error 'The selected setting is unavailable or contains malformed data.' + return 0 + } + type=$(cfr_setting_type "$setting") || return 0 + current=$(cfr_read_single_line "$setting/current_value") || return 0 + default=$(cfr_read_single_line "$setting/default_value") || return 0 + + if ! cfr_setting_writable "$setting" || + cfr_setting_has_flag "$setting" readonly || + cfr_setting_has_flag "$setting" volatile; then + display=$(cfr_read_single_line "$setting/display_name") || return 0 + cfr_info "$(printf '%s\n\nCurrent: %s\nDefault: %s\n\nThis setting is read-only in firmware.' "$display" "$current" "$default")" + return 0 + fi + + if [ "$type" = enumeration ]; then + local -a values choices + local raw_values + raw_values=$(cfr_enum_values "$setting/possible_values") || { + cfr_error 'The setting enumeration is malformed.' + return 0 + } + mapfile -t values <<< "$raw_values" + choices=() + for value in "${values[@]}"; do + choices+=("$value" "$value") + done + if ! whiptail_type normal --title "$(cfr_read_single_line "$setting/display_name")" \ + --menu "Current: $current\nDefault: $default" 0 100 12 \ + "${choices[@]}" 2>"$dialog_output"; then + return 0 + fi + selected=$(cat -- "$dialog_output") || return 0 + value=$selected + else + minimum=$(cfr_read_single_line "$setting/min_value") || return 0 + maximum=$(cfr_read_single_line "$setting/max_value") || return 0 + step=$(cfr_read_single_line "$setting/scalar_increment") || return 0 + if ! whiptail_type normal --title "$(cfr_read_single_line "$setting/display_name")" \ + --inputbox "Current: $current\nDefault: $default\nRange: $minimum-$maximum, step $step" \ + 0 100 "$current" 2>"$dialog_output"; then + return 0 + fi + value=$(cat -- "$dialog_output") || return 0 + cfr_number_valid "$value" "$minimum" "$maximum" "$step" || { + cfr_error 'The number is outside the firmware-provided range.' + return 0 + } + fi + + display=$(cfr_read_single_line "$setting/display_name") || return 0 + pending_text='pending_reboot will be checked after the write.' + if [ -e "$cfr_root/pending_reboot" ]; then + pending=$(cfr_read_single_line "$cfr_root/pending_reboot" || true) + case "$pending" in + 0) pending_text='pending_reboot currently reports no reboot required.' ;; + 1) pending_text='pending_reboot currently reports that a reboot may be required.' ;; + *) pending_text='pending_reboot is unavailable or malformed; it will be checked after the write.' ;; + esac + fi + confirmation=$(printf '%s\n\nCurrent: %s\nRequested: %s\n\n%s\n\nWrite this firmware setting?' \ + "$display" "$current" "$value" "$pending_text") + if ! whiptail_type normal --title 'Confirm Firmware Setting' \ + --yesno "$confirmation" 0 80; then + return 0 + fi + + if ! printf '%s\n' "$value" >"$setting/current_value"; then + cfr_error 'The firmware rejected the setting write.' + return 0 + fi + + current=$(cfr_read_single_line "$setting/current_value") || { + cfr_error 'The setting disappeared after the write.' + return 0 + } + [ "$current" = "$value" ] || { + cfr_error 'Firmware did not retain the requested value.' + return 0 + } + + if [ -e "$cfr_root/pending_reboot" ]; then + local pending + pending=$(cfr_read_single_line "$cfr_root/pending_reboot") || { + cfr_error 'The pending-reboot state is malformed.' + return 0 + } + case "$pending" in + 0) ;; + 1) cfr_info 'The setting was stored. Firmware reports that a reboot is pending.' ;; + *) cfr_error 'The pending-reboot state is malformed.' ;; + esac + fi +} + +[ -d "$cfr_root" ] || exit 0 +declare -F whiptail_type >/dev/null 2>&1 || exit 1 + +while true; do + setting=$(cfr_choose_setting) || exit 0 + cfr_edit_setting "$setting" +done diff --git a/initrd/bin/gui-init-basic.sh b/initrd/bin/gui-init-basic.sh index 51e06691d..ea6d3de70 100755 --- a/initrd/bin/gui-init-basic.sh +++ b/initrd/bin/gui-init-basic.sh @@ -105,13 +105,25 @@ show_main_menu() show_options_menu() { TRACE_FUNC + local -a options_menu + options_menu=( + 'b' ' Boot Options -->' + 'c' ' Change configuration settings -->' + 'f' ' Flash/Update the BIOS -->' + ) + if [ -r /etc/heads-cfr-enabled ] && + [ -d /sys/class/firmware-attributes/coreboot-cfr/attributes ]; then + options_menu+=( + 'e' ' Coreboot Firmware Settings -->' + ) + fi + options_menu+=( + 'x' ' Exit to recovery shell' + 'r' ' <-- Return to main menu' + ) whiptail_type $BG_COLOR_MAIN_MENU --title "$CONFIG_BRAND_NAME Basic Options" \ --menu "" 0 80 10 \ - 'b' ' Boot Options -->' \ - 'c' ' Change configuration settings -->' \ - 'f' ' Flash/Update the BIOS -->' \ - 'x' ' Exit to recovery shell' \ - 'r' ' <-- Return to main menu' \ + "${options_menu[@]}" \ 2>/tmp/whiptail || recovery "GUI menu failed" option=$(cat /tmp/whiptail) @@ -122,6 +134,9 @@ show_options_menu() c ) config-gui.sh ;; + e ) + /bin/cfr-settings.sh + ;; f ) flash-gui.sh ;; diff --git a/initrd/bin/gui-init.sh b/initrd/bin/gui-init.sh index ce03d953b..f796da063 100755 --- a/initrd/bin/gui-init.sh +++ b/initrd/bin/gui-init.sh @@ -628,22 +628,36 @@ show_main_menu() { show_options_menu() { TRACE_FUNC + local cfr_available='' + if [ -r /etc/heads-cfr-enabled ] && + [ -d /sys/class/firmware-attributes/coreboot-cfr/attributes ]; then + cfr_available='y' + fi + local -a options_menu + options_menu=( + 'b' ' Boot Options -->' + 't' ' TPM/TOTP/HOTP Options -->' + 'i' ' Investigate integrity discrepancies -->' + 'h' ' Change system time' + 'u' ' Update checksums and sign all files in /boot' + 'c' ' Change configuration settings -->' + 'f' ' Flash/Update the BIOS -->' + 'g' ' GPG Options -->' + 'F' ' OEM Factory Reset / Re-Ownership -->' + 'C' ' Reencrypt LUKS container -->' + 'P' ' Change LUKS Disk Recovery Key passphrase ->' + 'R' ' Check/Update file hashes on root disk -->' + ) + [ -n "$cfr_available" ] && options_menu+=( + 'e' ' Coreboot Firmware Settings -->' + ) + options_menu+=( + 'x' ' Exit to recovery shell' + 'r' ' <-- Return to main menu' + ) whiptail_type $BG_COLOR_MAIN_MENU --title "$CONFIG_BRAND_NAME Options" \ --menu "" 0 80 10 \ - 'b' ' Boot Options -->' \ - 't' ' TPM/TOTP/HOTP Options -->' \ - 'i' ' Investigate integrity discrepancies -->' \ - 'h' ' Change system time' \ - 'u' ' Update checksums and sign all files in /boot' \ - 'c' ' Change configuration settings -->' \ - 'f' ' Flash/Update the BIOS -->' \ - 'g' ' GPG Options -->' \ - 'F' ' OEM Factory Reset / Re-Ownership -->' \ - 'C' ' Reencrypt LUKS container -->' \ - 'P' ' Change LUKS Disk Recovery Key passphrase ->' \ - 'R' ' Check/Update file hashes on root disk -->' \ - 'x' ' Exit to recovery shell' \ - 'r' ' <-- Return to main menu' \ + "${options_menu[@]}" \ 2>/tmp/whiptail || recovery "GUI menu failed" option=$(cat /tmp/whiptail) @@ -666,6 +680,9 @@ show_options_menu() { c) config-gui.sh ;; + e) + /bin/cfr-settings.sh + ;; f) flash-gui.sh ;; diff --git a/tests/cfr/README.md b/tests/cfr/README.md new file mode 100644 index 000000000..a21748f18 --- /dev/null +++ b/tests/cfr/README.md @@ -0,0 +1,15 @@ +# CFR settings fixture + +`test_cfr_settings.sh` runs the production `cfr-settings.sh` entry point against +a temporary sysfs-shaped tree. Its whiptail stub drives enum and numeric writes, +changes a dependent attribute's writability, presents a read-only attribute, +removes attributes during selection, rejects malformed enum and unsigned +32-bit numeric metadata, and exercises an actual failed write through +`/dev/full`. The +fixture starts with `pending_reboot=1`, so a successful authoritative write also +checks the aggregate pending-reboot report. + +This is a host UI/ABI fixture only. It does not test the Linux +firmware-attributes driver, coreboot CFR parsing, EFI variables, SMMSTORE, or +the SMI/APM apply path. Production board policy and runtime apply callbacks +require hardware validation. diff --git a/tests/cfr/test_cfr_settings.sh b/tests/cfr/test_cfr_settings.sh new file mode 100755 index 000000000..f4c937481 --- /dev/null +++ b/tests/cfr/test_cfr_settings.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +set -e -o pipefail + +repo_root=$(cd "$(dirname "$0")/../.." && pwd) +test_root=$(mktemp -d "$repo_root/.cfr-test.XXXXXX") +trap 'rm -rf "$test_root"' EXIT +attributes=$test_root/sys/class/firmware-attributes/coreboot-cfr/attributes +mkdir -p "$attributes" + +printf '%s\n' '#!/bin/bash' >"$test_root/functions.sh" + +make_enum() { + local name=$1 display=$2 current=$3 default=$4 values=$5 mode=${6:-644} + local dir=$attributes/$name + mkdir -p "$dir" + printf '%s\n' enumeration >"$dir/type" + printf '%s\n' "$display" >"$dir/display_name" + printf '%s\n' "$current" >"$dir/current_value" + printf '%s\n' "$default" >"$dir/default_value" + printf '%s\n' "$values" >"$dir/possible_values" + chmod "$mode" "$dir/current_value" +} + +make_integer() { + local name=$1 display=$2 current=$3 default=$4 minimum=$5 maximum=$6 step=$7 mode=${8:-644} + local dir=$attributes/$name + mkdir -p "$dir" + printf '%s\n' integer >"$dir/type" + printf '%s\n' "$display" >"$dir/display_name" + printf '%s\n' "$current" >"$dir/current_value" + printf '%s\n' "$default" >"$dir/default_value" + printf '%s\n' "$minimum" >"$dir/min_value" + printf '%s\n' "$maximum" >"$dir/max_value" + printf '%s\n' "$step" >"$dir/scalar_increment" + chmod "$mode" "$dir/current_value" +} + +make_enum mode 'Performance mode' 'Balanced mode' 'Balanced mode' 'Balanced mode;Performance mode' +make_enum feature 'Optional feature' off off 'off;on' 444 +make_integer number 'Fan limit' 50 50 10 100 5 +make_enum readonly 'Read only' locked locked locked 444 +make_enum missing 'Disappearing setting' old old 'old;new' +make_enum error 'Write error setting' old old 'old;new' +make_enum malformed 'Malformed setting' A A 'A;;B' +make_enum trailing_empty 'Trailing empty enum' A A 'A;' +make_enum trailing_lines 'Trailing metadata lines' A A 'A;B' +printf 'enumeration\n\n' >"$attributes/trailing_lines/type" +make_enum invalid_default 'Invalid default setting' A C 'A;B' +make_integer expression_range 'Expression range' 5 5 '1+2' 10 1 +make_integer oversized_range 'Oversized range' 5 5 0 99999999999999999999 1 +make_enum inactive 'Inactive setting' off off 'off;on' +printf '%s\n' inactive >"$attributes/inactive/flags" +make_enum suppressed 'Suppressed setting' off off 'off;on' +printf '%s\n' suppressed >"$attributes/suppressed/flags" +make_enum flagged_readonly 'Flagged read only' off off 'off;on' +printf '%s\n' readonly >"$attributes/flagged_readonly/flags" +make_enum flagged_volatile 'Flagged volatile' off off 'off;on' +printf '%s\n' volatile >"$attributes/flagged_volatile/flags" +make_enum cancelled 'Cancelled setting' old old 'old;new' +printf '%s\n' 1 >"$attributes/pending_reboot" +printf '%s\n' 0 >"$test_root/state" +chmod -R a+rwX "$test_root" +chmod 444 "$attributes/feature/current_value" "$attributes/readonly/current_value" + +cat >"$test_root/gui_functions.sh" <<'EOF' +#!/bin/bash +whiptail_type() { "${WHIPTAIL_BIN:?}" "$@"; } +whiptail_error() { "${WHIPTAIL_BIN:?}" "$@"; } +EOF + +cat >"$test_root/whiptail" <<'EOF' +#!/bin/bash +set -e +root=${CFR_TEST_ROOT:?} +state=${CFR_TEST_STATE:?} +count=$(cat "$state") +count=$((count + 1)) +printf '%s\n' "$count" >"$state" +printf '%s\t%s\n' "$count" "$*" >>"$root/whiptail.log" +kind= +for arg in "$@"; do + case "$arg" in + --menu) kind=menu ;; + --inputbox) kind=input ;; + --yesno) kind=yesno ;; + esac +done +if [ "$kind" = menu ]; then + case "$count" in + 1) + # fbwhiptail writes terminal cursor controls to stdout. + printf '\033[?25l\033[?25h' + case " $* " in + *' inactive '*|*' suppressed '*|*' malformed '*|*' trailing_empty '*|\ + *' trailing_lines '*|\ + *' invalid_default '*|*' expression_range '*|*' oversized_range '*) exit 1 ;; + esac + printf '%s\n' mode >&2 + ;; + 2) printf '%s\n' 'Performance mode' >&2 ;; + 5) printf '%s\n' number >&2 ;; + 9) printf '%s\n' cancelled >&2 ;; + 10) printf '%s\n' new >&2 ;; + 12) printf '%s\n' readonly >&2 ;; + 14) rm -rf "$root/sys/class/firmware-attributes/coreboot-cfr/attributes/missing"; printf '%s\n' missing >&2 ;; + 16) printf '%s\n' error >&2 ;; + 17) printf '%s\n' new >&2 ;; + 20) printf '%s\n' flagged_volatile >&2 ;; + *) exit 1 ;; + esac +elif [ "$kind" = input ]; then + printf '%s\n' 75 >&2 +elif [ "$kind" = yesno ]; then + printf '%s' "$*" >"$root/last_confirmation" + case "$count" in + 3|7) exit 0 ;; + 18) + rm -f "$root/sys/class/firmware-attributes/coreboot-cfr/attributes/error/current_value" + ln -s /dev/full "$root/sys/class/firmware-attributes/coreboot-cfr/attributes/error/current_value" + exit 0 + ;; + 11) exit 1 ;; + *) exit 1 ;; + esac +fi +case " $* " in + *' The firmware rejected the setting write. '*) + rm -f "$root/sys/class/firmware-attributes/coreboot-cfr/attributes/error/current_value" + printf '%s\n' old >"$root/sys/class/firmware-attributes/coreboot-cfr/attributes/error/current_value" + printf '%s\n' seen >"$root/write_error_seen" + ;; +esac +if [ "$count" -eq 2 ]; then + chmod 644 "$root/sys/class/firmware-attributes/coreboot-cfr/attributes/feature/current_value" + printf '%s\n' on >"$root/sys/class/firmware-attributes/coreboot-cfr/attributes/feature/current_value" + chmod 644 "$root/sys/class/firmware-attributes/coreboot-cfr/attributes/feature/current_value" +fi +exit 0 +EOF +chmod +x "$test_root/whiptail" + +WHIPTAIL_BIN="$test_root/whiptail" CFR_TEST_ROOT="$test_root" CFR_TEST_STATE="$test_root/state" \ + "$repo_root/initrd/bin/cfr-settings.sh" --test-root "$test_root" + +[ "$(cat "$attributes/mode/current_value")" = 'Performance mode' ] +[ "$(cat "$attributes/number/current_value")" = 75 ] +[ "$(cat "$attributes/feature/current_value")" = on ] +[ "$(cat "$attributes/cancelled/current_value")" = old ] +[ "$(grep -c '^Current: old$' "$test_root/last_confirmation")" -eq 1 ] +[ "$(grep -c '^Requested: new$' "$test_root/last_confirmation")" -eq 1 ] +[ -w "$attributes/feature/current_value" ] +[ -e "$attributes/malformed/current_value" ] +[ -e "$attributes/trailing_empty/current_value" ] +[ -e "$attributes/trailing_lines/current_value" ] +[ -e "$attributes/invalid_default/current_value" ] +[ -e "$attributes/expression_range/current_value" ] +[ -e "$attributes/oversized_range/current_value" ] +[ -e "$test_root/write_error_seen" ] || { + cat "$test_root/whiptail.log" >&2 + false +} +[ "$(cat "$attributes/error/current_value")" = old ] +[ "$(cat "$attributes/flagged_volatile/current_value")" = off ] +grep -q 'flagged_volatile.*\[read-only\] \[volatile\]' "$test_root/whiptail.log" +grep -q 'Firmware reports that a reboot is pending.' "$test_root/whiptail.log" +printf '%s\n' 'CFR settings fixture passed'