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