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