Skip to content

Commit

Permalink
👷 gucc: add function to merge subvolumes into partition scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
vnepogodin committed Jul 26, 2024
1 parent 7caecbf commit 756d876
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
./gucc/tests/test-fetch_file
./gucc/tests/test-locale
./gucc/tests/test-package_profiles
./gucc/tests/test-btrfs
shell: bash
build-cmake_withoutdev:
name: Build with CMake (DEVENV OFF)
Expand Down Expand Up @@ -98,6 +99,7 @@ jobs:
./gucc/tests/test-fetch_file
./gucc/tests/test-locale
./gucc/tests/test-package_profiles
./gucc/tests/test-btrfs
shell: bash
build-meson_withoutdev:
name: Build with Meson (DEVENV OFF)
Expand Down
6 changes: 6 additions & 0 deletions gucc/include/gucc/btrfs.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef BTRFS_HPP
#define BTRFS_HPP

#include "gucc/partition.hpp"

#include <string> // for string
#include <string_view> // for string_view
#include <vector> // for vector
Expand All @@ -21,6 +23,10 @@ auto btrfs_create_subvols(const std::vector<BtrfsSubvolume>& subvols, std::strin
// Mounts btrfs subvolumes
auto btrfs_mount_subvols(const std::vector<BtrfsSubvolume>& subvols, std::string_view device, std::string_view root_mountpoint, std::string_view mount_opts) noexcept -> bool;

// Appends btrfs subvolumes into Partition scheme
// with sorting scheme by device field
auto btrfs_append_subvolumes(std::vector<Partition>& partitions, const std::vector<BtrfsSubvolume>& subvols) noexcept -> bool;

} // namespace gucc::fs

#endif // BTRFS_HPP
70 changes: 68 additions & 2 deletions gucc/src/btrfs.cpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
#include "gucc/btrfs.hpp"
#include "gucc/io_utils.hpp"
#include "gucc/partition.hpp"

#include <filesystem>
#include <algorithm> // for find_if
#include <filesystem> // for create_directories
#include <optional> // for optional
#include <ranges> // for ranges::*
#include <utility> // for make_optional

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

#include <spdlog/spdlog.h>

namespace fs = std::filesystem;
using namespace std::string_view_literals;

