~nabijaczleweli/klapki

6b4ab60fe1c99d2056140dfe77801a9b6e8bb3d8 — наб 3 months ago 252b04b
Thank mutt for people who *have* written C++ in the past two years
M Makefile => Makefile +3 -3
@@ 31,7 31,7 @@ SOURCES := $(sort $(wildcard $(SRCDIR)*.cpp $(SRCDIR)**/*.cpp $(SRCDIR)**/**/*.c
TEST_SOURCES := $(sort $(wildcard $(TSTDIR)*.cpp $(TSTDIR)**/*.cpp $(TSTDIR)**/**/*.cpp $(TSTDIR)**/**/**/*.cpp))
MANPAGE_SOURCES := $(sort $(wildcard $(MANDIR)*.md $(MANDIR)**/*.md))

# Building with -flto on Clang means we can't make useful $(ARCH)es, so don't
# Building with -flto on Clang means we can't make useful archives, so don't
LIBFMT := $(patsubst ext/fmt/src/%.cc,$(BLDDIR)fmt/obj/%$(OBJ),$(wildcard ext/fmt/src/*.cc))




@@ 62,8 62,8 @@ $(OUTDIR)klapki-test$(EXE) : $(subst $(TSTDIR),$(BLDDIR)test/,$(subst .cpp,$(OBJ
$(subst $(MANDIR),$(OUTDIR)man/,$(MANPAGE_SOURCES)) : $(MANDIR)index.txt $(MANPAGE_SOURCES)
	@rm -rf $(dir $@) && mkdir -p $(dir $@)
	cp $^ $(dir $@)
	ronn $@
	ronn -f $@
	ronn --organization="klapki developers"    $@
	ronn --organization="klapki developers" -f $@


$(OBJDIR)%$(OBJ) : $(SRCDIR)%.cpp

M configMakefile => configMakefile +1 -3
@@ 52,10 52,8 @@ INCCMAKEAR := CXXFLAGS="$(INCCXXAR)"
LNCMAKEAR := LDFLAGS="$(LNCXXAR)"

OBJ := .o
ARCH := .a
AR ?= ar
CXXAR := -O3 -g -std=c++17 -Wall -Wextra $(PEDANTIC) -pipe $(INCCXXAR) $(PIC)
STRIP ?= echo strip
STRIP ?= strip
STRIPAR := --strip-all --remove-section=.comment --remove-section=.note

OUTDIR := out/

M src/config.cpp => src/config.cpp +48 -46
@@ 43,60 43,62 @@ extern "C" {
	})


static std::string readline(const char * from) {
	std::ifstream in(from);
	std::string ret;
	std::getline(in, ret);
	return ret;
}

static std::string get_host() {
	if(auto host = std::getenv("KLAPKI_HOST"))
		return host;
	else {
		auto mid = readline("/etc/machine-id");
		if(!mid.empty())
			return mid;

		char hostname[HOST_NAME_MAX + 1];
		gethostname(hostname, sizeof(hostname) / sizeof(*hostname));
		return hostname;
namespace {
	std::string readline(const char * from) {
		std::ifstream in(from);
		std::string ret;
		std::getline(in, ret);
		return ret;
	}
}

static std::variant<std::string_view, std::string> get_wisom_root() {
	if(auto host = std::getenv("KLAPKI_WISDOM")) {
		std::string ret{host};
		if(!ret.empty() && ret.back() != '/')
			ret += '/';
		return ret;
	} else
		return std::string_view{"/etc/klapki/"};
}
	std::string get_host() {
		if(auto host = std::getenv("KLAPKI_HOST"))
			return host;
		else {
			auto mid = readline("/etc/machine-id");
			if(!mid.empty())
				return mid;

static std::variant<std::string_view, std::string> find_esp() {
	const char * esp = "/boot/efi";
			char hostname[HOST_NAME_MAX + 1];
			gethostname(hostname, sizeof(hostname) / sizeof(*hostname));
			return hostname;
		}
	}

	struct stat boot;
	if(stat("/boot", &boot) < 0)
		return fmt::format("find_esp(): stat(\"/boot\"): {}", strerror(errno));
	std::variant<std::string_view, std::string> get_wisom_root() {
		if(auto host = std::getenv("KLAPKI_WISDOM")) {
			std::string ret{host};
			if(!ret.empty() && ret.back() != '/')
				ret += '/';
			return ret;
		} else
			return std::string_view{"/etc/klapki/"};
	}

	struct stat boot_efi;
	if(stat("/boot/efi", &boot_efi) < 0) {
		if(errno != ENOENT)
			return fmt::format("find_esp(): stat(\"/boot/efi\"): {}", strerror(errno));
		else {
			esp      = "/boot";
			boot_efi = boot;
			if(stat("/", &boot) < 0)
				return fmt::format("find_esp(): stat(\"/\"): {}", strerror(errno));
	std::variant<std::string_view, std::string> find_esp() {
		const char * esp = "/boot/efi";

		struct stat boot;
		if(stat("/boot", &boot) < 0)
			return fmt::format("find_esp(): stat(\"/boot\"): {}", strerror(errno));

		struct stat boot_efi;
		if(stat("/boot/efi", &boot_efi) < 0) {
			if(errno != ENOENT)
				return fmt::format("find_esp(): stat(\"/boot/efi\"): {}", strerror(errno));
			else {
				esp      = "/boot";
				boot_efi = boot;
				if(stat("/", &boot) < 0)
					return fmt::format("find_esp(): stat(\"/\"): {}", strerror(errno));
			}
		}
	}

	if(boot.st_dev == boot_efi.st_dev)
		return fmt::format("find_esp(): {}: not a mount point?", esp);
		if(boot.st_dev == boot_efi.st_dev)
			return fmt::format("find_esp(): {}: not a mount point?", esp);

	return std::string_view{esp};
		return std::string_view{esp};
	}
}



M src/context_age.cpp => src/context_age.cpp +5 -11
@@ 44,15 44,9 @@ extern "C" {
		return err;


struct fresh_kernel {
	std::string_view version;
	std::pair<std::string_view, std::string_view> image;
	std::vector<std::pair<std::string_view, std::string_view>> initrds;
};

namespace {
	static std::variant<klapki::context::detail::bad_cow, std::string> unrelativise(const std::pair<std::string_view, std::string_view> & whom,
	                                                                                const char * version, const char * for_what) {
	std::variant<klapki::context::detail::bad_cow, std::string> unrelativise(const std::pair<std::string_view, std::string_view> & whom, const char * version,
	                                                                         const char * for_what) {
		if(whom.first[0] != '/') {
			fmt::print(stderr, "{} {} has relative path {}/{}", version, for_what, whom.first, whom.second);



@@ 71,7 65,7 @@ namespace {
	}

	/// Actually just find lowest unoccupied
	static std::variant<std::uint16_t, std::string> allocate_bootnum(const klapki::state::state & state) {
	std::variant<std::uint16_t, std::string> allocate_bootnum(const klapki::state::state & state) {
		std::uint16_t max{};
		if(auto found = adjacent_find(std::begin(state.entries), std::end(state.entries),
		                              [&](auto && prev, auto && next) {


@@ 86,7 80,7 @@ namespace {
			return "Out of boot entries";
	}

	static void append_bootorder_entry(klapki::state::boot_order_t & bord, std::uint16_t bootnum) {
	void append_bootorder_entry(klapki::state::boot_order_t & bord, std::uint16_t bootnum) {
		std::visit(klapki::overload{
		               [&](klapki::state::boot_order_flat &) { throw __func__; },  // can't (shouldn't) happen; not like we can do much with this
		               [&](klapki::state::boot_order_structured & bos) {


@@ 102,7 96,7 @@ namespace {
		           bord);
	}

	static std::vector<std::pair<klapki::state::nonbase_dirname_t, klapki::state::shaa_t>>
	std::vector<std::pair<klapki::state::nonbase_dirname_t, klapki::state::shaa_t>>
	compact_initrd_dirs(std::string_view prev_dir, std::vector<klapki::context::detail::bad_cow> initrd_dirs) {
		std::vector<std::pair<klapki::state::nonbase_dirname_t, klapki::state::shaa_t>> ret;
		ret.reserve(initrd_dirs.size());

M src/context_commit.cpp => src/context_commit.cpp +57 -55
@@ 51,67 51,69 @@
using sha_t = std::uint8_t[20];


/// https://stackoverflow.com/a/2180157/2851815 says FreeBSD and Darwin have fcopyfile(3),
/// but the only reference I could find was (a) copies of that answer and (b) Apple documentation, which wasn't helpful.
///
/// Plus, I've no idea if libefivar even works on Berkeley distros; it's got separate linux*.c implementations, but.
///
/// Just use sendfile(2) here and potentially ifdef for non-Linux later.
static std::variant<bool, std::string> copy_file(int srcdir, int destdir, const char * basename, sha_t cursha, bool verbose) {
	auto srcfd = openat(srcdir, basename, O_RDONLY);
	if(srcfd < 0)
		return fmt::format("Couldn't open {} for reading: {}\n", basename, strerror(errno));
	klapki::quickscope_wrapper srcfd_deleter{[&] { close(srcfd); }};

	struct stat src_sb;
	if(fstat(srcfd, &src_sb) < 0)
		return fmt::format("Couldn't stat() source {}: {}\n", basename, strerror(errno));

	sha_t insha{};
	{
		auto map = mmap(nullptr, src_sb.st_size, PROT_READ, MAP_PRIVATE, srcfd, 0);
		if(!map)
			return fmt::format("Couldn't mmap() source {}: {}\n", basename, strerror(errno));
		klapki::quickscope_wrapper map_deleter{[&] {
			if(munmap(map, src_sb.st_size))
				fmt::print(stderr, "munmap {}: {}\n", basename, strerror(errno));
		}};
namespace {
	/// https://stackoverflow.com/a/2180157/2851815 says FreeBSD and Darwin have fcopyfile(3),
	/// but the only reference I could find was (a) copies of that answer and (b) Apple documentation, which wasn't helpful.
	///
	/// Plus, I've no idea if libefivar even works on Berkeley distros; it's got separate linux*.c implementations, but.
	///
	/// Just use sendfile(2) here and potentially ifdef for non-Linux later.
	std::variant<bool, std::string> copy_file(int srcdir, int destdir, const char * basename, sha_t cursha, bool verbose) {
		auto srcfd = openat(srcdir, basename, O_RDONLY);
		if(srcfd < 0)
			return fmt::format("Couldn't open {} for reading: {}\n", basename, strerror(errno));
		klapki::quickscope_wrapper srcfd_deleter{[&] { close(srcfd); }};

		struct stat src_sb;
		if(fstat(srcfd, &src_sb) < 0)
			return fmt::format("Couldn't stat() source {}: {}\n", basename, strerror(errno));

		sha_t insha{};
		{
			auto map = mmap(nullptr, src_sb.st_size, PROT_READ, MAP_PRIVATE, srcfd, 0);
			if(!map)
				return fmt::format("Couldn't mmap() source {}: {}\n", basename, strerror(errno));
			klapki::quickscope_wrapper map_deleter{[&] {
				if(munmap(map, src_sb.st_size))
					fmt::print(stderr, "munmap {}: {}\n", basename, strerror(errno));
			}};

			SHA1(reinterpret_cast<const unsigned char *>(map), src_sb.st_size, insha);
		}
		if(!std::memcmp(cursha, insha, sizeof(sha_t))) {
			if(verbose)
				fmt::print("{} unchanged ({})\n", basename, klapki::context::detail::sha_f{cursha});

			struct stat dest_sb;  // Still copy if missning
			if(fstatat(destdir, basename, &dest_sb, 0) < 0) {
				if(errno != ENOENT)
					return fmt::format("Couldn't stat() source {}: {}\n", basename, strerror(errno));
			} else
				return false;
		}

		SHA1(reinterpret_cast<const unsigned char *>(map), src_sb.st_size, insha);
	}
	if(!std::memcmp(cursha, insha, sizeof(sha_t))) {
		if(verbose)
			fmt::print("{} unchanged ({})\n", basename, klapki::context::detail::sha_f{cursha});

		struct stat dest_sb;  // Still copy if missning
		if(fstatat(destdir, basename, &dest_sb, 0) < 0) {
			if(errno != ENOENT)
				return fmt::format("Couldn't stat() source {}: {}\n", basename, strerror(errno));
		} else
			return false;
	}

	if(verbose)
		fmt::print("{} changed ({} -> {})\n", basename, klapki::context::detail::sha_f{cursha}, klapki::context::detail::sha_f{insha});
	std::memcpy(cursha, insha, sizeof(sha_t));
			fmt::print("{} changed ({} -> {})\n", basename, klapki::context::detail::sha_f{cursha}, klapki::context::detail::sha_f{insha});
		std::memcpy(cursha, insha, sizeof(sha_t));

		auto destfd = openat(destdir, basename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
		if(destfd < 0)
			return fmt::format("Couldn't open {} for writing: {}\n", basename, strerror(errno));
		klapki::quickscope_wrapper destfd_deleter{[&] {
			if(close(destfd))
				fmt::print(stderr, "Warning: destination {} errored on close: {}", basename, strerror(errno));
		}};

	auto destfd = openat(destdir, basename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if(destfd < 0)
		return fmt::format("Couldn't open {} for writing: {}\n", basename, strerror(errno));
	klapki::quickscope_wrapper destfd_deleter{[&] {
		if(close(destfd))
			fmt::print(stderr, "Warning: destination {} errored on close: {}", basename, strerror(errno));
	}};
		off_t offset = 0;
		for(ssize_t res = 0; res < src_sb.st_size; res = sendfile(destfd, srcfd, &offset, src_sb.st_size)) {
			if(res < 0)
				return fmt::format("Couldn't copy to {} (offset {}): {}\n", basename, offset, strerror(errno));

	off_t offset = 0;
	for(ssize_t res = 0; res < src_sb.st_size; res = sendfile(destfd, srcfd, &offset, src_sb.st_size)) {
		if(res < 0)
			return fmt::format("Couldn't copy to {} (offset {}): {}\n", basename, offset, strerror(errno));
			src_sb.st_size -= res;
		}

		src_sb.st_size -= res;
		return true;
	}

	return true;
}



M src/context_detail.hpp => src/context_detail.hpp +1 -1
@@ 33,7 33,7 @@ extern "C" {

namespace klapki::context::detail {
	template <class F>
	static void tokenise_cmdline(std::string_view chunk, F && func) {
	void tokenise_cmdline(std::string_view chunk, F && func) {
		// Character set stolen from isspace()
		for(auto cur = chunk.find_first_not_of(" \f\n\r\t\v"); cur != std::string::npos; cur = chunk.find_first_not_of(" \f\n\r\t\v")) {
			chunk = chunk.substr(cur);

M src/context_save.cpp => src/context_save.cpp +2 -2
@@ 44,8 44,8 @@ extern "C" {
using sha_t = std::uint8_t[20];


static constexpr bool isslash(char c) {
	return c == '\\' || c == '/';
namespace {
	constexpr bool isslash(char c) { return c == '\\' || c == '/'; }
}



M src/context_wisen.cpp => src/context_wisen.cpp +4 -4
@@ 57,7 57,7 @@ namespace {
		}
	};

	static std::variant<mapped_chunk, std::string> gain_wisdom(const klapki::config & cfg, const char * from, const char * version, const char * variant) {
	std::variant<mapped_chunk, std::string> gain_wisdom(const klapki::config & cfg, const char * from, const char * version, const char * variant) {
		auto fd = memfd_create(from, MFD_CLOEXEC);
		if(fd < 0)
			return fmt::format("opening {} {} {} file: {}", from, version, variant, strerror(errno));


@@ 89,15 89,15 @@ namespace {
		}
	}

	static void trim(std::string_view & str) {
	void trim(std::string_view & str) {
		// Character set stolen from isspace()
		str.remove_prefix(std::min(str.find_first_not_of(" \f\n\r\t\v"), str.size()));
		str.remove_suffix(std::min(str.size() - str.find_last_not_of(" \f\n\r\t\v") - 1, str.size()));
	}

	static void denewline(std::string_view & str) { std::replace(const_cast<char *>(std::begin(str)), const_cast<char *>(std::end(str)), '\n', ' '); }
	void denewline(std::string_view & str) { std::replace(const_cast<char *>(std::begin(str)), const_cast<char *>(std::end(str)), '\n', ' '); }

	static void validate_cmdline(const std::string_view & cmdline) {
	void validate_cmdline(const std::string_view & cmdline) {
		klapki::context::detail::tokenise_cmdline(cmdline, [&](auto && arg) {
			if(arg.substr(0, std::strlen("initrd=")) == "initrd=") {  // string_view::starts_with() is C++20
				fmt::print(stderr, "Stray {} in cmdline, things might not work as planned\n", arg);

M src/main.cpp => src/main.cpp +1 -3
@@ 101,6 101,4 @@ namespace klapki {
	}
}

int main(int, const char ** argv) {
	return klapki::main(argv);
}
int main(int, const char ** argv) { return klapki::main(argv); }

M src/ops.cpp => src/ops.cpp +47 -53
@@ 25,57 25,59 @@
#include <limits>


template <class T, class N>
static std::variant<klapki::ops::op_t, std::string> pos_op(const char * argv0, const char **& argv, const char * name) {
	char * end;
	// strtoul(3): In particular, if *nptr is not '\0' but **endptr is '\0' on return, the entire string is valid.
	if(!argv[0] || argv[0][0] == '\0')
		return fmt::format("{}: {} requires a position", argv0, name);
	errno               = 0;
	const auto position = argv[0];
	++argv;
	const auto num = std::strtoull(position, &end, 0);
	if(num > std::numeric_limits<N>::max())
		return fmt::format("{}: {} position {} must fit in {} bits", argv0, name, position, sizeof(N) * 8);
	if(*end != '\0')
		return fmt::format("{}: {} position {}: {}", argv0, name, position, errno ? strerror(errno) : "not a number");

	return T{static_cast<N>(num)};
}
namespace {
	template <class T, class N>
	std::variant<klapki::ops::op_t, std::string> pos_op(const char * argv0, const char **& argv, const char * name) {
		char * end;
		// strtoul(3): In particular, if *nptr is not '\0' but **endptr is '\0' on return, the entire string is valid.
		if(!argv[0] || argv[0][0] == '\0')
			return fmt::format("{}: {} requires a position", argv0, name);
		errno               = 0;
		const auto position = argv[0];
		++argv;
		const auto num = std::strtoull(position, &end, 0);
		if(num > std::numeric_limits<N>::max())
			return fmt::format("{}: {} position {} must fit in {} bits", argv0, name, position, sizeof(N) * 8);
		if(*end != '\0')
			return fmt::format("{}: {} position {}: {}", argv0, name, position, errno ? strerror(errno) : "not a number");

template <class T>
static std::variant<klapki::ops::op_t, std::string> oneval_op(const char * argv0, const char **& argv, const char * name, const char * aname) {
	if(!argv[0])
		return fmt::format("{}: {} needs {}", argv0, name, aname);
	return T{argv++[0]};
}
		return T{static_cast<N>(num)};
	}

static std::variant<klapki::ops::op_t, std::string> addkernel_op(const char * argv0, const char **& argv) {
	klapki::ops::addkernel ret;
	template <class T>
	std::variant<klapki::ops::op_t, std::string> oneval_op(const char * argv0, const char **& argv, const char * name, const char * aname) {
		if(!argv[0])
			return fmt::format("{}: {} needs {}", argv0, name, aname);
		return T{argv++[0]};
	}

	if(!argv[0])
		return fmt::format("{}: addkernel needs <version> <image> [initrd…] <\"\">", argv0);
	ret.version = argv[0];
	++argv;
	std::variant<klapki::ops::op_t, std::string> addkernel_op(const char * argv0, const char **& argv) {
		klapki::ops::addkernel ret;

	if(!argv[0])
		return fmt::format("{}: addkernel {} needs <image> [initrd…] <\"\">", argv0, ret.version);
	if(argv[0][0] == '\0')
		return fmt::format("{}: addkernel {} image can't be empty", argv0, ret.version);
	ret.image = argv[0];
	++argv;
		if(!argv[0])
			return fmt::format("{}: addkernel needs <version> <image> [initrd…] <\"\">", argv0);
		ret.version = argv[0];
		++argv;

	while(argv[0]) {
		const auto initrd = argv[0];
		if(!argv[0])
			return fmt::format("{}: addkernel {} needs <image> [initrd…] <\"\">", argv0, ret.version);
		if(argv[0][0] == '\0')
			return fmt::format("{}: addkernel {} image can't be empty", argv0, ret.version);
		ret.image = argv[0];
		++argv;
		if(initrd[0] == '\0')
			break;
		ret.initrds.emplace_back(initrd);
	}
	if(argv[-1][0] != '\0')
		return fmt::format("{}: addkernel {} {} initrd list needs to end with empty argument", argv0, ret.version, ret.image);

	return ret;
		while(argv[0]) {
			const auto initrd = argv[0];
			++argv;
			if(initrd[0] == '\0')
				break;
			ret.initrds.emplace_back(initrd);
		}
		if(argv[-1][0] != '\0')
			return fmt::format("{}: addkernel {} {} initrd list needs to end with empty argument", argv0, ret.version, ret.image);

		return ret;
	}
}




@@ 103,13 105,5 @@ std::variant<klapki::ops::op_t, std::string> klapki::op::from_cmdline(const char


std::optional<std::string> klapki::op::execute(const klapki::ops::op_t & op, const config & cfg, state::state & state, context::context & context) {
	return std::visit(klapki::overload{
	                      [&](const klapki::ops::dump & d) { return ops::execute(d, cfg, state, context); },
	                      [&](const klapki::ops::bootpos & bp) { return ops::execute(bp, cfg, state, context); },
	                      [&](const klapki::ops::addkernel & ak) { return ops::execute(ak, cfg, state, context); },
	                      [&](const klapki::ops::delkernel & dk) { return ops::execute(dk, cfg, state, context); },
	                      [&](const klapki::ops::addvariant & av) { return ops::execute(av, cfg, state, context); },
	                      [&](const klapki::ops::delvariant & dv) { return ops::execute(dv, cfg, state, context); },
	                  },
	                  op);
	return std::visit([&](const auto & o) { return ops::execute(o, cfg, state, context); }, op);
}

M src/ops_execute.cpp => src/ops_execute.cpp +10 -8
@@ 26,14 26,16 @@
#include <fmt/format.h>


static void slash_path(std::pair<std::string_view, std::string_view> & which, const char * whose) {
	std::string_view path{whose};
	if(auto last_slash = path.rfind('/'); last_slash != std::string::npos) {
		which.first = path.substr(0, last_slash);  // drop trailing slash here
		path        = path.substr(last_slash + 1);
	} else
		which.first = ".";  // age() will resolve this to abspath
	which.second = path;
namespace {
	static void slash_path(std::pair<std::string_view, std::string_view> & which, const char * whose) {
		std::string_view path{whose};
		if(auto last_slash = path.rfind('/'); last_slash != std::string::npos) {
			which.first = path.substr(0, last_slash);  // drop trailing slash here
			path        = path.substr(last_slash + 1);
		} else
			which.first = ".";  // age() will resolve this to abspath
		which.second = path;
	}
}



M src/state.cpp => src/state.cpp +61 -65
@@ 33,72 33,68 @@ extern "C" {
}


template <class F>
static int iterate_efi_vars(F && func) {
	int status;
	efi_guid_t * iter_guid = nullptr;
	char * iter_name       = nullptr;

	while((status = efi_get_next_variable_name(&iter_guid, &iter_name)))
		func(*iter_guid, iter_name);

	if(status < 0)
		return errno;
	return 0;
}

static bool operator==(const efi_guid_t & lhs, const efi_guid_t & rhs) noexcept {
	return !memcmp(&lhs, &rhs, sizeof(efi_guid_t));
}

static bool is_boot_entry(const efi_guid_t & guid, const char * name) noexcept {
	return guid == efi_guid_global && !std::strncmp(name, "Boot", 4) && std::strlen(name) == 8 &&  //
	       std::isxdigit(name[4]) && std::isxdigit(name[5]) && std::isxdigit(name[6]) && std::isxdigit(name[7]);
}

static bool is_boot_order(const efi_guid_t & guid, const char * name) noexcept {
	return guid == efi_guid_global && !std::strcmp(name, "BootOrder");
}

static bool is_our_config(const efi_guid_t & guid, const char * name, std::string_view us) noexcept {
	return guid == klapki::efi_guid_klapki && name == us;
}

template <class F>
static int get_efi_data(const efi_guid_t & guid, const char * name, F && func) {
	std::uint8_t * raw_data{};
	std::size_t size{};
	std::uint32_t attr{};
	if(int res = efi_get_variable(guid, name, &raw_data, &size, &attr))
		return res;

	std::shared_ptr<std::uint8_t[]> data(raw_data, std::free);
	return func(std::move(data), size, attr);
}

static int get_boot_order(klapki::state::boot_order_flat & bord) {
	return get_efi_data(efi_guid_global, "BootOrder", [&](auto && data, auto size, auto) {
		// endianness?
		bord.order     = std::reinterpret_pointer_cast<std::uint16_t[]>(data);
		bord.order_cnt = size / 2;
		return 0;
	});
}

static int get_boot_entry(std::map<std::uint16_t, klapki::state::boot_entry> & bents, std::uint16_t num) {
	char name[4 + 4 + 1]{};
	fmt::format_to(name, "Boot{:04X}", num);
	return get_efi_data(efi_guid_global, name, [&](auto && data, auto size, auto attr) {
		bents.emplace(num, klapki::state::boot_entry{std::move(data), size, {}, attr});
		return 0;
	});
}

static int get_our_config(klapki::state::stated_config & statecfg, std::string_view us) {
	return get_efi_data(klapki::efi_guid_klapki, us.data(), [&](auto && data, auto size, auto) {
		klapki::state::stated_config::parse(statecfg, data.get(), size);
namespace {
	template <class F>
	int iterate_efi_vars(F && func) {
		int status;
		efi_guid_t * iter_guid = nullptr;
		char * iter_name       = nullptr;

		while((status = efi_get_next_variable_name(&iter_guid, &iter_name)))
			func(*iter_guid, iter_name);

		if(status < 0)
			return errno;
		return 0;
	});
	}

	bool operator==(const efi_guid_t & lhs, const efi_guid_t & rhs) noexcept { return !memcmp(&lhs, &rhs, sizeof(efi_guid_t)); }

	bool is_boot_entry(const efi_guid_t & guid, const char * name) noexcept {
		return guid == efi_guid_global && !std::strncmp(name, "Boot", 4) && std::strlen(name) == 8 &&  //
		       std::isxdigit(name[4]) && std::isxdigit(name[5]) && std::isxdigit(name[6]) && std::isxdigit(name[7]);
	}

	bool is_boot_order(const efi_guid_t & guid, const char * name) noexcept { return guid == efi_guid_global && !std::strcmp(name, "BootOrder"); }

	bool is_our_config(const efi_guid_t & guid, const char * name, std::string_view us) noexcept { return guid == klapki::efi_guid_klapki && name == us; }

	template <class F>
	int get_efi_data(const efi_guid_t & guid, const char * name, F && func) {
		std::uint8_t * raw_data{};
		std::size_t size{};
		std::uint32_t attr{};
		if(int res = efi_get_variable(guid, name, &raw_data, &size, &attr))
			return res;

		std::shared_ptr<std::uint8_t[]> data(raw_data, std::free);
		return func(std::move(data), size, attr);
	}

	int get_boot_order(klapki::state::boot_order_flat & bord) {
		return get_efi_data(efi_guid_global, "BootOrder", [&](auto && data, auto size, auto) {
			// endianness?
			bord.order     = std::reinterpret_pointer_cast<std::uint16_t[]>(data);
			bord.order_cnt = size / 2;
			return 0;
		});
	}

	int get_boot_entry(std::map<std::uint16_t, klapki::state::boot_entry> & bents, std::uint16_t num) {
		char name[4 + 4 + 1]{};
		fmt::format_to(name, "Boot{:04X}", num);
		return get_efi_data(efi_guid_global, name, [&](auto && data, auto size, auto attr) {
			bents.emplace(num, klapki::state::boot_entry{std::move(data), size, {}, attr});
			return 0;
		});
	}

	int get_our_config(klapki::state::stated_config & statecfg, std::string_view us) {
		return get_efi_data(klapki::efi_guid_klapki, us.data(), [&](auto && data, auto size, auto) {
			klapki::state::stated_config::parse(statecfg, data.get(), size);
			return 0;
		});
	}
}