~nabijaczleweli/klapki

26bbb5c4de94c96b8a7706baa3ed96bb518698ae — наб 1 year, 10 days ago ef0b31f
Add state loading+tests
M .build.yml => .build.yml +4 -3
@@ 2,9 2,10 @@ image: debian/sid
packages:
  - clang
tasks:
  - build-clang: |
      cd klapki
      CC=clang CXX=clang++ make
  - build-gcc: |
      cd klapki
      make
      make clean
  - build-clang: |
      cd klapki
      CC=clang CXX=clang++ make

M .gitignore => .gitignore +2 -2
@@ 13,7 13,7 @@
!src/**
!ext
!ext/**
!tests
!tests/**
!test
!test/**
!test-data
!test-data/**

M Makefile => Makefile +28 -8
@@ 23,30 23,39 @@
include configMakefile


LDDLLS := fmt
LDAR := $(LNCXXAR) $(foreach l,fmt,-L$(BLDDIR)$(l)) $(foreach dll,$(LDDLLS),-l$(dll))
INCAR := $(foreach l,$(foreach l,fmt,$(l)/include),-isystemext/$(l)) $(foreach l,,-isystem$(BLDDIR)$(l)/include)
LDDLLS := fmt efivar
LDAR := $(LNCXXAR) $(foreach l,fmt Catch2,-L$(BLDDIR)$(l)) $(foreach dll,$(LDDLLS),-l$(dll))
INCAR := $(foreach l,$(foreach l,fmt Catch2,$(l)/include),-isystemext/$(l)) $(foreach l,,-isystem$(BLDDIR)$(l)/include)
VERAR := $(foreach l,KLAPKI,-D$(l)_VERSION='$($(l)_VERSION)')
SOURCES := $(sort $(wildcard src/*.cpp src/**/*.cpp src/**/**/*.cpp src/**/**/**/*.cpp))
HEADERS := $(sort $(wildcard src/*.hpp src/**/*.hpp src/**/**/*.hpp src/**/**/**/*.hpp))
SOURCES := $(sort $(wildcard $(SRCDIR)*.cpp $(SRCDIR)**/*.cpp $(SRCDIR)**/**/*.cpp $(SRCDIR)**/**/**/*.cpp))
TEST_SOURCES := $(sort $(wildcard $(TSTDIR)*.cpp $(TSTDIR)**/*.cpp $(TSTDIR)**/**/*.cpp $(TSTDIR)**/**/**/*.cpp))


.PHONY : all clean exe audiere cpp-localiser cpr fmt seed11 semver zstd whereami-cpp
.PHONY : all clean build build-test audiere cpp-localiser cpr fmt catch seed11 semver zstd whereami-cpp


all : fmt exe
all : fmt catch build build-test test

test: build-test
	$(OUTDIR)klapki-test$(EXE)

clean :
	rm -rf $(OUTDIR)

exe : audiere cpp-localiser cpr seed11 fmt seed11 semver whereami-cpp zstd $(OUTDIR)klapki$(EXE)
build : audiere cpp-localiser cpr seed11 fmt catch seed11 semver whereami-cpp zstd $(OUTDIR)klapki$(EXE)
build-test : audiere cpp-localiser cpr seed11 fmt catch seed11 semver whereami-cpp zstd $(OUTDIR)klapki-test$(EXE)
fmt : $(BLDDIR)fmt/libfmt$(ARCH)
catch : $(BLDDIR)Catch2/libCatch2$(ARCH)


$(OUTDIR)klapki$(EXE) : $(subst $(SRCDIR),$(OBJDIR),$(subst .cpp,$(OBJ),$(SOURCES)))
	$(CXX) $(CXXAR) -o$@ $^ $(PIC) $(LDAR)
	$(STRIP) $(STRIPAR) $@

$(OUTDIR)klapki-test$(EXE) : $(subst $(TSTDIR),$(BLDDIR)test/,$(subst .cpp,$(OBJ),$(TEST_SOURCES))) $(subst $(SRCDIR),$(OBJDIR),$(subst .cpp,$(OBJ),$(filter-out $(SRCDIR)main.cpp,$(SOURCES))))
	$(CXX) $(CXXAR) -o$@ $^ $(PIC) $(LDAR) -Wl,--whole-archive -lCatch2 -Wl,--no-whole-archive
	$(STRIP) $(STRIPAR) $@

