--force-menu and LoaderConfigTimeout=menu-force only stopped --boot and
LoaderEntryOneShot from skipping the menu. The timeout was left as is,
so with a zero timeout the menu booted the default entry without being
drawn, and menu-force counted down and booted the default entry, where
the Boot Loader Interface says it should wait for the user.
Menus now take an optional timeout. menu-force waits for the user, and
--force-menu keeps the configured timeout unless it is zero, in which
case it waits for the user too.
The basic menu listed entries as [10], [11] and so on, but only read a
single digit, so those entries could never be selected.
Keep reading digits while another digit could still select a different
entry, and boot once it can't or when enter is pressed. With ten
entries or fewer, a single digit boots the entry right away as before.
Every key that didn't select an entry, like enter or an arrow key, made
the basic menu wait for the full timeout again. Stop the countdown on
any key instead, like the simple menu, so the menu waits for the user
once they start using it.
Some firmware signals the key event without a key being available. The
simple menu then skipped the countdown for that tick, and the basic menu
started waiting for the full timeout again, so firmware that kept
signaling the key event would never boot the default entry.
Read the key inside the wait, and when no key is available keep waiting
on the same timer.
The basic menu set a single timer for the whole timeout, and converting
a timeout of more than about 1.8e12 seconds into 100ns timer units
panics. The timeout can come from --menu-timeout or from
LoaderConfigTimeout, which persists, so the panic would repeat on every
boot. Limit a single timer to u32::MAX seconds.
The TCG2 specification says HashLogExtendEvent returns VOLUME_FULL when
the PCR was extended but the event log had no room for the event. This
was treated as a failure, which aborted loading the configuration and
booting Edera. Log a warning and continue instead.
Any error opening the TCG2 protocol, reading its capability, or calling
GetActivePcrBanks aborted boot, although the value is only reported in
LoaderTpm2ActivePcrBanks. TPM firmware bugs are common, so log a
warning and report zero banks instead, as systemd-boot does.
PlatformTimer::start panicked if the timer frequency was zero. On
aarch64 the frequency comes from CNTFRQ_EL0, which some firmware leaves
unprogrammed, and on x86_64 a Stall that returns immediately measures
zero. The timer is only used for bootloader interface timestamps, so
log a warning and report zero durations instead.
An unknown option, an unexpected positional argument, or an invalid
option value made Sprout exit with ABORTED, so a typo in a boot entry or
junk passed by the firmware made the machine unbootable.
Log a warning and drop the argument the parser rejected, then parse the
rest again. Invalid --menu-timeout and --menu-style values are ignored
with a warning too, which also replaces the misleading "invalid integer"
error for an unknown menu style.
Load options were decoded with CStr16::from_u16_with_nul, which needs an
even byte count and exactly one null at the very end. Firmware that
passed an odd size, left off the null terminator, or padded the string
with extra nulls made argument parsing fail, and Sprout refused to boot.
Decode the raw bytes as little-endian UTF-16 up to the first null
instead, replacing anything that isn't valid.
Add linux-initrd-chain to the chainload action and initrd-chain to the
edera action. Each file in the list is read in order and concatenated
into a single initrd. The chain can't be combined with the single
initrd option on the same action.
BLS entries with multiple initrds, either as repeated initrd lines or
as multiple paths on one line, previously only loaded the last one.
Parse all of them and set them in the initrd-0 through initrd-7 slots,
which the autoconfigured BLS action now chains. Unused slots stamp to
the filesystem root and are skipped. Entries with more than 8 initrds
are skipped with a warning.
The boot menu is now a BootMenu trait with two styles. The basic menu is
the existing one that prints entries and selects them by number. The new
simple menu is full-screen: entries are centered and highlighted, the
arrow keys, home, end, page up and page down move the selection, enter
boots it, and any key stops the countdown.
The style is picked with --menu-style or menu-style in [options], and
defaults to simple. The timer code used to wait for a key is shared
between both menus in menu.rs.
If the device path or filesystem protocol of any detected filesystem
could not be opened, autoconfiguration failed entirely and boot was
aborted. Log a warning and skip that filesystem instead, so the other
filesystems are still autoconfigured.
The late phase is documented to run after the entry is chosen but
before its actions are executed, but it ran before the boot menu was
shown. Run it after the entry is selected and reported to the
bootloader interface, right before executing the entry's actions.
compare_versions_optional treated a missing version as newer than any
version, so among entries with equal sort-key and machine-id, an entry
without a version sorted first. Treat a missing version as the oldest,
matching systemd's strverscmp_improved.
Sort keys were compared as Options, so an entry without a sort-key
sorted before one with a sort-key, and machine-id and version were
compared even when neither entry had a sort-key. Per the Boot Loader
Specification and systemd-boot, an entry with a sort-key sorts first,
and machine-id and version are only used when both entries have one.
Numeric segments were parsed into a saturating u64, so any two numbers
above u64::MAX compared equal. Compare the digits instead, by length
and then digit by digit, as systemd does.
When only one version had a digit at the current position, both sides
were parsed as numbers and the letter side read as 0. "0" then compared
equal to "a" and the result depended on what followed. Per the
specification, the side with digits is newer.
The '~' check ran after the end-of-string check, so "1~rc1" compared as
newer than "1". Per the UAPI version format specification, '~' must be
handled first so a pre-release sorts older than the release it
precedes. An empty string is kept as the oldest version, matching
systemd's strverscmp_improved.
A single BLS entry that could not be read, was not valid UTF-8, or
failed to parse aborted entry generation and therefore the whole boot,
contrary to the generator's documented best-effort behavior. Log a
warning and skip the entry instead.
Autoconfigure detects BLS when either \loader\loader.conf or
\loader\entries exists, but the BLS generator required the entries
directory and failed the boot without it. This broke ESPs that only
have a loader.conf, such as UKI-only systemd-boot setups. Generate no
entries when the directory is missing.
Autoconfigured BLS actions use "$root\\$initrd" as the initrd path.
For BLS entries without an initrd line this stamps to the root of the
filesystem, and Sprout tried to read that directory as the initrd,
failing the boot. Treat a path that resolves to no file as no initrd.
The chainload action held the child image's LoadedImage protocol open
exclusively while calling start_image. Any image that opens its own
LoadedImage protocol exclusively, such as another uefi-rs application
or Sprout itself, then failed with ACCESS_DENIED. Scope the protocol to
setting the load options.
The firmware passes BootPolicy as a UEFI BOOLEAN (a u8). Declaring it
as a Rust bool in the extern signature is undefined behavior for any
value other than 0 or 1. Use uefi_raw::Boolean instead.
If the security hook was installed while already installed, the
protocols contained Sprout's own handlers, which were then saved as the
"original" handlers. A verification failure would then call back into
the hook forever. Keep the existing saved state in that case.
EFI string variables written without a trailing NUL, or with extra NUL
padding, failed to decode and were silently treated as unset. Decode up
to the first NUL and terminate the string ourselves, as systemd-boot
does.
Because empty values now decode to an empty string rather than failing,
treat empty LoaderEntryDefault and LoaderEntryOneShot values as unset so
an empty one-shot entry doesn't fail to match any entry.
A LoaderConfigTimeout or LoaderConfigTimeoutOneShot value that is not a
number or a known keyword made Sprout abort boot. Log a warning and
treat the variable as unset instead, matching systemd-boot.
The hook was installed before retaining the shim protocol and reading
the image, so an error in either step returned without uninstalling
it. Install it immediately before LoadImage instead.
Some firmware signals the key event without a key being available.
This previously aborted boot with "no key was pressed"; treat it as a
no-op and keep waiting instead.
File names are lowercased before matching kernel prefixes, but the
arm64 prefix was "Image", so it could never match and arm64 kernels
were not autoconfigured. Use a lowercase "image" prefix.
Extend PCR 9 with the initrd, PCR 11 with the dom0 kernel,
and PCR 12 with the Xen options and dom0 cmdline.
Signed-off-by: Luca Di Maio <[email protected]>