From ae931b94f69ceec68812999da6f5cf67ebcf059b Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 21:16:03 +0200 Subject: [PATCH] watch config file --- src/config/config.cpp | 90 +++++++++++++++++++++++++++++++++++++------ src/main.cpp | 4 +- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index e026c7a..8fab2cc 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,21 +1,30 @@ #include "config/config.hpp" #include "common/exception.hpp" -#include -#include +#include +#include #include #include #include +#include #include #include +#include #include #include +#include +#include #include #include +#include +#include +#include +#include #include #include #include +#include using namespace Config; @@ -25,15 +34,17 @@ namespace { } bool Config::loadAndWatchConfig(const std::string& file) { + if (!std::filesystem::exists(file)) + return false; + // parse config file - toml::value toml{}; - if (std::filesystem::exists(file)) { - try { - toml = toml::parse(file); - } catch (const std::exception& e) { - throw LSFG::rethrowable_error("Unable to parse configuration file", e); - } + std::optional parsed; + try { + parsed.emplace(toml::parse(file)); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Unable to parse configuration file", e); } + auto& toml = *parsed; // parse global configuration auto& global = globalConf; @@ -81,9 +92,66 @@ bool Config::loadAndWatchConfig(const std::string& file) { } // prepare config watcher - // (TODO) + std::thread([file = file, valid = global.valid]() { + try { + const int fd = inotify_init(); + if (fd < 0) + throw std::runtime_error("Failed to initialize inotify\n" + "- " + std::string(strerror(errno))); - return false; + const int wd = inotify_add_watch(fd, file.c_str(), IN_MODIFY | IN_CLOSE_WRITE); + if (wd < 0) { + close(fd); + + throw std::runtime_error("Failed to add inotify watch for " + file + "\n" + "- " + std::string(strerror(errno))); + } + + // watch for changes + std::optional discard_until; + + std::array buffer{}; + while (true) { + const ssize_t len = read(fd, buffer.data(), buffer.size()); + if (len <= 0 && errno != EINTR) { + inotify_rm_watch(fd, wd); + close(fd); + + throw std::runtime_error("Error reading inotify event\n" + "- " + std::string(strerror(errno))); + } + + size_t i{}; + while (std::cmp_less(i, len)) { + auto* event = reinterpret_cast(&buffer.at(i)); + i += sizeof(inotify_event) + event->len; + if (event->len <= 0) + continue; + + // stall a bit, then mark as invalid + discard_until.emplace(std::chrono::steady_clock::now() + + std::chrono::milliseconds(500)); + } + + auto now = std::chrono::steady_clock::now(); + if (discard_until.has_value() && now > *discard_until) { + discard_until.reset(); + + // mark config as invalid + valid->store(false, std::memory_order_release); + + // and wait until it has been marked as valid again + while (!valid->load(std::memory_order_acquire)) + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + } + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: Error in config watcher thread:\n"; + std::cerr << "- " << e.what() << '\n'; + } + }).detach(); + + return true; } Configuration Config::getConfig(std::string_view name) { diff --git a/src/main.cpp b/src/main.cpp index 26afa20..39d54ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,7 +67,7 @@ namespace { try { Config::loadAndWatchConfig(file); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:" << '\n'; + std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:\n"; std::cerr << "- " << e.what() << '\n'; exit(0); } @@ -77,7 +77,7 @@ namespace { try { conf = Config::getConfig(name); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting." << '\n'; + std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting.\n"; std::cerr << e.what() << '\n'; exit(0); }