aboutsummaryrefslogtreecommitdiffstats
path: root/liblfjail/zfs.cc
blob: 5ecd8264d106e5d8a84d48749ada6005f94da51b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
 * This source code is released into the public domain.
 */

/*
 * Generic (non-jail-related) ZFS handling.
 *
 * It would be nice to use libzfs_core for this, but it still has some issues
 * on FreeBSD, e.g. https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=257222.
 */

#include <unordered_map>

#include <unistd.h>

#include "ctype.hh"
#include "spawn.hh"
#include "words.hh"
#include "zfs.hh"

using namespace std::literals;

namespace lfjail::zfs {

namespace {

// Hardcode the program locations so we don't rely on $PATH.
constexpr std::string path_zpool = "/sbin/zpool";
constexpr std::string path_zfs = "/sbin/zfs";

// Run a command (probably a zfs command), split its output into lines,
// and return each line.
//
// The spawn_how argument is the same as lfjail::spawn().
auto spawn_lines(exec::executor auto &&executor) 
	-> std::generator<std::string_view>
{
	std::string output;

	auto result = spawn(std::move(executor),
			    capture(STDOUT_FILENO, output)).wait();

	if (!result)
		throw zfs_error("failed to run zfs command");

	co_yield std::ranges::elements_of(words(output, '\n'));
}

// Like spawn_lines, but each line is returned as a vector of words.
auto spawn_words(auto &&spawn_how)
	-> std::generator<std::vector<std::string_view>>
{
	auto lines = spawn_lines(std::forward<decltype(spawn_how)>(spawn_how));

	for (auto &&line : lines)
		co_yield std::vector(std::from_range, words(line));
}

// Run a command which produces two columns of output, assumed to be
// a name and a value; call the function from the provided map on
// each line.
auto set_values(auto &&map, std::string command, auto &&...args) -> void
{
	auto lines = spawn_words(
		exec::execl(std::move(command), std::move(args)...));

	for (auto &&words : lines) {
		if (std::ranges::size(words) != 2)
			throw zfs_error("unexpected output from zfs command");

		auto property = words[0];
		auto value = words[1];

		auto handler = map.find(property);
		if (handler != map.end())
			handler->second(value);
	}
}

} // anonymous namespace

auto get_pool(std::string_view name) -> pool {
	auto ret = pool{};

	auto handlers = std::unordered_map{
		std::pair{"name"sv, [&ret](auto &&v) { ret.name = v; }},
	};

	set_values(handlers, path_zpool, "zpool", "get",
		   "-H", "-oproperty,value", "all", name);

	return ret;
}

auto get_pools() -> std::generator<pool> {
	auto lines = spawn_words(
		exec::execl(path_zpool, "zpool", "get", "-H", "-o", "name"));

	for (auto &&words : lines)
		co_yield get_pool(words[0]);
}

auto get_filesystem(std::string_view name) -> filesystem {
	auto ret = filesystem{};

	auto handlers = std::unordered_map{
		std::pair{"name"sv, [&ret](auto &&v) { ret.name = v; }},
	};

	set_values(handlers, path_zfs, "zfs", "get", "-H",
		   "-oproperty,value"sv, "all", name);

	return ret;
}

auto get_filesystems() -> std::generator<filesystem> {
	auto lines = spawn_words(
		exec::execl(path_zfs, "zfs", "get", "-H", "-o", "name"));

	for (auto &&words : lines)
		co_yield get_filesystem(words[0]);
}

} // namespace lfjail::zfs