~nabijaczleweli/klapki

985a61be52091a9233496b9511b03144b8cc7fe8 — наб 1 year, 4 days ago 2170ed1
Turns out you don't need to make and delete dozens of directories if you just think
4 files changed, 78 insertions(+), 102 deletions(-)

M src/context_save.cpp
M src/main.cpp
M src/state.cpp
M src/state_config.cpp
M src/context_save.cpp => src/context_save.cpp +67 -88
@@ 47,56 47,56 @@ 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));
static void print_devpath(const efidp_data * dp, ssize_t dp_len) {
	const auto size = efidp_format_device_path(nullptr, 0, dp, dp_len);
	if(size < 0)
		fmt::print("couldn't format?\n");
	else {
		std::string path(size, '\0');
		efidp_format_device_path(path.data(), path.size(), dp, dp_len);
		fmt::print("{}\n", path);
	}
}

	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;
std::optional<std::string> klapki::context::context::save(const config & cfg, state::state & state) {
	std::vector<std::uint8_t> esp_devpath_raw;
	efidp_data * esp_devpath{};
	if(!this->our_kernels.empty()) {
		do {
			esp_devpath_raw.resize(esp_devpath_raw.size() + 128);

			// 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(),
			// however, this funxion requires the File() to exist, so by passing just the ESP root, we can append our potentially-not-yet-existent paths later on.
			if(auto size = efi_generate_file_device_path(esp_devpath_raw.data(), esp_devpath_raw.size(),  //
			                                             fmt::format("{}/", cfg.esp).c_str(),             //
			                                             EFIBOOT_ABBREV_HD);
			   size >= 0)
				esp_devpath_raw.resize(size);
			else if(errno != ENOSPC)
				return fmt::format("Making device path for ESP: {}", strerror(errno));
		} while(errno == ENOSPC);


		esp_devpath = reinterpret_cast<efidp_data *>(esp_devpath_raw.data());
		{  // esp_devpath is currently HD(some path)/File("\"). Trim it to just HD() for appending later
			efidp_data * fnode;
			(void)efidp_next_node(esp_devpath, const_cast<const efidp_data **>(&fnode));
			fnode->type    = EFIDP_END_TYPE;
			fnode->subtype = EFIDP_END_ENTIRE;
		}
		return {};
	};
	klapki::quickscope_wrapper path_deleter{delete_paths};

	TRY_OPT(func());

	TRY_OPT(delete_paths());
	nul = std::string::npos;

	return {};
}
		if(cfg.verbose) {
			fmt::print("ESP devpath: ");
			print_devpath(reinterpret_cast<const efidp_data *>(esp_devpath_raw.data()), esp_devpath_raw.size());
		}
	}


std::optional<std::string> klapki::context::context::save(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; });


@@ 106,7 106,8 @@ std::optional<std::string> klapki::context::context::save(const config & cfg, st
		if(bent == std::end(state.entries))
			throw __func__;

		auto image_path = fmt::format("{}/{}{}{}", cfg.esp, kern.image_path.first, isslash(kern.image_path.first.back()) ? "" : "\\", kern.image_path.second);
		auto image_path = fmt::format("{}{}{}{}", isslash(kern.image_path.first.front()) ? "" : "\\", kern.image_path.first,
		                              isslash(kern.image_path.first.back()) ? "" : "\\", kern.image_path.second);
		image_path.erase(std::remove_if(std::begin(image_path), std::end(image_path),
		                                [prev = false](auto c) mutable {
			                                auto cur = isslash(c);


@@ 118,44 119,22 @@ 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; });

		// 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;
		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 {};
		}));
		std::transform(std::begin(image_path), std::end(image_path), std::begin(image_path), [](auto c) { return isslash(c) ? '\\' : c; });

		std::vector<std::uint8_t> devpath_file_node(efidp_make_file(nullptr, 0, image_path.data()));
		if(efidp_make_file(devpath_file_node.data(), devpath_file_node.size(), image_path.data()) < 0)
			return fmt::format("Entry {:04X}: creating devpath File(): {}", bootnum, strerror(errno));

		efidp_data * devpath;
		if(efidp_append_node(esp_devpath, reinterpret_cast<const efidp_data *>(devpath_file_node.data()), &devpath) < 0)
			return fmt::format("Entry {:04X}: creating appending File(): {}", bootnum, strerror(errno));
		quickscope_wrapper devpath_deleter{[&] { std::free(devpath); }};
		const auto devpath_len = efidp_size(devpath);

		if(cfg.verbose) {
			fmt::print("Entry {:04X} devpath: ", bootnum);
			print_devpath(devpath, devpath_len);
		}

		// Must be at start, we use position in derive() to match extraneous ones from cmdline
		std::string templine{};


