#include "kzsettings.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 KzSettingsPrivate { friend class KzSettings; KzSettings *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: KzSettingsPrivate(KzSettings *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 == "kzsettings") { // Only "kzsettings 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[KzSettingKey(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[KzSettingKey(category, key.substr(0, key.size() - 6))] = value; continue; } data[KzSettingKey(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); } }; }; KzSettings::KzSettings() : 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); } KzSettingValue KzSettings::get(const std::string &key, const std::string &category, KzSettingValue 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); } KzSettingKey pkey(gcategory, gkey); if(m_ptr->m_data.count(pkey)) { return m_ptr->m_data[pkey]; } return def; } bool KzSettings::set(const std::string &key, KzSettingValue value, const std::string &category) { // Check if variable is writable std::lock_guard lock(m_ptr->m_mutex_data); KzSettingKey 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 KzSettings::id() const { return m_ptr->m_id; } void KzSettings::setId(const std::string &newid) { m_ptr->m_id = newid; } void KzSettings::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 KzSettings::size() const { std::lock_guard lock(m_ptr->m_mutex_data); return m_ptr->m_data.size(); } void KzSettings::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); } } KzSettings::~KzSettings() { m_ptr->m_watcher.setEnable(false); }