/src/fwupd/libfwupdplugin/fu-config.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2017 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "FuConfig" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include <fcntl.h> |
12 | | #include <gio/gio.h> |
13 | | #include <glib/gstdio.h> |
14 | | #include <unistd.h> |
15 | | |
16 | | #include "fu-bytes.h" |
17 | | #include "fu-config-private.h" |
18 | | #include "fu-path.h" |
19 | | #include "fu-string.h" |
20 | | |
21 | | enum { SIGNAL_CHANGED, SIGNAL_LOADED, SIGNAL_LAST }; |
22 | | |
23 | | static guint signals[SIGNAL_LAST] = {0}; |
24 | | |
25 | 0 | #define FU_CONFIG_FILE_MODE_SECURE 0640 |
26 | | |
27 | | typedef struct { |
28 | | gchar *filename; |
29 | | GFile *file; |
30 | | GFileMonitor *monitor; /* nullable */ |
31 | | gboolean is_writable; |
32 | | gboolean is_mutable; |
33 | | } FuConfigItem; |
34 | | |
35 | | typedef struct { |
36 | | FuPathStore *pstore; |
37 | | GKeyFile *keyfile; |
38 | | GHashTable *default_values; |
39 | | GPtrArray *items; /* (element-type FuConfigItem) */ |
40 | | gchar *basename; |
41 | | } FuConfigPrivate; |
42 | | |
43 | | enum { PROP_0, PROP_PATH_STORE, PROP_LAST }; |
44 | | |
45 | 0 | G_DEFINE_TYPE_WITH_PRIVATE(FuConfig, fu_config, G_TYPE_OBJECT) |
46 | 0 | #define GET_PRIVATE(o) (fu_config_get_instance_private(o)) |
47 | | |
48 | | static void |
49 | | fu_config_item_free(FuConfigItem *item) |
50 | 0 | { |
51 | 0 | if (item->monitor != NULL) { |
52 | 0 | g_file_monitor_cancel(item->monitor); |
53 | 0 | g_object_unref(item->monitor); |
54 | 0 | } |
55 | 0 | g_object_unref(item->file); |
56 | 0 | g_free(item->filename); |
57 | 0 | g_free(item); |
58 | 0 | } |
59 | | |
60 | | G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuConfigItem, fu_config_item_free) |
61 | | |
62 | | static void |
63 | | fu_config_emit_changed(FuConfig *self) |
64 | 0 | { |
65 | 0 | g_debug("::configuration changed"); |
66 | 0 | g_signal_emit(self, signals[SIGNAL_CHANGED], 0); |
67 | 0 | } |
68 | | |
69 | | static void |
70 | | fu_config_emit_loaded(FuConfig *self) |
71 | 0 | { |
72 | 0 | g_debug("::configuration loaded"); |
73 | 0 | g_signal_emit(self, signals[SIGNAL_LOADED], 0); |
74 | 0 | } |
75 | | |
76 | | static gchar * |
77 | | fu_config_build_section_key(const gchar *section, const gchar *key) |
78 | 0 | { |
79 | 0 | return g_strdup_printf("%s::%s", section, key); |
80 | 0 | } |
81 | | |
82 | | static gboolean |
83 | | fu_config_load_bytes_replace(FuConfig *self, GBytes *blob, GError **error) |
84 | 0 | { |
85 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
86 | 0 | g_auto(GStrv) groups = NULL; |
87 | 0 | g_autoptr(GKeyFile) kf = g_key_file_new(); |
88 | |
|
89 | 0 | if (!g_key_file_load_from_data(kf, |
90 | 0 | (const gchar *)g_bytes_get_data(blob, NULL), |
91 | 0 | g_bytes_get_size(blob), |
92 | 0 | G_KEY_FILE_KEEP_COMMENTS, |
93 | 0 | error)) |
94 | 0 | return FALSE; |
95 | 0 | groups = g_key_file_get_groups(kf, NULL); |
96 | 0 | for (guint i = 0; groups[i] != NULL; i++) { |
97 | 0 | g_auto(GStrv) keys = NULL; |
98 | 0 | g_autofree gchar *comment_group = NULL; |
99 | 0 | keys = g_key_file_get_keys(kf, groups[i], NULL, error); |
100 | 0 | if (keys == NULL) { |
101 | 0 | g_prefix_error(error, "failed to get keys for [%s]: ", groups[i]); |
102 | 0 | return FALSE; |
103 | 0 | } |
104 | 0 | for (guint j = 0; keys[j] != NULL; j++) { |
105 | 0 | const gchar *default_value; |
106 | 0 | g_autofree gchar *comment_key = NULL; |
107 | 0 | g_autofree gchar *section_key = NULL; |
108 | 0 | g_autofree gchar *value = NULL; |
109 | |
|
110 | 0 | value = g_key_file_get_string(kf, groups[i], keys[j], error); |
111 | 0 | if (value == NULL) { |
112 | 0 | g_prefix_error(error, |
113 | 0 | "failed to get string for %s=%s: ", |
114 | 0 | groups[i], |
115 | 0 | keys[j]); |
116 | 0 | return FALSE; |
117 | 0 | } |
118 | | |
119 | | /* is the same as the default */ |
120 | 0 | section_key = fu_config_build_section_key(groups[i], keys[j]); |
121 | 0 | default_value = g_hash_table_lookup(priv->default_values, section_key); |
122 | 0 | if (g_strcmp0(value, default_value) == 0) { |
123 | 0 | g_debug("default config, ignoring [%s] %s=%s", |
124 | 0 | groups[i], |
125 | 0 | keys[j], |
126 | 0 | value); |
127 | 0 | continue; |
128 | 0 | } |
129 | | |
130 | 0 | g_debug("setting config [%s] %s=%s", groups[i], keys[j], value); |
131 | 0 | g_key_file_set_string(priv->keyfile, groups[i], keys[j], value); |
132 | 0 | comment_key = g_key_file_get_comment(kf, groups[i], keys[j], NULL); |
133 | 0 | if (comment_key != NULL && comment_key[0] != '\0') { |
134 | 0 | if (!g_key_file_set_comment(priv->keyfile, |
135 | 0 | groups[i], |
136 | 0 | keys[j], |
137 | 0 | comment_key, |
138 | 0 | error)) { |
139 | 0 | g_prefix_error(error, |
140 | 0 | "failed to set comment '%s' for %s=%s: ", |
141 | 0 | comment_key, |
142 | 0 | groups[i], |
143 | 0 | keys[j]); |
144 | 0 | return FALSE; |
145 | 0 | } |
146 | 0 | } |
147 | 0 | } |
148 | 0 | comment_group = g_key_file_get_comment(kf, groups[i], NULL, NULL); |
149 | 0 | if (comment_group != NULL && comment_group[0] != '\0') { |
150 | 0 | if (!g_key_file_set_comment(priv->keyfile, |
151 | 0 | groups[i], |
152 | 0 | NULL, |
153 | 0 | comment_group, |
154 | 0 | error)) { |
155 | 0 | g_prefix_error(error, |
156 | 0 | "failed to set comment '%s' for [%s]: ", |
157 | 0 | comment_group, |
158 | 0 | groups[i]); |
159 | 0 | return FALSE; |
160 | 0 | } |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | | /* success */ |
165 | 0 | return TRUE; |
166 | 0 | } |
167 | | |
168 | | static void |
169 | | fu_config_migrate_keyfile(FuConfig *self) |
170 | 0 | { |
171 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
172 | 0 | struct { |
173 | 0 | const gchar *group; |
174 | 0 | const gchar *key; |
175 | 0 | const gchar *value; |
176 | 0 | } key_values[] = {{"fwupd", "ApprovedFirmware", NULL}, |
177 | 0 | {"fwupd", "ArchiveSizeMax", "0"}, |
178 | 0 | {"fwupd", "DisabledDevices", NULL}, |
179 | 0 | {"fwupd", "EnumerateAllDevices", NULL}, |
180 | 0 | {"fwupd", "EspLocation", NULL}, |
181 | 0 | {"fwupd", "HostBkc", NULL}, |
182 | 0 | {"fwupd", "IdleTimeout", "7200"}, |
183 | 0 | {"fwupd", "IdleTimeout", NULL}, |
184 | 0 | {"fwupd", "IgnorePower", NULL}, |
185 | 0 | {"fwupd", "ShowDevicePrivate", NULL}, |
186 | 0 | {"fwupd", "TrustedUids", NULL}, |
187 | 0 | {"fwupd", "UpdateMotd", NULL}, |
188 | 0 | {"fwupd", "UriSchemes", NULL}, |
189 | 0 | {"fwupd", "VerboseDomains", NULL}, |
190 | 0 | {"fwupd", "OnlyTrusted", NULL}, |
191 | 0 | {"fwupd", "IgnorePower", NULL}, |
192 | 0 | {"fwupd", "RequireImmutableEnumeration", NULL}, |
193 | 0 | {"fwupd", "DisabledPlugins", "test;test_ble;invalid"}, |
194 | 0 | {"fwupd", "DisabledPlugins", "test;test_ble"}, |
195 | 0 | {"redfish", "IpmiDisableCreateUser", NULL}, |
196 | 0 | {"redfish", "ManagerResetTimeout", NULL}, |
197 | 0 | {"msr", "MinimumSmeKernelVersion", NULL}, |
198 | 0 | {"thunderbolt", "MinimumKernelVersion", NULL}, |
199 | 0 | {"thunderbolt", "DelayedActivation", NULL}, |
200 | 0 | {NULL, NULL, NULL}}; |
201 | 0 | for (guint i = 0; key_values[i].group != NULL; i++) { |
202 | 0 | const gchar *default_value; |
203 | 0 | g_autofree gchar *value = NULL; |
204 | 0 | g_auto(GStrv) keys = NULL; |
205 | |
|
206 | 0 | value = g_key_file_get_value(priv->keyfile, |
207 | 0 | key_values[i].group, |
208 | 0 | key_values[i].key, |
209 | 0 | NULL); |
210 | 0 | if (value == NULL) |
211 | 0 | continue; |
212 | 0 | if (key_values[i].value == NULL) { |
213 | 0 | g_autofree gchar *section_key = |
214 | 0 | fu_config_build_section_key(key_values[i].group, key_values[i].key); |
215 | 0 | default_value = g_hash_table_lookup(priv->default_values, section_key); |
216 | 0 | } else { |
217 | 0 | default_value = key_values[i].value; |
218 | 0 | } |
219 | 0 | if ((default_value != NULL && g_ascii_strcasecmp(value, default_value) == 0) || |
220 | 0 | (key_values[i].value == NULL && g_strcmp0(value, "") == 0)) { |
221 | 0 | g_debug("not migrating default value of [%s] %s=%s", |
222 | 0 | key_values[i].group, |
223 | 0 | key_values[i].key, |
224 | 0 | default_value); |
225 | 0 | g_key_file_remove_comment(priv->keyfile, |
226 | 0 | key_values[i].group, |
227 | 0 | key_values[i].key, |
228 | 0 | NULL); |
229 | 0 | g_key_file_remove_key(priv->keyfile, |
230 | 0 | key_values[i].group, |
231 | 0 | key_values[i].key, |
232 | 0 | NULL); |
233 | 0 | } |
234 | | |
235 | | /* remove the group if there are no keys left */ |
236 | 0 | keys = g_key_file_get_keys(priv->keyfile, key_values[i].group, NULL, NULL); |
237 | 0 | if (g_strv_length(keys) == 0) { |
238 | 0 | g_key_file_remove_comment(priv->keyfile, key_values[i].group, NULL, NULL); |
239 | 0 | g_key_file_remove_group(priv->keyfile, key_values[i].group, NULL); |
240 | 0 | } |
241 | 0 | } |
242 | 0 | } |
243 | | |
244 | | static gboolean |
245 | | fu_config_ensure_permissions(FuConfig *self, GError **error) |
246 | 0 | { |
247 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
248 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
249 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
250 | 0 | guint32 st_mode; |
251 | 0 | g_autoptr(GFileInfo) info = NULL; |
252 | | |
253 | | /* check permissions */ |
254 | 0 | if (!item->is_writable) { |
255 | 0 | g_debug("skipping mode check for %s as not writable", item->filename); |
256 | 0 | continue; |
257 | 0 | } |
258 | 0 | info = g_file_query_info(item->file, |
259 | 0 | G_FILE_ATTRIBUTE_UNIX_MODE, |
260 | 0 | G_FILE_QUERY_INFO_NONE, |
261 | 0 | NULL, |
262 | 0 | error); |
263 | 0 | if (info == NULL) { |
264 | 0 | g_prefix_error(error, "failed to query info about %s: ", item->filename); |
265 | 0 | return FALSE; |
266 | 0 | } |
267 | 0 | st_mode = g_file_info_get_attribute_uint32(info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777; |
268 | 0 | if (st_mode != FU_CONFIG_FILE_MODE_SECURE) { |
269 | 0 | g_info("fixing %s from mode 0%o to 0%o", |
270 | 0 | item->filename, |
271 | 0 | st_mode, |
272 | 0 | (guint)FU_CONFIG_FILE_MODE_SECURE); |
273 | 0 | g_file_info_set_attribute_uint32(info, |
274 | 0 | G_FILE_ATTRIBUTE_UNIX_MODE, |
275 | 0 | FU_CONFIG_FILE_MODE_SECURE); |
276 | 0 | if (!g_file_set_attributes_from_info(item->file, |
277 | 0 | info, |
278 | 0 | G_FILE_QUERY_INFO_NONE, |
279 | 0 | NULL, |
280 | 0 | error)) { |
281 | 0 | g_prefix_error(error, |
282 | 0 | "failed to set mode attribute of %s: ", |
283 | 0 | item->filename); |
284 | 0 | return FALSE; |
285 | 0 | } |
286 | 0 | } |
287 | 0 | } |
288 | | |
289 | | /* success */ |
290 | 0 | return TRUE; |
291 | 0 | } |
292 | | |
293 | | static gboolean |
294 | | fu_config_reload(FuConfig *self, FuConfigLoadFlags flags, GError **error) |
295 | 0 | { |
296 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
297 | |
|
298 | | #ifdef _WIN32 |
299 | | /* not relevant */ |
300 | | flags &= ~FU_CONFIG_LOAD_FLAG_FIX_PERMISSIONS; |
301 | | #endif |
302 | | |
303 | | /* ensure mutable config files are set to the correct permissions */ |
304 | 0 | if (flags & FU_CONFIG_LOAD_FLAG_FIX_PERMISSIONS) { |
305 | 0 | if (!fu_config_ensure_permissions(self, error)) |
306 | 0 | return FALSE; |
307 | 0 | } |
308 | | |
309 | | /* we have to copy each group/key from a temporary GKeyFile as g_key_file_load_from_file() |
310 | | * clears all keys before loading each file, and we want to allow the mutable version to be |
311 | | * incomplete and just *override* a specific option */ |
312 | 0 | if (!g_key_file_load_from_data(priv->keyfile, "", -1, G_KEY_FILE_NONE, error)) |
313 | 0 | return FALSE; |
314 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
315 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
316 | 0 | g_autoptr(GError) error_load = NULL; |
317 | 0 | g_autoptr(GBytes) blob_item = NULL; |
318 | |
|
319 | 0 | g_debug("trying to load config values from %s", item->filename); |
320 | 0 | blob_item = fu_bytes_get_contents(item->filename, &error_load); |
321 | 0 | if (blob_item == NULL) { |
322 | 0 | if (g_error_matches(error_load, |
323 | 0 | FWUPD_ERROR, |
324 | 0 | FWUPD_ERROR_PERMISSION_DENIED)) { |
325 | 0 | g_debug("ignoring config file %s: ", error_load->message); |
326 | 0 | continue; |
327 | 0 | } else if (g_error_matches(error_load, |
328 | 0 | FWUPD_ERROR, |
329 | 0 | FWUPD_ERROR_INVALID_FILE)) { |
330 | 0 | g_debug("%s", error_load->message); |
331 | 0 | continue; |
332 | 0 | } |
333 | 0 | g_propagate_error(error, g_steal_pointer(&error_load)); |
334 | 0 | g_prefix_error(error, "failed to read %s: ", item->filename); |
335 | 0 | return FALSE; |
336 | 0 | } |
337 | 0 | if (!fu_config_load_bytes_replace(self, blob_item, error)) { |
338 | 0 | g_prefix_error(error, "failed to load %s: ", item->filename); |
339 | 0 | return FALSE; |
340 | 0 | } |
341 | 0 | } |
342 | | |
343 | | /* success */ |
344 | 0 | return TRUE; |
345 | 0 | } |
346 | | |
347 | | static void |
348 | | fu_config_monitor_changed_cb(GFileMonitor *monitor, |
349 | | GFile *file, |
350 | | GFile *other_file, |
351 | | GFileMonitorEvent event_type, |
352 | | gpointer user_data) |
353 | 0 | { |
354 | 0 | FuConfig *self = FU_CONFIG(user_data); |
355 | 0 | g_autoptr(GError) error = NULL; |
356 | 0 | g_autofree gchar *fn = g_file_get_path(file); |
357 | | |
358 | | /* nothing we need to care about */ |
359 | 0 | if (event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) { |
360 | 0 | g_debug("%s attributes changed, ignoring", fn); |
361 | 0 | return; |
362 | 0 | } |
363 | | |
364 | | /* reload everything */ |
365 | 0 | g_info("%s changed, reloading all configs", fn); |
366 | 0 | if (!fu_config_reload(self, FU_CONFIG_LOAD_FLAG_NONE, &error)) |
367 | 0 | g_warning("failed to rescan daemon config: %s", error->message); |
368 | 0 | fu_config_emit_changed(self); |
369 | 0 | } |
370 | | |
371 | | /** |
372 | | * fu_config_set_default: |
373 | | * @self: a #FuConfig |
374 | | * @section: a settings section |
375 | | * @key: a settings key |
376 | | * @value: (nullable): a settings value |
377 | | * |
378 | | * Sets a default config value. |
379 | | * |
380 | | * Since: 2.0.0 |
381 | | **/ |
382 | | void |
383 | | fu_config_set_default(FuConfig *self, const gchar *section, const gchar *key, const gchar *value) |
384 | 0 | { |
385 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
386 | 0 | g_return_if_fail(FU_IS_CONFIG(self)); |
387 | 0 | g_return_if_fail(section != NULL); |
388 | 0 | g_return_if_fail(key != NULL); |
389 | 0 | g_hash_table_insert(priv->default_values, |
390 | 0 | fu_config_build_section_key(section, key), |
391 | 0 | g_strdup(value)); |
392 | 0 | } |
393 | | |
394 | | static gboolean |
395 | | fu_config_save(FuConfig *self, GError **error) |
396 | 0 | { |
397 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
398 | 0 | g_autofree gchar *data = NULL; |
399 | | |
400 | | /* sanity check */ |
401 | 0 | if (priv->items->len == 0) { |
402 | 0 | g_set_error_literal(error, |
403 | 0 | FWUPD_ERROR, |
404 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
405 | 0 | "no config locations found"); |
406 | 0 | return FALSE; |
407 | 0 | } |
408 | | |
409 | 0 | data = g_key_file_to_data(priv->keyfile, NULL, error); |
410 | 0 | if (data == NULL) |
411 | 0 | return FALSE; |
412 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
413 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
414 | 0 | if (!item->is_mutable) |
415 | 0 | continue; |
416 | 0 | if (!fu_path_mkdir_parent(item->filename, error)) |
417 | 0 | return FALSE; |
418 | 0 | if (!g_file_set_contents_full(item->filename, |
419 | 0 | data, |
420 | 0 | -1, |
421 | 0 | G_FILE_SET_CONTENTS_CONSISTENT, |
422 | 0 | FU_CONFIG_FILE_MODE_SECURE, /* only for root */ |
423 | 0 | error)) |
424 | 0 | return FALSE; |
425 | 0 | return fu_config_reload(self, FU_CONFIG_LOAD_FLAG_NONE, error); |
426 | 0 | } |
427 | | |
428 | | /* failed */ |
429 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no writable config"); |
430 | 0 | return FALSE; |
431 | 0 | } |
432 | | |
433 | | /** |
434 | | * fu_config_set_value_internal: |
435 | | * @self: a #FuConfig |
436 | | * @section: a settings section |
437 | | * @key: a settings key |
438 | | * @value: (nullable): a settings value |
439 | | * |
440 | | * Sets a plugin config value, *not* saving to a config file. |
441 | | * |
442 | | * Since: 2.1.1 |
443 | | **/ |
444 | | void |
445 | | fu_config_set_value_internal(FuConfig *self, |
446 | | const gchar *section, |
447 | | const gchar *key, |
448 | | const gchar *value) |
449 | 0 | { |
450 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
451 | 0 | g_return_if_fail(FU_IS_CONFIG(self)); |
452 | 0 | g_return_if_fail(section != NULL); |
453 | 0 | g_return_if_fail(key != NULL); |
454 | 0 | g_key_file_set_string(priv->keyfile, section, key, value); |
455 | 0 | } |
456 | | |
457 | | /** |
458 | | * fu_config_set_value: |
459 | | * @self: a #FuConfig |
460 | | * @section: a settings section |
461 | | * @key: a settings key |
462 | | * @value: (nullable): a settings value |
463 | | * @error: (nullable): optional return location for an error |
464 | | * |
465 | | * Sets a plugin config value, saving the new data back to the default config file. |
466 | | * |
467 | | * Returns: %TRUE for success |
468 | | * |
469 | | * Since: 1.9.1 |
470 | | **/ |
471 | | gboolean |
472 | | fu_config_set_value(FuConfig *self, |
473 | | const gchar *section, |
474 | | const gchar *key, |
475 | | const gchar *value, |
476 | | GError **error) |
477 | 0 | { |
478 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
479 | |
|
480 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
481 | 0 | g_return_val_if_fail(section != NULL, FALSE); |
482 | 0 | g_return_val_if_fail(key != NULL, FALSE); |
483 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
484 | | |
485 | | /* sanity check */ |
486 | 0 | if (priv->items->len == 0) { |
487 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no config to load"); |
488 | 0 | return FALSE; |
489 | 0 | } |
490 | | |
491 | | /* do not write default keys */ |
492 | 0 | fu_config_migrate_keyfile(self); |
493 | | |
494 | | /* only write the file to a mutable location */ |
495 | 0 | g_key_file_set_string(priv->keyfile, section, key, value); |
496 | 0 | return fu_config_save(self, error); |
497 | 0 | } |
498 | | |
499 | | /** |
500 | | * fu_config_reset_defaults: |
501 | | * @self: a #FuConfig |
502 | | * @section: a settings section |
503 | | * |
504 | | * Reset all the keys back to the default values. |
505 | | * |
506 | | * Since: 1.9.15 |
507 | | **/ |
508 | | gboolean |
509 | | fu_config_reset_defaults(FuConfig *self, const gchar *section, GError **error) |
510 | 0 | { |
511 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
512 | 0 | g_autoptr(GError) error_keyfile = NULL; |
513 | |
|
514 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
515 | 0 | g_return_val_if_fail(section != NULL, FALSE); |
516 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
517 | | |
518 | | /* remove all keys, and save */ |
519 | 0 | if (!g_key_file_remove_group(priv->keyfile, section, &error_keyfile)) { |
520 | | /* allow user to reset config sections twice */ |
521 | 0 | if (!g_error_matches(error_keyfile, |
522 | 0 | G_KEY_FILE_ERROR, |
523 | 0 | G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { |
524 | 0 | g_propagate_error(error, g_steal_pointer(&error_keyfile)); |
525 | 0 | fwupd_error_convert(error); |
526 | 0 | return FALSE; |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | 0 | return fu_config_save(self, error); |
531 | 0 | } |
532 | | |
533 | | /** |
534 | | * fu_config_get_value: |
535 | | * @self: a #FuConfig |
536 | | * @section: a settings section |
537 | | * @key: a settings key |
538 | | * |
539 | | * Return the value of a key, falling back to the default value if missing. |
540 | | * |
541 | | * NOTE: this function will return an empty string for `key=`. |
542 | | * |
543 | | * Returns: (transfer full): key value |
544 | | * |
545 | | * Since: 1.9.1 |
546 | | **/ |
547 | | gchar * |
548 | | fu_config_get_value(FuConfig *self, const gchar *section, const gchar *key) |
549 | 0 | { |
550 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
551 | 0 | g_autofree gchar *value = NULL; |
552 | |
|
553 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), NULL); |
554 | 0 | g_return_val_if_fail(section != NULL, NULL); |
555 | 0 | g_return_val_if_fail(key != NULL, NULL); |
556 | | |
557 | 0 | value = g_key_file_get_string(priv->keyfile, section, key, NULL); |
558 | 0 | if (value == NULL) { |
559 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
560 | 0 | const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
561 | 0 | return g_strdup(value_tmp); |
562 | 0 | } |
563 | 0 | return g_steal_pointer(&value); |
564 | 0 | } |
565 | | |
566 | | /** |
567 | | * fu_config_get_value_strv: |
568 | | * @self: a #FuConfig |
569 | | * @section: a settings section |
570 | | * @key: a settings key |
571 | | * |
572 | | * Return the value of a key, falling back to the default value if missing. |
573 | | * |
574 | | * NOTE: this function will return an empty string for `key=`. |
575 | | * |
576 | | * Returns: (transfer full) (nullable): key value |
577 | | * |
578 | | * Since: 1.9.1 |
579 | | **/ |
580 | | gchar ** |
581 | | fu_config_get_value_strv(FuConfig *self, const gchar *section, const gchar *key) |
582 | 0 | { |
583 | 0 | g_autofree gchar *value = NULL; |
584 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), NULL); |
585 | 0 | g_return_val_if_fail(section != NULL, NULL); |
586 | 0 | g_return_val_if_fail(key != NULL, NULL); |
587 | 0 | value = fu_config_get_value(self, section, key); |
588 | 0 | if (value == NULL) |
589 | 0 | return NULL; |
590 | 0 | return g_strsplit(value, ";", -1); |
591 | 0 | } |
592 | | |
593 | | /** |
594 | | * fu_config_get_value_bool: |
595 | | * @self: a #FuConfig |
596 | | * @section: a settings section |
597 | | * @key: a settings key |
598 | | * |
599 | | * Return the value of a key, falling back to the default value if missing or empty. |
600 | | * |
601 | | * Returns: boolean |
602 | | * |
603 | | * Since: 1.9.1 |
604 | | **/ |
605 | | gboolean |
606 | | fu_config_get_value_bool(FuConfig *self, const gchar *section, const gchar *key) |
607 | 0 | { |
608 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
609 | 0 | g_autofree gchar *value = fu_config_get_value(self, section, key); |
610 | 0 | if (value == NULL || value[0] == '\0') { |
611 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
612 | 0 | const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
613 | 0 | if (value_tmp == NULL) { |
614 | 0 | g_critical("no default for [%s] %s", section, key); |
615 | 0 | return FALSE; |
616 | 0 | } |
617 | 0 | return g_ascii_strcasecmp(value_tmp, "true") == 0; |
618 | 0 | } |
619 | 0 | return g_ascii_strcasecmp(value, "true") == 0; |
620 | 0 | } |
621 | | |
622 | | /** |
623 | | * fu_config_get_value_u64: |
624 | | * @self: a #FuConfig |
625 | | * @section: a settings section |
626 | | * @key: a settings key |
627 | | * |
628 | | * Return the value of a key, falling back to the default value if missing or empty. |
629 | | * |
630 | | * Returns: uint64 |
631 | | * |
632 | | * Since: 1.9.1 |
633 | | **/ |
634 | | guint64 |
635 | | fu_config_get_value_u64(FuConfig *self, const gchar *section, const gchar *key) |
636 | 0 | { |
637 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
638 | 0 | guint64 value = 0; |
639 | 0 | const gchar *value_tmp; |
640 | 0 | g_autofree gchar *tmp = fu_config_get_value(self, section, key); |
641 | 0 | g_autoptr(GError) error_local = NULL; |
642 | |
|
643 | 0 | if (tmp == NULL || tmp[0] == '\0') { |
644 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
645 | 0 | value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
646 | 0 | if (value_tmp == NULL) { |
647 | 0 | g_critical("no default for [%s] %s", section, key); |
648 | 0 | return G_MAXUINT64; |
649 | 0 | } |
650 | 0 | } else { |
651 | 0 | value_tmp = tmp; |
652 | 0 | } |
653 | 0 | if (!fu_strtoull(value_tmp, &value, 0, G_MAXUINT64, FU_INTEGER_BASE_AUTO, &error_local)) { |
654 | 0 | g_warning("failed to parse [%s] %s = %s as integer", section, key, value_tmp); |
655 | 0 | return G_MAXUINT64; |
656 | 0 | } |
657 | 0 | return value; |
658 | 0 | } |
659 | | |
660 | | /** |
661 | | * fu_config_set_basename: |
662 | | * @self: a #FuConfig |
663 | | * @basename: (nullable): a file basename, typically, `fwupd.conf` |
664 | | * |
665 | | * Sets the name of the filename to load. |
666 | | * |
667 | | * Since: 2.0.18 |
668 | | **/ |
669 | | void |
670 | | fu_config_set_basename(FuConfig *self, const gchar *basename) |
671 | 0 | { |
672 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
673 | 0 | g_return_if_fail(FU_IS_CONFIG(self)); |
674 | 0 | if (g_strcmp0(priv->basename, basename) == 0) |
675 | 0 | return; |
676 | 0 | g_free(priv->basename); |
677 | 0 | priv->basename = g_strdup(basename); |
678 | 0 | } |
679 | | |
680 | | static gboolean |
681 | | fu_config_add_location(FuConfig *self, const gchar *dirname, gboolean is_mutable, GError **error) |
682 | 0 | { |
683 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
684 | 0 | g_autoptr(FuConfigItem) item = g_new0(FuConfigItem, 1); |
685 | |
|
686 | 0 | item->is_mutable = is_mutable; |
687 | 0 | item->filename = g_build_filename(dirname, priv->basename, NULL); |
688 | 0 | item->file = g_file_new_for_path(item->filename); |
689 | | |
690 | | /* is writable */ |
691 | 0 | if (g_file_query_exists(item->file, NULL)) { |
692 | 0 | g_autoptr(GFileInfo) info = NULL; |
693 | |
|
694 | 0 | g_debug("loading config %s", item->filename); |
695 | 0 | info = g_file_query_info(item->file, |
696 | 0 | G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, |
697 | 0 | G_FILE_QUERY_INFO_NONE, |
698 | 0 | NULL, |
699 | 0 | error); |
700 | 0 | if (info == NULL) |
701 | 0 | return FALSE; |
702 | 0 | item->is_writable = |
703 | 0 | g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); |
704 | 0 | if (!item->is_writable) |
705 | 0 | g_debug("config %s is immutable", item->filename); |
706 | 0 | } else { |
707 | 0 | g_debug("not loading config %s", item->filename); |
708 | 0 | } |
709 | | |
710 | | /* success */ |
711 | 0 | g_ptr_array_add(priv->items, g_steal_pointer(&item)); |
712 | 0 | return TRUE; |
713 | 0 | } |
714 | | |
715 | | /** |
716 | | * fu_config_load: |
717 | | * @self: a #FuConfig |
718 | | * @flags: some #FuConfigLoadFlags, typically %FU_CONFIG_LOAD_FLAG_NONE |
719 | | * @error: (nullable): optional return location for an error |
720 | | * |
721 | | * Loads the configuration files from all possible locations. |
722 | | * |
723 | | * Returns: %TRUE for success |
724 | | * |
725 | | * Since: 1.9.1 |
726 | | **/ |
727 | | gboolean |
728 | | fu_config_load(FuConfig *self, FuConfigLoadFlags flags, GError **error) |
729 | 0 | { |
730 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
731 | 0 | const gchar *configdir_mut; |
732 | 0 | const gchar *configdir; |
733 | |
|
734 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
735 | 0 | g_return_val_if_fail(priv->items->len == 0, FALSE); |
736 | | |
737 | | /* load the main daemon config file */ |
738 | 0 | configdir = fu_path_store_get_path(priv->pstore, FU_PATH_KIND_SYSCONFDIR_PKG, NULL); |
739 | 0 | if (configdir != NULL) { |
740 | 0 | if (!fu_config_add_location(self, configdir, FALSE, error)) |
741 | 0 | return FALSE; |
742 | 0 | } |
743 | 0 | configdir_mut = fu_path_store_get_path(priv->pstore, FU_PATH_KIND_LOCALCONFDIR_PKG, NULL); |
744 | 0 | if (configdir_mut != NULL) { |
745 | 0 | if (!fu_config_add_location(self, configdir_mut, TRUE, error)) |
746 | 0 | return FALSE; |
747 | 0 | } |
748 | 0 | if (!fu_config_reload(self, flags, error)) |
749 | 0 | return FALSE; |
750 | | |
751 | | /* set up a notify watches */ |
752 | 0 | if (flags & FU_CONFIG_LOAD_FLAG_WATCH_FILES) { |
753 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
754 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
755 | 0 | g_autoptr(GFile) file = g_file_new_for_path(item->filename); |
756 | 0 | item->monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error); |
757 | 0 | if (item->monitor == NULL) |
758 | 0 | return FALSE; |
759 | 0 | g_signal_connect(G_FILE_MONITOR(item->monitor), |
760 | 0 | "changed", |
761 | 0 | G_CALLBACK(fu_config_monitor_changed_cb), |
762 | 0 | self); |
763 | 0 | } |
764 | 0 | } |
765 | | |
766 | | /* success */ |
767 | 0 | fu_config_emit_loaded(self); |
768 | 0 | return TRUE; |
769 | 0 | } |
770 | | static void |
771 | | fu_config_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) |
772 | 0 | { |
773 | 0 | FuConfig *self = FU_CONFIG(object); |
774 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
775 | 0 | switch (prop_id) { |
776 | 0 | case PROP_PATH_STORE: |
777 | 0 | g_value_set_object(value, priv->pstore); |
778 | 0 | break; |
779 | 0 | default: |
780 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
781 | 0 | break; |
782 | 0 | } |
783 | 0 | } |
784 | | |
785 | | static void |
786 | | fu_config_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) |
787 | 0 | { |
788 | 0 | FuConfig *self = FU_CONFIG(object); |
789 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
790 | 0 | switch (prop_id) { |
791 | 0 | case PROP_PATH_STORE: |
792 | 0 | g_set_object(&priv->pstore, g_value_get_object(value)); |
793 | 0 | break; |
794 | 0 | default: |
795 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
796 | 0 | break; |
797 | 0 | } |
798 | 0 | } |
799 | | |
800 | | static void |
801 | | fu_config_init(FuConfig *self) |
802 | 0 | { |
803 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
804 | 0 | priv->basename = g_strdup("fwupd.conf"); |
805 | 0 | priv->keyfile = g_key_file_new(); |
806 | 0 | priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_config_item_free); |
807 | 0 | priv->default_values = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
808 | 0 | } |
809 | | |
810 | | static void |
811 | | fu_config_finalize(GObject *obj) |
812 | 0 | { |
813 | 0 | FuConfig *self = FU_CONFIG(obj); |
814 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
815 | 0 | g_object_unref(priv->pstore); |
816 | 0 | g_free(priv->basename); |
817 | 0 | g_key_file_unref(priv->keyfile); |
818 | 0 | g_ptr_array_unref(priv->items); |
819 | 0 | g_hash_table_unref(priv->default_values); |
820 | 0 | G_OBJECT_CLASS(fu_config_parent_class)->finalize(obj); |
821 | 0 | } |
822 | | |
823 | | static void |
824 | | fu_config_class_init(FuConfigClass *klass) |
825 | 0 | { |
826 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
827 | 0 | GParamSpec *pspec; |
828 | |
|
829 | 0 | object_class->finalize = fu_config_finalize; |
830 | 0 | object_class->get_property = fu_config_get_property; |
831 | 0 | object_class->set_property = fu_config_set_property; |
832 | |
|
833 | 0 | pspec = g_param_spec_object("path-store", |
834 | 0 | NULL, |
835 | 0 | NULL, |
836 | 0 | FU_TYPE_PATH_STORE, |
837 | 0 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); |
838 | 0 | g_object_class_install_property(object_class, PROP_PATH_STORE, pspec); |
839 | | |
840 | | /** |
841 | | * FuConfig::changed: |
842 | | * @self: the #FuConfig instance that emitted the signal |
843 | | * |
844 | | * The ::changed signal is emitted when the config file has |
845 | | * changed, for instance when a parameter has been modified. |
846 | | **/ |
847 | 0 | signals[SIGNAL_CHANGED] = g_signal_new("changed", |
848 | 0 | G_TYPE_FROM_CLASS(object_class), |
849 | 0 | G_SIGNAL_RUN_LAST, |
850 | 0 | 0, |
851 | 0 | NULL, |
852 | 0 | NULL, |
853 | 0 | g_cclosure_marshal_VOID__VOID, |
854 | 0 | G_TYPE_NONE, |
855 | 0 | 0); |
856 | | |
857 | | /** |
858 | | * FuConfig::loaded: |
859 | | * @self: the #FuConfig instance that emitted the signal |
860 | | * |
861 | | * The ::loaded signal is emitted when the config file has |
862 | | * loaded, typically at startup. |
863 | | **/ |
864 | 0 | signals[SIGNAL_LOADED] = g_signal_new("loaded", |
865 | 0 | G_TYPE_FROM_CLASS(object_class), |
866 | 0 | G_SIGNAL_RUN_LAST, |
867 | 0 | 0, |
868 | 0 | NULL, |
869 | 0 | NULL, |
870 | 0 | g_cclosure_marshal_VOID__VOID, |
871 | 0 | G_TYPE_NONE, |
872 | 0 | 0); |
873 | 0 | } |
874 | | |
875 | | /** |
876 | | * fu_config_new: |
877 | | * @pstore: a #FuPathStore |
878 | | * |
879 | | * Creates a new #FuConfig. |
880 | | * |
881 | | * Returns: (transfer full): a new #FuConfig |
882 | | * |
883 | | * Since: 1.9.1 |
884 | | **/ |
885 | | FuConfig * |
886 | | fu_config_new(FuPathStore *pstore) |
887 | 0 | { |
888 | 0 | FuConfig *self; |
889 | 0 | g_return_val_if_fail(FU_IS_PATH_STORE(pstore), NULL); |
890 | 0 | self = g_object_new(FU_TYPE_CONFIG, "path-store", pstore, NULL); |
891 | 0 | return FU_CONFIG(self); |
892 | 0 | } |