From c842548fef050ac5f8b56a5fcb4f579820247434 Mon Sep 17 00:00:00 2001 From: Fabien Proriol Date: Thu, 22 May 2025 15:42:13 +0200 Subject: Initial Commit --- src/settings.cpp | 285 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 src/settings.cpp (limited to 'src/settings.cpp') diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..addaa32 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,285 @@ +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "changewatcher.h" + +using namespace KaZoe; + +static inline void trim(std::string & str) +{ + str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](const unsigned char ch){return !std::isspace(ch);})); + str.erase(std::find_if(str.rbegin(), str.rend(), [](const unsigned char ch){return !std::isspace(ch);}).base(), str.end()); +} + +static inline bool list_contains(std::vector list, const std::string & str) +{ + return (std::find(list.begin(), list.end(), str) != list.end()); +} + +namespace KaZoe { +class SettingsPrivate { + friend class Settings; + Settings *m_parent; + std::map m_data; + ChangeWatcher m_watcher; + std::map m_owner; + std::string m_id; + std::mutex m_mutex_data; + std::mutex m_mutex_notifier; + bool m_bypass {false}; + std::vector> m_notifier; + +public: + SettingsPrivate(Settings *parent) + : m_parent(parent) { + char result[PATH_MAX]; + ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); + std::string binary = std::string(result, (count > 0) ? count : 0); + std::filesystem::path bin = binary; + m_id = bin.filename(); + if(m_id == "settings") + { + // Only "settings cli tools can bypass the owner protection" + m_bypass = true; + } + + m_watcher.setFileAdded([this](const std::string path){ refreshDirectory(path); }); + m_watcher.setFileRemoved([this](const std::string path){ refreshAll(); }); + m_watcher.setFileWrited([this](const std::string path){ refreshFile(path); }); + } + + void parseFile(const std::string &filename, std::map &data, bool watch = false) + { + if(!std::filesystem::exists(filename)) + { + return; + } + std::filesystem::path pfile = filename; + if(pfile.filename().string().starts_with(".")) return; + if(pfile.extension() != ".conf") return; + + if(watch) + { + m_watcher.watch(filename); + } + + std::ifstream file(filename); + std::string line; + std::string category = ""; + + while (std::getline(file, line)) { + trim(line); + + if(line.empty() || line.starts_with("#") || line.ends_with(";")) { + continue; + } + if(line.starts_with("[") && line.ends_with("]")) { + category = line.substr(1, line.length() - 2); + if(category == "General") category.clear(); + continue; + } + size_t pos = line.find("="); + std::string key = line.substr(0, pos); + trim(key); + std::string value = line.substr(pos + 1); + trim(value); + + if(key.starts_with("@")) + { + m_owner[SettingKey(category, key.substr(1))] = value; + continue; + } + if(key.ends_with("/owner")) + { + std::cerr << "WARNING: Legacy usage for owner: " << key << " should be @" << key.substr(0, key.size() - 6) << std::endl; + m_owner[SettingKey(category, key.substr(0, key.size() - 6))] = value; + continue; + } + + data[SettingKey(category, key)] = makeValue(value); + } + } + + void parseDir(const std::string &dconf, std::map &data, bool watch) + { + if(std::filesystem::exists(dconf) && std::filesystem::is_directory(dconf)) + { + for (const auto& entry : std::filesystem::directory_iterator(dconf)) + { + if(entry.is_directory()) continue; + parseFile(std::filesystem::absolute(entry.path()), data, watch); + } + } + } + + void parseConf(const std::string &fconf, std::map &data, bool watch) + { + if(std::filesystem::exists(fconf)) + { + parseFile(fconf, data, watch); + } + std::string dconf = fconf + ".d"; + parseDir(dconf, data, watch); + if(watch) + { + m_watcher.watch(dconf); + } + } + + void refreshData(const std::map &cust_data) + { + std::lock_guard lock(m_mutex_data); + std::lock_guard lock_notifier(m_mutex_notifier); + for(const auto& [key, value] : cust_data) { + if(!m_data.contains(key) || m_data[key] != value) + { + // New ITEM OR Data changed + m_data[key] = value; + for(auto ¬ifier: m_notifier) + { + notifier(key.first, key.second, value); + } + continue; + } + } + } + + void refreshFile(const std::string &file) + { + std::map cust_data; + parseFile(file, cust_data); + refreshData(cust_data); + } + + void refreshDirectory(const std::string &dirname) + { + std::map cust_data; + parseDir(dirname, cust_data, true); + refreshData(cust_data); + } + + void refreshAll() + { + std::map cust_data; + parseConf("/etc/kazoe.conf", cust_data, false); + parseConf("/var/kazoe.conf", cust_data, true); + refreshData(cust_data); + } +}; +}; + +Settings::Settings() + : m_ptr(std::make_unique(this)) +{ + m_ptr->parseConf("/etc/kazoe.conf", m_ptr->m_data, false); + m_ptr->parseConf("/var/kazoe.conf", m_ptr->m_data, true); +} + +SettingValue Settings::get(const std::string &key, const std::string &category, SettingValue def) const +{ + std::lock_guard lock(m_ptr->m_mutex_data); + std::string gkey = key; + std::string gcategory = category; + if(gcategory.empty() && gkey.starts_with("[")) + { + gcategory = gkey.substr(1, gkey.find(']') - 1); + gkey = gkey.substr(gkey.find(']') + 1); + } + + SettingKey pkey(gcategory, gkey); + + if(m_ptr->m_data.count(pkey)) + { + return m_ptr->m_data[pkey]; + } + return def; +} + +bool Settings::set(const std::string &key, SettingValue value, const std::string &category) +{ + // Check if variable is writable + std::lock_guard lock(m_ptr->m_mutex_data); + SettingKey pkey(category, key); + std::string id; + + if(!m_ptr->m_owner.count(pkey)) + { + std::cerr << "Variable " << category << ">" << key << " is not writable" << std::endl; + return false; + } + + if(m_ptr->m_bypass) + { + id = m_ptr->m_owner[pkey]; + } + else + { + id = m_ptr->m_id; + if(m_ptr->m_owner[pkey] != id) + { + std::cerr << "Variable " << category << ">" << key << " is not own by " << m_ptr->m_id << std::endl; + return false; + } + } + + // Permission is OK, get old custom variable + std::map cust_data; + m_ptr->parseFile("/var/kazoe.conf.d/"+id+".conf", cust_data); + cust_data[pkey] = value; + std::string last_category = ""; + std::ofstream file("/var/kazoe.conf.d/"+id+".conf"); + + for(const auto& [key, value] : cust_data) { + if(key.first != last_category) + { + last_category = key.first.empty() ? "General" : key.first; + file << "[" << last_category << "]\n"; + } + file << key.second << " = " << KaZoe::valueToStr(value) << "\n"; + } + + m_ptr->m_data[pkey] = value; + return true; +} + +std::string Settings::id() const +{ + return m_ptr->m_id; +} + +void Settings::setId(const std::string &newid) +{ + m_ptr->m_id = newid; +} + +void Settings::setNotifier(const std::function &callback) +{ + std::lock_guard lock(m_ptr->m_mutex_notifier); + m_ptr->m_notifier.push_back(callback); + m_ptr->m_watcher.setEnable(true); +} + +std::size_t Settings::size() const +{ + std::lock_guard lock(m_ptr->m_mutex_data); + return m_ptr->m_data.size(); +} + +void Settings::forEach(const std::function &callback) const +{ + std::lock_guard lock(m_ptr->m_mutex_data); + for (const auto& [key, value] : m_ptr->m_data) { + callback(key, value); + } +} + +Settings::~Settings() +{ + m_ptr->m_watcher.setEnable(false); +} -- cgit v1.2.3