/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 | } |
328 | 0 | if (g_error_matches(error_load, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { |
329 | 0 | g_debug("%s", error_load->message); |
330 | 0 | continue; |
331 | 0 | } |
332 | 0 | g_propagate_error(error, g_steal_pointer(&error_load)); |
333 | 0 | g_prefix_error(error, "failed to read %s: ", item->filename); |
334 | 0 | return FALSE; |
335 | 0 | } |
336 | 0 | if (!fu_config_load_bytes_replace(self, blob_item, error)) { |
337 | 0 | g_prefix_error(error, "failed to load %s: ", item->filename); |
338 | 0 | return FALSE; |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | | /* success */ |
343 | 0 | return TRUE; |
344 | 0 | } |
345 | | |
346 | | static void |
347 | | fu_config_monitor_changed_cb(GFileMonitor *monitor, |
348 | | GFile *file, |
349 | | GFile *other_file, |
350 | | GFileMonitorEvent event_type, |
351 | | gpointer user_data) |
352 | 0 | { |
353 | 0 | FuConfig *self = FU_CONFIG(user_data); |
354 | 0 | g_autoptr(GError) error = NULL; |
355 | 0 | g_autofree gchar *fn = g_file_get_path(file); |
356 | | |
357 | | /* nothing we need to care about */ |
358 | 0 | if (event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) { |
359 | 0 | g_debug("%s attributes changed, ignoring", fn); |
360 | 0 | return; |
361 | 0 | } |
362 | | |
363 | | /* reload everything */ |
364 | 0 | g_info("%s changed, reloading all configs", fn); |
365 | 0 | if (!fu_config_reload(self, FU_CONFIG_LOAD_FLAG_NONE, &error)) |
366 | 0 | g_warning("failed to rescan daemon config: %s", error->message); |
367 | 0 | fu_config_emit_changed(self); |
368 | 0 | } |
369 | | |
370 | | /** |
371 | | * fu_config_set_default: |
372 | | * @self: a #FuConfig |
373 | | * @section: a settings section |
374 | | * @key: a settings key |
375 | | * @value: (nullable): a settings value |
376 | | * |
377 | | * Sets a default config value. |
378 | | * |
379 | | * Since: 2.0.0 |
380 | | **/ |
381 | | void |
382 | | fu_config_set_default(FuConfig *self, const gchar *section, const gchar *key, const gchar *value) |
383 | 0 | { |
384 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
385 | 0 | g_return_if_fail(FU_IS_CONFIG(self)); |
386 | 0 | g_return_if_fail(section != NULL); |
387 | 0 | g_return_if_fail(key != NULL); |
388 | 0 | g_hash_table_insert(priv->default_values, |
389 | 0 | fu_config_build_section_key(section, key), |
390 | 0 | g_strdup(value)); |
391 | 0 | } |
392 | | |
393 | | static gboolean |
394 | | fu_config_save(FuConfig *self, GError **error) |
395 | 0 | { |
396 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
397 | 0 | g_autofree gchar *data = NULL; |
398 | | |
399 | | /* sanity check */ |
400 | 0 | if (priv->items->len == 0) { |
401 | 0 | g_set_error_literal(error, |
402 | 0 | FWUPD_ERROR, |
403 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
404 | 0 | "no config locations found"); |
405 | 0 | return FALSE; |
406 | 0 | } |
407 | | |
408 | 0 | data = g_key_file_to_data(priv->keyfile, NULL, error); |
409 | 0 | if (data == NULL) |
410 | 0 | return FALSE; |
411 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
412 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
413 | 0 | if (!item->is_mutable) |
414 | 0 | continue; |
415 | 0 | if (!fu_path_mkdir_parent(item->filename, error)) |
416 | 0 | return FALSE; |
417 | 0 | if (!g_file_set_contents_full(item->filename, |
418 | 0 | data, |
419 | 0 | -1, |
420 | 0 | G_FILE_SET_CONTENTS_CONSISTENT, |
421 | 0 | FU_CONFIG_FILE_MODE_SECURE, /* only for root */ |
422 | 0 | error)) |
423 | 0 | return FALSE; |
424 | 0 | return fu_config_reload(self, FU_CONFIG_LOAD_FLAG_NONE, error); |
425 | 0 | } |
426 | | |
427 | | /* failed */ |
428 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no writable config"); |
429 | 0 | return FALSE; |
430 | 0 | } |
431 | | |
432 | | /** |
433 | | * fu_config_set_value_internal: |
434 | | * @self: a #FuConfig |
435 | | * @section: a settings section |
436 | | * @key: a settings key |
437 | | * @value: (nullable): a settings value |
438 | | * |
439 | | * Sets a plugin config value, *not* saving to a config file. |
440 | | * |
441 | | * Since: 2.1.1 |
442 | | **/ |
443 | | void |
444 | | fu_config_set_value_internal(FuConfig *self, |
445 | | const gchar *section, |
446 | | const gchar *key, |
447 | | const gchar *value) |
448 | 0 | { |
449 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
450 | 0 | g_return_if_fail(FU_IS_CONFIG(self)); |
451 | 0 | g_return_if_fail(section != NULL); |
452 | 0 | g_return_if_fail(key != NULL); |
453 | 0 | if (value == NULL) { |
454 | 0 | g_key_file_remove_key(priv->keyfile, section, key, NULL); |
455 | 0 | return; |
456 | 0 | } |
457 | 0 | g_key_file_set_string(priv->keyfile, section, key, value); |
458 | 0 | } |
459 | | |
460 | | /** |
461 | | * fu_config_set_value: |
462 | | * @self: a #FuConfig |
463 | | * @section: a settings section |
464 | | * @key: a settings key |
465 | | * @value: (nullable): a settings value |
466 | | * @error: (nullable): optional return location for an error |
467 | | * |
468 | | * Sets a plugin config value, saving the new data back to the default config file. |
469 | | * |
470 | | * Returns: %TRUE for success |
471 | | * |
472 | | * Since: 1.9.1 |
473 | | **/ |
474 | | gboolean |
475 | | fu_config_set_value(FuConfig *self, |
476 | | const gchar *section, |
477 | | const gchar *key, |
478 | | const gchar *value, |
479 | | GError **error) |
480 | 0 | { |
481 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
482 | |
|
483 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
484 | 0 | g_return_val_if_fail(section != NULL, FALSE); |
485 | 0 | g_return_val_if_fail(key != NULL, FALSE); |
486 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
487 | | |
488 | | /* sanity check */ |
489 | 0 | if (priv->items->len == 0) { |
490 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no config to load"); |
491 | 0 | return FALSE; |
492 | 0 | } |
493 | | |
494 | | /* do not write default keys */ |
495 | 0 | fu_config_migrate_keyfile(self); |
496 | | |
497 | | /* only write the file to a mutable location */ |
498 | 0 | g_key_file_set_string(priv->keyfile, section, key, value); |
499 | 0 | return fu_config_save(self, error); |
500 | 0 | } |
501 | | |
502 | | /** |
503 | | * fu_config_reset_defaults: |
504 | | * @self: a #FuConfig |
505 | | * @section: a settings section |
506 | | * |
507 | | * Reset all the keys back to the default values. |
508 | | * |
509 | | * Since: 1.9.15 |
510 | | **/ |
511 | | gboolean |
512 | | fu_config_reset_defaults(FuConfig *self, const gchar *section, GError **error) |
513 | 0 | { |
514 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
515 | 0 | g_autoptr(GError) error_keyfile = NULL; |
516 | |
|
517 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
518 | 0 | g_return_val_if_fail(section != NULL, FALSE); |
519 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
520 | | |
521 | | /* remove all keys, and save */ |
522 | 0 | if (!g_key_file_remove_group(priv->keyfile, section, &error_keyfile)) { |
523 | | /* allow user to reset config sections twice */ |
524 | 0 | if (!g_error_matches(error_keyfile, |
525 | 0 | G_KEY_FILE_ERROR, |
526 | 0 | G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { |
527 | 0 | g_propagate_error(error, g_steal_pointer(&error_keyfile)); |
528 | 0 | fwupd_error_convert(error); |
529 | 0 | return FALSE; |
530 | 0 | } |
531 | 0 | } |
532 | | |
533 | 0 | return fu_config_save(self, error); |
534 | 0 | } |
535 | | |
536 | | /** |
537 | | * fu_config_get_value: |
538 | | * @self: a #FuConfig |
539 | | * @section: a settings section |
540 | | * @key: a settings key |
541 | | * |
542 | | * Return the value of a key, falling back to the default value if missing. |
543 | | * |
544 | | * NOTE: this function will return an empty string for `key=`. |
545 | | * |
546 | | * Returns: (transfer full): key value |
547 | | * |
548 | | * Since: 1.9.1 |
549 | | **/ |
550 | | gchar * |
551 | | fu_config_get_value(FuConfig *self, const gchar *section, const gchar *key) |
552 | 0 | { |
553 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
554 | 0 | g_autofree gchar *value = NULL; |
555 | |
|
556 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), NULL); |
557 | 0 | g_return_val_if_fail(section != NULL, NULL); |
558 | 0 | g_return_val_if_fail(key != NULL, NULL); |
559 | | |
560 | 0 | value = g_key_file_get_string(priv->keyfile, section, key, NULL); |
561 | 0 | if (value == NULL) { |
562 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
563 | 0 | const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
564 | 0 | return g_strdup(value_tmp); |
565 | 0 | } |
566 | 0 | return g_steal_pointer(&value); |
567 | 0 | } |
568 | | |
569 | | /** |
570 | | * fu_config_get_value_strv: |
571 | | * @self: a #FuConfig |
572 | | * @section: a settings section |
573 | | * @key: a settings key |
574 | | * |
575 | | * Return the value of a key, falling back to the default value if missing. |
576 | | * |
577 | | * NOTE: this function will return an empty string for `key=`. |
578 | | * |
579 | | * Returns: (transfer full) (nullable): key value |
580 | | * |
581 | | * Since: 1.9.1 |
582 | | **/ |
583 | | gchar ** |
584 | | fu_config_get_value_strv(FuConfig *self, const gchar *section, const gchar *key) |
585 | 0 | { |
586 | 0 | g_autofree gchar *value = NULL; |
587 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), NULL); |
588 | 0 | g_return_val_if_fail(section != NULL, NULL); |
589 | 0 | g_return_val_if_fail(key != NULL, NULL); |
590 | 0 | value = fu_config_get_value(self, section, key); |
591 | 0 | if (value == NULL) |
592 | 0 | return NULL; |
593 | 0 | return g_strsplit(value, ";", -1); |
594 | 0 | } |
595 | | |
596 | | /** |
597 | | * fu_config_get_value_bool: |
598 | | * @self: a #FuConfig |
599 | | * @section: a settings section |
600 | | * @key: a settings key |
601 | | * |
602 | | * Return the value of a key, falling back to the default value if missing or empty. |
603 | | * |
604 | | * Returns: boolean |
605 | | * |
606 | | * Since: 1.9.1 |
607 | | **/ |
608 | | gboolean |
609 | | fu_config_get_value_bool(FuConfig *self, const gchar *section, const gchar *key) |
610 | 0 | { |
611 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
612 | 0 | g_autofree gchar *value = fu_config_get_value(self, section, key); |
613 | 0 | if (value == NULL || value[0] == '\0') { |
614 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
615 | 0 | const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
616 | 0 | if (value_tmp == NULL) { |
617 | 0 | g_critical("no default for [%s] %s", section, key); |
618 | 0 | return FALSE; |
619 | 0 | } |
620 | 0 | return g_ascii_strcasecmp(value_tmp, "true") == 0; |
621 | 0 | } |
622 | 0 | return g_ascii_strcasecmp(value, "true") == 0; |
623 | 0 | } |
624 | | |
625 | | /** |
626 | | * fu_config_get_value_u64: |
627 | | * @self: a #FuConfig |
628 | | * @section: a settings section |
629 | | * @key: a settings key |
630 | | * |
631 | | * Return the value of a key, falling back to the default value if missing or empty. |
632 | | * |
633 | | * Returns: uint64 |
634 | | * |
635 | | * Since: 1.9.1 |
636 | | **/ |
637 | | guint64 |
638 | | fu_config_get_value_u64(FuConfig *self, const gchar *section, const gchar *key) |
639 | 0 | { |
640 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
641 | 0 | guint64 value = 0; |
642 | 0 | const gchar *value_tmp; |
643 | 0 | g_autofree gchar *tmp = fu_config_get_value(self, section, key); |
644 | 0 | g_autoptr(GError) error_local = NULL; |
645 | |
|
646 | 0 | if (tmp == NULL || tmp[0] == '\0') { |
647 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
648 | 0 | value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
649 | 0 | if (value_tmp == NULL) { |
650 | 0 | g_critical("no default for [%s] %s", section, key); |
651 | 0 | return G_MAXUINT64; |
652 | 0 | } |
653 | 0 | } else { |
654 | 0 | value_tmp = tmp; |
655 | 0 | } |
656 | 0 | if (!fu_strtoull(value_tmp, &value, 0, G_MAXUINT64, FU_INTEGER_BASE_AUTO, &error_local)) { |
657 | 0 | g_warning("failed to parse [%s] %s = %s as integer", section, key, value_tmp); |
658 | 0 | return G_MAXUINT64; |
659 | 0 | } |
660 | 0 | return value; |
661 | 0 | } |
662 | | |
663 | | /** |
664 | | * fu_config_set_basename: |
665 | | * @self: a #FuConfig |
666 | | * @basename: (nullable): a file basename, typically, `fwupd.conf` |
667 | | * |
668 | | * Sets the name of the filename to load. |
669 | | * |
670 | | * Since: 2.0.18 |
671 | | **/ |
672 | | void |
673 | | fu_config_set_basename(FuConfig *self, const gchar *basename) |
674 | 0 | { |
675 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
676 | 0 | g_return_if_fail(FU_IS_CONFIG(self)); |
677 | 0 | if (g_strcmp0(priv->basename, basename) == 0) |
678 | 0 | return; |
679 | 0 | g_free(priv->basename); |
680 | 0 | priv->basename = g_strdup(basename); |
681 | 0 | } |
682 | | |
683 | | static gboolean |
684 | | fu_config_add_location(FuConfig *self, const gchar *dirname, gboolean is_mutable, GError **error) |
685 | 0 | { |
686 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
687 | 0 | g_autoptr(FuConfigItem) item = g_new0(FuConfigItem, 1); |
688 | |
|
689 | 0 | item->is_mutable = is_mutable; |
690 | 0 | item->filename = g_build_filename(dirname, priv->basename, NULL); |
691 | 0 | item->file = g_file_new_for_path(item->filename); |
692 | | |
693 | | /* is writable */ |
694 | 0 | if (g_file_query_exists(item->file, NULL)) { |
695 | 0 | g_autoptr(GFileInfo) info = NULL; |
696 | |
|
697 | 0 | g_debug("loading config %s", item->filename); |
698 | 0 | info = g_file_query_info(item->file, |
699 | 0 | G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, |
700 | 0 | G_FILE_QUERY_INFO_NONE, |
701 | 0 | NULL, |
702 | 0 | error); |
703 | 0 | if (info == NULL) |
704 | 0 | return FALSE; |
705 | 0 | item->is_writable = |
706 | 0 | g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); |
707 | 0 | if (!item->is_writable) |
708 | 0 | g_debug("config %s is immutable", item->filename); |
709 | 0 | } else { |
710 | 0 | g_debug("not loading config %s", item->filename); |
711 | 0 | } |
712 | | |
713 | | /* success */ |
714 | 0 | g_ptr_array_add(priv->items, g_steal_pointer(&item)); |
715 | 0 | return TRUE; |
716 | 0 | } |
717 | | |
718 | | /** |
719 | | * fu_config_load: |
720 | | * @self: a #FuConfig |
721 | | * @flags: some #FuConfigLoadFlags, typically %FU_CONFIG_LOAD_FLAG_NONE |
722 | | * @error: (nullable): optional return location for an error |
723 | | * |
724 | | * Loads the configuration files from all possible locations. |
725 | | * |
726 | | * Returns: %TRUE for success |
727 | | * |
728 | | * Since: 1.9.1 |
729 | | **/ |
730 | | gboolean |
731 | | fu_config_load(FuConfig *self, FuConfigLoadFlags flags, GError **error) |
732 | 0 | { |
733 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
734 | 0 | const gchar *configdir_mut; |
735 | 0 | const gchar *configdir; |
736 | |
|
737 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
738 | 0 | g_return_val_if_fail(priv->items->len == 0, FALSE); |
739 | | |
740 | | /* load the main daemon config file */ |
741 | 0 | configdir = fu_path_store_get_path(priv->pstore, FU_PATH_KIND_SYSCONFDIR_PKG, NULL); |
742 | 0 | if (configdir != NULL) { |
743 | 0 | if (!fu_config_add_location(self, configdir, FALSE, error)) |
744 | 0 | return FALSE; |
745 | 0 | } |
746 | 0 | configdir_mut = fu_path_store_get_path(priv->pstore, FU_PATH_KIND_LOCALCONFDIR_PKG, NULL); |
747 | 0 | if (configdir_mut != NULL) { |
748 | 0 | if (!fu_config_add_location(self, configdir_mut, TRUE, error)) |
749 | 0 | return FALSE; |
750 | 0 | } |
751 | 0 | if (!fu_config_reload(self, flags, error)) |
752 | 0 | return FALSE; |
753 | | |
754 | | /* set up a notify watches */ |
755 | 0 | if (flags & FU_CONFIG_LOAD_FLAG_WATCH_FILES) { |
756 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
757 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
758 | 0 | g_autoptr(GFile) file = g_file_new_for_path(item->filename); |
759 | 0 | item->monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error); |
760 | 0 | if (item->monitor == NULL) |
761 | 0 | return FALSE; |
762 | 0 | g_signal_connect(G_FILE_MONITOR(item->monitor), |
763 | 0 | "changed", |
764 | 0 | G_CALLBACK(fu_config_monitor_changed_cb), |
765 | 0 | self); |
766 | 0 | } |
767 | 0 | } |
768 | | |
769 | | /* success */ |
770 | 0 | fu_config_emit_loaded(self); |
771 | 0 | return TRUE; |
772 | 0 | } |
773 | | static void |
774 | | fu_config_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) |
775 | 0 | { |
776 | 0 | FuConfig *self = FU_CONFIG(object); |
777 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
778 | 0 | switch (prop_id) { |
779 | 0 | case PROP_PATH_STORE: |
780 | 0 | g_value_set_object(value, priv->pstore); |
781 | 0 | break; |
782 | 0 | default: |
783 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
784 | 0 | break; |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | | static void |
789 | | fu_config_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) |
790 | 0 | { |
791 | 0 | FuConfig *self = FU_CONFIG(object); |
792 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
793 | 0 | switch (prop_id) { |
794 | 0 | case PROP_PATH_STORE: |
795 | 0 | g_set_object(&priv->pstore, g_value_get_object(value)); |
796 | 0 | break; |
797 | 0 | default: |
798 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
799 | 0 | break; |
800 | 0 | } |
801 | 0 | } |
802 | | |
803 | | static void |
804 | | fu_config_init(FuConfig *self) |
805 | 0 | { |
806 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
807 | 0 | priv->basename = g_strdup("fwupd.conf"); |
808 | 0 | priv->keyfile = g_key_file_new(); |
809 | 0 | priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_config_item_free); |
810 | 0 | priv->default_values = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
811 | 0 | } |
812 | | |
813 | | static void |
814 | | fu_config_finalize(GObject *obj) |
815 | 0 | { |
816 | 0 | FuConfig *self = FU_CONFIG(obj); |
817 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
818 | 0 | g_object_unref(priv->pstore); |
819 | 0 | g_free(priv->basename); |
820 | 0 | g_key_file_unref(priv->keyfile); |
821 | 0 | g_ptr_array_unref(priv->items); |
822 | 0 | g_hash_table_unref(priv->default_values); |
823 | 0 | G_OBJECT_CLASS(fu_config_parent_class)->finalize(obj); |
824 | 0 | } |
825 | | |
826 | | static void |
827 | | fu_config_class_init(FuConfigClass *klass) |
828 | 0 | { |
829 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
830 | 0 | GParamSpec *pspec; |
831 | |
|
832 | 0 | object_class->finalize = fu_config_finalize; |
833 | 0 | object_class->get_property = fu_config_get_property; |
834 | 0 | object_class->set_property = fu_config_set_property; |
835 | |
|
836 | 0 | pspec = g_param_spec_object("path-store", |
837 | 0 | NULL, |
838 | 0 | NULL, |
839 | 0 | FU_TYPE_PATH_STORE, |
840 | 0 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); |
841 | 0 | g_object_class_install_property(object_class, PROP_PATH_STORE, pspec); |
842 | | |
843 | | /** |
844 | | * FuConfig::changed: |
845 | | * @self: the #FuConfig instance that emitted the signal |
846 | | * |
847 | | * The ::changed signal is emitted when the config file has |
848 | | * changed, for instance when a parameter has been modified. |
849 | | **/ |
850 | 0 | signals[SIGNAL_CHANGED] = g_signal_new("changed", |
851 | 0 | G_TYPE_FROM_CLASS(object_class), |
852 | 0 | G_SIGNAL_RUN_LAST, |
853 | 0 | 0, |
854 | 0 | NULL, |
855 | 0 | NULL, |
856 | 0 | g_cclosure_marshal_VOID__VOID, |
857 | 0 | G_TYPE_NONE, |
858 | 0 | 0); |
859 | | |
860 | | /** |
861 | | * FuConfig::loaded: |
862 | | * @self: the #FuConfig instance that emitted the signal |
863 | | * |
864 | | * The ::loaded signal is emitted when the config file has |
865 | | * loaded, typically at startup. |
866 | | **/ |
867 | 0 | signals[SIGNAL_LOADED] = g_signal_new("loaded", |
868 | 0 | G_TYPE_FROM_CLASS(object_class), |
869 | 0 | G_SIGNAL_RUN_LAST, |
870 | 0 | 0, |
871 | 0 | NULL, |
872 | 0 | NULL, |
873 | 0 | g_cclosure_marshal_VOID__VOID, |
874 | 0 | G_TYPE_NONE, |
875 | 0 | 0); |
876 | 0 | } |
877 | | |
878 | | /** |
879 | | * fu_config_new: |
880 | | * @pstore: a #FuPathStore |
881 | | * |
882 | | * Creates a new #FuConfig. |
883 | | * |
884 | | * Returns: (transfer full): a new #FuConfig |
885 | | * |
886 | | * Since: 1.9.1 |
887 | | **/ |
888 | | FuConfig * |
889 | | fu_config_new(FuPathStore *pstore) |
890 | 0 | { |
891 | 0 | FuConfig *self; |
892 | 0 | g_return_val_if_fail(FU_IS_PATH_STORE(pstore), NULL); |
893 | 0 | self = g_object_new(FU_TYPE_CONFIG, "path-store", pstore, NULL); |
894 | 0 | return FU_CONFIG(self); |
895 | 0 | } |