Skip to content

Commit eb3f7df

Browse files
committed
👷 disk: extract lvm/crypt queries + fix interactive mount loop
- Fix interactive additional partition loop - Fix lvm_show_vg - Extract list_mounted_devices, list_containing_crypt, list_non_crypt to installer-lib - Add gucc::lvm::parse_vgs_output and show_volume_groups with tests
1 parent 4975483 commit eb3f7df

10 files changed

Lines changed: 208 additions & 79 deletions

File tree

‎gucc/include/gucc/lvm.hpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <string> // for string
55
#include <string_view> // for string_view
6+
#include <utility> // for pair
67
#include <vector> // for vector
78

89
namespace gucc::lvm {
@@ -24,6 +25,14 @@ auto detect_lvm() noexcept -> LvmInfo;
2425
// Activate LVM volumes (load dm-mod, vgscan, vgchange)
2526
auto activate_lvm() noexcept -> bool;
2627

28+
// Show volume groups with their sizes
29+
auto show_volume_groups() noexcept -> std::vector<std::pair<std::string, std::string>>;
30+
31+
/// @brief Parse vgs command output (tab-separated vg_name and vg_size)
32+
/// @param output Raw output from: vgs -o vg_name,vg_size --noheading --separator='\t' --units=g
33+
/// @return Vector of pairs (vg_name, vg_size)
34+
auto parse_vgs_output(std::string_view output) noexcept -> std::vector<std::pair<std::string, std::string>>;
35+
2736
} // namespace gucc::lvm
2837

2938
#endif // LVM_HPP

‎gucc/src/lvm.cpp‎

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
#include "gucc/io_utils.hpp"
33
#include "gucc/string_utils.hpp"
44

5-
#include <ranges> // for ranges::*
5+
#include <algorithm> // for transform, filter
6+
#include <ranges> // for ranges::*
7+
#include <utility> // for make_pair
68

79
#include <fmt/compile.h>
810
#include <fmt/format.h>
@@ -12,39 +14,64 @@
1214
using namespace std::string_view_literals;
1315

