Skip to content

Commit

Permalink
👷 gucc: add fstab generation based on provided partitions
Browse files Browse the repository at this point in the history
  • Loading branch information
vnepogodin committed Jun 29, 2024
1 parent aba9a8d commit 8b40474
Show file tree
Hide file tree
Showing 7 changed files with 304 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 @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions gucc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
41 changes: 41 additions & 0 deletions gucc/include/gucc/fstab.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef FSTAB_HPP
#define FSTAB_HPP

#include <optional> // for optional
#include <string> // for string
#include <string_view> // for string_view
#include <vector> // 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<std::string> luks_mapper_name;
std::optional<std::string> luks_uuid;

/*
// subvolumes per partition
// e.g we have partition /dev/nvme0n1p1 with subvolumes: /@, /@home, /@cache
std::optional<std::vector<BtrfsSubvolume>> subvols;
*/
// subvolume name if the partition is btrrfs subvolume
std::optional<std::string> subvolume;
};

// Generate fstab
auto generate_fstab(const std::vector<Partition>& partitions, std::string_view root_mountpoint, std::string_view crypttab_opts) noexcept -> bool;

// Generate fstab into string
auto generate_fstab_content(const std::vector<Partition>& partitions) noexcept -> std::string;

} // namespace gucc::fs

#endif // FSTAB_HPP
1 change: 1 addition & 0 deletions gucc/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
138 changes: 138 additions & 0 deletions gucc/src/fstab.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#include "gucc/fstab.hpp"
#include "gucc/io_utils.hpp"

#include <cctype> // for tolower

#include <algorithm> // for transform
#include <filesystem>
#include <fstream>

#include <fmt/compile.h>
#include <fmt/format.h>

#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 "-Wuseless-cast"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif

#include <range/v3/algorithm/transform.hpp>

#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.

Check failure on line 36 in gucc/src/fstab.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/fstab.cpp:36:23 [readability-static-definition-in-anonymous-namespace

'FSTAB_HEADER' is a static definition in anonymous namespace; static is redundant here
# See fstab(5) for details.
# <file system> <dir> <type> <options> <dump> <pass>
)"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) {

Check failure on line 45 in gucc/src/fstab.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/fstab.cpp:45:7 [readability-else-after-return

do not use 'else' after 'return'
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<char>(std::tolower(static_cast<unsigned char>(char_elem))); });
return res;
}

} // namespace

namespace gucc::fs {

auto gen_fstab_entry(const Partition& partition) noexcept -> std::optional<std::string> {
// 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) {

Check failure on line 82 in gucc/src/fstab.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/fstab.cpp:82:11 [readability-else-after-return

do not use 'else' after 'return'
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<std::string>(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<Partition>& 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);

Check failure on line 120 in gucc/src/fstab.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/fstab.cpp:120:26 [performance-move-const-arg

passing result of std::move() as a const reference argument; no move will actually happen
}

return fstab_content;
}

auto generate_fstab(const std::vector<Partition>& 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
8 changes: 8 additions & 0 deletions gucc/tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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)
113 changes: 113 additions & 0 deletions gucc/tests/unit-fstab_gen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "gucc/fstab.hpp"

#include <cassert>

#include <string>
#include <string_view>
#include <vector>

#include <spdlog/sinks/callback_sink.h>
#include <spdlog/spdlog.h>

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.
# <file system> <dir> <type> <options> <dump> <pass>
# /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.
# <file system> <dir> <type> <options> <dump> <pass>
# /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.
# <file system> <dir> <type> <options> <dump> <pass>
# /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.
# <file system> <dir> <type> <options> <dump> <pass>
# /dev/nvme0n1p2
UUID=8EFB-4B84 /boot vfat defaults,noatime 0 2
)"sv;

int main() {

Check failure on line 66 in gucc/tests/unit-fstab_gen.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/tests/unit-fstab_gen.cpp:66:5 [modernize-use-trailing-return-type

use a trailing return type for this function
auto callback_sink = std::make_shared<spdlog::sinks::callback_sink_mt>([](const spdlog::details::log_msg& msg) {
// noop
});
spdlog::set_default_logger(std::make_shared<spdlog::logger>("default", callback_sink));

// btrfs with snapshots
{
const std::vector<gucc::fs::Partition> 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<gucc::fs::Partition> 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<gucc::fs::Partition> 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<gucc::fs::Partition> 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);
}
}

0 comments on commit 8b40474

Please sign in to comment.