M src/context.hpp => src/context.hpp +8 -41
@@ 26,6 26,7 @@
#include "state.hpp"
#include <fmt/format.h>
#include <map>
+#include <set>
#include <string_view>
#include <variant>
#include <vector>
@@ 40,40 41,6 @@ namespace klapki {
namespace klapki::context {
- namespace detail {
- template <class F>
- static 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);
-
- auto next = chunk.find_first_of(" \f\n\r\t\v");
- if(!func(chunk.substr(0, next)))
- break;
-
- if(next == std::string::npos)
- break;
- chunk = chunk.substr(next);
- }
- }
-
- struct bad_cow {
- std::variant<std::string_view, std::string> data;
-
- constexpr std::string_view get() const noexcept {
- return std::visit([](auto && h) { return std::string_view{h}; }, this->data);
- }
-
- template <class O>
- constexpr bool operator==(const O & other) const noexcept {
- return this->get() == other;
- }
- constexpr bool operator==(const bad_cow & other) const noexcept { return this->get() == other.get(); }
-
- constexpr bool operator<(const bad_cow & other) const noexcept { return this->get() < other.get(); }
- };
- }
-
struct our_kernel {
std::string description;
std::string cmdline;
@@ 91,7 58,7 @@ namespace klapki::context {
};
struct context {
- std::vector<std::string> kernel_versions;
+ std::set<std::string, std::greater<std::string>> kernel_versions;
std::map<std::uint16_t, our_kernel> our_kernels;
std::vector<fresh_kernel> fresh_kernels;
@@ 99,7 66,7 @@ namespace klapki::context {
std::optional<std::string> age(const config & cfg, state::state & state);
std::optional<std::string> wisen(const config & cfg, state::state & state);
std::optional<std::string> save(const config & cfg, state::state & state);
- std::optional<std::string> commit(const config & cfg, const state::state & state);
+ std::optional<std::string> commit(const config & cfg, state::state & state); // Only updates file SHAs in state
static context derive(state::state & input_state);
};
@@ 174,7 141,7 @@ struct fmt::formatter<klapki::context::our_kernel> {
auto format(const klapki::context::our_kernel & kern, FormatContext & ctx) {
auto out = ctx.out();
- out = format_to(out, "{{ \"{}\", \"{}\", {}, [\"{}\", \"{}\"], [", kern.description, kern.cmdline,
+ out = format_to(out, "{{ \"{}\", \"{}\", {}, (\"{}\", \"{}\"), [", kern.description, kern.cmdline,
kern.device.header.length ? "(device)" : "(unknown device)", kern.image_path.first, kern.image_path.second);
bool first = true;
@@ 184,13 151,13 @@ struct fmt::formatter<klapki::context::our_kernel> {
else
first = false;
- *out++ = '[';
+ *out++ = '(';
if(el.first)
*out++ = '\"';
out = format_to(out, "{}", el.first ? std::string_view{*el.first} : "←");
if(el.first)
*out++ = '\"';
- out = format_to(out, ", \"{}\"]", el.second);
+ out = format_to(out, ", \"{}\")", el.second);
}
out = format_to(out, "] }}");
@@ 207,7 174,7 @@ struct fmt::formatter<klapki::context::fresh_kernel> {
auto format(const klapki::context::fresh_kernel & kern, FormatContext & ctx) {
auto out = ctx.out();
- out = format_to(out, "{{ \"{}\", [\"{}\", \"{}\"], [", kern.version, kern.image.first, kern.image.second);
+ out = format_to(out, "{{ \"{}\", (\"{}\", \"{}\"), [", kern.version, kern.image.first, kern.image.second);
bool first = true;
for(auto && el : kern.initrds) {
@@ 216,7 183,7 @@ struct fmt::formatter<klapki::context::fresh_kernel> {
else
first = false;
- out = format_to(out, "[\"{}\", \"{}\"]", el.first, el.second);
+ out = format_to(out, "(\"{}\", \"{}\")", el.first, el.second);
}
out = format_to(out, "] }}");
M src/context_age.cpp => src/context_age.cpp +7 -6
@@ 22,6 22,7 @@
#include "config.hpp"
#include "context.hpp"
+#include "context_detail.hpp"
#include "quickscope_wrapper.hpp"
#include <algorithm>
#include <stdlib.h>
@@ 97,16 98,16 @@ namespace {
bord);
}
- static std::vector<klapki::state::nonbase_dirname_t> compact_initrd_dirs(std::string_view prev_dir,
- std::vector<klapki::context::detail::bad_cow> initrd_dirs) {
- std::vector<klapki::state::nonbase_dirname_t> ret;
+ static 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());
for(auto && idir : initrd_dirs) {
if(idir.get() == prev_dir)
- ret.emplace_back();
+ ret.emplace_back(klapki::state::nonbase_dirname_t{}, klapki::state::shaa_t{});
else
- ret.emplace_back(idir.get());
+ ret.emplace_back(idir.get(), klapki::state::shaa_t{});
prev_dir = idir.get();
}
@@ 151,7 152,7 @@ std::optional<std::string> klapki::context::context::age(const config & cfg, sta
append_bootorder_entry(state.order, new_bootnum);
state.statecfg.wanted_entries.emplace_back(
- state::stated_config_entry{new_bootnum, {0xFF}, std::string{fkern.version}, var, std::string{image_dir.get()}, initrd_dirs});
+ state::stated_config_entry{new_bootnum, {0xFF}, std::string{fkern.version}, var, std::string{image_dir.get()}, {0x00}, initrd_dirs});
state.entries.emplace(new_bootnum,
state::boot_entry{{}, 0, {0x00}, EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS});
M src/context_commit.cpp => src/context_commit.cpp +47 -14
@@ 22,28 22,36 @@
#include "config.hpp"
#include "context.hpp"
+#include "context_detail.hpp"
#include "quickscope_wrapper.hpp"
#include <algorithm>
#include <fcntl.h>
+#include <openssl/sha.h>
#include <set>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <chrono>
+#include <sys/mman.h>
+
#define TRY_OPT(...) \
if(auto err = __VA_ARGS__; err) \
return err;
+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::optional<std::string> copy_file(int srcdir, int destdir, const char * basename) {
+static std::optional<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));
@@ 53,6 61,29 @@ static std::optional<std::string> copy_file(int srcdir, int destdir, const char
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((const unsigned char *)map, src_sb.st_size, insha);
+ }
+ auto mid = std::chrono::high_resolution_clock::now();
+ if(!std::memcmp(cursha, insha, sizeof(sha_t))) {
+ if(verbose)
+ fmt::print("{} unchanged ({})\n", basename, klapki::context::detail::sha_f{cursha});
+ return {};
+ }
+
+ 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));
+
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));
@@ 68,14 99,14 @@ static std::optional<std::string> copy_file(int srcdir, int destdir, const char
src_sb.st_size -= res;
}
+ auto end = std::chrono::high_resolution_clock::now();
+ fmt::print(stderr, "{} ({}/{})\n", (end - mid).count(), decltype(end - mid)::period{}.num, decltype(end - mid)::period{}.den);
return {};
}
-std::optional<std::string> klapki::context::context::commit(const config & cfg, const state::state & state) {
- // TODO: parallelise the copying somehow?
-
+std::optional<std::string> klapki::context::context::commit(const config & cfg, state::state & state) {
std::map<std::string_view, int> esp_dirs;
quickscope_wrapper esp_dirs_deleter{[&] {
for(auto && [dir, fd] : esp_dirs)
@@ 132,7 163,7 @@ std::optional<std::string> klapki::context::context::commit(const config & cfg,
// TODO: maybe, just maybe, deduplicate these?
for(auto && skern : state.statecfg.wanted_entries) {
source_dir_fds[detail::bad_cow{skern.kernel_dirname}] = -1;
- for(auto && initrd_dirname : skern.initrd_dirnames)
+ for(auto && [initrd_dirname, _] : skern.initrd_dirnames)
if(initrd_dirname)
source_dir_fds[detail::bad_cow{*initrd_dirname}] = -1;
}
@@ 144,15 175,16 @@ std::optional<std::string> klapki::context::context::commit(const config & cfg,
}
- std::set<std::pair<int, std::string_view>> already_copied;
- auto do_copy = [&](auto bootnum, auto && srcdir, auto && destdir, auto && basename) -> std::optional<std::string> {
+ std::map<std::pair<int, std::string_view>, const std::uint8_t *> already_copied;
+ auto do_copy = [&](auto bootnum, auto && srcdir, auto && destdir, auto && basename, auto cursha) -> std::optional<std::string> {
auto destfd = esp_dirs.find(destdir);
if(destfd == std::end(esp_dirs))
return fmt::format("ESP dir {} not in cache?", destdir);
- if(already_copied.count(std::pair{destfd->second, basename})) {
+ if(auto ac = already_copied.find(std::pair{destfd->second, basename}); ac != std::end(already_copied)) {
if(cfg.verbose)
fmt::print("Entry {:04X}: already copied {} from {} to {}\n", bootnum, basename, srcdir, destdir);
+ std::memcpy(cursha, ac->second, sizeof(sha_t));
return {};
}
@@ 162,8 194,8 @@ std::optional<std::string> klapki::context::context::commit(const config & cfg,
if(cfg.verbose)
fmt::print("Entry {:04X}: copying {} from {} to {}\n", bootnum, basename, srcdir, destdir);
- TRY_OPT(copy_file(srcfd->second, destfd->second, basename.c_str()));
- already_copied.emplace(destfd->second, basename);
+ TRY_OPT(copy_file(srcfd->second, destfd->second, basename.c_str(), cursha, cfg.verbose));
+ already_copied.emplace(std::pair{destfd->second, basename}, cursha);
return {};
};
@@ 173,7 205,7 @@ std::optional<std::string> klapki::context::context::commit(const config & cfg,
if(skern == std::end(state.statecfg.wanted_entries))
throw __func__;
- TRY_OPT(do_copy(bootnum, skern->kernel_dirname, kern.image_path.first, kern.image_path.second));
+ TRY_OPT(do_copy(bootnum, skern->kernel_dirname, kern.image_path.first, kern.image_path.second, skern->kernel_image_sha));
if(skern->initrd_dirnames.size() != kern.initrd_paths.size())
throw __func__;
@@ 182,12 214,13 @@ std::optional<std::string> klapki::context::context::commit(const config & cfg,
std::string_view dprev = kern.image_path.first;
for(std::size_t i = 0; i < skern->initrd_dirnames.size(); ++i) {
auto && [didir, dibase] = kern.initrd_paths[i];
+ auto && [sidir, ssha] = skern->initrd_dirnames[i];
- if(skern->initrd_dirnames[i])
- sprev = *skern->initrd_dirnames[i];
+ if(sidir)
+ sprev = *sidir;
if(didir)
dprev = *didir;
- TRY_OPT(do_copy(bootnum, sprev, dprev, dibase));
+ TRY_OPT(do_copy(bootnum, sprev, dprev, dibase, &ssha[0]));
}
}
M src/context_derive.cpp => src/context_derive.cpp +2 -1
@@ 21,6 21,7 @@
#include "context.hpp"
+#include "context_detail.hpp"
#include <fmt/format.h>
#include <string_view>
extern "C" {
@@ 144,7 145,7 @@ klapki::context::context klapki::context::context::derive(state::state & output_
for(auto && went : output_state.statecfg.wanted_entries)
if(auto bent = output_state.entries.find(went.bootnum_hint); bent != std::end(output_state.entries))
- ret.kernel_versions.push_back(went.version);
+ ret.kernel_versions.insert(went.version);
else
throw __func__; // unreachable, by this point all wanted entries match up to boot entries
A src/context_detail.hpp => src/context_detail.hpp +79 -0
@@ 0,0 1,79 @@
+// The MIT License (MIT)
+
+// Copyright (c) 2020 наб <nabijaczleweli@nabijaczleweli.xyz>
+
+// 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 <fmt/format.h>
+#include <string_view>
+#include <numeric>
+
+
+namespace klapki::context::detail {
+ template <class F>
+ static 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);
+
+ auto next = chunk.find_first_of(" \f\n\r\t\v");
+ if(!func(chunk.substr(0, next)))
+ break;
+
+ if(next == std::string::npos)
+ break;
+ chunk = chunk.substr(next);
+ }
+ }
+
+ struct bad_cow {
+ std::variant<std::string_view, std::string> data;
+
+ constexpr std::string_view get() const noexcept {
+ return std::visit([](auto && h) { return std::string_view{h}; }, this->data);
+ }
+
+ template <class O>
+ constexpr bool operator==(const O & other) const noexcept {
+ return this->get() == other;
+ }
+ constexpr bool operator==(const bad_cow & other) const noexcept { return this->get() == other.get(); }
+
+ constexpr bool operator<(const bad_cow & other) const noexcept { return this->get() < other.get(); }
+ };
+
+ struct sha_f {
+ const std::uint8_t * sha;
+ };
+}
+
+
+template <>
+struct fmt::formatter<klapki::context::detail::sha_f> {
+ constexpr auto parse(format_parse_context & ctx) { return ctx.begin(); }
+
+ template <typename FormatContext>
+ auto format(klapki::context::detail::sha_f s, FormatContext & ctx) {
+ using sha_t = std::uint8_t[20];
+ return std::accumulate(s.sha, s.sha + sizeof(sha_t), ctx.out(), [](auto && out, auto b) { return format_to(out, "{:02X}", b); });
+ }
+};
M src/context_save.cpp => src/context_save.cpp +92 -27
@@ 22,15 22,24 @@
#include "config.hpp"
#include "context.hpp"
+#include "quickscope_wrapper.hpp"
#include <algorithm>
-// #include <fcntl.h>
+#include <fcntl.h>
#include <openssl/sha.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <ucs2.h>
+#include <unistd.h>
extern "C" {
#include <efivar/efiboot.h>
}
+#define TRY_OPT(...) \
+ if(auto err = __VA_ARGS__; err) \
+ return err;
+
+
using sha_t = std::uint8_t[20];
@@ 38,6 47,54 @@ static constexpr bool isslash(char c) {
return c == '\\' || c == '/';
}
+/// Make temp dirs down to fpath, call func, delete the ones created.
+/// Zero-alloc, but fpath isn't reverted to being useful at the end, hence the &&.
+template <class F>
+static std::optional<std::string> with_temp_at(std::string && fpath, F && func) {
+ auto nul = std::string::npos;
+ for(struct stat sb; stat(fpath.c_str(), &sb) < 0;)
+ if(errno == ENOENT) {
+ if(nul != std::string::npos)
+ fpath[nul] = '\0';
+
+ nul = fpath.rfind('/', nul);
+ if(nul == std::string::npos)
+ break;
+ } else
+ return fmt::format("stat({}): {}", fpath.c_str(), strerror(errno));
+
+ for(auto mknul = nul; (mknul = fpath.find('\0', mknul)) != std::string::npos;) {
+ fpath[mknul] = '/';
+ if(mkdir(fpath.c_str(), 0500) < 0)
+ return fmt::format("mkdir({}): {}", fpath.c_str(), strerror(errno));
+ }
+
+ auto delete_paths = [&]() -> std::optional<std::string> {
+ for(auto rmnul = std::string::npos; nul != std::string::npos;) {
+ if(rmnul != std::string::npos)
+ fpath[rmnul] = '\0';
+
+ auto next = fpath.rfind('/', rmnul);
+ if(next <= nul)
+ break;
+
+ if(rmdir(fpath.c_str()) < 0)
+ return fmt::format("rmdir({}): {}", fpath.c_str(), strerror(errno));
+
+ rmnul = next;
+ }
+ return {};
+ };
+ klapki::quickscope_wrapper path_deleter{delete_paths};
+
+ TRY_OPT(func());
+
+ TRY_OPT(delete_paths());
+ nul = std::string::npos;
+
+ return {};
+}
+
std::optional<std::string> klapki::context::context::save(const config & cfg, state::state & state) {
for(auto && [bootnum, kern] : this->our_kernels) {
@@ 62,34 119,42 @@ std::optional<std::string> klapki::context::context::save(const config & cfg, st
}),
std::end(image_path));
std::transform(std::begin(image_path), std::end(image_path), std::begin(image_path), [](auto c) { return isslash(c) ? '/' : c; });
- fmt::print("{}\n", image_path);
- fmt::print("{}\n", kern.image_path.second);
+
+ // efi_generate_file_device_path() requires the image file to exist,
+ // and the implementation used for ESP detexion there is (a) hidden so we can't link to it and (b) massive and not something I wish to, or can, maintain.
+ // This blows, but we take precautions to revert the filesystem back to how we found it if we needed to modify it.
std::vector<std::uint8_t> devpath;
- do {
- devpath.resize(devpath.size() + 512);
-
- // extern ssize_t efi_generate_file_device_path(uint8_t *buf, ssize_t size,
- // const char * const filepath,
- // uint32_t options, ...)
- if(auto size = efi_generate_file_device_path(devpath.data(), devpath.size(), //
- image_path.c_str(), //
- EFIBOOT_ABBREV_HD);
- size >= 0)
- devpath.resize(size);
- else if(errno != ENOSPC)
- return fmt::format("Making device path for {:04X}: {}", bootnum, strerror(errno));
- } while(errno == ENOSPC);
- if(cfg.verbose) {
- const auto size = efidp_format_device_path(nullptr, 0, reinterpret_cast<const efidp_data *>(devpath.data()), devpath.size());
- fmt::print("Entry {:04X} devpath: ", bootnum);
- if(size < 0)
- fmt::print("couldn't format?\n");
- else {
- std::string path(size, '\0');
- efidp_format_device_path(path.data(), path.size(), reinterpret_cast<const efidp_data *>(devpath.data()), devpath.size());
- fmt::print("{}\n", path);
+ TRY_OPT(with_temp_at(std::move(image_path), [&, bootnum = bootnum]() -> std::optional<std::string> {
+ do {
+ devpath.resize(devpath.size() + 512);
+
+ // extern ssize_t efi_generate_file_device_path(uint8_t *buf, ssize_t size,
+ // const char * const filepath,
+ // uint32_t options, ...)
+ // EFIBOOT_ABBREV_HD matches what's produced by bootctl(1) install, and produces just HD()\File()
+ if(auto size = efi_generate_file_device_path(devpath.data(), devpath.size(), //
+ image_path.c_str(), //
+ EFIBOOT_ABBREV_HD);
+ size >= 0)
+ devpath.resize(size);
+ else if(errno != ENOSPC)
+ return fmt::format("Making device path for {:04X}: {}", bootnum, strerror(errno));
+ } while(errno == ENOSPC);
+
+ if(cfg.verbose) {
+ const auto size = efidp_format_device_path(nullptr, 0, reinterpret_cast<const efidp_data *>(devpath.data()), devpath.size());
+ fmt::print("Entry {:04X} devpath: ", bootnum);
+ if(size < 0)
+ fmt::print("couldn't format?\n");
+ else {
+ std::string path(size, '\0');
+ efidp_format_device_path(path.data(), path.size(), reinterpret_cast<const efidp_data *>(devpath.data()), devpath.size());
+ fmt::print("{}\n", path);
+ }
}
- }
+
+ return {};
+ }));
// Must be at start, we use position in derive() to match extraneous ones from cmdline
M src/context_state.cpp => src/context_state.cpp +3 -15
@@ 21,6 21,7 @@
#include "context.hpp"
+#include "context_detail.hpp"
#include "util.hpp"
#include <algorithm>
#include <cstring>
@@ 32,19 33,6 @@
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::context::resolve_state_context(const state::state & input_state) {
std::map<std::uint16_t, state::boot_entry> entries{input_state.entries};
@@ 69,7 57,7 @@ std::variant<klapki::state::state, std::string> klapki::context::resolve_state_c
[&](auto && stored_sha) { return !std::memcmp(went.load_option_sha, stored_sha.second, sizeof(sha_t)); });
dupe != std::end(shas)) {
fmt::print(stderr, "Boot entry {:04X}: duplicate SHA {} with entry {:04X}. Dropping\n", went.bootnum_hint,
- sha_f{went.load_option_sha}, dupe->first);
+ detail::sha_f{went.load_option_sha}, dupe->first);
return true;
}
@@ 105,7 93,7 @@ std::variant<klapki::state::state, std::string> klapki::context::resolve_state_c
return true;
} else {
- fmt::print(stderr, "Entry formerly {:04X} ({}) not found. Abandoning.\n", went.bootnum_hint, sha_f{went.load_option_sha});
+ fmt::print(stderr, "Entry formerly {:04X} ({}) not found. Abandoning.\n", went.bootnum_hint, detail::sha_f{went.load_option_sha});
return false;
}
});
M src/context_wisen.cpp => src/context_wisen.cpp +1 -0
@@ 22,6 22,7 @@
#include "config.hpp"
#include "context.hpp"
+#include "context_detail.hpp"
#include "quickscope_wrapper.hpp"
#include <sys/mman.h>
#include <sys/types.h>
M src/main.cpp => src/main.cpp +3 -2
@@ 103,6 103,7 @@ namespace klapki {
}
}
-int main(int, const char ** argv) {
- return klapki::main(argv);
+int main(int, const char ** argv) try { return klapki::main(argv); } catch(const char * thrown) {
+ fmt::print(stderr, "{}\n", thrown);
+ throw;
}
M src/ops_execute.cpp => src/ops_execute.cpp +6 -0
@@ 86,6 86,11 @@ std::optional<std::string> klapki::ops::execute(const klapki::ops::bootpos & bp,
}
std::optional<std::string> klapki::ops::execute(const klapki::ops::addkernel & ak, const klapki::config &, klapki::state::state &, context::context & context) {
+ if(context.kernel_versions.count(ak.version)) {
+ fmt::print(stderr, "addkernel: kernel version {} already known\n", ak.version);
+ return {};
+ }
+
klapki::context::fresh_kernel kern{ak.version, {}, {}};
slash_path(kern.image, ak.image);
@@ 95,6 100,7 @@ std::optional<std::string> klapki::ops::execute(const klapki::ops::addkernel & a
slash_path(kern.initrds.back(), initrd);
}
+ context.kernel_versions.emplace(std::move(ak.version));
context.fresh_kernels.emplace_back(std::move(kern));
return {};
}
M src/state.cpp => src/state.cpp +6 -5
@@ 152,11 152,12 @@ std::variant<klapki::state::state, std::string> klapki::state::state::load(const
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->version == other.version && //
- this->variant == other.variant && //
- this->kernel_dirname == other.kernel_dirname && //
+ return this->bootnum_hint == other.bootnum_hint && //
+ !std::memcmp(this->load_option_sha, other.load_option_sha, sizeof(sha_t)) && //
+ this->version == other.version && //
+ this->variant == other.variant && //
+ this->kernel_dirname == other.kernel_dirname && //
+ !std::memcmp(this->kernel_image_sha, other.kernel_image_sha, sizeof(sha_t)) && //
this->initrd_dirnames == other.initrd_dirnames;
}
M src/state.hpp => src/state.hpp +8 -5
@@ 24,6 24,7 @@
#include "util.hpp"
+#include <array>
#include <cstdint>
#include <fmt/format.h>
#include <map>
@@ 38,6 39,7 @@ namespace klapki {
namespace klapki::state {
using sha_t = std::uint8_t[20];
+ using shaa_t = std::array<std::uint8_t, 20>;
using nonbase_dirname_t = std::optional<std::string>; // Use SUB to copy from previous entry
struct boot_order_flat {
@@ 62,10 64,11 @@ namespace klapki::state {
/// Hint initially, then pointer
std::uint16_t bootnum_hint; // big-endian
sha_t load_option_sha;
- std::string version; // NUL-terminated
- std::string variant; // NUL-terminated
- std::string kernel_dirname; // on rootfs; NUL-terminated
- std::vector<nonbase_dirname_t> initrd_dirnames; // on rootfs; entries NUL-terminated; list empty-terminated
+ std::string version; // NUL-terminated
+ std::string variant; // NUL-terminated
+ std::string kernel_dirname; // on rootfs; NUL-terminated
+ sha_t kernel_image_sha;
+ std::vector<std::pair<nonbase_dirname_t, shaa_t>> initrd_dirnames; // on rootfs; entries NUL-terminated; list empty-terminated w/o SHA
bool operator==(const stated_config_entry & other) const noexcept;
@@ 153,7 156,7 @@ struct fmt::formatter<klapki::state::stated_config_entry> {
ent.kernel_dirname);
bool first = true;
- for(auto && el : ent.initrd_dirnames) {
+ for(auto && [el, _] : ent.initrd_dirnames) {
if(!first)
out = format_to(out, ", ");
else
M src/state_config.cpp => src/state_config.cpp +25 -5
@@ 105,6 105,14 @@ int klapki::state::stated_config::parse(klapki::state::stated_config & into, con
size -= new_entry.kernel_dirname.size() + 1; // NUL
// fmt::print(stderr, "variant: {} ({})\n", new_entry.kernel_dirname, new_entry.kernel_dirname.size());
+ if(size <= sizeof(new_entry.kernel_image_sha)) {
+ fmt::print(stderr, "extraneous data; 1.5\n");
+ break;
+ }
+ memcpy(&new_entry.kernel_image_sha, data, sizeof(new_entry.kernel_image_sha));
+ data += sizeof(new_entry.kernel_image_sha);
+ size -= sizeof(new_entry.kernel_image_sha);
+
for(;;) {
const auto idir_end = std::find_if(data, data + size, [](auto b) { return b == '\0'; });
if(idir_end == data + size) {
@@ 115,14 123,23 @@ int klapki::state::stated_config::parse(klapki::state::stated_config & into, con
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();
+
+ shaa_t idir_sha;
+ if(size <= idir_sha.size()) {
+ fmt::print(stderr, "extraneous data; 2.5\n");
+ break;
+ }
+ memcpy(&idir_sha[0], data, idir_sha.size());
+ data += idir_sha.size();
+ size -= idir_sha.size();
+
+ if(idir == SUB)
+ new_entry.initrd_dirnames.emplace_back(nonbase_dirname_t{}, idir_sha);
else
- new_entry.initrd_dirnames.emplace_back(std::move(idir));
+ new_entry.initrd_dirnames.emplace_back(std::move(idir), idir_sha);
}
into.wanted_entries.emplace_back(std::move(new_entry));
@@ 154,13 171,16 @@ std::vector<std::uint8_t> klapki::state::stated_config::serialise() const {
*cur++ = '\0';
cur = std::copy(std::begin(went.kernel_dirname), std::end(went.kernel_dirname), cur);
*cur++ = '\0';
+ cur = std::copy(std::begin(went.kernel_image_sha), std::end(went.kernel_image_sha), cur);
- for(auto && idir : went.initrd_dirnames) {
+ for(auto && [idir, isha] : went.initrd_dirnames) {
if(idir)
cur = std::copy(std::begin(*idir), std::end(*idir), cur);
else
cur = std::copy(SUB, (SUB) + std::strlen(SUB), cur);
*cur++ = '\0';
+
+ cur = std::copy(std::begin(isha), std::end(isha), cur);
}
*cur++ = '\0';
}
M test-data/state_parse/exempli-gratia => test-data/state_parse/exempli-gratia +0 -0
M test-data/state_parse/just-kbase => test-data/state_parse/just-kbase +0 -0
M test-data/state_parse/one-initrd => test-data/state_parse/one-initrd +0 -0
M test-data/state_parse/one-initrd-copy+two-initrds => test-data/state_parse/one-initrd-copy+two-initrds +0 -0
M test/state_parse.cpp => test/state_parse.cpp +48 -39
@@ 37,49 37,58 @@ static std::unordered_map<const char *, klapki::state::stated_config> states = {
"5.8.0-1-amd64",
"",
"/boot/",
+ {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00},
{}}}}},
{"one-initrd",
- klapki::state::stated_config{0x0b02,
- {"test"},
- {{0x0102,
- {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
- "5.8.0-1-amd64",
- "debug",
- "/boot/",
- {"/boot/initrds"}}}}},
+ klapki::state::stated_config{
+ 0x0b02,
+ {"test"},
+ {{0x0102,
+ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
+ "5.8.0-1-amd64",
+ "debug",
+ "/boot/",
+ {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00},
+ {{"/boot/initrds", {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}}}}}}},
{"one-initrd-copy+two-initrds",
- klapki::state::stated_config{0x0b02,
- {"test", "2st"},
- {{0x0102,
- {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
- "5.8.0-1-amd64",
- "loud",
- "/boot/",
- {{}}},
- {0x0102,
- {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
- "5.8.0-1-amd64",
- "quiet",
- "/boot/",
- {
- "/boot/initrds",
- "/boot",
- }}}}},
+ klapki::state::stated_config{
+ 0x0b02,
+ {"test", "2st"},
+ {{0x0102,
+ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
+ "5.8.0-1-amd64",
+ "loud",
+ "/boot/",
+ {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00},
+ {{{}, {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}}}},
+ {0x0102,
+ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13},
+ "5.8.0-1-amd64",
+ "quiet",
+ "/boot/",
+ {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00},
+ {
+ {"/boot/initrds", {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}},
+ {"/boot", {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}},
+ }}}}},
{"exempli-gratia",
- klapki::state::stated_config{0x0020,
- {"owo"},
- {{0x000B,
- {0x7C, 0x72, 0x2D, 0xF4, 0x1B, 0x78, 0xEF, 0xC7, 0xC4, 0x07, 0x20, 0xBA, 0x67, 0x54, 0xFC, 0x36, 0x6D, 0x8F, 0xE5, 0x39},
- "5.8.0-1-amd64",
- "",
- "/boot",
- {{}}},
- {0x000D,
- {0xB0, 0x7C, 0x71, 0x35, 0x33, 0xEC, 0x0F, 0x2E, 0x21, 0x3C, 0xBF, 0xA2, 0x46, 0x1F, 0xD7, 0x25, 0xD5, 0x5D, 0xFE, 0xD4},
- "5.8.0-1-amd64",
- "owo",
- "/boot",
- {{}}}}}},
+ klapki::state::stated_config{
+ 0x0020,
+ {"owo"},
+ {{0x000B,
+ {0x7C, 0x72, 0x2D, 0xF4, 0x1B, 0x78, 0xEF, 0xC7, 0xC4, 0x07, 0x20, 0xBA, 0x67, 0x54, 0xFC, 0x36, 0x6D, 0x8F, 0xE5, 0x39},
+ "5.8.0-1-amd64",
+ "",
+ "/boot",
+ {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00},
+ {{{}, {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}}}},
+ {0x000D,
+ {0xB0, 0x7C, 0x71, 0x35, 0x33, 0xEC, 0x0F, 0x2E, 0x21, 0x3C, 0xBF, 0xA2, 0x46, 0x1F, 0xD7, 0x25, 0xD5, 0x5D, 0xFE, 0xD4},
+ "5.8.0-1-amd64",
+ "owo",
+ "/boot",
+ {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00},
+ {{{}, {0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}}}}}}},
};