~nabijaczleweli/klapki

d8d4d6151863a2dd6a30e6c2db4e88806438badd — наб 1 year, 10 days ago c9ef8ed
Context, folding, reduced allocations; we're living life
M Makefile => Makefile +1 -1
@@ 23,7 23,7 @@
include configMakefile


LDDLLS := fmt efivar
LDDLLS := fmt efivar crypto
LDAR := $(LNCXXAR) $(foreach l,fmt,-L$(BLDDIR)$(l)) $(foreach dll,$(LDDLLS),-l$(dll))
INCAR := $(foreach l,$(foreach l,fmt,$(l)/include) Catch2/single_include/catch2,-isystemext/$(l)) $(foreach l,,-isystem$(BLDDIR)$(l)/include)
VERAR := $(foreach l,KLAPKI,-D$(l)_VERSION='$($(l)_VERSION)')

M README.md => README.md +5 -2
@@ 10,7 10,8 @@ dunno yet

#### probably a building sexion here

<!-- libssl-dev not required -->
CXX='clang++ -stdlib=libc++' make -j && ./to-zoot owo scp -P 10023 out/klapki nab@127.0.0.1:
libssl-dev
libefivar

#### From Debian repository


@@ 42,7 43,9 @@ There's [the tracker](//todo.sr.ht/~nabijaczleweli/klapki), but also see the lis

## Contributing

