~nabijaczleweli/klapki

ref: 0c5db6f80b18bbd93683b19df0d6148dec31fa0f klapki/src/context_wisen.cpp -rw-r--r-- 4.9 KiB
0c5db6f8наб General cleanup 1 year, 1 month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 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.


#include "config.hpp"
#include "context.hpp"
#include "context_detail.hpp"
#include "quickscope_wrapper.hpp"
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>


#define TRY(...)                                       \
	({                                                   \
		auto ret = __VA_ARGS__;                            \
		if(auto err = std::get_if<std::string>(&ret); err) \
			return std::move(*err);                          \
		std::move(std::get<0>(ret));                       \
	})


namespace {
	struct mapped_chunk {
		std::string_view data;
		std::shared_ptr<void> data_cont;

		static std::variant<mapped_chunk, std::string> from_fd(const char * for_whom, int fd) {
			struct stat sb;
			if(fstat(fd, &sb) < 0)
				return fmt::format("{} file stat(): {}", for_whom, strerror(errno));

			auto map = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
			if(map == MAP_FAILED)
				return fmt::format("{} file mmap(): {}", for_whom, strerror(errno));

			return mapped_chunk{{static_cast<char *>(map), static_cast<std::size_t>(sb.st_size)}, {map, [size = sb.st_size](auto map) { munmap(map, size); }}};
		}
	};

	static 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));
		klapki::quickscope_wrapper cmdline_fd_del{[&] { close(fd); }};

		switch(auto pid = fork()) {
			case -1:
				return fmt::format("forking for {} {} {}: {}", from, version, variant, strerror(errno));

			case 0: {  // child
				if(dup2(fd, 1) < 0)
					exit(0x6B);

				const auto hook = fmt::format("{}{}", cfg.wisdom_root(), from);
				execl(hook.c_str(), from, version, variant, (char *)NULL);
				fmt::print(stderr, "exec {} {} {}: {}\n", hook, version, variant, strerror(errno));
				exit(0x6B);
			}

			default:  // parent
				int result;
				waitpid(pid, &result, 0);
				if(WIFEXITED(result) && WEXITSTATUS(result) != 0)
					return fmt::format("{} child returned {}", from, WEXITSTATUS(result));
				else if(WIFSIGNALED(result))
					return fmt::format("{} child killed by signal {}", from, WTERMSIG(result));

				return mapped_chunk::from_fd("cmdline", fd);
		}
	}

	static 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 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);
				return false;
			} else
				return true;
		});
	}
}

std::optional<std::string> klapki::context::context::wisen(const config & cfg, state::state & state) {
	for(auto && [bootnum, kern] : this->our_kernels) {
		auto skern = std::find_if(std::begin(state.statecfg.wanted_entries), std::end(state.statecfg.wanted_entries),
		                          [bn = bootnum](auto && skern) { return skern.bootnum_hint == bn; });
		if(skern == std::end(state.statecfg.wanted_entries))
			throw __func__;

		auto description = TRY(gain_wisdom(cfg, "description", skern->version.c_str(), skern->variant.c_str()));
		auto cmdline     = TRY(gain_wisdom(cfg, "cmdline", skern->version.c_str(), skern->variant.c_str()));
		trim(description.data);
		trim(cmdline.data);
		validate_cmdline(cmdline.data);

		kern.description = description.data;
		kern.cmdline     = cmdline.data;
	}

	return {};
}