1416
namespace {
15-
16-
/// Parse LVM output lines, trimming whitespace and filtering empty lines
17-
auto parse_lvm_output(std::string_view output) noexcept -> std::vector<std::string> {
18-
return gucc::utils::make_multiline_view(output)
17+
auto parse_simple_lvm_output(std::string_view output) noexcept -> std::vector<std::string> {
18+
return utils::make_multiline_view(output)
1919
| std::ranges::views::transform([](std::string_view sv) { return gucc::utils::trim(sv); })
2020
| std::ranges::views::filter([](std::string_view sv) { return !sv.empty(); })
2121
| std::ranges::views::transform([](std::string_view sv) { return std::string{sv}; })
2222
| std::ranges::to<std::vector<std::string>>();
2323
}
24-
2524
} // namespace
2625

2726
namespace gucc::lvm {
2827

28+
auto parse_vgs_output(std::string_view output) noexcept -> std::vector<std::pair<std::string, std::string>> {
29+
if (output.empty()) {
30+
return {};
31+
}
32+
33+
return utils::make_multiline_view(output)
34+
| std::ranges::views::transform([](std::string_view line) {
35+
auto trimmed = utils::trim(line);
36+
auto split = utils::make_split_view(trimmed, '\t');
37+
auto it = std::ranges::begin(split);
38+
39+
if (it == std::ranges::end(split)) {
40+
return std::pair<std::string, std::string>{};
41+
}
42+
auto vg_name = std::string{utils::trim(*it)};
43+
++it;
44+
45+
if (it == split.end()) {
46+
return std::make_pair(std::move(vg_name), std::string{});
47+
}
48+
auto vg_size = std::string{utils::trim(*it)};
49+
50+
return std::make_pair(std::move(vg_name), std::move(vg_size));
51+
})
52+
| std::ranges::views::filter([](auto&& pair) { return !pair.first.empty(); })
53+
| std::ranges::to<std::vector<std::pair<std::string, std::string>>>();
54+
}
55+
2956
auto detect_lvm() noexcept -> LvmInfo {
3057
LvmInfo info{};
3158

3259
// Get physical volumes using --noheading for clean output
3360
const auto& pv_output = utils::exec("pvs -o pv_name --noheading 2>/dev/null");
3461
if (!pv_output.empty()) {
35-
info.physical_volumes = parse_lvm_output(pv_output);
62+
info.physical_volumes = parse_simple_lvm_output(pv_output);
3663
}
3764

3865
// Get volume groups
3966
const auto& vg_output = utils::exec("vgs -o vg_name --noheading 2>/dev/null");
4067
if (!vg_output.empty()) {
41-
info.volume_groups = parse_lvm_output(vg_output);
68+
info.volume_groups = parse_simple_lvm_output(vg_output);
4269
}
4370

4471
// Get logical volumes as vg-lv pairs
4572
const auto& lv_output = utils::exec("lvs -o vg_name,lv_name --noheading --separator='-' 2>/dev/null");
4673
if (!lv_output.empty()) {
47-
info.logical_volumes = parse_lvm_output(lv_output);
74+
info.logical_volumes = parse_simple_lvm_output(lv_output);
4875
}
4976

5077
return info;
@@ -73,4 +100,9 @@ auto activate_lvm() noexcept -> bool {
73100
return true;
74101
}
75102

103+
auto show_volume_groups() noexcept -> std::vector<std::pair<std::string, std::string>> {
104+
const auto& output = utils::exec("vgs -o vg_name,vg_size --noheading --separator='\t' --units=g 2>/dev/null");
105+
return parse_vgs_output(output);
106+
}
107+
76108
} // namespace gucc::lvm

‎gucc/tests/unit-lvm.cpp‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,64 @@ TEST_CASE("lvm test")
4949
REQUIRE(full_info.is_active());
5050
}
5151
}
52+
53+
TEST_CASE("parse_vgs_output test")
54+
{
55+
using gucc::lvm::parse_vgs_output;
56+
57+
SECTION("empty input")
58+
{
59+
auto result = parse_vgs_output(""sv);
60+
CHECK(result.empty());
61+
}
62+
63+
SECTION("single volume group")
64+
{
65+
constexpr auto input = " vg0\t100.00g\n"sv;
66+
auto result = parse_vgs_output(input);
67+
REQUIRE(result.size() == 1);
68+
CHECK(result[0].first == "vg0");
69+
CHECK(result[0].second == "100.00g");
70+
}
71+
72+
SECTION("multiple volume groups")
73+
{
74+
constexpr auto input = " vg0\t100.00g\n vg1\t500.00g\n vg_data\t1000.00g\n"sv;
75+
auto result = parse_vgs_output(input);
76+
REQUIRE(result.size() == 3);
77+
CHECK(result[0].first == "vg0");
78+
CHECK(result[0].second == "100.00g");
79+
CHECK(result[1].first == "vg1");
80+
CHECK(result[1].second == "500.00g");
81+
CHECK(result[2].first == "vg_data");
82+
CHECK(result[2].second == "1000.00g");
83+
}
84+
85+
SECTION("handles whitespace")
86+
{
87+
constexpr auto input = " vg0 \t 50.00g \n"sv;
88+
auto result = parse_vgs_output(input);
89+
REQUIRE(result.size() == 1);
90+
CHECK(result[0].first == "vg0");
91+
CHECK(result[0].second == "50.00g");
92+
}
93+
94+
SECTION("filters empty lines")
95+
{
96+
constexpr auto input = "\n vg0\t100.00g\n\n vg1\t200.00g\n\n"sv;
97+
auto result = parse_vgs_output(input);
98+
REQUIRE(result.size() == 2);
99+
CHECK(result[0].first == "vg0");
100+
CHECK(result[1].first == "vg1");
101+
}
102+
103+
SECTION("typical vgs output")
104+
{
105+
// Output from: vgs -o vg_name,vg_size --noheading --separator='\t' --units=g
106+
constexpr auto input = " cachyos-vg\t476.45g\n"sv;
107+
auto result = parse_vgs_output(input);
108+
REQUIRE(result.size() == 1);
109+
CHECK(result[0].first == "cachyos-vg");
110+
CHECK(result[0].second == "476.45g");
111+
}
112+
}

‎installer-lib/include/cachyos/disk.hpp‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ struct MountPartitionResult {
7575
std::string luks_name;
7676
std::string luks_dev;
7777
std::string luks_uuid;
78-
std::string fstype; // detected filesystem type at mountpoint
79-
std::string uuid; // device UUID
78+
std::string fstype; // detected filesystem type at mountpoint
79+
std::string uuid; // device UUID
8080
};
8181

8282
/// Result of applying swap selection.
@@ -172,6 +172,15 @@ struct MountApplicationResult {
172172
/// Loads kernel module for specific filesystem if needed.
173173
void load_filesystem_module(std::string_view fstype) noexcept;
174174

175+
/// Lists mounted devices under the given mountpoint as a joined string.
176+
[[nodiscard]] auto list_mounted_devices(std::string_view mountpoint) noexcept -> std::string;
177+
178+
/// Lists devices containing LUKS crypto as a newline-separated string.
179+
[[nodiscard]] auto list_containing_crypt() noexcept -> std::string;
180+
181+
/// Lists devices NOT containing LUKS crypto as a newline-separated string.
182+
[[nodiscard]] auto list_non_crypt() noexcept -> std::string;
183+
175184
} // namespace cachyos::installer
176185

177186
#endif // CACHYOS_INSTALLER_DISK_HPP