Send a patch inline, as an attachment, or a git link and a ref to pull from to [the list](//lists.sr.ht/~nabijaczleweli/klapki) or [me](mailto:nabijaczleweli@nabijaczleweli.xyz) directly. I'm not picky, just include the repo name in the subject prefix.
Send a patch inline, as an attachment, or a git link and a ref to pull from to
[the list](//lists.sr.ht/~nabijaczleweli/klapki) ([~nabijaczleweli/klapki@lists.sr.ht](mailto:~nabijaczleweli/klapki)) or [me](mailto:nabijaczleweli@nabijaczleweli.xyz)
directly. I'm not picky, just include the repo name in the subject prefix.

## Discussing


A src/context.cpp => src/context.cpp +133 -0
@@ 0,0 1,133 @@
// The MIT License (MIT)

// Copyright (c) 2020 nabijaczleweli

// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


#include "context.hpp"
#include "util.hpp"
#include <algorithm>
#include <cstring>
#include <fmt/format.h>
#include <iterator>
#include <numeric>
#include <openssl/sha.h>


using sha_t = std::uint8_t[20];

struct sha_f {
	const std::uint8_t * sha;
};
template <>
struct fmt::formatter<sha_f> {
	constexpr auto parse(format_parse_context & ctx) { return ctx.begin(); }

	template <typename FormatContext>
	auto format(sha_f s, FormatContext & ctx) {
		return std::accumulate(s.sha, s.sha + sizeof(sha_t), ctx.out(), [](auto && out, auto b) { return format_to(out, "{:02X}", b); });
	}
};


std::variant<klapki::state::state, std::string> klapki::resolve_state_context(const state::state & input_state) {
	std::map<std::uint16_t, state::boot_entry> entries{input_state.entries};
	// TODO: parallellise this
	// [02:06] ((наб *)(войд)())(): argh fuck
	// [02:07] ((наб *)(войд)())(): libc++12 from the LLVM repo doesn't have a working <execution>
	// [02:08] Griwes: lmao the only actually shipping and actually working <execution> in existence is the one in the NVHPC toolkit :vv:
	// [02:08] Griwes: ...well, maybe msvc has something, can't remember if they shipped yet
	std::for_each(std::begin(entries), std::end(entries),
	              [](auto & bent) { SHA1(bent.second.load_option.get(), bent.second.load_option_len, bent.second.load_option_sha); });


	state::stated_config statecfg{input_state.statecfg.boot_position, {}};
	statecfg.wanted_entries.reserve(input_state.statecfg.wanted_entries.size());


	// TODO: uniquify by SHA


	std::vector<std::pair<sha_t, std::uint16_t>> remaps;
	std::copy_if(std::begin(input_state.statecfg.wanted_entries), std::end(input_state.statecfg.wanted_entries), std::back_inserter(statecfg.wanted_entries),
	             [&](auto && went) {
		             if(auto bent = entries.find(went.bootnum_hint); bent != std::end(entries)) {
			             if(!std::memcmp(bent->second.load_option_sha, went.load_option_sha, sizeof(sha_t))) {
				             fmt::print("{:04X} matches\b");  // verbose
				             return true;
			             } else
				             fmt::print(stderr, "Boot entry {:04X}: mismatched invalid hash, searching elsewhere\n", went.bootnum_hint);
		             } else
			             fmt::print(stderr, "Boot entry {:04X} doesn't exist; moved?, searching elsewhere\n", went.bootnum_hint);

		             if(auto bent = std::find_if(begin(entries), end(entries),
		                                         [&](const auto & bent) { return !std::memcmp(bent.second.load_option_sha, went.load_option_sha, sizeof(sha_t)); });
		                bent != end(entries)) {
			             fmt::print(stderr, "Found entry formerly {:04X} at {:04X}.\n", went.bootnum_hint, bent->first);

			             std::pair<sha_t, std::uint16_t> remap;
			             memcpy(remap.first, went.load_option_sha, sizeof(sha_t));
			             remap.second = bent->first;
			             remaps.emplace_back(std::move(remap));

			             return true;
		             } else {
			             fmt::print(stderr, "Entry formerly {:04X} ({}) not found. Abandoning.\n", went.bootnum_hint, sha_f{went.load_option_sha});
			             return false;
		             }
	             });

	for(auto && [sha, bootnum] : remaps)
		if(auto went = std::find_if(std::begin(statecfg.wanted_entries), std::end(statecfg.wanted_entries),
		                            [&, &sha = sha](auto && went) { return !std::memcmp(went.load_option_sha, sha, sizeof(sha_t)); });
		   went != std::end(statecfg.wanted_entries))
			went->bootnum_hint = bootnum;


	auto boot_order = std::visit(visit_variant{[&](const state::boot_order_flat & bof) {
		                                           state::boot_order_structured ret{};

		                                           bool ours = false;
		                                           std::vector<std::uint16_t> bootnums;
		                                           for(std::uint16_t * cur = bof.order.get(); cur < bof.order.get() + bof.order_cnt; ++cur) {
			                                           bool our =
			                                               std::find_if(std::begin(statecfg.wanted_entries), std::end(statecfg.wanted_entries),
			                                                            [&](auto && went) { return went.bootnum_hint == *cur; }) != std::end(statecfg.wanted_entries);

			                                           if(our == ours)
				                                           bootnums.emplace_back(*cur);
			                                           else {
				                                           std::vector<std::uint16_t> bns{*cur};
				                                           bns.swap(bootnums);
				                                           ret.order.emplace_back(std::move(bns), ours);
				                                           ours = our;
			                                           }
		                                           }

		                                           if(!bootnums.empty())
			                                           ret.order.emplace_back(std::move(bootnums), ours);

		                                           return ret;
	                                           },
	                                           [](const state::boot_order_structured & bos) { return bos; }},
	                             input_state.order);


	return state::state{std::move(boot_order), std::move(entries), std::move(statecfg)};
}

A src/context.hpp => src/context.hpp +32 -0
@@ 0,0 1,32 @@
// The MIT License (MIT)

// Copyright (c) 2020 nabijaczleweli

// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


#pragma once


#include <variant>
#include "state.hpp"


namespace klapki {
	std::variant<state::state, std::string> resolve_state_context(const state::state & input_state);
}

M src/main.cpp => src/main.cpp +22 -41
@@ 21,57 21,38 @@


#include "config.hpp"
#include "context.hpp"
#include "state.hpp"
#include "util.hpp"
#include <fmt/format.h>


namespace klapki {
	static std::map<std::uint16_t, state::boot_entry> clone_entries(const std::map<std::uint16_t, state::boot_entry> & entries) {
		std::map<std::uint16_t, state::boot_entry> ret;
		for(auto && [num, ent] : entries) {
			void * op = malloc(ent.load_option_len);
			std::memcpy(op, ent.load_option.get(), ent.load_option_len);
			ret.emplace(num, state::boot_entry{std::unique_ptr<std::uint8_t[], free_deleter>{(std::uint8_t *)op}, ent.load_option_len, ent.attributes});
		}
		return ret;
	}

	static state::state clone_state(const state::state & input_state) {
		return {std::visit(visit_variant{
		                       [&](const state::boot_order_flat & f) {
			                       void * bb = calloc(2, f.order_cnt);
			                       std::memcpy(bb, f.order.get(), 2 * f.order_cnt);
			                       return state::boot_order_flat{std::unique_ptr<std::uint16_t[], free_deleter>{(std::uint16_t *)bb}, f.order_cnt};
		                       },
		                       [](const state::boot_order_structured &) {
			                       throw;
			                       return state::boot_order_flat{{}, 0};
		                       },
		                   },
		                   input_state.order),
		        clone_entries(input_state.entries), input_state.statecfg};
	}

	static int main(const char ** argv) {
		return std::visit(visit_variant{[](config && cfg) {
			if(cfg.verbose)fmt::print("{}\n", cfg);
			                                if(cfg.verbose)
				                                fmt::print("{}\n", cfg);

			                                return std::visit(visit_variant{[&](const state::state & input_state) {
				                                                                // TODO: parse, reduce here, obtain output_state that way
				                                                                state::state output_state{clone_state(input_state)};

				                                                                for(auto && op : cfg.ops) {
					                                                                if(cfg.verbose)
						                                                                fmt::print("Running {}\n", klapki::op{op});

					                                                                if(auto err = op::execute(op, cfg, output_state); err) {
						                                                                fmt::print("{}\n", *err);
						                                                                return 3;
					                                                                }
				                                                                }

				                                                                return 0;
				                                                                return std::visit(visit_variant{[&](state::state && output_state) {
					                                                                                                for(auto && op : cfg.ops) {
						                                                                                                if(cfg.verbose)
							                                                                                                fmt::print("Running {}\n", klapki::op{op});

						                                                                                                if(auto err = op::execute(op, cfg, output_state);
						                                                                                                   err) {
							                                                                                                fmt::print("{}\n", *err);
							                                                                                                return 4;
						                                                                                                }
					                                                                                                }

					                                                                                                return 0;
				                                                                                                },
				                                                                                                [](std::string && errmsg) {
					                                                                                                fmt::print("{}\n", errmsg);
					                                                                                                return 3;
				                                                                                                }},
				                                                                                  resolve_state_context(input_state));
			                                                                },
			                                                                [](std::string && errmsg) {
				                                                                fmt::print("{}\n", errmsg);

M src/ops.cpp => src/ops.cpp +4 -3
@@ 38,12 38,13 @@ std::variant<klapki::ops::op_t, std::string> klapki::op::from_cmdline(const char
		if(!argv[0] || argv[0][0] == '\0')
			return fmt::format("{}: bootpos requires a position", argv0);
		errno          = 0;
		const auto pos = std::strtoul(argv[0], &end, 0);
		const auto position = argv[0];
		++argv;
		const auto pos = std::strtoul(position, &end, 0);
		if(pos > 0xFFFF)
			return fmt::format("{}: bootpos position must fit in 16 bits", argv0);
			return fmt::format("{}: bootpos position {} must fit in 16 bits", argv0, position);
		if(*end != '\0')
			return fmt::format("{}: bootpos position: {}", argv0, errno ? strerror(errno) : "not a number");
			return fmt::format("{}: bootpos position {}: {}", argv0, position, errno ? strerror(errno) : "not a number");

		return ops::bootpos{static_cast<std::uint16_t>(pos)};
	} else if(!std::strcmp(opname, "addkernel")) {

M src/state.cpp => src/state.cpp +3 -3
@@ 72,14 72,14 @@ static int get_efi_data(const efi_guid_t & guid, const char * name, F && func) {
	if(int res = efi_get_variable(guid, name, &raw_data, &size, &attr))
		return res;

	std::unique_ptr<std::uint8_t[], klapki::free_deleter> data(raw_data);
	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.reset(reinterpret_cast<std::uint16_t *>(data.release()));
		bord.order = std::reinterpret_pointer_cast<std::uint16_t[]>(data);
		bord.order_cnt = size / 2;
		return 0;
	});


@@ 88,7 88,7 @@ static int get_boot_order(klapki::state::boot_order_flat & bord) {
static int get_boot_entry(std::map<std::uint16_t, klapki::state::boot_entry> & bents, std::uint16_t num) {
	// TODO: sprintf() and avoid an allocation instead?
	return get_efi_data(efi_guid_global, fmt::format("Boot{:04X}", num).c_str(), [&](auto && data, auto size, auto attr) {
		bents.emplace(num, klapki::state::boot_entry{std::move(data), size, attr});
		bents.emplace(num, klapki::state::boot_entry{std::move(data), size, {}, attr});
		return 0;
	});
}

M src/state.hpp => src/state.hpp +7 -5
@@ 37,7 37,7 @@ namespace klapki::state {
	using nonbase_dirname_t = std::optional<std::string>;  // Use SUB to copy from previous entry

	struct boot_order_flat {
		std::unique_ptr<std::uint16_t[], free_deleter> order;
		std::shared_ptr<std::uint16_t[]> order;
		std::size_t order_cnt;
	};
	struct boot_order_structured {


@@ 46,13 46,15 @@ namespace klapki::state {
	using boot_order_t = std::variant<boot_order_flat, boot_order_structured>;

	struct boot_entry {
		std::unique_ptr<std::uint8_t[], free_deleter> load_option;
		std::shared_ptr<std::uint8_t[]> load_option;
		std::size_t load_option_len;
		sha_t load_option_sha;

		std::uint32_t attributes;
	};

	struct stated_config_entry {
		/// Hint initially, then pointer
		std::uint16_t bootnum_hint;  // Big-Endian
		sha_t load_option_sha;
		std::string kernel_dirname;                      // NUL-terminated


@@ 90,7 92,7 @@ struct fmt::formatter<klapki::state::boot_order_t> {
		for(; cnt--; ++first) {
			out = format_to(out, "{:04X}", *first);
			if(cnt != 0)
				out = format_to(out, ",");
				*out++ = ',';
		}
		return out;
	}


@@ 107,13 109,13 @@ struct fmt::formatter<klapki::state::boot_order_t> {
			               bool first = true;
			               for(auto && el : bord.order) {
				               if(!first)
					               out = format_to(out, ",");
					               *out++ = ',';
				               else
					               first = false;

				               *out++ = el.second ? '{' : '[';
				               out    = write(out, el.first.data(), el.first.size());
				               *out++ = el.second ? '}' : '}';
				               *out++ = el.second ? '}' : ']';
			               }
		               },
		           },

M src/state_parse.cpp => src/state_parse.cpp +1 -1
@@ 68,7 68,7 @@ int klapki::state::stated_config::parse(klapki::state::stated_config & into, con
			const auto idir_end = std::find_if(data, data + size, [](auto b) { return b == '\0'; });
			if(idir_end == data + size) {
				fmt::print(stderr, "extraneous data; 2\n");
				goto end;  // break outer loop; 2020 and C++ does not have this??
				goto end;  // break outer loop; 2020 and C++ does not have this
			}

			std::string idir{data, static_cast<std::size_t>(idir_end - data)};

M src/util.hpp => src/util.hpp +0 -8
@@ 37,12 37,4 @@ namespace klapki {
	};
	template <class... Ts>
	visit_variant(Ts...)->visit_variant<Ts...>;

	/// {shared,unique}_ptr deleter that uses free(3)
	struct free_deleter {
		template <class T>
		void operator()(T * ptr) const noexcept {
			std::free(ptr);
		}
	};
}