👷 gucc: add function to partition device using sfdisk command

This commit is contained in:
Vladislav Nepogodin 2025-01-13 03:55:40 +04:00
parent 36007299a6
commit a6d4bbe5c3
No known key found for this signature in database
GPG Key ID: B62C3D10C54D5DA9
6 changed files with 221 additions and 0 deletions

View File

@ -24,6 +24,7 @@ add_library(${PROJECT_NAME} #SHARED
src/pacmanconf_repo.cpp include/gucc/pacmanconf_repo.hpp src/pacmanconf_repo.cpp include/gucc/pacmanconf_repo.hpp
src/initcpio.cpp include/gucc/initcpio.hpp src/initcpio.cpp include/gucc/initcpio.hpp
src/block_devices.cpp include/gucc/block_devices.hpp src/block_devices.cpp include/gucc/block_devices.hpp
src/partitioning.cpp include/gucc/partitioning.hpp
src/luks.cpp include/gucc/luks.hpp src/luks.cpp include/gucc/luks.hpp
src/zfs.cpp include/gucc/zfs.hpp src/zfs.cpp include/gucc/zfs.hpp
src/btrfs.cpp include/gucc/btrfs.hpp src/btrfs.cpp include/gucc/btrfs.hpp

View File

@ -12,6 +12,10 @@ struct Partition final {
std::string uuid_str{}; std::string uuid_str{};
std::string device{}; std::string device{};
// partition size,
// e.g 2G, 512G
std::string size{};
// mount points that will be written in fstab, // mount points that will be written in fstab,
// excluding subvol={subvol name} // excluding subvol={subvol name}
// if device is ssd, mount options for ssd should be appended // if device is ssd, mount options for ssd should be appended

View File

@ -0,0 +1,19 @@
#ifndef PARTITIONING_HPP
#define PARTITIONING_HPP
#include "gucc/partition.hpp"
#include <string> // for string
#include <string_view> // for string_view
#include <vector> // for vector
namespace gucc::disk {
// Generates sfdisk commands from Partition scheme
auto gen_sfdisk_command(const std::vector<fs::Partition>& partitions, bool is_efi) noexcept -> std::string;
// Runs disk partitioning using sfdisk command on device
auto run_sfdisk_part(std::string_view commands, std::string_view device) noexcept -> bool;
} // namespace gucc::disk
#endif // PARTITIONING_HPP

View File

@ -8,6 +8,7 @@ gucc_lib = library('gucc',
'src/pacmanconf_repo.cpp', 'src/pacmanconf_repo.cpp',
'src/initcpio.cpp', 'src/initcpio.cpp',
'src/block_devices.cpp', 'src/block_devices.cpp',
'src/partitioning.cpp',
'src/luks.cpp', 'src/luks.cpp',
'src/zfs.cpp', 'src/zfs.cpp',
'src/btrfs.cpp', 'src/btrfs.cpp',

104
gucc/src/partitioning.cpp Normal file
View File

@ -0,0 +1,104 @@
#include "gucc/partitioning.hpp"
#include "gucc/io_utils.hpp"
#include <algorithm> // for sort, unique_copy
#include <ranges> // for ranges::*
#include <string_view> // for string_view
#include <fmt/compile.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
using namespace std::string_view_literals;
using namespace std::string_literals;
namespace {
constexpr auto convert_fsname(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;
}
constexpr auto get_part_type_alias(std::string_view fsname) noexcept -> std::string_view {
if (fsname == "vfat"sv) {
return "U"sv;
} else if (fsname == "swap"sv) {
return "S"sv;
}
return "L"sv;
}
} // namespace
namespace gucc::disk {
auto gen_sfdisk_command(const std::vector<fs::Partition>& partitions, bool is_efi) noexcept -> std::string {
// sfdisk does not create partition table without partitions by default. The lines with partitions are expected in the script by default.
std::string sfdisk_commands{"label: gpt\n"s};
if (!is_efi) {
sfdisk_commands = "label: msdos\n"s;
}
// sort by mountpoint & device
auto partitions_sorted{partitions};
std::ranges::sort(partitions_sorted, {}, &fs::Partition::mountpoint);
std::ranges::sort(partitions_sorted, {}, &fs::Partition::device);
// filter duplicates
std::vector<fs::Partition> partitions_filtered{};
std::ranges::unique_copy(
partitions_sorted, std::back_inserter(partitions_filtered),
{},
&fs::Partition::device);
for (const auto& part : partitions_filtered) {
const auto& fsname = convert_fsname(part.fstype);
const auto& fs_alias = get_part_type_alias(fsname);
// L - alias 'linux'. Linux
// U - alias 'uefi'. EFI System partition
sfdisk_commands += fmt::format(FMT_COMPILE(",type={}"), fs_alias);
// The field size= support '+' and '-' in the same way as Unnamed-fields
// format. The default value of size indicates "as much as possible";
// i.e., until the next partition or end-of-device. A numerical argument is
// by default interpreted as a number of sectors, however if the size
// is followed by one of the multiplicative suffixes (KiB, MiB, GiB,
// TiB, PiB, EiB, ZiB and YiB) then the number is interpreted
// as the size of the partition in bytes and it is then aligned
// according to the device I/O limits. A '+' can be used instead of a
// number to enlarge the partition as much as possible. Note '+' is
// equivalent to the default behaviour for a new partition; existing
// partitions will be resized as required.
if (!part.size.empty()) {
sfdisk_commands += fmt::format(FMT_COMPILE(",size={}"), part.size);
}
// set boot flag
if (fsname == "vfat"sv) {
// bootable is specified as [*|-], with as default not-bootable.
// The value of this field is irrelevant for Linux
// - when Linux runs it has been booted already
// - but it might play a role for certain boot loaders and for other operating systems.
sfdisk_commands += ",bootable"s;
}
sfdisk_commands += "\n"s;
}
return sfdisk_commands;
}
auto run_sfdisk_part(std::string_view commands, std::string_view device) noexcept -> bool {
const auto& sfdisk_cmd = fmt::format(FMT_COMPILE("echo -e '{}' | sfdisk '{}' 2>>/tmp/cachyos-install.log &>/dev/null"), commands, device);
if (!utils::exec_checked(sfdisk_cmd)) {
spdlog::error("Failed to run partitioning with sfdisk: {}", sfdisk_cmd);
return false;
}
return true;
}
} // namespace gucc::disk

View File

@ -0,0 +1,92 @@
#include "doctest_compatibility.h"
#include "gucc/partitioning.hpp"
#include "gucc/logger.hpp"
#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 PART_TEST = R"(label: gpt
,type=L
,type=U,size=2G,bootable
)"sv;
static constexpr auto PART_BIOS_TEST = R"(label: msdos
,type=L
,type=L,size=2G
)"sv;
static constexpr auto PART_SWAP_TEST = R"(label: gpt
,type=L
,type=U,size=2G,bootable
,type=S,size=16G
)"sv;
TEST_CASE("partitioning gen test")
{
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);
SECTION("btrfs with subvolumes")
{
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, .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, .size = "2G", .mount_opts = "defaults,noatime"s},
};
const auto& sfdisk_content = gucc::disk::gen_sfdisk_command(partitions, true);
REQUIRE_EQ(sfdisk_content, PART_TEST);
}
SECTION("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, .size = "2G", .mount_opts = "defaults,noatime"s},
};
const auto& sfdisk_content = gucc::disk::gen_sfdisk_command(partitions, true);
REQUIRE_EQ(sfdisk_content, PART_TEST);
}
SECTION("basic xfs bios")
{
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 = "ext4"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .size = "2G", .mount_opts = "defaults,noatime"s},
};
const auto& sfdisk_content = gucc::disk::gen_sfdisk_command(partitions, false);
REQUIRE_EQ(sfdisk_content, PART_BIOS_TEST);
}
SECTION("swap 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, .size = "16G", .mount_opts = "defaults,noatime"s},
gucc::fs::Partition{.fstype = "vfat"s, .mountpoint = "/boot"s, .uuid_str = "8EFB-4B84"s, .device = "/dev/nvme0n1p2"s, .size = "2G", .mount_opts = "defaults,noatime"s},
};
const auto& sfdisk_content = gucc::disk::gen_sfdisk_command(partitions, true);
REQUIRE_EQ(sfdisk_content, PART_SWAP_TEST);
}
SECTION("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, .size = "2G", .mount_opts = "defaults,noatime"s},
};
const auto& sfdisk_content = gucc::disk::gen_sfdisk_command(partitions, true);
REQUIRE_EQ(sfdisk_content, PART_TEST);
}
// TODO(vnepogodin): add tests for raid and lvm
}