@@ 178,17 157,17 @@ std::optional<std::string> klapki::context::context::save(const config & cfg, st
		//				  ssize_t dp_size, unsigned char *description,
		//				  uint8_t *optional_data,
		//				  size_t optional_data_size)
		bent->second.load_option_len = efi_loadopt_create(nullptr, 0,                                                      //
		                                                  bent->second.attributes,                                         //
		                                                  reinterpret_cast<efidp_data *>(devpath.data()), devpath.size(),  //
		                                                  reinterpret_cast<unsigned char *>(kern.description.data()),      //
		bent->second.load_option_len = efi_loadopt_create(nullptr, 0,                                                  //
		                                                  bent->second.attributes,                                     //
		                                                  devpath, devpath_len,                                        //
		                                                  reinterpret_cast<unsigned char *>(kern.description.data()),  //
		                                                  reinterpret_cast<std::uint8_t *>(cmdline.data()), cmdline.size() * sizeof(std::uint16_t));

		bent->second.load_option = std::shared_ptr<std::uint8_t[]>{new std::uint8_t[bent->second.load_option_len]};
		if(efi_loadopt_create(bent->second.load_option.get(), bent->second.load_option_len,    //
		                      bent->second.attributes,                                         //
		                      reinterpret_cast<efidp_data *>(devpath.data()), devpath.size(),  //
		                      reinterpret_cast<unsigned char *>(kern.description.data()),      //
		if(efi_loadopt_create(bent->second.load_option.get(), bent->second.load_option_len,  //
		                      bent->second.attributes,                                       //
		                      devpath, devpath_len,                                          //
		                      reinterpret_cast<unsigned char *>(kern.description.data()),    //
		                      reinterpret_cast<std::uint8_t *>(cmdline.data()), cmdline.size() * sizeof(std::uint16_t)) < 0)
			return fmt::format("Making load option for {:04X}: {}", bootnum, strerror(errno));


M src/main.cpp => src/main.cpp +1 -1
@@ 68,7 68,7 @@ namespace klapki {
		}


		// TODO: reap
		// TODO: reap, propagate variants


		TRY_OPT(5, "context.age()", context.age(cfg, output_state));

M src/state.cpp => src/state.cpp +6 -11
@@ 111,11 111,6 @@ std::variant<klapki::state::state, std::string> klapki::state::state::load(const
	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))


@@ 128,22 123,22 @@ std::variant<klapki::state::state, std::string> klapki::state::state::load(const

	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));
		if(get_boot_order(bord) < 0)
			return fmt::format("{}: klapki::state::state::load(): getting BootOrder: {}", argv0, strerror(errno));
	} 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));
		if(get_boot_entry(entries, bent) < 0)
			return fmt::format("{}: klapki::state::state::load(): getting Boot{:04X}: {}", argv0, bent, strerror(errno));


	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));
		if(get_our_config(statecfg, us) < 0)
			return fmt::format("{}: klapki::state::state::load(): getting {}-{}: {}", argv0, efi_guid_klapki_s, us, strerror(errno));
	} else
		fmt::print(stderr, "{}: no config for this host ({}) found; going to the top\n", argv0, us);


M src/state_config.cpp => src/state_config.cpp +4 -2
@@ 30,8 30,10 @@


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

	const char * data = reinterpret_cast<const char *>(_data);
	memcpy(&into.boot_position, data, sizeof(into.boot_position));