‎installer-lib/src/disk.cpp‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
// import gucc
55
#include "gucc/block_devices.hpp"
66
#include "gucc/btrfs.hpp"
7+
#include "gucc/crypto_detection.hpp"
78
#include "gucc/fs_utils.hpp"
89
#include "gucc/fstab.hpp"
910
#include "gucc/io_utils.hpp"
1011
#include "gucc/mount_partitions.hpp"
1112
#include "gucc/partition_config.hpp"
1213
#include "gucc/partitioning.hpp"
14+
#include "gucc/string_utils.hpp"
1315
#include "gucc/subprocess.hpp"
1416
#include "gucc/swap.hpp"
1517
#include "gucc/system_query.hpp"
@@ -478,4 +480,22 @@ void load_filesystem_module(std::string_view fstype) noexcept {
478480
}
479481
}
480482

483+
auto list_mounted_devices(std::string_view mountpoint) noexcept -> std::string {
484+
const auto& devices = gucc::disk::list_block_devices();
485+
if (!devices) {
486+
spdlog::error("failed to find block devices");
487+
return {};
488+
}
489+
const auto& mounted_names = gucc::disk::list_mounted_devices(*devices, mountpoint);
490+
return gucc::utils::join(mounted_names);
491+
}
492+
493+
auto list_containing_crypt() noexcept -> std::string {
494+
return gucc::utils::exec("blkid | awk '/TYPE=\"crypto_LUKS\"/{print $1}' | sed 's/.$//'");
495+
}
496+
497+
auto list_non_crypt() noexcept -> std::string {
498+
return gucc::utils::exec("blkid | awk '!/TYPE=\"crypto_LUKS\"/{print $1}' | sed 's/.$//'");
499+
}
500+
481501
} // namespace cachyos::installer

‎src/disk.cpp‎

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
// import gucc
1111
#include "gucc/block_devices.hpp"
1212
#include "gucc/btrfs.hpp"
13-
#include "gucc/fs_utils.hpp"
1413
#include "gucc/io_utils.hpp"
14+
#include "gucc/lvm.hpp"
1515
#include "gucc/partition.hpp"
1616
#include "gucc/partition_config.hpp"
1717
#include "gucc/string_utils.hpp"
@@ -45,12 +45,12 @@ constexpr auto find_root_btrfs_part(auto&& parts) noexcept {
4545
}
4646

4747
/// Store LUKS/LVM state from a mount result into the Config singleton.
48-
void store_luks_config(auto& config_data, cachyos::installer::MountPartitionResult&& mount_result) noexcept {
48+
void store_luks_config(auto& config_data, const cachyos::installer::MountPartitionResult& mount_result) noexcept {
4949
std::get<std::int32_t>(config_data["LUKS"]) = mount_result.is_luks;
5050
std::get<std::int32_t>(config_data["LVM"]) = mount_result.is_lvm;
51-
std::get<std::string>(config_data["LUKS_NAME"]) = std::move(mount_result.luks_name);
52-
std::get<std::string>(config_data["LUKS_DEV"]) = std::move(mount_result.luks_dev);
53-
std::get<std::string>(config_data["LUKS_UUID"]) = std::move(mount_result.luks_uuid);
51+
std::get<std::string>(config_data["LUKS_NAME"]) = mount_result.luks_name;
52+
std::get<std::string>(config_data["LUKS_DEV"]) = mount_result.luks_dev;
53+
std::get<std::string>(config_data["LUKS_UUID"]) = mount_result.luks_uuid;
5454
}
5555

5656
} // namespace
@@ -223,17 +223,13 @@ auto mount_existing_subvols(std::vector<gucc::fs::Partition>& partitions) noexce
223223
return true;
224224
}
225225

