Serenity Operating System
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}