diff --git a/gucc/CMakeLists.txt b/gucc/CMakeLists.txt index fd43494..943e1e1 100644 --- a/gucc/CMakeLists.txt +++ b/gucc/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(${PROJECT_NAME} SHARED src/initcpio.cpp include/gucc/initcpio.hpp src/luks.cpp include/gucc/luks.hpp src/zfs.cpp include/gucc/zfs.hpp + src/btrfs.cpp include/gucc/btrfs.hpp #src/chwd_profiles.cpp src/chwd_profiles.hpp #src/disk.cpp src/disk.hpp ) diff --git a/gucc/include/gucc/btrfs.hpp b/gucc/include/gucc/btrfs.hpp new file mode 100644 index 0000000..4942639 --- /dev/null +++ b/gucc/include/gucc/btrfs.hpp @@ -0,0 +1,22 @@ +#ifndef BTRFS_HPP +#define BTRFS_HPP + +#include // for string_view +#include // for vector + +namespace gucc::fs { + +struct BtrfsSubvolume final { + std::string_view subvolume; + std::string_view mountpoint; +}; + +// Creates btrfs subvolume +auto btrfs_create_subvol(std::string_view subvolume, std::string_view root_mountpoint) noexcept -> bool; + +// Creates btrfs subvolumes and mounts them +auto btrfs_create_subvols(const std::vector& subvols, std::string_view device, std::string_view root_mountpoint, std::string_view mount_opts) noexcept -> bool; + +} // namespace gucc::fs + +#endif // BTRFS_HPP diff --git a/gucc/meson.build b/gucc/meson.build index 1c8adcd..5e16044 100644 --- a/gucc/meson.build +++ b/gucc/meson.build @@ -8,6 +8,7 @@ gucc_lib = library('gucc', 'src/initcpio.cpp', 'src/luks.cpp', 'src/zfs.cpp', + 'src/btrfs.cpp', ], include_directories : [include_directories('include')], dependencies: deps diff --git a/gucc/src/btrfs.cpp b/gucc/src/btrfs.cpp new file mode 100644 index 0000000..c5cac8c --- /dev/null +++ b/gucc/src/btrfs.cpp @@ -0,0 +1,79 @@ +#include "gucc/btrfs.hpp" +#include "gucc/io_utils.hpp" + +#include + +#include +#include + +#include + +namespace fs = std::filesystem; + +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 == "/") { + return full_path; + } + auto pos = full_path.find_last_of('/'); + if (pos == std::string_view::npos) { + return {}; + } + return full_path.substr(0, pos); +} + +} // namespace + +namespace gucc::fs { + +auto btrfs_create_subvol(std::string_view subvolume, std::string_view root_mountpoint) noexcept -> bool { + const auto& subvol_dirs_path = fmt::format(FMT_COMPILE("{}{}"), root_mountpoint, get_dirname(subvolume)); + std::error_code err{}; + ::fs::create_directories(subvol_dirs_path, err); + if (err) { + spdlog::error("Failed to create directories for btrfs subvolume {}: {}", subvol_dirs_path, err.message()); + return false; + } + auto cmd = fmt::format(FMT_COMPILE("btrfs subvolume create {}{} 2>>/tmp/cachyos-install.log"), root_mountpoint, subvolume); + return utils::exec(cmd, true) == "0"; +} + +auto btrfs_create_subvols(const std::vector& subvols, std::string_view device, std::string_view root_mountpoint, std::string_view mount_opts) noexcept -> bool { + // Create subvolumes + for (const auto& subvol : subvols) { + if (subvol.subvolume.empty()) { + continue; + } + if (!fs::btrfs_create_subvol(subvol.subvolume, root_mountpoint)) { + spdlog::error("Failed to create btrfs subvolume {} on root mountpoint {}", subvol.subvolume, root_mountpoint); + return false; + } + } + // TODO(vnepogodin): handle exit code + utils::exec(fmt::format(FMT_COMPILE("umount -v {} &>>/tmp/cachyos-install.log"), root_mountpoint)); + + // Mount subvolumes + for (const auto& subvol : subvols) { + auto mount_option = fmt::format(FMT_COMPILE("subvol={},{}"), subvol.subvolume, mount_opts); + if (subvol.subvolume.empty()) { + mount_option = mount_opts; + } + + const auto& subvolume_mountpoint = fmt::format(FMT_COMPILE("{}{}"), root_mountpoint, subvol.subvolume); + + // TODO(vnepogodin): refactor create dir and mount into own function + std::error_code err{}; + ::fs::create_directories(subvolume_mountpoint, err); + if (err) { + spdlog::error("Failed to create directories for btrfs subvols mountpoint {}: {}", subvolume_mountpoint, err.message()); + return false; + } + // TODO(vnepogodin): handle exit code + utils::exec(fmt::format(FMT_COMPILE("mount -o {} \"{}\" {}"), mount_option, device, subvolume_mountpoint)); + } + return true; +} + +} // namespace gucc::fs diff --git a/src/disk.cpp b/src/disk.cpp index 580fe67..96125a0 100644 --- a/src/disk.cpp +++ b/src/disk.cpp @@ -4,6 +4,7 @@ #include "widgets.hpp" // import gucc +#include "gucc/btrfs.hpp" #include "gucc/io_utils.hpp" #include "gucc/string_utils.hpp" #include "gucc/zfs.hpp" @@ -29,26 +30,28 @@ void btrfs_create_subvols([[maybe_unused]] const disk_part& disk, const std::str /* clang-format on */ #ifdef NDEVENV + const auto root_mountpoint = "/mnt"sv; + // save mount options and name of the root partition gucc::utils::exec(R"(mount | grep 'on /mnt ' | grep -Po '(?<=\().*(?=\))' > /tmp/.root_mount_options)"sv); // gucc::utils::exec("lsblk -lno MOUNTPOINT,NAME | awk '/^\\/mnt / {print $2}' > /tmp/.root_partition"sv); if (mode == "manual"sv) { // Create subvolumes manually - std::string subvols{"@ @home @cache"}; + std::string subvols{"/@ /@home /@cache"}; static constexpr auto subvols_body = "\nInput names of the subvolumes separated by spaces.\nThe first one will be used for mounting /.\n"sv; if (!tui::detail::inputbox_widget(subvols, subvols_body, size(ftxui::HEIGHT, ftxui::GREATER_THAN, 4))) { return; } const auto& saved_path = fs::current_path(); - fs::current_path("/mnt"); + fs::current_path(root_mountpoint); auto subvol_list = gucc::utils::make_multiline(subvols, false, ' '); for (const auto& subvol : subvol_list) { gucc::utils::exec(fmt::format(FMT_COMPILE("btrfs subvolume create {} 2>>/tmp/cachyos-install.log"), subvol), true); } fs::current_path(saved_path); // Mount subvolumes - umount("/mnt"); + umount(root_mountpoint.data()); // Mount the first subvolume as / gucc::utils::exec(fmt::format(FMT_COMPILE("mount -o {},subvol=\"{}\" \"{}\" /mnt"), disk.mount_opts, subvol_list[0], disk.root)); // Remove the first subvolume from the subvolume list @@ -62,14 +65,14 @@ void btrfs_create_subvols([[maybe_unused]] const disk_part& disk, const std::str return; } - const auto& mountp_formatted = fmt::format(FMT_COMPILE("/mnt{}"), mountp); + const auto& mountp_formatted = fmt::format(FMT_COMPILE("{}{}"), root_mountpoint, mountp); fs::create_directories(mountp_formatted); gucc::utils::exec(fmt::format(FMT_COMPILE("mount -o {},subvol=\"{}\" \"{}\" \"{}\""), disk.mount_opts, subvol, disk.root, mountp_formatted)); } return; } if (!ignore_note) { - static constexpr auto content = "\nThis creates subvolumes:\n@ for /,\n@home for /home,\n@cache for /var/cache.\n"sv; + static constexpr auto content = "\nThis creates subvolumes:\n/@ for /,\n/@home for /home,\n/@cache for /var/cache.\n"sv; const auto& do_create = tui::detail::yesno_widget(content, size(ftxui::HEIGHT, ftxui::LESS_THAN, 15) | size(ftxui::WIDTH, ftxui::LESS_THAN, 75)); /* clang-format off */ if (!do_create) { return; } @@ -77,20 +80,15 @@ void btrfs_create_subvols([[maybe_unused]] const disk_part& disk, const std::str } // Create subvolumes automatically - const auto& saved_path = fs::current_path(); - fs::current_path("/mnt"); - gucc::utils::exec("btrfs subvolume create @ 2>>/tmp/cachyos-install.log"sv, true); - gucc::utils::exec("btrfs subvolume create @home 2>>/tmp/cachyos-install.log"sv, true); - gucc::utils::exec("btrfs subvolume create @cache 2>>/tmp/cachyos-install.log"sv, true); - // gucc::utils::exec("btrfs subvolume create @snapshots 2>>/tmp/cachyos-install.log"sv, true); - fs::current_path(saved_path); - // Mount subvolumes - umount("/mnt"); - gucc::utils::exec(fmt::format(FMT_COMPILE("mount -o {},subvol=@ \"{}\" /mnt"), disk.mount_opts, disk.root)); - fs::create_directories("/mnt/home"); - fs::create_directories("/mnt/var/cache"); - gucc::utils::exec(fmt::format(FMT_COMPILE("mount -o {},subvol=@home \"{}\" /mnt/home"), disk.mount_opts, disk.root)); - gucc::utils::exec(fmt::format(FMT_COMPILE("mount -o {},subvol=@cache \"{}\" /mnt/var/cache"), disk.mount_opts, disk.root)); + const std::vector subvolumes{ + gucc::fs::BtrfsSubvolume{.subvolume = "/@"sv, .mountpoint = "/"sv}, + gucc::fs::BtrfsSubvolume{.subvolume = "/@home"sv, .mountpoint = "/home"sv}, + gucc::fs::BtrfsSubvolume{.subvolume = "/@cache"sv, .mountpoint = "/var/cache"sv}, + // gucc::fs::BtrfsSubvolume{.subvolume = "/@snapshots"sv, .mountpoint = "/.snapshots"sv}, + }; + if (!gucc::fs::btrfs_create_subvols(subvolumes, disk.root, root_mountpoint, disk.mount_opts)) { + spdlog::error("Failed to create subvolumes automatically"); + } #else spdlog::info("Do we ignore note? {}", ignore_note); #endif