$(BLDDIR)audiere/lib/libaudiere$(DLL) : ext/audiere/CMakeLists.txt
	@mkdir -p $(abspath $(dir $@)../build)
	# FLAC doesn't seem to work on Travis by default so v0v


@@ 60,6 69,9 @@ $(BLDDIR)cpp-localiser/libcpp-localiser$(ARCH) : ext/cpp-localiser/Makefile
$(BLDDIR)seed11/libseed11$(ARCH) : $(foreach src,seed11_system_agnostic seed11_$(SEED11_SYSTEM_TYPE) deterministic_unsafe_seed_device,$(BLDDIR)seed11/obj/$(src)$(OBJ))
	$(AR) crs $@ $^

$(BLDDIR)Catch2/libCatch2$(ARCH) : $(patsubst ext/Catch2/include/%.cpp,$(BLDDIR)Catch2/obj/%$(OBJ),$(wildcard ext/Catch2/include/*.cpp ext/Catch2/include/**/*.cpp ext/Catch2/include/**/**/*.cpp))
	$(AR) crs $@ $^

$(BLDDIR)fmt/libfmt$(ARCH) : $(patsubst ext/fmt/src/%.cc,$(BLDDIR)fmt/obj/%$(OBJ),$(wildcard ext/fmt/src/*.cc))
	$(AR) crs $@ $^



@@ 86,10 98,18 @@ $(OBJDIR)%$(OBJ) : $(SRCDIR)%.cpp
	@mkdir -p $(dir $@)
	$(CXX) $(CXXAR) $(INCAR) $(VERAR) -c -o$@ $^

$(BLDDIR)test/%$(OBJ) : $(TSTDIR)%.cpp
	@mkdir -p $(dir $@)
	$(CXX) $(CXXAR) $(INCAR) -I$(SRCDIR) $(VERAR) -c -o$@ $^

$(BLDDIR)fmt/obj/%$(OBJ) : ext/fmt/src/%.cc
	@mkdir -p $(dir $@)
	$(CXX) $(CXXAR) -Iext/fmt/include -c -o$@ $^

$(BLDDIR)Catch2/obj/%$(OBJ) : ext/Catch2/include/%.cpp
	@mkdir -p $(dir $@)
	$(CXX) $(CXXAR) -c -o$@ $^

#$(BLDDIR)semver/obj/%$(OBJ) : ext/semver/src/%.cpp
#	@mkdir -p $(dir $@)
#	$(CXX) $(CXXAR) -Iext/semver/include -c -o$@ $^

M README.md => README.md +1 -0
@@ 11,6 11,7 @@ dunno yet
#### probably a building sexion here

<!-- libssl-dev not required -->
libefivar

#### From Debian repository


M configMakefile => configMakefile +1 -0
@@ 55,3 55,4 @@ OUTDIR := out/
BLDDIR := out/build/
OBJDIR := $(BLDDIR)obj/
SRCDIR := src/
TSTDIR := test/

M klapki.sublime-project => klapki.sublime-project +2 -2
@@ 27,8 27,8 @@
		},
		{
			"follow_symlinks": true,
			"name": "Tests",
			"path": "tests"
			"name": "Test",
			"path": "test"
		},
		{
			"follow_symlinks": true,

M src/config.cpp => src/config.cpp +4 -2
@@ 49,7 49,7 @@ const char * klapki::config::host() const noexcept {
}


std::variant<klapki::config, std::string> klapki::read_config(const char ** argv) {
std::variant<klapki::config, std::string> klapki::config::read(const char ** argv) {
	const auto argv0 = argv[0];
	++argv;



@@ 69,7 69,9 @@ std::variant<klapki::config, std::string> klapki::read_config(const char ** argv
					                   "-h\tShow this help\n"
					                   "\n"
					                   "Recognised ops:\n"
					                   "\tdump",
					                   "\tdump"
					                   "\tbootorder <pos>"
					                   "\taddkernel <version> <image> <cmdline> [initrd…] <\"\">",
					                   KLAPKI_VERSION, argv0);

				default:

M src/config.hpp => src/config.hpp +2 -2
@@ 38,9 38,9 @@ namespace klapki {
		std::vector<ops::op_t> ops;

		std::variant<std::string_view, std::string> host_raw;
	};

	std::variant<config, std::string> read_config(const char ** argv);
		static std::variant<config, std::string> read(const char ** argv);
	};
}



A src/efi.cpp => src/efi.cpp +38 -0
@@ 0,0 1,38 @@
// 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 "efi.hpp"
#include <byteswap.h>
#include <endian.h>


// As seen in <efivar/efivar.h>, ish
#if BYTE_ORDER == LITTLE_ENDIAN
#define MAYBESWAP(d) __builtin_bswap16(d)
#else
#define MAYBESWAP(d) (d)
#endif


/// uuid(1) spat this out
const char * const klapki::efi_guid_klapki_s = "a8a9ad3a-f831-11ea-946d-674ccd7415cc";
const efi_guid_t klapki::efi_guid_klapki{0xa8a9ad3a, 0xf831, 0x11ea, MAYBESWAP(0x946d), {0x67, 0x4c, 0xcd, 0x74, 0x15, 0xcc}};

A src/efi.hpp => src/efi.hpp +34 -0
@@ 0,0 1,34 @@
// 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


extern "C" {
#include <efivar/efivar.h>
}


namespace klapki {
	extern const char * const efi_guid_klapki_s;
	extern const efi_guid_t efi_guid_klapki;
}

M src/main.cpp => src/main.cpp +19 -2
@@ 21,6 21,7 @@


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



@@ 29,16 30,32 @@ namespace klapki {
	static int main(const char ** argv) {
		return std::visit(visit_variant{[](config && cfg) {
			                                fmt::print("{}\n", cfg);

			                                return std::visit(visit_variant{[](const state::state && input_state) {
				                                                                fmt::print("{}\n", input_state.order);
				                                                                fmt::print("{}\n", input_state.entries.size());

				                                                                return 0;
			                                                                },
			                                                                [](std::string && errmsg) {
				                                                                fmt::print("{}\n", errmsg);
				                                                                return 2;
			                                                                }},
			                                                  state::state::load(cfg.argv0, cfg.host()));

			                                return 0;
		                                },
		                                [](std::string && errmsg) {
			                                fmt::print("{}\n", errmsg);
			                                fmt::print(stderr, "{}\n", errmsg);
			                                return 1;
		                                }},
		                  read_config(argv));
		                  config::read(argv));
	}
}

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

// int err = efi_set_variable(klapki::efi_guid_klapki, us, (std::uint8_t *)us, strlen(us),
//                            EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, 0);

A src/state.cpp => src/state.cpp +162 -0
@@ 0,0 1,162 @@
// 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 "state.hpp"
#include "efi.hpp"
#include <cstdlib>
#include <fmt/format.h>
#include <memory>
#include <optional>
extern "C" {
#include <efivar/efivar.h>
// Doesn't include efivar.h:
#include <efivar/efivar-guids.h>
}


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, const char * us) noexcept {
	return guid == klapki::efi_guid_klapki && !std::strcmp(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::unique_ptr<std::uint8_t[], klapki::free_deleter> data(raw_data);
	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_cnt = size / 2;
		return 0;
	});
}

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});
		return 0;
	});
}

static int get_our_config(klapki::state::stated_config & statecfg, const char * us) {
	return get_efi_data(klapki::efi_guid_klapki, us,
	                    // TODO: error recovery; flag to ignore? force?
	                    [&](auto && data, auto size, auto) { return klapki::state::stated_config::parse(statecfg, data.get(), size); });
}


std::variant<klapki::state::state, std::string> klapki::state::state::load(const char * argv0, const char * us) {
	if(!efi_variables_supported())
		return fmt::format("{}: EFI not supported?", argv0);


	std::vector<std::uint16_t> boot_entries;
	bool have_boot_order = false;
	bool have_our_config = false;

	if(int res = iterate_efi_vars([&](auto && guid, auto name) {
		   char id_guid_[36 + 2 + 1];
		   auto id_guid = id_guid_;
		   efi_guid_to_id_guid(&guid, &id_guid);
		   // fmt::print("{}\t\t{}\t\t{}\n", id_guid, guid == efi_guid_global, name);

		   if(is_boot_entry(guid, name))
			   boot_entries.emplace_back(std::strtoul(name + std::strlen("Boot"), nullptr, 16));
		   else if(is_boot_order(guid, name))
			   have_boot_order = true;
		   else if(is_our_config(guid, name, us))
			   have_our_config = true;
	   }))
		return fmt::format("{}: klapki::state::state::load(): iteration: {}", argv0, strerror(res));  // No threads here, and we avoid a strerror_r() clusterfuck


	boot_order_flat bord{};
	if(have_boot_order) {
		if(int res = get_boot_order(bord))
			return fmt::format("{}: klapki::state::state::load(): getting BootOrder: {}", argv0, strerror(res));
	} else
		fmt::print(stderr, "{}: no BootOrder?\n", argv0);


	std::map<std::uint16_t, boot_entry> entries;
	for(auto bent : boot_entries)
		if(int res = get_boot_entry(entries, bent))
			return fmt::format("{}: klapki::state::state::load(): getting Boot{:04X}: {}", argv0, bent, strerror(res));


	stated_config statecfg{};
	if(have_our_config) {
		if(int res = get_our_config(statecfg, us))
			return fmt::format("{}: klapki::state::state::load(): getting {}-{}: {}", argv0, efi_guid_klapki_s, us, strerror(res));
	} else
		fmt::print(stderr, "{}: no config for this host ({}) found; going to the top\n", argv0, us);

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


bool klapki::state::stated_config_entry::operator==(const klapki::state::stated_config_entry & other) const noexcept {
	return this->bootnum_hint == other.bootnum_hint &&                                   //
	       !std::memcmp(this->load_option_sha, other.load_option_sha, sizeof(sha_t)) &&  //
	       this->kernel_dirname == other.kernel_dirname &&                               //
	       this->initrd_dirnames == other.initrd_dirnames;
}

bool klapki::state::stated_config::operator==(const klapki::state::stated_config & other) const noexcept {
	return this->boot_place == other.boot_place && this->wanted_entries == other.wanted_entries;
}

A src/state.hpp => src/state.hpp +124 -0
@@ 0,0 1,124 @@
// 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 "util.hpp"
#include <cstdint>
#include <fmt/format.h>
#include <map>
#include <string>
#include <variant>
#include <vector>


namespace klapki::state {
	using sha_t             = std::uint8_t[20];
	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::size_t order_cnt;
	};
	struct boot_order_structured {
		std::vector<std::pair<std::vector<std::uint16_t>, bool>> order;
	};
	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::size_t load_option_len;

		std::uint32_t attributes;
	};

	struct stated_config_entry {
		std::uint16_t bootnum_hint;  // Big-Endian
		sha_t load_option_sha;
		std::string kernel_dirname;                      // NUL-terminated
		std::vector<nonbase_dirname_t> initrd_dirnames;  // entries NUL-terminated; list empty-terminated


		bool operator==(const stated_config_entry & other) const noexcept;
	};

	struct stated_config {
		std::uint16_t boot_place;  // Big-Endian
		std::vector<stated_config_entry> wanted_entries;

		/// 0 on OK, errno on error
		static int parse(stated_config & into, const void * data, std::size_t size);


		bool operator==(const stated_config & other) const noexcept;
	};

	struct state {
		boot_order_t order;
		std::map<std::uint16_t, boot_entry> entries;
		stated_config statecfg;

		static std::variant<state, std::string> load(const char * argv0, const char * us);
	};
}


template <>
struct fmt::formatter<klapki::state::boot_order_t> {
	template <class OItr>
	static OItr write(OItr out, const std::uint16_t * first, std::size_t cnt) {
		for(; cnt--; ++first) {
			out = format_to(out, "{:04X}", *first);
			if(cnt != 0)
				out = format_to(out, ",");
		}
		return out;
	}

	constexpr auto parse(format_parse_context & ctx) { return ctx.begin(); }

	template <typename FormatContext>
	auto format(const klapki::state::boot_order_t & bord, FormatContext & ctx) {
		auto out = ctx.out();

		std::visit(klapki::visit_variant{
		               [&](const klapki::state::boot_order_flat & bord) { out = write(out, bord.order.get(), bord.order_cnt); },
		               [&](const klapki::state::boot_order_structured & bord) {
			               bool first = true;
			               for(auto && el : bord.order) {
				               if(!first)
					               out = format_to(out, ",");
				               else
					               first = false;

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

		return out;
	}
};

A src/state_parse.cpp => src/state_parse.cpp +92 -0
@@ 0,0 1,92 @@
// 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 "state.hpp"
#include <arpa/inet.h>
#include <fmt/format.h>


#define SUB "\x1A"


int klapki::state::stated_config::parse(klapki::state::stated_config & into, const void * _data, std::size_t size) {
	if(size < 2)
		return ENODATA;

	const char * data = reinterpret_cast<const char *>(_data);
	memcpy(&into.boot_place, data, sizeof(into.boot_place));
	into.boot_place = ntohs(into.boot_place);
	data += sizeof(into.boot_place);
	size -= sizeof(into.boot_place);

	while(size != 0) {
		stated_config_entry new_entry{};
		if(size <= sizeof(new_entry.bootnum_hint) + sizeof(new_entry.load_option_sha)) {
			fmt::print(stderr, "extraneous data; 0\n");
			break;
		}

		memcpy(&new_entry.bootnum_hint, data, sizeof(new_entry.bootnum_hint));
		new_entry.bootnum_hint = ntohs(new_entry.bootnum_hint);
		data += sizeof(new_entry.bootnum_hint);
		size -= sizeof(new_entry.bootnum_hint);

		memcpy(&new_entry.load_option_sha, data, sizeof(new_entry.load_option_sha));
		data += sizeof(new_entry.load_option_sha);
		size -= sizeof(new_entry.load_option_sha);

		const auto kdir_end = std::find_if(data, data + size, [](auto b) { return b == '\0'; });
		if(kdir_end == data + size) {
			fmt::print(stderr, "extraneous data; 1; have: {:04X}\n", new_entry.bootnum_hint);
			break;
		}
		new_entry.kernel_dirname.assign(data, kdir_end - data);
		data += new_entry.kernel_dirname.size() + 1;  // NUL
		size -= new_entry.kernel_dirname.size() + 1;  // NUL
		// fmt::print(stderr, "kernel: {} ({})\n", new_entry.kernel_dirname, new_entry.kernel_dirname.size());

		for(;;) {
			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??
			}

			std::string idir{data, static_cast<std::size_t>(idir_end - data)};
			data += idir.size() + 1;  // NUL
			size -= idir.size() + 1;  // NUL
			// fmt::print(stderr, "initrd: {} ({})\n", idir, idir.size());

			if(idir.empty())
				break;
			else if(idir == SUB)
				new_entry.initrd_dirnames.emplace_back();
			else
				new_entry.initrd_dirnames.emplace_back(std::move(idir));
		}

		into.wanted_entries.emplace_back(std::move(new_entry));
	}
end:

	return 0;
}

M src/util.hpp => src/util.hpp +10 -2
@@ 23,18 23,26 @@
#pragma once



#include <cstdlib>
#include <string>


namespace klapki {
	std::string readline(const char * from);

	// Stolen from https://en.cppreference.com/w/cpp/utility/variant/visit
	/// Stolen from https://en.cppreference.com/w/cpp/utility/variant/visit
	template <class... Ts>
	struct visit_variant : Ts... {
		using Ts::operator()...;
	};
	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);
		}
	};
}

A test-data/state_parse/just-bootplace => test-data/state_parse/just-bootplace +2 -0
@@ 0,0 1,2 @@


\ No newline at end of file

A test-data/state_parse/just-kbase => test-data/state_parse/just-kbase +0 -0
A test-data/state_parse/one-initrd => test-data/state_parse/one-initrd +0 -0
A test-data/state_parse/one-initrd-copy+two-initrds => test-data/state_parse/one-initrd-copy+two-initrds +0 -0
A test/main.cpp => test/main.cpp +24 -0
@@ 0,0 1,24 @@
// 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.


#define CATCH_CONFIG_MAIN
#include <catch.hpp>

A test/state_parse.cpp => test/state_parse.cpp +97 -0
@@ 0,0 1,97 @@
// 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 "state.hpp"
#include "test-util.hpp"
#include <catch.hpp>
#include <fmt/format.h>
#include <unordered_map>


static std::unordered_map<const char *, klapki::state::stated_config> states = {
    {"just-bootplace", klapki::state::stated_config{0x0a01, {}}},
    {"just-kbase",
     klapki::state::stated_config{
         0x0b02,
         {{0x0102, {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}, "/boot/", {}}}}},
    {"one-initrd",
     klapki::state::stated_config{0x0b02,
                                  {{0x0102,
                                    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
                                    "/boot/",
                                    {"/boot/initrds"}}}}},
    {"one-initrd-copy+two-initrds",
     klapki::state::stated_config{
         0x0b02,
         {{0x0102, {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}, "/boot/", {{}}},
          {0x0102,
           {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
           "/boot/",
           {
               "/boot/initrds",
               "/boot",
           }}}}},
};


TEST_CASE("klapki::state::stated_config::parse() ENODATA", "[klapki::state::stated_config::parse]") {
	klapki::state::stated_config clean{};

	REQUIRE(klapki::state::stated_config::parse(clean, nullptr, 0) == ENODATA);
	REQUIRE(clean == klapki::state::stated_config{});

	REQUIRE(klapki::state::stated_config::parse(clean, nullptr, 1) == ENODATA);
	REQUIRE(clean == klapki::state::stated_config{});
}

TEST_CASE("klapki::state::stated_config::parse() okay", "[klapki::state::stated_config::parse]") {
	for(auto && [name, cfg] : states)
		SECTION(name) {
			const auto indata = klapki::test::read_file(std::string{"test-data/state_parse/"} + name);

			klapki::state::stated_config incfg{};
			REQUIRE(klapki::state::stated_config::parse(incfg, indata.data(), indata.size()) == 0);
			// for(auto && ent : incfg.wanted_entries)
			// 	fmt::print(stderr,
			// 	           "\tbootnum_hint: {}\n"
			// 	           "\tload_option_sha: {}\n"
			// 	           "\tkernel_dirname: {}\n"
			// 	           "\tinitrd_dirnames: [{}]\n",
			// 	           ent.bootnum_hint, std::string{(char *)ent.load_option_sha, sizeof(ent.load_option_sha)}, ent.kernel_dirname, ent.initrd_dirnames.size());
			// REQUIRE(incfg.boot_place == cfg.boot_place);
			// REQUIRE(incfg.wanted_entries.size() == cfg.wanted_entries.size());
			// for(std::size_t i = 0; i < incfg.wanted_entries.size(); ++i) {
			// 	REQUIRE(incfg.wanted_entries[i].bootnum_hint == cfg.wanted_entries[i].bootnum_hint);
			// 	// REQUIRE(incfg.wanted_entries[i].load_option_sha == cfg.wanted_entries[i].load_option_sha);
			// 	REQUIRE(incfg.wanted_entries[i].kernel_dirname == cfg.wanted_entries[i].kernel_dirname);
			// 	REQUIRE(incfg.wanted_entries[i].initrd_dirnames.size() == cfg.wanted_entries[i].initrd_dirnames.size());
			// 	for(std::size_t n = 0; n < incfg.wanted_entries[i].initrd_dirnames.size(); ++n) {
			// 		fmt::print(stderr, "{}\n", n);
			// 		fmt::print(stderr, "{}\n", incfg.wanted_entries[i].initrd_dirnames[n].value_or("<blegh>"));
			// 		fmt::print(stderr, "{}\n", cfg.wanted_entries[i].initrd_dirnames[n].value_or("<blegh>"));
			// 		REQUIRE(incfg.wanted_entries[i].initrd_dirnames[n] == cfg.wanted_entries[i].initrd_dirnames[n]);
			// 	}
			// }
			// REQUIRE(incfg.wanted_entries == cfg.wanted_entries);
			REQUIRE(incfg == cfg);
		}
}

A test/test-util.cpp => test/test-util.cpp +37 -0
@@ 0,0 1,37 @@
// 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 "test-util.hpp"
#include <fstream>


std::vector<std::uint8_t> klapki::test::read_file(const std::string & filename) {
	return read_file(filename.c_str());
}

std::vector<std::uint8_t> klapki::test::read_file(const char * filename) {
	std::ifstream file(filename, std::ios::binary);
	if(!file.is_open())
		throw filename;

	return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
}

A test/test-util.hpp => test/test-util.hpp +34 -0
@@ 0,0 1,34 @@
// 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 <vector>
#include <cstdint>
#include <string>


namespace klapki::test {
	std::vector<std::uint8_t> read_file(const std::string & filename);
	std::vector<std::uint8_t> read_file(const char * filename);
}