From 8b404747b6abc9d97219529816273dde375d0c03 Mon Sep 17 00:00:00 2001 From: Vladislav Nepogodin Date: Sun, 30 Jun 2024 03:49:47 +0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=20gucc:=20add=20fstab=20generation?= =?UTF-8?q?=20based=20on=20provided=20partitions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 2 + gucc/CMakeLists.txt | 1 + gucc/include/gucc/fstab.hpp | 41 ++++++++++ gucc/meson.build | 1 + gucc/src/fstab.cpp | 138 ++++++++++++++++++++++++++++++++++ gucc/tests/meson.build | 8 ++ gucc/tests/unit-fstab_gen.cpp | 113 ++++++++++++++++++++++++++++ 7 files changed, 304 insertions(+) create mode 100644 gucc/include/gucc/fstab.hpp create mode 100644 gucc/src/fstab.cpp create mode 100644 gucc/tests/unit-fstab_gen.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20293b1..cb87df6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,6 +38,7 @@ jobs: cd ${{github.workspace}}/build ./gucc/tests/test-initcpio ./gucc/tests/test-pacmanconf + ./gucc/tests/test-fstab_gen shell: bash build-cmake_withoutdev: name: Build with CMake (DEVENV OFF) @@ -82,6 +83,7 @@ jobs: cd ${{github.workspace}}/build ./gucc/tests/test-initcpio ./gucc/tests/test-pacmanconf + ./gucc/tests/test-fstab_gen shell: bash build-meson_withoutdev: name: Build with Meson (DEVENV OFF) diff --git a/gucc/CMakeLists.txt b/gucc/CMakeLists.txt index 8c9f097..24cceb4 100644 --- a/gucc/CMakeLists.txt +++ b/gucc/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(${PROJECT_NAME} SHARED src/btrfs.cpp include/gucc/btrfs.hpp src/user.cpp include/gucc/user.hpp src/locale.cpp include/gucc/locale.hpp + src/fstab.cpp include/gucc/fstab.hpp #src/chwd_profiles.cpp src/chwd_profiles.hpp #src/disk.cpp src/disk.hpp ) diff --git a/gucc/include/gucc/fstab.hpp b/gucc/include/gucc/fstab.hpp new file mode 100644 index 0000000..ce66954 --- /dev/null +++ b/gucc/include/gucc/fstab.hpp @@ -0,0 +1,41 @@ +#ifndef FSTAB_HPP +#define FSTAB_HPP + +#include // for optional +#include // for string +#include // for string_view +#include // for vector + +namespace gucc::fs { + +struct Partition final { + std::string fstype; + std::string mountpoint; + std::string uuid_str; + std::string device; + + // mount points that will be written in fstab, + // excluding subvol={subvol name} + // if device is ssd, mount options for ssd should be appended + std::string mount_opts; + std::optional luks_mapper_name; + std::optional luks_uuid; + + /* + // subvolumes per partition + // e.g we have partition /dev/nvme0n1p1 with subvolumes: /@, /@home, /@cache + std::optional> subvols; + */ + // subvolume name if the partition is btrrfs subvolume + std::optional subvolume; +}; + +// Generate fstab +auto generate_fstab(const std::vector& partitions, std::string_view root_mountpoint, std::string_view crypttab_opts) noexcept -> bool; + +// Generate fstab into string +auto generate_fstab_content(const std::vector& partitions) noexcept -> std::string; + +} // namespace gucc::fs + +#endif // FSTAB_HPP diff --git a/gucc/meson.build b/gucc/meson.build index 4378c6d..454c821 100644 --- a/gucc/meson.build +++ b/gucc/meson.build @@ -12,6 +12,7 @@ gucc_lib = library('gucc', 'src/btrfs.cpp', 'src/user.cpp', 'src/locale.cpp', + 'src/fstab.cpp', ], include_directories : [include_directories('include')], dependencies: deps diff --git a/gucc/src/fstab.cpp b/gucc/src/fstab.cpp new file mode 100644 index 0000000..8b84a06 --- /dev/null +++ b/gucc/src/fstab.cpp @@ -0,0 +1,138 @@ +#include "gucc/fstab.hpp" +#include "gucc/io_utils.hpp" + +#include // for tolower + +#include // for transform +#include +#include + +#include +#include + +#include + +#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 "-Wuseless-cast" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + +#include + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +namespace fs = std::filesystem; +using namespace std::string_view_literals; + +namespace { +static constexpr auto FSTAB_HEADER = R"(# Static information about the filesystems. +# See fstab(5) for details. + +# +)"sv; + +constexpr auto convert_fsname_fstab(std::string_view fsname) noexcept -> std::string_view { + if (fsname == "fat16"sv || fsname == "fat32"sv) { + return "vfat"sv; + } else if (fsname == "linuxswap"sv) { + return "swap"sv; + } + return fsname; +} + +auto string_tolower(std::string_view text) noexcept -> std::string { + std::string res{text}; + ranges::transform(res, res.begin(), + [](char char_elem) { return static_cast(std::tolower(static_cast(char_elem))); }); + return res; +} + +} // namespace + +namespace gucc::fs { + +auto gen_fstab_entry(const Partition& partition) noexcept -> std::optional { + // Apperently some FS names named differently in /etc/fstab. + const auto& fstype = [](auto&& fstype) -> std::string { + auto lower_fstype = string_tolower(fstype); + return std::string{convert_fsname_fstab(lower_fstype)}; + }(partition.fstype); + + const auto& luks_mapper_name = partition.luks_mapper_name; + const auto& mountpoint = partition.mountpoint; + const auto& disk_name = ::fs::path{partition.device}.filename().string(); + + // NOTE: ignore swap support for now + if (fstype == "swap"sv) { + spdlog::info("fstab: skipping swap partition"); + return std::nullopt; + } + + const auto check_num = [](auto&& mountpoint, auto&& fstype) -> std::int32_t { + if (mountpoint == "/"sv && fstype != "btrfs"sv) { + return 1; + } else if (mountpoint != "swap"sv && fstype != "btrfs"sv) { + return 2; + } + return 0; + }(mountpoint, fstype); + + const auto& mount_options = [](auto&& mount_opts, auto&& subvolume, auto&& fstype) -> std::string { + if (fstype == "btrfs"sv && !subvolume.empty()) { + return fmt::format(FMT_COMPILE("subvol={},{}"), subvolume, mount_opts); + } + return {mount_opts}; + }(partition.mount_opts, partition.subvolume.value_or(""), fstype); + + std::string device_str{partition.device}; + if (luks_mapper_name) { + device_str = fmt::format(FMT_COMPILE("/dev/mapper/{}"), *luks_mapper_name); + } else if (!partition.uuid_str.empty()) { + device_str = fmt::format(FMT_COMPILE("UUID={}"), partition.uuid_str); + } + return std::make_optional(fmt::format(FMT_COMPILE("# {}\n{:41} {:<14} {:<7} {:<10} 0 {}\n\n"), partition.device, device_str, mountpoint, fstype, mount_options, check_num)); +} + +auto generate_fstab_content(const std::vector& partitions) noexcept -> std::string { + std::string fstab_content{FSTAB_HEADER}; + + for (auto&& partition : partitions) { + if (partition.fstype == "zfs"sv) { + // zfs partitions don't need an entry in fstab + continue; + } + // if btrfs and root mountpoint + /*if (partition.fstype == "btrfs"sv && partition.mountpoint == "/"sv) { + // NOTE: should we handle differently here? + }*/ + auto fstab_entry = gen_fstab_entry(partition); + if (!fstab_entry) { + continue; + } + fstab_content += std::move(*fstab_entry); + } + + return fstab_content; +} + +auto generate_fstab(const std::vector& partitions, std::string_view root_mountpoint) noexcept -> bool { + const auto& fstab_filepath = fmt::format(FMT_COMPILE("{}/etc/fstab"), root_mountpoint); + + std::ofstream fstab_file{fstab_filepath, std::ios::out | std::ios::trunc}; + if (!fstab_file.is_open()) { + spdlog::error("Failed to open fstab for writing {}", fstab_filepath); + return false; + } + fstab_file << fs::generate_fstab_content(partitions); + return true; +} + +} // namespace gucc::fs diff --git a/gucc/tests/meson.build b/gucc/tests/meson.build index c98cb27..4114570 100644 --- a/gucc/tests/meson.build +++ b/gucc/tests/meson.build @@ -13,3 +13,11 @@ executable( link_with: [gucc_lib], include_directories: [include_directories('../include')], install: false) + +executable( + 'test-fstab_gen', + files('unit-fstab_gen.cpp'), + dependencies: deps, + link_with: [gucc_lib], + include_directories: [include_directories('../include')], + install: false) diff --git a/gucc/tests/unit-fstab_gen.cpp b/gucc/tests/unit-fstab_gen.cpp new file mode 100644 index 0000000..ccb8f6d --- /dev/null +++ b/gucc/tests/unit-fstab_gen.cpp @@ -0,0 +1,113 @@ +#include "gucc/fstab.hpp" + +#include + +#include +#include +#include + +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; + +static constexpr auto FSTAB_BTRFS_TEST = R"(# Static information about the filesystems. +# See fstab(5) for details. + +# +# /dev/nvme0n1p1 +UUID=6bdb3301-8efb-4b84-b0b7-4caeef26fd6f / btrfs subvol=/@,defaults,noatime,compress=zstd,space_cache=v2,commit=120 0 0 + +# /dev/nvme0n1p1 +UUID=6bdb3301-8efb-4b84-b0b7-4caeef26fd6f /home btrfs subvol=/@home,defaults,noatime,compress=zstd,space_cache=v2,commit=120 0 0 + +# /dev/nvme0n1p1 +UUID=6bdb3301-8efb-4b84-b0b7-4caeef26fd6f /var/cache btrfs subvol=/@cache,defaults,noatime,compress=zstd,space_cache=v2,commit=120 0 0 + +# /dev/nvme0n1p2 +UUID=8EFB-4B84 /boot vfat defaults,noatime 0 2 + +)"sv; + +static constexpr auto FSTAB_XFS_TEST = R"(# Static information about the filesystems. +# See fstab(5) for details. + +# +# /dev/nvme0n1p1 +UUID=6bdb3301-8efb-4b84-b0b7-4caeef26fd6f / xfs defaults,lazytime,noatime,attr2,inode64,logbsize=256k,noquota 0 1 + +# /dev/nvme0n1p2 +UUID=8EFB-4B84 /boot vfat defaults,noatime 0 2 + +)"sv; + +static constexpr auto FSTAB_LUKS_XFS_TEST = R"(# Static information about the filesystems. +# See fstab(5) for details. + +# +# /dev/nvme0n1p1 +/dev/mapper/luks_device / xfs defaults,lazytime,noatime,attr2,inode64,logbsize=256k,noquota 0 1 + +# /dev/nvme0n1p2 +UUID=8EFB-4B84 /boot vfat defaults,noatime 0 2 + +)"sv; + +static constexpr auto FSTAB_ZFS_TEST = R"(# Static information about the filesystems. +# See fstab(5) for details. + +# +# /dev/nvme0n1p2 +UUID=8EFB-4B84 /boot vfat defaults,noatime 0 2 + +)"sv; + +int main() { + auto callback_sink = std::make_shared([](const spdlog::details::log_msg& msg) { + // noop + }); + spdlog::set_default_logger(std::make_shared("default", callback_sink)); + + // btrfs with snapshots + { + const std::vector partitions{ + gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s, .luks_mapper_name = {}, .subvolume = "/@"s}, + gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/home"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s, .luks_mapper_name = {}, .subvolume = "/@home"s}, + gucc::fs::Partition{.fstype = "btrfs"s, .mountpoint = "/var/cache"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s, .luks_mapper_name = {}, .subvolume = "/@cache"s}, + gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s}, + }; + const auto& fstab_content = gucc::fs::generate_fstab_content(partitions); + assert(fstab_content == FSTAB_BTRFS_TEST); + } + // basic xfs + { + const std::vector partitions{ + gucc::fs::Partition{.fstype = "xfs"s, .mountpoint = "/"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,lazytime,noatime,attr2,inode64,logbsize=256k,noquota"s}, + gucc::fs::Partition{.fstype = "fat16"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s}, + }; + const auto& fstab_content = gucc::fs::generate_fstab_content(partitions); + assert(fstab_content == FSTAB_XFS_TEST); + } + // luks xfs + { + const std::vector partitions{ + gucc::fs::Partition{.fstype = "xfs"s, .mountpoint = "/"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,lazytime,noatime,attr2,inode64,logbsize=256k,noquota"s, .luks_mapper_name = "luks_device"s, .luks_uuid = "00e1b836-81b6-433f-83ca-0fd373e3cd50"s}, + gucc::fs::Partition{.fstype = "linuxswap"s, .mountpoint = ""s, .uuid_str = ""s, .device = "/dev/nvme0n1p3"s, .mount_opts = "defaults,noatime"s}, + gucc::fs::Partition{.fstype = "vfat"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s}, + }; + const auto& fstab_content = gucc::fs::generate_fstab_content(partitions); + assert(fstab_content == FSTAB_LUKS_XFS_TEST); + } + // zfs + { + const std::vector partitions{ + gucc::fs::Partition{.fstype = "zfs"s, .mountpoint = "/"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s}, + gucc::fs::Partition{.fstype = "zfs"s, .mountpoint = "/home"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s}, + gucc::fs::Partition{.fstype = "zfs"s, .mountpoint = "/var/cache"s, .uuid_str = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .device = "/dev/nvme0n1p1"s, .mount_opts = "defaults,noatime,compress=zstd,space_cache=v2,commit=120"s}, + gucc::fs::Partition{.fstype = "vfat"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s}, + }; + const auto& fstab_content = gucc::fs::generate_fstab_content(partitions); + assert(fstab_content == FSTAB_ZFS_TEST); + } +}