226-
std::vector<std::string> lvm_show_vg() noexcept {
227-
const auto& vg_list = gucc::utils::make_multiline(gucc::utils::exec("lvs --noheadings | awk '{print $2}' | uniq"sv));
228-
229-
std::vector<std::string> res{};
230-
res.reserve(vg_list.size());
231-
for (const auto& vg : vg_list) {
232-
const auto& temp = gucc::utils::exec(fmt::format(FMT_COMPILE("vgdisplay {} | grep -i \"vg size\" | {}"), vg, "awk '{print $3$4}'"sv));
233-
res.push_back(temp);
234-
}
235-
236-
return res;
226+
auto lvm_show_vg() noexcept -> std::vector<std::pair<std::string, std::string>> {
227+
#ifdef NDEVENV
228+
return gucc::lvm::show_volume_groups();
229+
#else
230+
spdlog::info("[DRY-RUN] Would query volume groups");
231+
return {};
232+
#endif
237233
}
238234

239235
// Automated configuration of zfs. Creates a new zpool and a default set of filesystems
@@ -502,7 +498,6 @@ auto apply_mount_selections_interactive(const MountSelections& selections) noexc
502498
// 3. Apply additional partitions
503499
const auto& mountpoint_info = std::get<std::string>(config_data["MOUNTPOINT"]);
504500
for (const auto& p : selections.additional) {
505-
// Format if requested
506501
if (p.format_requested) {
507502
#ifdef NDEVENV
508503
const auto& mkfs_cmd = fmt::format(FMT_COMPILE("{} {}"), p.mkfs_command, p.device);
@@ -516,33 +511,36 @@ auto apply_mount_selections_interactive(const MountSelections& selections) noexc
516511
#endif
517512
}
518513

519-
// Mount
520-
if (!utils::mount_partition(p.device, mountpoint_info, p.mountpoint, p.mount_opts)) {
521-
spdlog::error("Failed to mount {} at {}", p.device, p.mountpoint);
514+
#ifdef NDEVENV
515+
auto mount_res = cachyos::installer::mount_partition(p.device, mountpoint_info, p.mountpoint, p.mount_opts);
516+
if (!mount_res) {
517+
spdlog::error("{}", mount_res.error());
522518
return false;
523519
}
524520

525-
// get mountpoint
526-
const auto& part_mountpoint = fmt::format(FMT_COMPILE("{}{}"), mountpoint_info, p.mountpoint);
527-
const auto& part_fs = gucc::fs::utils::get_mountpoint_fs(part_mountpoint);
528-
auto part_struct = gucc::fs::Partition{
529-
.fstype = part_fs.empty() ? p.fstype : std::string{part_fs},
521+
auto part_struct = gucc::fs::Partition{
522+
.fstype = mount_res->fstype.empty() ? p.fstype : mount_res->fstype,
530523
.mountpoint = p.mountpoint,
531524
.device = p.device,
532525
.mount_opts = p.mount_opts,
533526
};
534-
part_struct.uuid_str = gucc::fs::utils::get_device_uuid(p.device);
535-
536-
// get luks information about the additional partition
537-
const auto& luks_name = std::get<std::string>(config_data["LUKS_NAME"]);
538-
const auto& luks_uuid = std::get<std::string>(config_data["LUKS_UUID"]);
539-
if (!luks_name.empty()) {
540-
part_struct.luks_mapper_name = luks_name;
527+
part_struct.uuid_str = mount_res->uuid;
528+
if (mount_res->is_luks && !mount_res->luks_name.empty()) {
529+
part_struct.luks_mapper_name = mount_res->luks_name;
541530
}
542-
if (!luks_uuid.empty()) {
543-
part_struct.luks_uuid = luks_uuid;
531+
if (mount_res->is_luks && !mount_res->luks_uuid.empty()) {
532+
part_struct.luks_uuid = mount_res->luks_uuid;
544533
}
545-
534+
store_luks_config(config_data, *mount_res);
535+
#else
536+
spdlog::info("[DRY-RUN] Would mount {} at {}{}", p.device, mountpoint_info, p.mountpoint);
537+
auto part_struct = gucc::fs::Partition{
538+
.fstype = p.fstype,
539+
.mountpoint = p.mountpoint,
540+
.device = p.device,
541+
.mount_opts = p.mount_opts,
542+
};
543+
#endif
546544
utils::dump_partition_to_log(part_struct);
547545
// insert partition
548546
partitions.emplace_back(std::move(part_struct));
@@ -556,7 +554,7 @@ auto apply_mount_selections_interactive(const MountSelections& selections) noexc
556554
const auto& boot_devices = gucc::disk::list_block_devices();
557555
if (boot_devices) {
558556
const auto& boot_dev = gucc::disk::find_device_by_name(*boot_devices, p.device);
559-
if (boot_dev && boot_dev->type == "lvm") {
557+
if (boot_dev && boot_dev->type == "lvm"sv) {
560558
config_data["LVM_SEP_BOOT"] = 2;
561559
}
562560
}

‎src/disk.hpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <string> // for string
1010
#include <string_view> // for string_view
11+
#include <utility> // for pair
1112
#include <vector> // for vector
1213

1314
namespace utils {
@@ -84,7 +85,7 @@ auto select_btrfs_subvolumes(std::string_view root_mountpoint) noexcept -> std::
8485
// Mounts partitions
8586
[[nodiscard]] auto apply_mount_selections(const MountSelections& selections) noexcept -> bool;
8687

87-
auto lvm_show_vg() noexcept -> std::vector<std::string>;
88+
auto lvm_show_vg() noexcept -> std::vector<std::pair<std::string, std::string>>;
8889

8990
[[nodiscard]] bool zfs_auto_pres(const std::string_view& partition, const std::string_view& zfs_zpool_name) noexcept;
9091
[[nodiscard]] bool zfs_create_zpool(const std::string_view& partition, const std::string_view& pool_name) noexcept;

0 commit comments

Comments
 (0)