namespace {

// same behaviour as os.path.dirname from python
constexpr auto get_dirname(std::string_view full_path) noexcept -> std::string_view {
if (full_path == "/") {
if (full_path == "/"sv) {
return full_path;
}
auto pos = full_path.find_last_of('/');
Expand All @@ -24,6 +30,19 @@ constexpr auto get_dirname(std::string_view full_path) noexcept -> std::string_v
return full_path.substr(0, pos);
}

constexpr auto find_partition(const gucc::fs::Partition& part, const gucc::fs::BtrfsSubvolume& subvol) noexcept -> bool {
return (part.mountpoint == subvol.mountpoint) || (part.subvolume && *part.subvolume == subvol.subvolume);
}

constexpr auto is_root_btrfs_part(const gucc::fs::Partition& part) noexcept -> bool {
return (part.mountpoint == "/"sv) && (part.fstype == "btrfs"sv);
}

constexpr auto find_root_btrfs_part(auto&& parts) noexcept {
return std::ranges::find_if(parts,
[](auto&& part) { return is_root_btrfs_part(part); });
}

} // namespace

namespace gucc::fs {
Expand Down Expand Up @@ -92,4 +111,51 @@ auto btrfs_mount_subvols(const std::vector<BtrfsSubvolume>& subvols, std::string
return true;
}

auto btrfs_append_subvolumes(std::vector<Partition>& partitions, const std::vector<BtrfsSubvolume>& subvols) noexcept -> bool {
// if the list of subvolumes is empty, just return success at the beginning of the function
if (subvols.empty()) {
return true;
}

auto root_part_it = find_root_btrfs_part(partitions);
if (root_part_it == std::ranges::end(partitions)) {
spdlog::error("Unable to find root btrfs partition!");
return false;
}

for (auto&& subvol : subvols) {
// check if we already have a partition with such subvolume
auto part_it = std::ranges::find_if(partitions,
[&subvol](auto&& part) { return find_partition(part, subvol); });

// we have found it. proceed to overwrite the values
if (part_it != std::ranges::end(partitions)) {
part_it->mountpoint = subvol.mountpoint;
part_it->subvolume = std::make_optional<std::string>(subvol.subvolume);
continue;
}
// overwise, let's insert the partition based on the root partition

// NOTE: we don't need to check for root part here,
// because it was already checked in the beginning
root_part_it = find_root_btrfs_part(partitions);

Partition part{};
part.fstype = root_part_it->fstype;
part.mountpoint = subvol.mountpoint;
part.uuid_str = root_part_it->uuid_str;
part.device = root_part_it->device;
part.mount_opts = root_part_it->mount_opts;
part.subvolume = std::make_optional<std::string>(subvol.subvolume);
part.luks_mapper_name = root_part_it->luks_mapper_name;
part.luks_uuid = root_part_it->luks_uuid;
part.luks_passphrase = root_part_it->luks_passphrase;
partitions.emplace_back(std::move(part));
}

// sort by device
std::ranges::sort(partitions, {}, &Partition::device);
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 @@ -77,3 +77,11 @@ executable(
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)

executable(
'test-btrfs',
files('unit-btrfs.cpp'),
dependencies: deps,
link_with: [gucc_lib],
include_directories: [include_directories('../include')],
install: false)
141 changes: 141 additions & 0 deletions gucc/tests/unit-btrfs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#include "gucc/btrfs.hpp"
#include "gucc/logger.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;

int main() {
auto callback_sink = std::make_shared<spdlog::sinks::callback_sink_mt>([](const spdlog::details::log_msg&) {
// noop
});
auto logger = std::make_shared<spdlog::logger>("default", callback_sink);
spdlog::set_default_logger(logger);
gucc::logger::set_logger(logger);

const std::vector<gucc::fs::BtrfsSubvolume> subvolumes{
gucc::fs::BtrfsSubvolume{.subvolume = "/@"s, .mountpoint = "/"s},
gucc::fs::BtrfsSubvolume{.subvolume = "/@home"s, .mountpoint = "/home"s},
gucc::fs::BtrfsSubvolume{.subvolume = "/@cache"s, .mountpoint = "/var/cache"s},
// gucc::fs::BtrfsSubvolume{.subvolume = "/@snapshots"sv, .mountpoint = "/.snapshots"sv},
};

// btrfs with subvolumes
{
const std::vector<gucc::fs::Partition> expected_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, .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, .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, .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},
};
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},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
assert(gucc::fs::btrfs_append_subvolumes(partitions, subvolumes));
assert(partitions.size() == 4);
assert(partitions == expected_partitions);
}
// invalid btrfs with subvolumes
{
const std::vector<gucc::fs::Partition> expected_partitions{
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, .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, .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},
};
std::vector<gucc::fs::Partition> partitions{
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, .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, .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},
};
assert(!gucc::fs::btrfs_append_subvolumes(partitions, subvolumes));
assert(partitions.size() == 3);
assert(partitions == expected_partitions);
}
// invalid (without root part) btrfs with subvolumes
{
const std::vector<gucc::fs::Partition> expected_partitions{
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, .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, .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},
};
std::vector<gucc::fs::Partition> partitions{
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, .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, .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},
};
assert(!gucc::fs::btrfs_append_subvolumes(partitions, subvolumes));
assert(partitions.size() == 3);
assert(partitions == expected_partitions);
}
// btrfs without subvolumes
{
const std::vector<gucc::fs::Partition> expected_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},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
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},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
assert(gucc::fs::btrfs_append_subvolumes(partitions, {}));
assert(partitions.size() == 2);
assert(partitions == expected_partitions);
}
// luks xfs
{
const std::vector<gucc::fs::Partition> expected_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},
};
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},
};
assert(!gucc::fs::btrfs_append_subvolumes(partitions, subvolumes));
assert(partitions == expected_partitions);
}
// valid zfs
{
const std::vector<gucc::fs::Partition> expected_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},
};
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},
};
assert(!gucc::fs::btrfs_append_subvolumes(partitions, subvolumes));
assert(partitions == expected_partitions);
}
// luks btrfs with subvolumes
{
const std::vector<gucc::fs::Partition> expected_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, .subvolume = "/@"s, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"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, .subvolume = "/@home"s, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"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, .subvolume = "/@cache"s, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
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, .subvolume = "/@"s, .luks_mapper_name = "luks-6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s, .luks_uuid = "6bdb3301-8efb-4b84-b0b7-4caeef26fd6f"s},
gucc::fs::Partition{.fstype = "fat32"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .mount_opts = "defaults,noatime"s},
};
assert(gucc::fs::btrfs_append_subvolumes(partitions, subvolumes));
assert(partitions.size() == 4);
assert(partitions == expected_partitions);
}
}

0 comments on commit 756d876

Please sign in to comment.