diff --git a/gucc/CMakeLists.txt b/gucc/CMakeLists.txt index a8e23f9..a559b14 100644 --- a/gucc/CMakeLists.txt +++ b/gucc/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(${PROJECT_NAME} #SHARED src/pacmanconf_repo.cpp include/gucc/pacmanconf_repo.hpp src/initcpio.cpp include/gucc/initcpio.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/zfs.cpp include/gucc/zfs.hpp src/btrfs.cpp include/gucc/btrfs.hpp diff --git a/gucc/include/gucc/partition.hpp b/gucc/include/gucc/partition.hpp index 0519a96..be48cfb 100644 --- a/gucc/include/gucc/partition.hpp +++ b/gucc/include/gucc/partition.hpp @@ -12,6 +12,10 @@ struct Partition final { std::string uuid_str{}; std::string device{}; + // partition size, + // e.g 2G, 512G + std::string size{}; + // mount points that will be written in fstab, // excluding subvol={subvol name} // if device is ssd, mount options for ssd should be appended diff --git a/gucc/include/gucc/partitioning.hpp b/gucc/include/gucc/partitioning.hpp new file mode 100644 index 0000000..91c46f2 --- /dev/null +++ b/gucc/include/gucc/partitioning.hpp @@ -0,0 +1,19 @@ +#ifndef PARTITIONING_HPP +#define PARTITIONING_HPP + +#include "gucc/partition.hpp" + +#include // for string +#include // for string_view +#include // for vector + +namespace gucc::disk { + +// Generates sfdisk commands from Partition scheme +auto gen_sfdisk_command(const std::vector& 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 diff --git a/gucc/meson.build b/gucc/meson.build index f209116..719143d 100644 --- a/gucc/meson.build +++ b/gucc/meson.build @@ -8,6 +8,7 @@ gucc_lib = library('gucc', 'src/pacmanconf_repo.cpp', 'src/initcpio.cpp', 'src/block_devices.cpp', + 'src/partitioning.cpp', 'src/luks.cpp', 'src/zfs.cpp', 'src/btrfs.cpp', diff --git a/gucc/src/partitioning.cpp b/gucc/src/partitioning.cpp new file mode 100644 index 0000000..55d26d3 --- /dev/null +++ b/gucc/src/partitioning.cpp @@ -0,0 +1,104 @@ +#include "gucc/partitioning.hpp" +#include "gucc/io_utils.hpp" + +#include // for sort, unique_copy +#include // for ranges::* +#include // for string_view + +#include +#include + +#include + +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& 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 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 diff --git a/gucc/tests/unit-partitioning_gen.cpp b/gucc/tests/unit-partitioning_gen.cpp new file mode 100644 index 0000000..d2d9612 --- /dev/null +++ b/gucc/tests/unit-partitioning_gen.cpp @@ -0,0 +1,92 @@ +#include "doctest_compatibility.h" + +#include "gucc/partitioning.hpp" +#include "gucc/logger.hpp" + +#include +#include +#include + +#include +#include + +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([](const spdlog::details::log_msg&) { + // noop + }); + auto logger = std::make_shared("default", callback_sink); + spdlog::set_default_logger(logger); + gucc::logger::set_logger(logger); + + SECTION("btrfs with subvolumes") + { + 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, .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 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 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 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 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 +}