Serenity Operating System
at master 336 lines 12 kB view raw
1/* 2 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "ConnectionFromClient.h" 8#include <ConfigServer/ConfigClientEndpoint.h> 9#include <LibCore/ConfigFile.h> 10#include <LibCore/FileWatcher.h> 11#include <LibCore/Timer.h> 12 13namespace ConfigServer { 14 15static HashMap<int, RefPtr<ConnectionFromClient>> s_connections; 16 17struct CachedDomain { 18 DeprecatedString domain; 19 NonnullRefPtr<Core::ConfigFile> config; 20 RefPtr<Core::FileWatcher> watcher; 21}; 22 23static HashMap<DeprecatedString, NonnullOwnPtr<CachedDomain>> s_cache; 24static constexpr int s_disk_sync_delay_ms = 5'000; 25 26static void for_each_monitoring_connection(DeprecatedString const& domain, ConnectionFromClient* excluded_connection, Function<void(ConnectionFromClient&)> callback) 27{ 28 for (auto& it : s_connections) { 29 if (it.value->is_monitoring_domain(domain) && (!excluded_connection || it.value != excluded_connection)) 30 callback(*it.value); 31 } 32} 33 34static Core::ConfigFile& ensure_domain_config(DeprecatedString const& domain) 35{ 36 auto it = s_cache.find(domain); 37 if (it != s_cache.end()) 38 return *it->value->config; 39 40 auto config = Core::ConfigFile::open_for_app(domain, Core::ConfigFile::AllowWriting::Yes).release_value_but_fixme_should_propagate_errors(); 41 // FIXME: Use a single FileWatcher with multiple watches inside. 42 auto watcher_or_error = Core::FileWatcher::create(Core::FileWatcherFlags::Nonblock); 43 VERIFY(!watcher_or_error.is_error()); 44 auto result = watcher_or_error.value()->add_watch(config->filename(), Core::FileWatcherEvent::Type::ContentModified); 45 VERIFY(!result.is_error()); 46 watcher_or_error.value()->on_change = [config, domain](auto&) { 47 auto new_config = Core::ConfigFile::open(config->filename(), Core::ConfigFile::AllowWriting::Yes).release_value_but_fixme_should_propagate_errors(); 48 for (auto& group : config->groups()) { 49 for (auto& key : config->keys(group)) { 50 if (!new_config->has_key(group, key)) { 51 for_each_monitoring_connection(domain, nullptr, [&domain, &group, &key](ConnectionFromClient& connection) { 52 connection.async_notify_removed_key(domain, group, key); 53 }); 54 } 55 } 56 } 57 // FIXME: Detect type of keys. 58 for (auto& group : new_config->groups()) { 59 for (auto& key : new_config->keys(group)) { 60 auto old_value = config->read_entry(group, key); 61 auto new_value = new_config->read_entry(group, key); 62 if (old_value != new_value) { 63 for_each_monitoring_connection(domain, nullptr, [&domain, &group, &key, &new_value](ConnectionFromClient& connection) { 64 connection.async_notify_changed_string_value(domain, group, key, new_value); 65 }); 66 } 67 } 68 } 69 // FIXME: Refactor this whole thing so that we don't need a cache lookup here. 70 s_cache.get(domain).value()->config = new_config; 71 }; 72 auto cache_entry = make<CachedDomain>(domain, config, watcher_or_error.release_value()); 73 s_cache.set(domain, move(cache_entry)); 74 return *config; 75} 76 77ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr<Core::LocalSocket> client_socket, int client_id) 78 : IPC::ConnectionFromClient<ConfigClientEndpoint, ConfigServerEndpoint>(*this, move(client_socket), client_id) 79 , m_sync_timer(Core::Timer::create_single_shot(s_disk_sync_delay_ms, [this]() { sync_dirty_domains_to_disk(); }).release_value_but_fixme_should_propagate_errors()) 80{ 81 s_connections.set(client_id, *this); 82} 83 84void ConnectionFromClient::die() 85{ 86 s_connections.remove(client_id()); 87 m_sync_timer->stop(); 88 sync_dirty_domains_to_disk(); 89} 90 91void ConnectionFromClient::pledge_domains(Vector<DeprecatedString> const& domains) 92{ 93 if (m_has_pledged) { 94 did_misbehave("Tried to pledge domains twice."); 95 return; 96 } 97 m_has_pledged = true; 98 for (auto& domain : domains) 99 m_pledged_domains.set(domain); 100} 101 102void ConnectionFromClient::monitor_domain(DeprecatedString const& domain) 103{ 104 if (m_has_pledged && !m_pledged_domains.contains(domain)) { 105 did_misbehave("Attempt to monitor non-pledged domain"); 106 return; 107 } 108 109 m_monitored_domains.set(domain); 110} 111 112bool ConnectionFromClient::validate_access(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key) 113{ 114 if (!m_has_pledged) 115 return true; 116 if (m_pledged_domains.contains(domain)) 117 return true; 118 did_misbehave(DeprecatedString::formatted("Blocked attempt to access domain '{}', group={}, key={}", domain, group, key).characters()); 119 return false; 120} 121 122void ConnectionFromClient::sync_dirty_domains_to_disk() 123{ 124 if (m_dirty_domains.is_empty()) 125 return; 126 auto dirty_domains = move(m_dirty_domains); 127 dbgln("Syncing {} dirty domains to disk", dirty_domains.size()); 128 for (auto domain : dirty_domains) { 129 auto& config = ensure_domain_config(domain); 130 if (auto result = config.sync(); result.is_error()) { 131 dbgln("Failed to write config '{}' to disk: {}", domain, result.error()); 132 // Put it back in the list since it's still dirty. 133 m_dirty_domains.set(domain); 134 } 135 } 136} 137 138Messages::ConfigServer::ListConfigKeysResponse ConnectionFromClient::list_config_keys(DeprecatedString const& domain, DeprecatedString const& group) 139{ 140 if (!validate_access(domain, group, "")) 141 return Vector<DeprecatedString> {}; 142 auto& config = ensure_domain_config(domain); 143 return { config.keys(group) }; 144} 145 146Messages::ConfigServer::ListConfigGroupsResponse ConnectionFromClient::list_config_groups(DeprecatedString const& domain) 147{ 148 if (!validate_access(domain, "", "")) 149 return Vector<DeprecatedString> {}; 150 auto& config = ensure_domain_config(domain); 151 return { config.groups() }; 152} 153 154Messages::ConfigServer::ReadStringValueResponse ConnectionFromClient::read_string_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key) 155{ 156 if (!validate_access(domain, group, key)) 157 return nullptr; 158 159 auto& config = ensure_domain_config(domain); 160 if (!config.has_key(group, key)) 161 return Optional<DeprecatedString> {}; 162 return Optional<DeprecatedString> { config.read_entry(group, key) }; 163} 164 165Messages::ConfigServer::ReadI32ValueResponse ConnectionFromClient::read_i32_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key) 166{ 167 if (!validate_access(domain, group, key)) 168 return nullptr; 169 170 auto& config = ensure_domain_config(domain); 171 if (!config.has_key(group, key)) 172 return Optional<i32> {}; 173 return Optional<i32> { config.read_num_entry(group, key) }; 174} 175 176Messages::ConfigServer::ReadU32ValueResponse ConnectionFromClient::read_u32_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key) 177{ 178 if (!validate_access(domain, group, key)) 179 return nullptr; 180 181 auto& config = ensure_domain_config(domain); 182 if (!config.has_key(group, key)) 183 return Optional<u32> {}; 184 return Optional<u32> { config.read_num_entry<u32>(group, key) }; 185} 186 187Messages::ConfigServer::ReadBoolValueResponse ConnectionFromClient::read_bool_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key) 188{ 189 if (!validate_access(domain, group, key)) 190 return nullptr; 191 192 auto& config = ensure_domain_config(domain); 193 if (!config.has_key(group, key)) 194 return Optional<bool> {}; 195 return Optional<bool> { config.read_bool_entry(group, key) }; 196} 197 198void ConnectionFromClient::start_or_restart_sync_timer() 199{ 200 if (m_sync_timer->is_active()) 201 m_sync_timer->restart(); 202 else 203 m_sync_timer->start(); 204} 205 206void ConnectionFromClient::write_string_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value) 207{ 208 if (!validate_access(domain, group, key)) 209 return; 210 211 auto& config = ensure_domain_config(domain); 212 213 if (config.has_key(group, key) && config.read_entry(group, key) == value) 214 return; 215 216 config.write_entry(group, key, value); 217 m_dirty_domains.set(domain); 218 start_or_restart_sync_timer(); 219 220 for_each_monitoring_connection(domain, this, [&domain, &group, &key, &value](ConnectionFromClient& connection) { 221 connection.async_notify_changed_string_value(domain, group, key, value); 222 }); 223} 224 225void ConnectionFromClient::write_i32_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, i32 value) 226{ 227 if (!validate_access(domain, group, key)) 228 return; 229 230 auto& config = ensure_domain_config(domain); 231 232 if (config.has_key(group, key) && config.read_num_entry(group, key) == value) 233 return; 234 235 config.write_num_entry(group, key, value); 236 m_dirty_domains.set(domain); 237 start_or_restart_sync_timer(); 238 239 for_each_monitoring_connection(domain, this, [&domain, &group, &key, &value](ConnectionFromClient& connection) { 240 connection.async_notify_changed_i32_value(domain, group, key, value); 241 }); 242} 243 244void ConnectionFromClient::write_u32_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, u32 value) 245{ 246 if (!validate_access(domain, group, key)) 247 return; 248 249 auto& config = ensure_domain_config(domain); 250 251 if (config.has_key(group, key) && config.read_num_entry<u32>(group, key) == value) 252 return; 253 254 config.write_num_entry(group, key, value); 255 m_dirty_domains.set(domain); 256 start_or_restart_sync_timer(); 257 258 for_each_monitoring_connection(domain, this, [&domain, &group, &key, &value](ConnectionFromClient& connection) { 259 connection.async_notify_changed_u32_value(domain, group, key, value); 260 }); 261} 262 263void ConnectionFromClient::write_bool_value(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, bool value) 264{ 265 if (!validate_access(domain, group, key)) 266 return; 267 268 auto& config = ensure_domain_config(domain); 269 270 if (config.has_key(group, key) && config.read_bool_entry(group, key) == value) 271 return; 272 273 config.write_bool_entry(group, key, value); 274 m_dirty_domains.set(domain); 275 start_or_restart_sync_timer(); 276 277 for_each_monitoring_connection(domain, this, [&domain, &group, &key, &value](ConnectionFromClient& connection) { 278 connection.async_notify_changed_bool_value(domain, group, key, value); 279 }); 280} 281 282void ConnectionFromClient::remove_key_entry(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key) 283{ 284 if (!validate_access(domain, group, key)) 285 return; 286 287 auto& config = ensure_domain_config(domain); 288 if (!config.has_key(group, key)) 289 return; 290 291 config.remove_entry(group, key); 292 m_dirty_domains.set(domain); 293 start_or_restart_sync_timer(); 294 295 for_each_monitoring_connection(domain, this, [&domain, &group, &key](ConnectionFromClient& connection) { 296 connection.async_notify_removed_key(domain, group, key); 297 }); 298} 299 300void ConnectionFromClient::remove_group_entry(DeprecatedString const& domain, DeprecatedString const& group) 301{ 302 if (!validate_access(domain, group, {})) 303 return; 304 305 auto& config = ensure_domain_config(domain); 306 if (!config.has_group(group)) 307 return; 308 309 config.remove_group(group); 310 m_dirty_domains.set(domain); 311 start_or_restart_sync_timer(); 312 313 for_each_monitoring_connection(domain, this, [&domain, &group](ConnectionFromClient& connection) { 314 connection.async_notify_removed_group(domain, group); 315 }); 316} 317 318void ConnectionFromClient::add_group_entry(DeprecatedString const& domain, DeprecatedString const& group) 319{ 320 if (!validate_access(domain, group, {})) 321 return; 322 323 auto& config = ensure_domain_config(domain); 324 if (config.has_group(group)) 325 return; 326 327 config.add_group(group); 328 m_dirty_domains.set(domain); 329 start_or_restart_sync_timer(); 330 331 for_each_monitoring_connection(domain, this, [&domain, &group](ConnectionFromClient& connection) { 332 connection.async_notify_added_group(domain, group); 333 }); 334} 335 336}