Skip to content

Commit

Permalink
👷 gucc: add grub config gen from provided struct
Browse files Browse the repository at this point in the history
  • Loading branch information
vnepogodin committed Jul 1, 2024
1 parent 9c8f8f2 commit 24a250d
Show file tree
Hide file tree
Showing 5 changed files with 435 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
./gucc/tests/test-pacmanconf
./gucc/tests/test-fstab_gen
./gucc/tests/test-crypttab_gen
./gucc/tests/test-grub_config_gen
shell: bash
build-cmake_withoutdev:
name: Build with CMake (DEVENV OFF)
Expand Down Expand Up @@ -86,6 +87,7 @@ jobs:
./gucc/tests/test-pacmanconf
./gucc/tests/test-fstab_gen
./gucc/tests/test-crypttab_gen
./gucc/tests/test-grub_config_gen
shell: bash
build-meson_withoutdev:
name: Build with Meson (DEVENV OFF)
Expand Down
54 changes: 54 additions & 0 deletions gucc/include/gucc/bootloader.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,64 @@
#ifndef BOOTLOADER_HPP
#define BOOTLOADER_HPP

#include <cinttypes>

#include <optional> // for optional
#include <string> // for string
#include <string_view> // for string_view

namespace gucc::bootloader {

struct GrubConfig final {
// e.g GRUB_DEFAULT=0
std::string default_entry{"0"};
// e.g GRUB_TIMEOUT=5
std::int32_t grub_timeout{5};

Check failure on line 16 in gucc/include/gucc/bootloader.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/include/gucc/bootloader.hpp:16:31 [cppcoreguidelines-avoid-magic-numbers

5 is a magic number; consider replacing it with a named constant
// e.g GRUB_DISTRIBUTOR="Arch"
std::string grub_distributor{"Arch"};
// e.g GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet"
std::string cmdline_linux_default{"loglevel=3 quiet"};
// e.g GRUB_CMDLINE_LINUX=""
std::string cmdline_linux{};
// e.g GRUB_PRELOAD_MODULES="part_gpt part_msdos"
std::string preload_modules{"part_gpt part_msdos"};
// e.g #GRUB_ENABLE_CRYPTODISK=y
std::optional<bool> enable_cryptodisk{};
// e.g GRUB_TIMEOUT_STYLE=menu
std::string timeout_style{"menu"};
// e.g GRUB_TERMINAL_INPUT=console
std::string terminal_input{"console"};
// e.g #GRUB_TERMINAL_OUTPUT=console
std::optional<std::string> terminal_output{};
// e.g GRUB_GFXMODE=auto
std::string gfxmode{"auto"};
// e.g GRUB_GFXPAYLOAD_LINUX=keep
std::string gfxpayload_linux{"keep"};
// e.g #GRUB_DISABLE_LINUX_UUID=true
std::optional<bool> disable_linux_uuid{};
// e.g GRUB_DISABLE_RECOVERY=true
bool disable_recovery{true};
// e.g #GRUB_COLOR_NORMAL="light-blue/black"
std::optional<std::string> color_normal{};
// e.g #GRUB_COLOR_HIGHLIGHT="light-cyan/blue"
std::optional<std::string> color_highlight{};
// e.g #GRUB_BACKGROUND="/path/to/wallpaper"
std::optional<std::string> background{};
// e.g #GRUB_THEME="/path/to/gfxtheme"
std::optional<std::string> theme{};
// e.g #GRUB_INIT_TUNE="480 440 1"
std::optional<std::string> init_tune{};
// e.g #GRUB_SAVEDEFAULT=true
std::optional<bool> savedefault{};
// e.g #GRUB_DISABLE_SUBMENU=y
std::optional<bool> disable_submenu{};
// e.g #GRUB_DISABLE_OS_PROBER=false
std::optional<bool> disable_os_prober{};
};

// Generate grub_config into string
auto gen_grub_config(const GrubConfig& grub_config) noexcept -> std::string;

// Installs & configures systemd-boot on system
auto install_systemd_boot(std::string_view root_mountpoint, std::string_view efi_directory, bool is_volume_removable) noexcept -> bool;

Expand Down
194 changes: 194 additions & 0 deletions gucc/src/bootloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,204 @@

#include <spdlog/spdlog.h>

#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnull-dereference"
#pragma GCC diagnostic ignored "-Wuseless-cast"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif

#include <range/v3/range/conversion.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/split.hpp>
#include <range/v3/view/transform.hpp>

#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

using namespace std::string_view_literals;

#define CONV_REQ_F(needle, f) \

Check failure on line 33 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:33:9 [cppcoreguidelines-macro-usage

function-like macro 'CONV_REQ_F' used; consider a 'constexpr' template function
if (line.starts_with(needle)) { \
return fmt::format(needle "{}", f); \
}
#define CONV_REQ_F_S(needle, f) \

Check failure on line 37 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:37:9 [cppcoreguidelines-macro-usage

function-like macro 'CONV_REQ_F_S' used; consider a 'constexpr' template function
if (line.starts_with(needle)) { \
return fmt::format(needle "\"{}\"", f); \
}
#define CONV_OPT_F(needle, f, default_val) \

Check failure on line 41 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:41:9 [cppcoreguidelines-macro-usage

function-like macro 'CONV_OPT_F' used; consider a 'constexpr' template function
if (line.starts_with(needle)) { \
if (f) { \
return fmt::format(needle "{}", *f); \

Check failure on line 44 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:44:46 [bugprone-macro-parentheses

macro argument should be enclosed in parentheses
} \
return {"#" needle default_val}; \
}
#define CONV_OPT_F_S(needle, f, default_val) \

Check failure on line 48 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:48:9 [cppcoreguidelines-macro-usage

function-like macro 'CONV_OPT_F_S' used; consider a 'constexpr' template function
if (line.starts_with(needle)) { \
if (f) { \
return fmt::format(needle "\"{}\"", *f); \

Check failure on line 51 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:51:50 [bugprone-macro-parentheses

macro argument should be enclosed in parentheses
} \
return {"#" needle "\"" default_val "\""}; \
}
#define CONV_REQ_B(needle, f) \

Check failure on line 55 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:55:9 [cppcoreguidelines-macro-usage

function-like macro 'CONV_REQ_B' used; consider a 'constexpr' template function
if (line.starts_with(needle)) { \
return fmt::format(needle "{}", convert_boolean_val(needle, f)); \
}
#define CONV_OPT_B(needle, f, default_val) \

Check failure on line 59 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:59:9 [cppcoreguidelines-macro-usage

function-like macro 'CONV_OPT_B' used; consider a 'constexpr' template function
if (line.starts_with(needle)) { \
if (f) { \
return fmt::format(needle "{}", convert_boolean_val(needle, *f)); \

Check failure on line 62 in gucc/src/bootloader.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/bootloader.cpp:62:74 [bugprone-macro-parentheses

macro argument should be enclosed in parentheses
} \
return {"#" needle default_val}; \
}

namespace {

// NOLINTNEXTLINE
static constexpr auto GRUB_DEFAULT_CONFIG = R"(# GRUB boot loader configuration
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="Arch"
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet"
GRUB_CMDLINE_LINUX=""
# Preload both GPT and MBR modules so that they are not missed
GRUB_PRELOAD_MODULES="part_gpt part_msdos"
# Uncomment to enable booting from LUKS encrypted devices
#GRUB_ENABLE_CRYPTODISK=y
# Set to 'countdown' or 'hidden' to change timeout behavior,
# press ESC key to display menu.
GRUB_TIMEOUT_STYLE=menu
# Uncomment to use basic console
GRUB_TERMINAL_INPUT=console
# Uncomment to disable graphical terminal
#GRUB_TERMINAL_OUTPUT=console
# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `videoinfo'
GRUB_GFXMODE=auto
# Uncomment to allow the kernel use the same resolution used by grub
GRUB_GFXPAYLOAD_LINUX=keep
# Uncomment if you want GRUB to pass to the Linux kernel the old parameter
# format "root=/dev/xxx" instead of "root=/dev/disk/by-uuid/xxx"
#GRUB_DISABLE_LINUX_UUID=true
# Uncomment to disable generation of recovery mode menu entries
GRUB_DISABLE_RECOVERY=true
# Uncomment and set to the desired menu colors. Used by normal and wallpaper
# modes only. Entries specified as foreground/background.
#GRUB_COLOR_NORMAL="light-blue/black"
#GRUB_COLOR_HIGHLIGHT="light-cyan/blue"
# Uncomment one of them for the gfx desired, a image background or a gfxtheme
#GRUB_BACKGROUND="/path/to/wallpaper"
#GRUB_THEME="/path/to/gfxtheme"
# Uncomment to get a beep at GRUB start
#GRUB_INIT_TUNE="480 440 1"
# Uncomment to make GRUB remember the last selection. This requires
# setting 'GRUB_DEFAULT=saved' above.
#GRUB_SAVEDEFAULT=true
# Uncomment to disable submenus in boot menu
#GRUB_DISABLE_SUBMENU=y
# Probing for other operating systems is disabled for security reasons. Read
# documentation on GRUB_DISABLE_OS_PROBER, if still want to enable this
# functionality install os-prober and uncomment to detect and include other
# operating systems.
#GRUB_DISABLE_OS_PROBER=false
)"sv;

template <typename T, typename npos_type = std::remove_cvref_t<decltype(T::npos)>>
concept string_findable = requires(T value) {
// check that type of T::npos is T::size_type
{ npos_type{} } -> std::same_as<typename T::size_type>;
// check that type of T::find is T::size_type
{ value.find(std::string_view{""}) } -> std::same_as<typename T::size_type>;
};

// simple helper function to check if string contains a string
constexpr auto contains(string_findable auto const& str, std::string_view needle) noexcept -> bool {
using str_type = std::remove_reference_t<decltype(str)>;
return str.find(needle) != str_type::npos;
}

constexpr auto convert_boolean_val(std::string_view needle, bool value) noexcept -> std::string_view {
if (needle == "GRUB_ENABLE_CRYPTODISK="sv || needle == "GRUB_DISABLE_SUBMENU="sv) {
return value ? "y"sv : "n"sv;
}
return value ? "true"sv : "false"sv;
}

auto parse_grub_line(const gucc::bootloader::GrubConfig& grub_config, std::string_view line) noexcept -> std::string {
if (line.starts_with("#GRUB_")) {
// uncomment grub setting
line.remove_prefix(1);
}

if (line.starts_with("GRUB_")) {
CONV_REQ_F("GRUB_DEFAULT=", grub_config.default_entry);
CONV_REQ_F("GRUB_TIMEOUT=", grub_config.grub_timeout);
CONV_REQ_F_S("GRUB_DISTRIBUTOR=", grub_config.grub_distributor);
CONV_REQ_F_S("GRUB_CMDLINE_LINUX_DEFAULT=", grub_config.cmdline_linux_default);
CONV_REQ_F_S("GRUB_CMDLINE_LINUX=", grub_config.cmdline_linux);
CONV_REQ_F_S("GRUB_PRELOAD_MODULES=", grub_config.preload_modules);
CONV_REQ_F("GRUB_TIMEOUT_STYLE=", grub_config.timeout_style);
CONV_REQ_F("GRUB_TERMINAL_INPUT=", grub_config.terminal_input);
CONV_OPT_F("GRUB_TERMINAL_OUTPUT=", grub_config.terminal_output, "console");
CONV_REQ_F("GRUB_GFXMODE=", grub_config.gfxmode);
CONV_REQ_F("GRUB_GFXPAYLOAD_LINUX=", grub_config.gfxpayload_linux);
CONV_OPT_F_S("GRUB_COLOR_NORMAL=", grub_config.color_normal, "light-blue/black");
CONV_OPT_F_S("GRUB_COLOR_HIGHLIGHT=", grub_config.color_highlight, "light-cyan/blue");
CONV_OPT_F_S("GRUB_BACKGROUND=", grub_config.background, "/path/to/wallpaper");
CONV_OPT_F_S("GRUB_THEME=", grub_config.theme, "/path/to/gfxtheme");
CONV_OPT_F_S("GRUB_INIT_TUNE=", grub_config.init_tune, "480 440 1");
if (contains(line, "=y") || contains(line, "=true") || contains(line, "=false")) {
// booleans
CONV_OPT_B("GRUB_ENABLE_CRYPTODISK=", grub_config.enable_cryptodisk, "y");
CONV_OPT_B("GRUB_DISABLE_LINUX_UUID=", grub_config.disable_linux_uuid, "true");
CONV_REQ_B("GRUB_DISABLE_RECOVERY=", grub_config.disable_recovery);
CONV_OPT_B("GRUB_SAVEDEFAULT=", grub_config.savedefault, "true");
CONV_OPT_B("GRUB_DISABLE_SUBMENU=", grub_config.disable_submenu, "y");
CONV_OPT_B("GRUB_DISABLE_OS_PROBER=", grub_config.disable_os_prober, "false");
}
}
return std::string{line.data(), line.size()};
}

} // namespace

namespace gucc::bootloader {

auto gen_grub_config(const GrubConfig& grub_config) noexcept -> std::string {
std::string result = GRUB_DEFAULT_CONFIG | ranges::views::split('\n')
| ranges::views::transform([&](auto&& rng) {
auto&& line = std::string_view(&*rng.begin(), static_cast<size_t>(ranges::distance(rng)));
return parse_grub_line(grub_config, line);
})
| ranges::views::join('\n')
| ranges::to<std::string>();
result += '\n';
return result;
}

auto install_systemd_boot(std::string_view root_mountpoint, std::string_view efi_directory, bool is_volume_removable) noexcept -> bool {
// Install systemd-boot onto EFI
const auto& bootctl_cmd = fmt::format(FMT_COMPILE("bootctl --path={} install"), efi_directory);
Expand Down
8 changes: 8 additions & 0 deletions gucc/tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@ executable(
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)

executable(
'test-grub_config_gen',
files('unit-grub_config_gen.cpp'),
dependencies: deps,
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)
Loading

0 comments on commit 24a250d

Please sign in to comment.