From 78ca6cfedbab7789982009e6c7e0a6bd24849221 Mon Sep 17 00:00:00 2001 From: Vladislav Nepogodin Date: Sun, 28 Jul 2024 16:25:30 +0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=20gucc:=20add=20refind=20installat?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 2 + gucc/include/gucc/bootloader.hpp | 14 + gucc/src/bootloader.cpp | 69 ++ gucc/tests/files/refind.conf-sample | 749 ++++++++++++++++++ gucc/tests/meson.build | 8 + gucc/tests/unit-refind_extra_kern_strings.cpp | 79 ++ 6 files changed, 921 insertions(+) create mode 100644 gucc/tests/files/refind.conf-sample create mode 100644 gucc/tests/unit-refind_extra_kern_strings.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4957a50..29a1deb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,6 +48,7 @@ jobs: ./gucc/tests/test-package_profiles ./gucc/tests/test-btrfs ./gucc/tests/test-refind_config_gen + ./gucc/tests/test-refind_extra_kern_strings shell: bash build-cmake_withoutdev: name: Build with CMake (DEVENV OFF) @@ -102,6 +103,7 @@ jobs: ./gucc/tests/test-package_profiles ./gucc/tests/test-btrfs ./gucc/tests/test-refind_config_gen + ./gucc/tests/test-refind_extra_kern_strings shell: bash build-meson_withoutdev: name: Build with Meson (DEVENV OFF) diff --git a/gucc/include/gucc/bootloader.hpp b/gucc/include/gucc/bootloader.hpp index 389d5d4..910176b 100644 --- a/gucc/include/gucc/bootloader.hpp +++ b/gucc/include/gucc/bootloader.hpp @@ -66,6 +66,14 @@ struct GrubInstallConfig final { std::optional bootloader_id{}; }; +struct RefindInstallConfig final { + bool is_removable{}; + std::string_view root_mountpoint; + std::string_view boot_mountpoint; + const std::vector& extra_kernel_versions; + const std::vector& kernel_params; +}; + // Generate grub config into string auto gen_grub_config(const GrubConfig& grub_config) noexcept -> std::string; @@ -81,6 +89,12 @@ auto install_systemd_boot(std::string_view root_mountpoint, std::string_view efi // Generate refind config into system auto gen_refind_config(const std::vector& kernel_params) noexcept -> std::string; +// Edit refind config with provided extra kernel strings +auto refind_write_extra_kern_strings(std::string_view file_path, const std::vector& extra_kernel_versions) noexcept -> bool; + +// Installs & configures refind on system +auto install_refind(const RefindInstallConfig& refind_install_config) noexcept -> bool; + } // namespace gucc::bootloader #endif // BOOTLOADER_HPP diff --git a/gucc/src/bootloader.cpp b/gucc/src/bootloader.cpp index 364371f..5eddafb 100644 --- a/gucc/src/bootloader.cpp +++ b/gucc/src/bootloader.cpp @@ -2,6 +2,7 @@ #include "gucc/file_utils.hpp" #include "gucc/initcpio.hpp" #include "gucc/io_utils.hpp" +#include "gucc/kernel_params.hpp" #include "gucc/string_utils.hpp" #include // for ranges::* @@ -266,4 +267,72 @@ auto gen_refind_config(const std::vector& kernel_params) noexcept - return refind_config; } +auto refind_write_extra_kern_strings(std::string_view file_path, const std::vector& extra_kernel_versions) noexcept -> bool { + auto&& file_content = file_utils::read_whole_file(file_path); + if (file_content.empty() || extra_kernel_versions.empty()) { + return false; + } + + std::string&& result = file_content | std::ranges::views::split('\n') + | std::ranges::views::transform([&](auto&& rng) { + /* clang-format off */ + auto&& line = std::string_view(&*rng.begin(), static_cast(std::ranges::distance(rng))); + if (line.starts_with("#extra_kernel_version_strings"sv) || line.starts_with("extra_kernel_version_strings"sv)) { + return fmt::format(FMT_COMPILE("extra_kernel_version_strings {}"), utils::join(extra_kernel_versions, ',')); + } + /* clang-format on */ + return std::string{line.data(), line.size()}; + }) + | std::ranges::views::join_with('\n') + | std::ranges::to(); + + return file_utils::create_file_for_overwrite(file_path, result); +} + +auto install_refind(const RefindInstallConfig& refind_install_config) noexcept -> bool { + // Get refind install cmd + const auto& refind_install_cmd = [](auto&& root_path, bool is_removable_drive) -> std::string { + std::string result = fmt::format(FMT_COMPILE("refind-install --root {}"), root_path); + if (is_removable_drive) { + result += " --alldrivers --yes"; + } + result += " &>>/tmp/cachyos-install.log"; + return result; + }(refind_install_config.root_mountpoint, refind_install_config.is_removable); + + // Install refind on the system + if (!utils::exec_checked(refind_install_cmd)) { + spdlog::error("Failed to install refind on path {} with: {}", refind_install_config.root_mountpoint, refind_install_cmd); + return false; + } + + // Adjust mkinitcpio for removable drive + if (refind_install_config.is_removable) { + const auto& initcpio_filename = fmt::format(FMT_COMPILE("{}/etc/mkinitcpio.conf"), refind_install_config.root_mountpoint); + + // Remove autodetect hook + auto initcpio = detail::Initcpio{initcpio_filename}; + initcpio.remove_hook("autodetect"); + spdlog::info("'Autodetect' hook was removed"); + } + + // Generate refind configuration + const auto& refind_config_content = bootloader::gen_refind_config(refind_install_config.kernel_params); + + // Write generated config to system + const auto& refind_config_path = fmt::format(FMT_COMPILE("{}/boot/refind_linux.conf"), refind_install_config.root_mountpoint); + if (!file_utils::create_file_for_overwrite(refind_config_path, refind_config_content)) { + spdlog::error("Failed to open refind config for writing {}", refind_config_path); + return false; + } + + // handle extra kernel version strings in {efi boot partition}/EFI/refind/refind.conf + const auto& extra_refind_config_path = fmt::format(FMT_COMPILE("{}/EFI/refind/refind.conf"), refind_install_config.boot_mountpoint); + if (!bootloader::refind_write_extra_kern_strings(extra_refind_config_path, refind_install_config.extra_kernel_versions)) { + spdlog::error("Failed to write extra kernel strings into {}", refind_config_path); + return false; + } + return true; +} + } // namespace gucc::bootloader diff --git a/gucc/tests/files/refind.conf-sample b/gucc/tests/files/refind.conf-sample new file mode 100644 index 0000000..2ef1fea --- /dev/null +++ b/gucc/tests/files/refind.conf-sample @@ -0,0 +1,749 @@ +# +# refind.conf +# Configuration file for the rEFInd boot menu +# + +# Timeout in seconds for the main menu screen. Setting the timeout to 0 +# disables automatic booting (i.e., no timeout). Setting it to -1 causes +# an immediate boot to the default OS *UNLESS* a keypress is in the buffer +# when rEFInd launches, in which case that keypress is interpreted as a +# shortcut key. If no matching shortcut is found, rEFInd displays its +# menu with no timeout. +# +timeout 20 + +# Set the logging level. When set to 0, rEFInd does not log its actions. +# When set to 1 or above, rEFInd creates a file called refind.log in +# its home directory on the ESP and records information about what it's +# doing. Higher values record more information, up to a maximum of 4. +# This token should be left at the default of 0 except when debugging +# problems. +# Default value is 0 +# +#log_level 1 + +# Normally, when the timeout period has passed, rEFInd boots the +# default_selection. If the following option is uncommented, though, +# rEFInd will instead attempt to shut down the computer. +# CAUTION: MANY COMPUTERS WILL INSTEAD HANG OR REBOOT! Macs and more +# recent UEFI-based PCs are most likely to work with this feature. +# Default value is true +# +#shutdown_after_timeout + +# Whether to store rEFInd's rEFInd-specific variables in NVRAM (1, true, +# or on) or in files in the "vars" subdirectory of rEFInd's directory on +# disk (0, false, or off). Using NVRAM works well with most computers; +# however, it increases wear on the motherboard's NVRAM, and if the EFI +# is buggy or the NVRAM is old and worn out, it may not work at all. +# Storing variables on disk is a viable alternative in such cases, or +# if you want to minimize wear and tear on the NVRAM; however, it won't +# work if rEFInd is stored on a filesystem that's read-only to the EFI +# (such as an HFS+ volume), and it increases the risk of filesystem +# damage. Note that this option affects ONLY rEFInd's own variables, +# such as the PreviousBoot, HiddenTags, HiddenTools, and HiddenLegacy +# variables. It does NOT affect Secure Boot or other non-rEFInd +# variables. +# Default is true +# +use_nvram false + +# Screen saver timeout; the screen blanks after the specified number of +# seconds with no keyboard input. The screen returns after most keypresses +# (unfortunately, not including modifier keys such as Shift, Control, Alt, +# or Option). Setting a value of "-1" causes rEFInd to start up with its +# screen saver active. The default is 0, which disables the screen saver. +# +#screensaver 300 + +# Hide user interface elements for personal preference or to increase +# security: +# banner - the rEFInd title banner (built-in or loaded via "banner") +# label - boot option text label in the menu +# singleuser - remove the submenu options to boot macOS in single-user +# or verbose modes; affects ONLY macOS +# safemode - remove the submenu option to boot macOS in "safe mode" +# hwtest - the submenu option to run Apple's hardware test +# arrows - scroll arrows on the OS selection tag line +# hints - brief command summary in the menu +# editor - the options editor (+, F2, or Insert on boot options menu) +# badges - device-type badges for boot options +# all - all of the above +# Default is none of these (all elements active) +# +#hideui singleuser +#hideui all + +# Set the name of a subdirectory in which icons are stored. Icons must +# have the same names they have in the standard directory. The directory +# name is specified relative to the main rEFInd binary's directory. If +# an icon can't be found in the specified directory, an attempt is made +# to load it from the default directory; thus, you can replace just some +# icons in your own directory and rely on the default for others. +# Icon files may be in any supported format -- ICNS (*.icns), BMP (*.bmp), +# PNG (*.png), or JPEG (*.jpg or *.jpeg); however, rEFInd's BMP and JPEG +# implementations do not support transparency, which is highly desirable +# in icons. +# Default is "icons". +# +#icons_dir myicons +#icons_dir icons/snowy + +# Use a custom title banner instead of the rEFInd icon and name. The file +# path is relative to the directory where refind.efi is located. The color +# in the top left corner of the image is used as the background color +# for the menu screens. Currently uncompressed BMP images with color +# depths of 24, 8, 4 or 1 bits are supported, as well as PNG and JPEG +# images. (ICNS images can also be used, but ICNS has limitations that +# make it a poor choice for this purpose.) PNG and JPEG support is +# limited by the underlying libraries; some files, like progressive JPEGs, +# will not work. +# +#banner hostname.bmp +#banner mybanner.jpg +#banner icons/snowy/banner-snowy.png + +# Specify how to handle banners that aren't exactly the same as the screen +# size: +# noscale - Crop if too big, show with border if too small +# fillscreen - Fill the screen +# Default is noscale +# +#banner_scale fillscreen + +# Icon sizes. All icons are square, so just one value is specified. The +# big icons are used for OS selectors in the first row and the small +# icons are used for tools on the second row. Drive-type badges are 1/4 +# the size of the big icons. Legal values are 32 and above. If the icon +# files do not hold icons of the proper size, the icons are scaled to +# the specified size. The default values are 48 and 128 for small and +# big icons, respectively. +# +#small_icon_size 96 +#big_icon_size 256 + +# Custom images for the selection background. There is a big one (144 x 144) +# for the OS icons, and a small one (64 x 64) for the function icons in the +# second row. If only a small image is given, that one is also used for +# the big icons by stretching it in the middle. If only a big one is given, +# the built-in default will be used for the small icons. If an image other +# than the optimal size is specified, it will be scaled in a way that may +# be ugly. +# +# Like the banner option above, these options take a filename of an +# uncompressed BMP, PNG, JPEG, or ICNS image file with a color depth of +# 24, 8, 4, or 1 bits. The PNG or ICNS format is required if you need +# transparency support (to let you "see through" to a full-screen banner). +# +#selection_big selection-big.bmp +#selection_small selection-small.bmp + +# Set the font to be used for all textual displays in graphics mode. +# For best results, the font must be a PNG file with alpha channel +# transparency. It must contain ASCII characters 32-126 (space through +# tilde), inclusive, plus a glyph to be displayed in place of characters +# outside of this range, for a total of 96 glyphs. Only monospaced fonts +# are supported. Fonts may be of any size, although large fonts can +# produce display irregularities. +# The default is rEFInd's built-in font, Luxi Mono Regular 12 point. +# +#font myfont.png + +# Use text mode only. When enabled, this option forces rEFInd into text mode. +# Passing this option a "0" value causes graphics mode to be used. Pasing +# it no value or any non-0 value causes text mode to be used. +# Default is to use graphics mode. +# +#textonly + +# Set the EFI text mode to be used for textual displays. This option +# takes a single digit that refers to a mode number. Mode 0 is normally +# 80x25, 1 is sometimes 80x50, and higher numbers are system-specific +# modes. Mode 1024 is a special code that tells rEFInd to not set the +# text mode; it uses whatever was in use when the program was launched. +# If you specify an invalid mode, rEFInd pauses during boot to inform +# you of valid modes. +# CAUTION: On VirtualBox, and perhaps on some real computers, specifying +# a text mode and uncommenting the "textonly" option while NOT specifying +# a resolution can result in an unusable display in the booted OS. +# Default is 1024 (no change) +# +#textmode 2 + +# Set the screen's video resolution. Pass this option one of the following: +# * two integer values, corresponding to the X and Y resolutions +# * one integer value, corresponding to a GOP (UEFI) video mode +# * the string "max", which sets the maximum available resolution +# Note that not all resolutions are supported. On UEFI systems, passing +# an incorrect value results in a message being shown on the screen to +# that effect, along with a list of supported modes. On EFI 1.x systems +# (e.g., Macintoshes), setting an incorrect mode silently fails. On both +# types of systems, setting an incorrect resolution results in the default +# resolution being used. A resolution of 1024x768 usually works, but higher +# values often don't. +# Default is "0 0" (use the system default resolution, usually 800x600). +# +#resolution 1024 768 +#resolution 1440 900 +#resolution 3 +#resolution max + +# Enable touch screen support. If active, this feature enables use of +# touch screen controls (as on tablets). Note, however, that not all +# tablets' EFIs provide the necessary underlying support, so this +# feature may not work for you. If it does work, you should be able +# to launch an OS or tool by touching it. In a submenu, touching +# anywhere launches the currently-selection item; there is, at present, +# no way to select a specific submenu item. This feature is mutually +# exclusive with the enable_mouse feature. If both are uncommented, +# the one read most recently takes precedence. +# +#enable_touch + +# Enable mouse support. If active, this feature enables use of the +# computer's mouse. Note, however, that not all computers' EFIs +# provide the necessary underlying support, so this feature may not +# work for you. If it does work, you should be able to launch an +# OS or tool by clicking it with the mouse pointer. This feature +# is mutually exclusive with the enable_touch feature. If both +# are uncommented, the one read most recently takes precedence. +# +#enable_mouse + +# Size of the mouse pointer, in pixels, per side. +# Default is 16 +# +#mouse_size 16 + +# Speed of mouse tracking. Higher numbers equate to faster +# mouse movement. This option requires that enable_mouse be +# uncommented. +# Legal values are between 1 and 32. Default is 4. +# +#mouse_speed 4 + +# Launch specified OSes in graphics mode. By default, rEFInd switches +# to text mode and displays basic pre-launch information when launching +# all OSes except macOS. Using graphics mode can produce a more seamless +# transition, but displays no information, which can make matters +# difficult if you must debug a problem. Also, on at least one known +# computer, using graphics mode prevents a crash when using the Linux +# kernel's EFI stub loader. You can specify an empty list to boot all +# OSes in text mode. +# Valid options: +# osx - macOS +# linux - A Linux kernel with EFI stub loader +# elilo - The ELILO boot loader +# grub - The GRUB (Legacy or 2) boot loader +# windows - Microsoft Windows +# Default value: osx +# +#use_graphics_for osx,linux + +# Which non-bootloader tools to show on the tools line, and in what +# order to display them: +# shell - the EFI shell (requires external program; see rEFInd +# documentation for details) +# memtest - the memtest86 program, in EFI/tools, EFI/memtest86, +# EFI/memtest, EFI/tools/memtest86, EFI/tools/memtest, +# or a boot loader's directory +# gptsync - the (dangerous) gptsync.efi utility (requires external +# program; see rEFInd documentation for details) +# gdisk - the gdisk partitioning program +# apple_recovery - boots the Apple Recovery HD partition, if present +# windows_recovery - boots an OEM Windows recovery tool, if present +# (see also the windows_recovery_files option) +# mok_tool - makes available the Machine Owner Key (MOK) maintenance +# tool, MokManager.efi, used on Secure Boot systems +# csr_rotate - adjusts Apple System Integrity Protection (SIP) +# policy. Requires "csr_values" to be set. +# install - an option to install rEFInd from the current location +# to another ESP +# bootorder - adjust the EFI's (NOT rEFInd's) boot order +# about - an "about this program" option +# hidden_tags - manage hidden tags +# exit - a tag to exit from rEFInd +# shutdown - shuts down the computer (a bug causes this to reboot +# many UEFI systems) +# reboot - a tag to reboot the computer +# firmware - a tag to reboot the computer into the firmware's +# user interface (ignored on older computers) +# fwupdate - a tag to update the firmware; launches the fwupx64.efi +# (or similar) program +# netboot - launch the ipxe.efi tool for network (PXE) booting +# Default is shell,memtest,gdisk,apple_recovery,windows_recovery,mok_tool,about,hidden_tags,shutdown,reboot,firmware,fwupdate +# To completely disable scanning for all tools, provide a showtools line +# with no options. +# +#showtools shell, bootorder, gdisk, memtest, mok_tool, apple_recovery, windows_recovery, about, hidden_tags, reboot, exit, firmware, fwupdate + +# Additional directories to scan for tools. You may specify a directory +# alone or a volume identifier plus pathname. The default is to scan no +# extra directories, beyond EFI/tools and any directory in which an EFI +# loader is found. +# +#also_scan_tool_dirs EFI/memtest,ESP2:/EFI/tools/memtest86 + +# Tool binaries to be excluded from the tools line, even if the +# general class is specified in showtools. This enables trimming an +# overabundance of tools, as when you see multiple mok_tool entries +# after installing multiple Linux distributions. +# Just as with dont_scan_files, you can specify a filename alone, a +# full pathname, or a volume identifier (filesystem label, partition +# name, or partition GUID) and a full pathname. +# Default is an empty list (nothing is excluded) +# +#dont_scan_tools ESP2:/EFI/ubuntu/mmx64.efi,gptsync_x64.efi + +# Boot loaders that can launch a Windows restore or emergency system. +# These tend to be OEM-specific. +# Default is LRS_ESP:/EFI/Microsoft/Boot/LrsBootmgr.efi +# +#windows_recovery_files LRS_ESP:/EFI/Microsoft/Boot/LrsBootmgr.efi + +# Directories in which to search for EFI drivers. These drivers can +# provide filesystem support, give access to hard disks on plug-in +# controllers, etc. In most cases none are needed, but if you add +# EFI drivers and you want rEFInd to automatically load them, you +# should specify one or more paths here. rEFInd always scans the +# "drivers" and "drivers_{arch}" subdirectories of its own installation +# directory (where "{arch}" is your architecture code); this option +# specifies ADDITIONAL directories to scan. +# Default is to scan no additional directories for EFI drivers +# +#scan_driver_dirs EFI/tools/drivers,drivers + +# Which types of boot loaders to search, and in what order to display them: +# internal - internal EFI disk-based boot loaders +# external - external EFI disk-based boot loaders +# optical - EFI optical discs (CD, DVD, etc.) +# netboot - EFI network (PXE) boot options +# hdbios - BIOS disk-based boot loaders +# biosexternal - BIOS external boot loaders (USB, eSATA, etc.) +# cd - BIOS optical-disc boot loaders +# manual - use stanzas later in this configuration file +# firmware - boot EFI programs set in the firmware's NVRAM +# Note that the legacy BIOS options require firmware support, which is +# not present on all computers. +# The netboot option is experimental and relies on the ipxe.efi and +# ipxe_discover.efi program files. +# On UEFI PCs, default is internal,external,optical,manual +# On Macs, default is internal,hdbios,external,biosexternal,optical,cd,manual +# +#scanfor internal,external,optical,manual,firmware + +# By default, rEFInd relies on the UEFI firmware to detect BIOS-mode boot +# devices. This sometimes doesn't detect all the available devices, though. +# For these cases, uefi_deep_legacy_scan results in a forced scan and +# modification of NVRAM variables on each boot. Adding "0", "off", or +# "false" resets to the default value. This token has no effect on Macs or +# when no BIOS-mode options are set via scanfor. +# Default is unset (or "uefi_deep_legacy_scan false") +# +#uefi_deep_legacy_scan + +# Delay for the specified number of seconds before scanning disks. +# This can help some users who find that some of their disks +# (usually external or optical discs) aren't detected initially, +# but are detected after pressing Esc. +# The default is 0. +# +#scan_delay 5 + +# When scanning volumes for EFI boot loaders, rEFInd always looks for +# macOS's and Microsoft Windows' boot loaders in their normal locations, +# and scans the root directory and every subdirectory of the /EFI directory +# for additional boot loaders, but it doesn't recurse into these directories. +# The also_scan_dirs token adds more directories to the scan list. +# Directories are specified relative to the volume's root directory. This +# option applies to ALL the volumes that rEFInd scans UNLESS you include +# a volume name and colon before the directory name, as in "myvol:/somedir" +# to scan the somedir directory only on the filesystem named myvol. If a +# specified directory doesn't exist, it's ignored (no error condition +# results). The "+" symbol denotes appending to the list of scanned +# directories rather than overwriting that list. +# The default is to scan the "boot" and "@/boot" directories in addition +# to various hard-coded directories. +# +#also_scan_dirs boot,ESP2:EFI/linux/kernels +#also_scan_dirs boot,@/boot +#also_scan_dirs +,@/kernels + +# Partitions (or whole disks, for legacy-mode boots) to omit from scans. +# For EFI-mode scans, you normally specify a volume by its label, which you +# can obtain in an EFI shell by typing "vol", from Linux by typing +# "blkid /dev/{devicename}", or by examining the disk's label in various +# OSes' file browsers. It's also possible to identify a partition by its +# unique GUID (aka its "PARTUUID" in Linux parlance). (Note that this is +# NOT the partition TYPE CODE GUID.) This identifier can be obtained via +# "blkid" in Linux or "diskutil info {partition-id}" in macOS. +# For legacy-mode scans, you can specify any subset of the boot loader +# description shown when you highlight the option in rEFInd. +# The default is "LRS_ESP". +# +#dont_scan_volumes "Recovery HD" + +# Directories that should NOT be scanned for boot loaders. By default, +# rEFInd doesn't scan its own directory, the EFI/tools directory, the +# EFI/memtest directory, the EFI/memtest86 directory, or the +# com.apple.recovery.boot directory. Using the dont_scan_dirs option +# enables you to "blacklist" other directories; but be sure to use "+" +# as the first element if you want to continue blacklisting existing +# directories. You might use this token to keep EFI/boot/bootx64.efi out +# of the menu if that's a duplicate of another boot loader or to exclude +# a directory that holds drivers or non-bootloader utilities provided by +# a hardware manufacturer. If a directory is listed both here and in +# also_scan_dirs, dont_scan_dirs takes precedence. Note that this +# blacklist applies to ALL the filesystems that rEFInd scans, not just +# the ESP, unless you precede the directory name by a filesystem name or +# partition unique GUID, as in "myvol:EFI/somedir" to exclude EFI/somedir +# from the scan on the myvol volume but not on other volumes. +# +#dont_scan_dirs ESP:/EFI/boot,EFI/Dell,EFI/memtest86 + +# Files that should NOT be included as EFI boot loaders (on the +# first line of the display). If you're using a boot loader that +# relies on support programs or drivers that are installed alongside +# the main binary or if you want to "blacklist" certain loaders by +# name rather than location, use this option. Note that this will +# NOT prevent certain binaries from showing up in the second-row +# set of tools. Most notably, various Secure Boot and recovery +# tools are present in this list, but may appear as second-row +# items. +# The file may be specified as a bare name (e.g., "notme.efi"), as +# a complete pathname (e.g., "/EFI/somedir/notme.efi"), or as a +# complete pathname with volume (e.g., "SOMEDISK:/EFI/somedir/notme.efi" +# or 2C17D5ED-850D-4F76-BA31-47A561740082:/EFI/somedir/notme.efi"). +# OS tags hidden via the Delete or '-' key in the rEFInd menu are +# added to this list, but stored in NVRAM. +# The default is shim.efi,shim-fedora.efi,shimx64.efi,PreLoader.efi, +# TextMode.efi,ebounce.efi,GraphicsConsole.efi,MokManager.efi,HashTool.efi, +# HashTool-signed.efi,bootmgr.efi,fb{arch}.efi +# (where "{arch}" is the architecture code, like "x64"). +# If you want to keep these defaults but add to them, be sure to +# specify "+" as the first item in the new list; if you don't, then +# items from the default list are likely to appear. +# +#dont_scan_files shim.efi,MokManager.efi + +# EFI NVRAM Boot#### variables that should NOT be presented as loaders +# when "firmware" is an option to "scanfor". The comma-separated list +# presented here contains strings that are matched against the +# description field -- if a value here is a case-insensitive substring +# of the boot option description, then it will be excluded from the +# boot list. To specify a string that includes a space, enclose it +# in quotes. Specifying "shell" will counteract the automatic +# inclusion of built-in EFI shells. +# +#dont_scan_firmware HARDDISK,shell,"Removable Device" + +# Scan for Linux kernels that lack a ".efi" filename extension. This is +# useful for better integration with Linux distributions that provide +# kernels with EFI stub loaders but that don't give those kernels filenames +# that end in ".efi", particularly if the kernels are stored on a +# filesystem that the EFI can read. When set to "1", "true", or "on", this +# option causes all files in scanned directories with names that begin with +# "vmlinuz", "bzImage", or "kernel" to be included as loaders, even if they +# lack ".efi" extensions. Passing this option a "0", "false", or "off" value +# causes kernels without ".efi" extensions to NOT be scanned. +# Default is "true" -- to scan for kernels without ".efi" extensions. +# +#scan_all_linux_kernels false + +# Support loaders that have been compressed with gzip. +# On x86 and x86-64 platforms, Linux kernels are self-decompressing. +# On ARM64, Linux kernel files are typically compressed with gzip, +# including the EFI stub loader. This makes them unloadable in rEFInd +# unless rEFInd itself uncompresses them. This option enables rEFInd +# to do this. This feature is unnecessary on x86 and x86-64 systems. +# Default is "false" on x86 and x86-64; "true" on ARM64. +# +#support_gzipped_loaders true + +# Combine all Linux kernels in a given directory into a single entry. +# When so set, the kernel with the most recent time stamp will be launched +# by default, and its filename will appear in the entry's description. +# To launch other kernels, the user must press F2 or Insert; alternate +# kernels then appear as options on the sub-menu. +# Default is "true" -- kernels are "folded" into a single menu entry. +# +#fold_linux_kernels false + +# Filename prefixes that indicate a file is a Linux kernel. Files that +# begin with any of these strings are treated as Linux kernels, if they +# are also EFI boot loaders. To include the default string, use "+" +# Default is "vmlinuz,bzImage,kernel", except on ARM64, where it is +# "vmlinuz,Image,kernel". +# +#linux_prefixes vmlinuz,bzImage,kernel +#linux_prefixes +,zImage + +# Comma-delimited list of strings to treat as if they were numbers for the +# purpose of kernel version number detection. These strings are matched on a +# first-found basis; that is, if you want to treat both "linux-lts" and +# "linux" as version strings, they MUST be specified as "linux-lts,linux", +# since if you specify it the other way, both vmlinuz-linux and +# vmlinuz-linux-lts will return with "linux" as the "version string," which +# is not what you'd want. Also, if the kernel or initrd file includes both a +# specified string and digits, the "version string" includes both. For +# instance, "vmlinuz-linux-4.8" would yield a version string of "linux-4.8". +# This option is intended for Arch and other distributions that don't include +# version numbers in their kernel filenames, but may provide other uniquely +# identifying strings for multiple kernels. If this feature causes problems +# (say, if your kernel filename includes "linux" but the initrd filename +# doesn't), be sure this is set to an empty string +# (extra_kernel_version_strings "") or comment out the option to disable it. +# Default is no extra version strings +# +#extra_kernel_version_strings linux-lts,linux + +# Write to systemd EFI variables (currently only LoaderDevicePartUUID) when +# launching Linux via an EFI stub loader, ELILO, or GRUB. This variable, +# when present, causes systemd to mount the ESP at /boot or /efi *IF* either +# directory is empty and nothing else is mounted there. +# Default is "false" +# +#write_systemd_vars true + +# Symlinked loaders will be processed when this setting is set to true. +# These are ignored by default as they may result in undesirable outcomes. +# This token may, however, be useful on Linux setups that provide symbolic +# links in scanned locations that point to kernels in unscanned locations, +# such as some openSUSE installations. +# +#follow_symlinks true + +# Set the maximum number of tags that can be displayed on the screen at +# any time. If more loaders are discovered than this value, rEFInd shows +# a subset in a scrolling list. If this value is set too high for the +# screen to handle, it's reduced to the value that the screen can manage. +# If this value is set to 0 (the default), it's adjusted to the number +# that the screen can handle. +# +#max_tags 0 + +# Set the default menu selection. The available arguments match the +# keyboard accelerators available within rEFInd. You may select the +# default loader using: +# - A digit between 1 and 9, in which case the Nth loader in the menu +# will be the default. +# - A "+" symbol at the start of the string, which refers to the most +# recently booted loader. +# - Any substring that corresponds to a portion of the loader's title +# (usually the OS's name, boot loader's path, or a volume or +# filesystem title). +# You may also specify multiple selectors by separating them with commas +# and enclosing the list in quotes. (The "+" option is only meaningful in +# this context.) +# If you follow the selector(s) with two times, in 24-hour format, the +# default will apply only between those times. The times are in the +# motherboard's time standard, whether that's UTC or local time, so if +# you use UTC, you'll need to adjust this from local time manually. +# Times may span midnight as in "23:30 00:30", which applies to 11:30 PM +# to 12:30 AM. You may specify multiple default_selection lines, in which +# case the last one to match takes precedence. Thus, you can set a main +# option without a time followed by one or more that include times to +# set different defaults for different times of day. +# The default behavior is to boot the previously-booted OS. +# +#default_selection 1 +#default_selection Microsoft +#default_selection "+,bzImage,vmlinuz" +#default_selection Maintenance 23:30 2:00 +#default_selection "Maintenance,macOS" 1:00 2:30 + +# Enable VMX bit and lock the CPU MSR if unlocked. +# On some Intel Apple computers, the firmware does not lock the MSR 0x3A. +# The symptom on Windows is Hyper-V not working even if the CPU +# meets the minimum requirements (HW assisted virtualization and SLAT) +# DO NOT SET THIS EXCEPT ON INTEL CPUs THAT SUPPORT VMX! See +# http://www.thomas-krenn.com/en/wiki/Activating_the_Intel_VT_Virtualization_Feature +# for more on this subject. +# The default is false: Don't try to enable and lock the MSR. +# +#enable_and_lock_vmx false + +# Tell a Mac's EFI that macOS is about to be launched, even when it's not. +# This option causes some Macs to initialize their hardware differently than +# when a third-party OS is launched normally. In some cases (particularly on +# Macs with multiple video cards), using this option can cause hardware to +# work that would not otherwise work. On the other hand, using this option +# when it is not necessary can cause hardware (such as keyboards and mice) to +# become inaccessible. Therefore, you should not enable this option if your +# non-Apple OSes work correctly; enable it only if you have problems with +# some hardware devices. When needed, a value of "10.9" usually works, but +# you can experiment with other values. This feature has no effect on +# non-Apple computers. +# The default is inactive (no macOS spoofing is done). +# +#spoof_osx_version 10.9 + +# Set the CSR values for Apple's System Integrity Protection (SIP) feature. +# Values are two-byte (four-character) hexadecimal numbers. These values +# define which specific security features are enabled. Below are the codes +# for what the values mean. Add them up (in hexadecimal!) to set new values. +# Apple's "csrutil enable" and "csrutil disable" commands set values of 10 +# and 877, respectively. (Prior to OS 11, 77 was used rather than 877; 877 +# is required for OS 11, and should work for OS X 10.x, too.) +# CSR_ALLOW_UNTRUSTED_KEXTS 0x0001 +# CSR_ALLOW_UNRESTRICTED_FS 0x0002 +# CSR_ALLOW_TASK_FOR_PID 0x0004 +# CSR_ALLOW_KERNEL_DEBUGGER 0x0008 +# CSR_ALLOW_APPLE_INTERNAL 0x0010 +# CSR_ALLOW_UNRESTRICTED_DTRACE 0x0020 +# CSR_ALLOW_UNRESTRICTED_NVRAM 0x0040 +# CSR_ALLOW_DEVICE_CONFIGURATION 0x0080 +# CSR_ALLOW_ANY_RECOVERY_OS 0x0100 +# CSR_ALLOW_UNAPPROVED_KEXTS 0x0200 +# CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE 0x0400 +# CSR_ALLOW_UNAUTHENTICATED_ROOT 0x0800 +#csr_values 10,877 + +# Include a secondary configuration file within this one. This secondary +# file is loaded as if its options appeared at the point of the "include" +# token itself, so if you want to override a setting in the main file, +# the secondary file must be referenced AFTER the setting you want to +# override. Note that the secondary file may NOT load a tertiary file. +# +#include manual.conf + +# Sample manual configuration stanzas. Each begins with the "menuentry" +# keyword followed by a name that's to appear in the menu (use quotes +# if you want the name to contain a space) and an open curly brace +# ("{"). Each entry ends with a close curly brace ("}"). Common +# keywords within each stanza include: +# +# volume - identifies the filesystem from which subsequent files +# are loaded. You can specify the volume by filesystem +# label, by partition label, or by partition GUID number +# (but NOT yet by filesystem UUID number). +# loader - identifies the boot loader file +# initrd - Specifies an initial RAM disk file +# icon - specifies a custom boot loader icon +# ostype - OS type code to determine boot options available by +# pressing Insert. Valid values are "MacOS", "Linux", +# "Windows", and "XOM". Case-sensitive. +# graphics - set to "on" to enable graphics-mode boot (useful +# mainly for MacOS) or "off" for text-mode boot. +# Default is auto-detected from loader filename. +# options - sets options to be passed to the boot loader; use +# quotes if more than one option should be passed or +# if any options use characters that might be changed +# by rEFInd parsing procedures (=, /, #, or tab). +# disabled - use alone or set to "yes" to disable this entry. +# +# Note that you can use either DOS/Windows/EFI-style backslashes (\) +# or Unix-style forward slashes (/) as directory separators. Either +# way, all file references are on the ESP from which rEFInd was +# launched. +# Use of quotes around parameters causes them to be interpreted as +# one keyword, and for parsing of special characters (spaces, =, /, +# and #) to be disabled. This is useful mainly with the "options" +# keyword. Use of quotes around parameters that specify filenames is +# permissible, but you must then use backslashes instead of slashes, +# except when you must pass a forward slash to the loader, as when +# passing a root= option to a Linux kernel. + +# Below are several sample boot stanzas. All are disabled by default. +# Find one similar to what you need, copy it, remove the "disabled" line, +# and adjust the entries to suit your needs. + +# A sample entry for a Linux 3.13 kernel with EFI boot stub support +# on a partition with a GUID of 904404F8-B481-440C-A1E3-11A5A954E601. +# This entry includes Linux-specific boot options and specification +# of an initial RAM disk. Note uses of Linux-style forward slashes. +# Also note that a leading slash is optional in file specifications. +menuentry Linux { + icon EFI/refind/icons/os_linux.png + volume 904404F8-B481-440C-A1E3-11A5A954E601 + loader bzImage-3.3.0-rc7 + initrd initrd-3.3.0.img + options "ro root=UUID=5f96cafa-e0a7-4057-b18f-fa709db5b837" + disabled +} + +# Below is a more complex Linux example, specifically for Arch Linux. +# This example MUST be modified for your specific installation; if nothing +# else, the PARTUUID code must be changed for your disk. Because Arch Linux +# does not include version numbers in its kernel and initrd filenames, you +# may need to use manual boot stanzas when using fallback initrds or +# multiple kernels with Arch. This example is modified from one in the Arch +# wiki page on rEFInd (https://wiki.archlinux.org/index.php/rEFInd). +menuentry "Arch Linux" { + icon /EFI/refind/icons/os_arch.png + volume "Arch Linux" + loader /boot/vmlinuz-linux + initrd /boot/initramfs-linux.img + options "root=PARTUUID=5028fa50-0079-4c40-b240-abfaf28693ea rw add_efi_memmap" + submenuentry "Boot using fallback initramfs" { + initrd /boot/initramfs-linux-fallback.img + } + submenuentry "Boot to terminal" { + add_options "systemd.unit=multi-user.target" + } + disabled +} + +# A sample entry for loading Ubuntu using its standard name for +# its GRUB 2 boot loader. Note uses of Linux-style forward slashes +menuentry Ubuntu { + loader /EFI/ubuntu/grubx64.efi + icon /EFI/refind/icons/os_linux.png + disabled +} + +# A minimal ELILO entry, which probably offers nothing that +# auto-detection can't accomplish. +menuentry "ELILO" { + loader \EFI\elilo\elilo.efi + disabled +} + +# Like the ELILO entry, this one offers nothing that auto-detection +# can't do; but you might use it if you want to disable auto-detection +# but still boot Windows.... +menuentry "Windows 7" { + loader \EFI\Microsoft\Boot\bootmgfw.efi + disabled +} + +# EFI shells are programs just like boot loaders, and can be +# launched in the same way. You can pass a shell the name of a +# script that it's to run on the "options" line. The script +# could initialize hardware and then launch an OS, or it could +# do something entirely different. +menuentry "Windows via shell script" { + icon \EFI\refind\icons\os_win.png + loader \EFI\tools\shell.efi + options "fs0:\EFI\tools\launch_windows.nsh" + disabled +} + +# MacOS is normally detected and run automatically; however, +# if you want to do something unusual, a manual boot stanza may +# be the way to do it. This one does nothing very unusual, but +# it may serve as a starting point. Note that you'll almost +# certainly need to change the "volume" line for this example +# to work. +menuentry "My macOS" { + icon \EFI\refind\icons\os_mac.png + volume "macOS boot" + loader \System\Library\CoreServices\boot.efi + disabled +} + +# The firmware_bootnum token takes a HEXADECIMAL value as an option +# and sets that value using the EFI's BootNext variable and then +# reboots the computer. This then causes a one-time boot of the +# computer using this EFI boot option. It can be used for various +# purposes, but one that's likely to interest some rEFInd users is +# that some Macs with HiDPI displays produce lower-resolution +# desktops when booted through rEFInd than when booted via Apple's +# own boot manager. Booting using the firmware_bootnum option +# produces the better resolution. Note that no loader option is +# used in this type of configuration. +menuentry "macOS via BootNext" { + icon /EFI/refind/icons/os_mac.png + firmware_bootnum 80 + disabled +} diff --git a/gucc/tests/meson.build b/gucc/tests/meson.build index f38c506..cad39fb 100644 --- a/gucc/tests/meson.build +++ b/gucc/tests/meson.build @@ -93,3 +93,11 @@ executable( link_with: [gucc_lib], include_directories: [include_directories('../include')], install: false) + +executable( + 'test-refind_extra_kern_strings', + files('unit-refind_extra_kern_strings.cpp'), + dependencies: deps, + link_with: [gucc_lib], + include_directories: [include_directories('../include')], + install: false) diff --git a/gucc/tests/unit-refind_extra_kern_strings.cpp b/gucc/tests/unit-refind_extra_kern_strings.cpp new file mode 100644 index 0000000..b629f3b --- /dev/null +++ b/gucc/tests/unit-refind_extra_kern_strings.cpp @@ -0,0 +1,79 @@ +#include "gucc/bootloader.hpp" +#include "gucc/file_utils.hpp" +#include "gucc/logger.hpp" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace fs = std::filesystem; +using namespace std::string_view_literals; + +static constexpr auto REFIND_CONF_TEST = R"(extra_kernel_version_strings linux-lts,linux,linux-zen +)"sv; + +inline auto filtered_res(std::string_view content) noexcept -> std::string { + auto&& result = content | std::ranges::views::split('\n') + | std::ranges::views::filter([](auto&& rng) { + auto&& line = std::string_view(&*rng.begin(), static_cast(std::ranges::distance(rng))); + return line.starts_with("extra_kernel_version_strings"); + }) + | std::ranges::views::join_with('\n') + | std::ranges::to(); + return result + '\n'; +} + +int main() { + auto callback_sink = std::make_shared([](const spdlog::details::log_msg&) { + // noop + }); + auto logger = std::make_shared("default", callback_sink); + spdlog::set_default_logger(logger); + gucc::logger::set_logger(logger); + + // prepare test data + static constexpr std::string_view file_testpath{"/tmp/test-extrakernverstr-refind.conf"}; + static constexpr std::string_view file_sample_path{GUCC_TEST_DIR "/files/refind.conf-sample"}; + fs::copy_file(file_sample_path, file_testpath, fs::copy_options::overwrite_existing); + + // test valid extkernstr with valid file. + const std::vector extra_kernel_strings{"linux-lts", "linux", "linux-zen"}; + assert(gucc::bootloader::refind_write_extra_kern_strings(file_testpath, extra_kernel_strings)); + + auto refind_conf_content = gucc::file_utils::read_whole_file(file_testpath); + auto filtered_content = filtered_res(refind_conf_content); + + // Cleanup. + fs::remove(file_testpath); + + assert(filtered_content == REFIND_CONF_TEST); + + // test empty kernel strings with valid file. + fs::copy_file(file_sample_path, file_testpath, fs::copy_options::overwrite_existing); + assert(!gucc::bootloader::refind_write_extra_kern_strings(file_testpath, {})); + auto sample_conf_content = gucc::file_utils::read_whole_file(file_testpath); + refind_conf_content = gucc::file_utils::read_whole_file(file_testpath); + assert(refind_conf_content == sample_conf_content); + + // Cleanup. + fs::remove(file_testpath); + + // test empty file + std::ofstream test_file{file_testpath.data()}; + assert(test_file.is_open()); + + // setup file + test_file << ""; + test_file.close(); + + assert(!gucc::bootloader::refind_write_extra_kern_strings(file_testpath, extra_kernel_strings)); + + // Cleanup. + fs::remove(file_testpath); +}