/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 | | } FuConfigPrivate; |
40 | | |
41 | 0 | 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_ensure_permissions(FuConfig *self, GError **error) |
243 | 0 | { |
244 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
245 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
246 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
247 | 0 | guint32 st_mode; |
248 | 0 | g_autoptr(GFileInfo) info = NULL; |
249 | | |
250 | | /* check permissions */ |
251 | 0 | if (!item->is_writable) { |
252 | 0 | g_debug("skipping mode check for %s as not writable", item->filename); |
253 | 0 | continue; |
254 | 0 | } |
255 | 0 | info = g_file_query_info(item->file, |
256 | 0 | G_FILE_ATTRIBUTE_UNIX_MODE, |
257 | 0 | G_FILE_QUERY_INFO_NONE, |
258 | 0 | NULL, |
259 | 0 | error); |
260 | 0 | if (info == NULL) { |
261 | 0 | g_prefix_error(error, "failed to query info about %s: ", item->filename); |
262 | 0 | return FALSE; |
263 | 0 | } |
264 | 0 | st_mode = g_file_info_get_attribute_uint32(info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777; |
265 | 0 | if (st_mode != FU_CONFIG_FILE_MODE_SECURE) { |
266 | 0 | g_info("fixing %s from mode 0%o to 0%o", |
267 | 0 | item->filename, |
268 | 0 | st_mode, |
269 | 0 | (guint)FU_CONFIG_FILE_MODE_SECURE); |
270 | 0 | g_file_info_set_attribute_uint32(info, |
271 | 0 | G_FILE_ATTRIBUTE_UNIX_MODE, |
272 | 0 | FU_CONFIG_FILE_MODE_SECURE); |
273 | 0 | if (!g_file_set_attributes_from_info(item->file, |
274 | 0 | info, |
275 | 0 | G_FILE_QUERY_INFO_NONE, |
276 | 0 | NULL, |
277 | 0 | error)) { |
278 | 0 | g_prefix_error(error, |
279 | 0 | "failed to set mode attribute of %s: ", |
280 | 0 | item->filename); |
281 | 0 | return FALSE; |
282 | 0 | } |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | | /* success */ |
287 | 0 | return TRUE; |
288 | 0 | } |
289 | | |
290 | | static gboolean |
291 | | fu_config_migrate_keyfiles(FuConfig *self, GError **error) |
292 | 0 | { |
293 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
294 | 0 | g_autoptr(GPtrArray) legacy_cfg_files = g_ptr_array_new_with_free_func(g_free); |
295 | 0 | const gchar *fn_merge[] = {"daemon.conf", |
296 | 0 | "msr.conf", |
297 | 0 | "redfish.conf", |
298 | 0 | "thunderbolt.conf", |
299 | 0 | "uefi_capsule.conf", |
300 | 0 | NULL}; |
301 | |
|
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_autofree gchar *dirname = g_path_get_dirname(item->filename); |
305 | |
|
306 | 0 | for (guint j = 0; fn_merge[j] != NULL; j++) { |
307 | 0 | g_autofree gchar *fncompat = g_build_filename(dirname, fn_merge[j], NULL); |
308 | 0 | if (g_file_test(fncompat, G_FILE_TEST_EXISTS)) { |
309 | 0 | g_autoptr(GBytes) blob_compat = |
310 | 0 | fu_bytes_get_contents(fncompat, error); |
311 | 0 | if (blob_compat == NULL) { |
312 | 0 | g_prefix_error(error, "failed to read %s: ", fncompat); |
313 | 0 | return FALSE; |
314 | 0 | } |
315 | 0 | if (!fu_config_load_bytes_replace(self, blob_compat, error)) { |
316 | 0 | g_prefix_error(error, "failed to load %s: ", fncompat); |
317 | 0 | return FALSE; |
318 | 0 | } |
319 | 0 | g_ptr_array_add(legacy_cfg_files, g_steal_pointer(&fncompat)); |
320 | 0 | } |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | /* migration needed */ |
325 | 0 | if (legacy_cfg_files->len > 0) { |
326 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, 0); |
327 | 0 | const gchar *fn_default = item->filename; |
328 | 0 | g_autofree gchar *data = NULL; |
329 | | |
330 | | /* do not write empty keys migrated from daemon.conf */ |
331 | 0 | fu_config_migrate_keyfile(self); |
332 | | |
333 | | /* make sure we can save the new file first */ |
334 | 0 | data = g_key_file_to_data(priv->keyfile, NULL, error); |
335 | 0 | if (data == NULL) |
336 | 0 | return FALSE; |
337 | 0 | if (!g_file_set_contents_full( |
338 | 0 | fn_default, |
339 | 0 | data, |
340 | 0 | -1, |
341 | 0 | G_FILE_SET_CONTENTS_CONSISTENT, |
342 | 0 | FU_CONFIG_FILE_MODE_SECURE, /* only readable by root */ |
343 | 0 | error)) { |
344 | 0 | g_prefix_error(error, "failed to save %s: ", fn_default); |
345 | 0 | return FALSE; |
346 | 0 | } |
347 | | |
348 | | /* give the legacy files a .old extension */ |
349 | 0 | for (guint i = 0; i < legacy_cfg_files->len; i++) { |
350 | 0 | const gchar *fn_old = g_ptr_array_index(legacy_cfg_files, i); |
351 | 0 | g_autofree gchar *fn_new = g_strdup_printf("%s.old", fn_old); |
352 | 0 | g_info("renaming legacy config file %s to %s", fn_old, fn_new); |
353 | 0 | if (g_rename(fn_old, fn_new) != 0) { |
354 | 0 | g_set_error(error, |
355 | 0 | FWUPD_ERROR, |
356 | 0 | FWUPD_ERROR_INVALID_FILE, |
357 | 0 | "failed to change rename %s to %s", |
358 | 0 | fn_old, |
359 | 0 | fn_new); |
360 | 0 | return FALSE; |
361 | 0 | } |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | | /* success */ |
366 | 0 | return TRUE; |
367 | 0 | } |
368 | | |
369 | | static gboolean |
370 | | fu_config_reload(FuConfig *self, FuConfigLoadFlags flags, GError **error) |
371 | 0 | { |
372 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
373 | |
|
374 | | #ifdef _WIN32 |
375 | | /* not relevant */ |
376 | | flags &= ~FU_CONFIG_LOAD_FLAG_FIX_PERMISSIONS; |
377 | | #endif |
378 | | |
379 | | /* ensure mutable config files are set to the correct permissions */ |
380 | 0 | if (flags & FU_CONFIG_LOAD_FLAG_FIX_PERMISSIONS) { |
381 | 0 | if (!fu_config_ensure_permissions(self, error)) |
382 | 0 | return FALSE; |
383 | 0 | } |
384 | | |
385 | | /* we have to copy each group/key from a temporary GKeyFile as g_key_file_load_from_file() |
386 | | * clears all keys before loading each file, and we want to allow the mutable version to be |
387 | | * incomplete and just *override* a specific option */ |
388 | 0 | if (!g_key_file_load_from_data(priv->keyfile, "", -1, G_KEY_FILE_NONE, error)) |
389 | 0 | return FALSE; |
390 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
391 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
392 | 0 | g_autoptr(GError) error_load = NULL; |
393 | 0 | g_autoptr(GBytes) blob_item = NULL; |
394 | |
|
395 | 0 | g_debug("trying to load config values from %s", item->filename); |
396 | 0 | blob_item = fu_bytes_get_contents(item->filename, &error_load); |
397 | 0 | if (blob_item == NULL) { |
398 | 0 | if (g_error_matches(error_load, |
399 | 0 | FWUPD_ERROR, |
400 | 0 | FWUPD_ERROR_PERMISSION_DENIED)) { |
401 | 0 | g_debug("ignoring config file %s: ", error_load->message); |
402 | 0 | continue; |
403 | 0 | } else if (g_error_matches(error_load, |
404 | 0 | FWUPD_ERROR, |
405 | 0 | FWUPD_ERROR_INVALID_FILE)) { |
406 | 0 | g_debug("%s", error_load->message); |
407 | 0 | continue; |
408 | 0 | } |
409 | 0 | g_propagate_error(error, g_steal_pointer(&error_load)); |
410 | 0 | g_prefix_error(error, "failed to read %s: ", item->filename); |
411 | 0 | return FALSE; |
412 | 0 | } |
413 | 0 | if (!fu_config_load_bytes_replace(self, blob_item, error)) { |
414 | 0 | g_prefix_error(error, "failed to load %s: ", item->filename); |
415 | 0 | return FALSE; |
416 | 0 | } |
417 | 0 | } |
418 | | |
419 | | /* are any of the legacy files found in this location? */ |
420 | 0 | if (flags & FU_CONFIG_LOAD_FLAG_MIGRATE_FILES) { |
421 | 0 | if (!fu_config_migrate_keyfiles(self, error)) |
422 | 0 | return FALSE; |
423 | 0 | } |
424 | | |
425 | | /* success */ |
426 | 0 | return TRUE; |
427 | 0 | } |
428 | | |
429 | | static void |
430 | | fu_config_monitor_changed_cb(GFileMonitor *monitor, |
431 | | GFile *file, |
432 | | GFile *other_file, |
433 | | GFileMonitorEvent event_type, |
434 | | gpointer user_data) |
435 | 0 | { |
436 | 0 | FuConfig *self = FU_CONFIG(user_data); |
437 | 0 | g_autoptr(GError) error = NULL; |
438 | 0 | g_autofree gchar *fn = g_file_get_path(file); |
439 | | |
440 | | /* nothing we need to care about */ |
441 | 0 | if (event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) { |
442 | 0 | g_debug("%s attributes changed, ignoring", fn); |
443 | 0 | return; |
444 | 0 | } |
445 | | |
446 | | /* reload everything */ |
447 | 0 | g_info("%s changed, reloading all configs", fn); |
448 | 0 | if (!fu_config_reload(self, FU_CONFIG_LOAD_FLAG_NONE, &error)) |
449 | 0 | g_warning("failed to rescan daemon config: %s", error->message); |
450 | 0 | fu_config_emit_changed(self); |
451 | 0 | } |
452 | | |
453 | | /** |
454 | | * fu_config_set_default: |
455 | | * @self: a #FuConfig |
456 | | * @section: a settings section |
457 | | * @key: a settings key |
458 | | * @value: (nullable): a settings value |
459 | | * |
460 | | * Sets a default config value. |
461 | | * |
462 | | * Since: 2.0.0 |
463 | | **/ |
464 | | void |
465 | | fu_config_set_default(FuConfig *self, const gchar *section, const gchar *key, const gchar *value) |
466 | 0 | { |
467 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
468 | 0 | g_return_if_fail(FU_IS_CONFIG(self)); |
469 | 0 | g_return_if_fail(section != NULL); |
470 | 0 | g_return_if_fail(key != NULL); |
471 | 0 | g_hash_table_insert(priv->default_values, |
472 | 0 | fu_config_build_section_key(section, key), |
473 | 0 | g_strdup(value)); |
474 | 0 | } |
475 | | |
476 | | static gboolean |
477 | | fu_config_save(FuConfig *self, GError **error) |
478 | 0 | { |
479 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
480 | 0 | g_autofree gchar *data = NULL; |
481 | |
|
482 | 0 | data = g_key_file_to_data(priv->keyfile, NULL, error); |
483 | 0 | if (data == NULL) |
484 | 0 | return FALSE; |
485 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
486 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
487 | 0 | if (!item->is_mutable) |
488 | 0 | continue; |
489 | 0 | if (!fu_path_mkdir_parent(item->filename, error)) |
490 | 0 | return FALSE; |
491 | 0 | if (!g_file_set_contents_full(item->filename, |
492 | 0 | data, |
493 | 0 | -1, |
494 | 0 | G_FILE_SET_CONTENTS_CONSISTENT, |
495 | 0 | FU_CONFIG_FILE_MODE_SECURE, /* only for root */ |
496 | 0 | error)) |
497 | 0 | return FALSE; |
498 | 0 | return fu_config_reload(self, FU_CONFIG_LOAD_FLAG_NONE, error); |
499 | 0 | } |
500 | | |
501 | | /* failed */ |
502 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no writable config"); |
503 | 0 | return FALSE; |
504 | 0 | } |
505 | | |
506 | | /** |
507 | | * fu_config_set_value: |
508 | | * @self: a #FuConfig |
509 | | * @section: a settings section |
510 | | * @key: a settings key |
511 | | * @value: (nullable): a settings value |
512 | | * @error: (nullable): optional return location for an error |
513 | | * |
514 | | * Sets a plugin config value, saving the new data back to the default config file. |
515 | | * |
516 | | * Returns: %TRUE for success |
517 | | * |
518 | | * Since: 1.9.1 |
519 | | **/ |
520 | | gboolean |
521 | | fu_config_set_value(FuConfig *self, |
522 | | const gchar *section, |
523 | | const gchar *key, |
524 | | const gchar *value, |
525 | | GError **error) |
526 | 0 | { |
527 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
528 | |
|
529 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
530 | 0 | g_return_val_if_fail(section != NULL, FALSE); |
531 | 0 | g_return_val_if_fail(key != NULL, FALSE); |
532 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
533 | | |
534 | | /* sanity check */ |
535 | 0 | if (priv->items->len == 0) { |
536 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no config to load"); |
537 | 0 | return FALSE; |
538 | 0 | } |
539 | | |
540 | | /* do not write default keys */ |
541 | 0 | fu_config_migrate_keyfile(self); |
542 | | |
543 | | /* only write the file to a mutable location */ |
544 | 0 | g_key_file_set_string(priv->keyfile, section, key, value); |
545 | 0 | return fu_config_save(self, error); |
546 | 0 | } |
547 | | |
548 | | /** |
549 | | * fu_config_reset_defaults: |
550 | | * @self: a #FuConfig |
551 | | * @section: a settings section |
552 | | * |
553 | | * Reset all the keys back to the default values. |
554 | | * |
555 | | * Since: 1.9.15 |
556 | | **/ |
557 | | gboolean |
558 | | fu_config_reset_defaults(FuConfig *self, const gchar *section, GError **error) |
559 | 0 | { |
560 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
561 | 0 | g_autoptr(GError) error_keyfile = NULL; |
562 | |
|
563 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
564 | 0 | g_return_val_if_fail(section != NULL, FALSE); |
565 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
566 | | |
567 | | /* remove all keys, and save */ |
568 | 0 | if (!g_key_file_remove_group(priv->keyfile, section, &error_keyfile)) { |
569 | | /* allow user to reset config sections twice */ |
570 | 0 | if (!g_error_matches(error_keyfile, |
571 | 0 | G_KEY_FILE_ERROR, |
572 | 0 | G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { |
573 | 0 | g_propagate_error(error, g_steal_pointer(&error_keyfile)); |
574 | 0 | fwupd_error_convert(error); |
575 | 0 | return FALSE; |
576 | 0 | } |
577 | 0 | } |
578 | | |
579 | 0 | return fu_config_save(self, error); |
580 | 0 | } |
581 | | |
582 | | /** |
583 | | * fu_config_get_value: |
584 | | * @self: a #FuConfig |
585 | | * @section: a settings section |
586 | | * @key: a settings key |
587 | | * |
588 | | * Return the value of a key, falling back to the default value if missing. |
589 | | * |
590 | | * NOTE: this function will return an empty string for `key=`. |
591 | | * |
592 | | * Returns: (transfer full): key value |
593 | | * |
594 | | * Since: 1.9.1 |
595 | | **/ |
596 | | gchar * |
597 | | fu_config_get_value(FuConfig *self, const gchar *section, const gchar *key) |
598 | 0 | { |
599 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
600 | 0 | g_autofree gchar *value = NULL; |
601 | |
|
602 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), NULL); |
603 | 0 | g_return_val_if_fail(section != NULL, NULL); |
604 | 0 | g_return_val_if_fail(key != NULL, NULL); |
605 | | |
606 | 0 | value = g_key_file_get_string(priv->keyfile, section, key, NULL); |
607 | 0 | if (value == NULL) { |
608 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
609 | 0 | const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
610 | 0 | return g_strdup(value_tmp); |
611 | 0 | } |
612 | 0 | return g_steal_pointer(&value); |
613 | 0 | } |
614 | | |
615 | | /** |
616 | | * fu_config_get_value_strv: |
617 | | * @self: a #FuConfig |
618 | | * @section: a settings section |
619 | | * @key: a settings key |
620 | | * |
621 | | * Return the value of a key, falling back to the default value if missing. |
622 | | * |
623 | | * NOTE: this function will return an empty string for `key=`. |
624 | | * |
625 | | * Returns: (transfer full) (nullable): key value |
626 | | * |
627 | | * Since: 1.9.1 |
628 | | **/ |
629 | | gchar ** |
630 | | fu_config_get_value_strv(FuConfig *self, const gchar *section, const gchar *key) |
631 | 0 | { |
632 | 0 | g_autofree gchar *value = NULL; |
633 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), NULL); |
634 | 0 | g_return_val_if_fail(section != NULL, NULL); |
635 | 0 | g_return_val_if_fail(key != NULL, NULL); |
636 | 0 | value = fu_config_get_value(self, section, key); |
637 | 0 | if (value == NULL) |
638 | 0 | return NULL; |
639 | 0 | return g_strsplit(value, ";", -1); |
640 | 0 | } |
641 | | |
642 | | /** |
643 | | * fu_config_get_value_bool: |
644 | | * @self: a #FuConfig |
645 | | * @section: a settings section |
646 | | * @key: a settings key |
647 | | * |
648 | | * Return the value of a key, falling back to the default value if missing or empty. |
649 | | * |
650 | | * Returns: boolean |
651 | | * |
652 | | * Since: 1.9.1 |
653 | | **/ |
654 | | gboolean |
655 | | fu_config_get_value_bool(FuConfig *self, const gchar *section, const gchar *key) |
656 | 0 | { |
657 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
658 | 0 | g_autofree gchar *value = fu_config_get_value(self, section, key); |
659 | 0 | if (value == NULL || value[0] == '\0') { |
660 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
661 | 0 | const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
662 | 0 | if (value_tmp == NULL) { |
663 | 0 | g_critical("no default for [%s] %s", section, key); |
664 | 0 | return FALSE; |
665 | 0 | } |
666 | 0 | return g_ascii_strcasecmp(value_tmp, "true") == 0; |
667 | 0 | } |
668 | 0 | return g_ascii_strcasecmp(value, "true") == 0; |
669 | 0 | } |
670 | | |
671 | | /** |
672 | | * fu_config_get_value_u64: |
673 | | * @self: a #FuConfig |
674 | | * @section: a settings section |
675 | | * @key: a settings key |
676 | | * |
677 | | * Return the value of a key, falling back to the default value if missing or empty. |
678 | | * |
679 | | * Returns: uint64 |
680 | | * |
681 | | * Since: 1.9.1 |
682 | | **/ |
683 | | guint64 |
684 | | fu_config_get_value_u64(FuConfig *self, const gchar *section, const gchar *key) |
685 | 0 | { |
686 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
687 | 0 | guint64 value = 0; |
688 | 0 | const gchar *value_tmp; |
689 | 0 | g_autofree gchar *tmp = fu_config_get_value(self, section, key); |
690 | 0 | g_autoptr(GError) error_local = NULL; |
691 | |
|
692 | 0 | if (tmp == NULL || tmp[0] == '\0') { |
693 | 0 | g_autofree gchar *section_key = fu_config_build_section_key(section, key); |
694 | 0 | value_tmp = g_hash_table_lookup(priv->default_values, section_key); |
695 | 0 | if (value_tmp == NULL) { |
696 | 0 | g_critical("no default for [%s] %s", section, key); |
697 | 0 | return G_MAXUINT64; |
698 | 0 | } |
699 | 0 | } else { |
700 | 0 | value_tmp = tmp; |
701 | 0 | } |
702 | 0 | if (!fu_strtoull(value_tmp, &value, 0, G_MAXUINT64, FU_INTEGER_BASE_AUTO, &error_local)) { |
703 | 0 | g_warning("failed to parse [%s] %s = %s as integer", section, key, value_tmp); |
704 | 0 | return G_MAXUINT64; |
705 | 0 | } |
706 | 0 | return value; |
707 | 0 | } |
708 | | |
709 | | static gboolean |
710 | | fu_config_add_location(FuConfig *self, const gchar *dirname, gboolean is_mutable, GError **error) |
711 | 0 | { |
712 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
713 | 0 | g_autoptr(FuConfigItem) item = g_new0(FuConfigItem, 1); |
714 | |
|
715 | 0 | item->is_mutable = is_mutable; |
716 | 0 | item->filename = g_build_filename(dirname, "fwupd.conf", NULL); |
717 | 0 | item->file = g_file_new_for_path(item->filename); |
718 | | |
719 | | /* is writable */ |
720 | 0 | if (g_file_query_exists(item->file, NULL)) { |
721 | 0 | g_autoptr(GFileInfo) info = NULL; |
722 | |
|
723 | 0 | g_debug("loading config %s", item->filename); |
724 | 0 | info = g_file_query_info(item->file, |
725 | 0 | G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, |
726 | 0 | G_FILE_QUERY_INFO_NONE, |
727 | 0 | NULL, |
728 | 0 | error); |
729 | 0 | if (info == NULL) |
730 | 0 | return FALSE; |
731 | 0 | item->is_writable = |
732 | 0 | g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); |
733 | 0 | if (!item->is_writable) |
734 | 0 | g_debug("config %s is immutable", item->filename); |
735 | 0 | } else { |
736 | 0 | g_debug("not loading config %s", item->filename); |
737 | 0 | } |
738 | | |
739 | | /* success */ |
740 | 0 | g_ptr_array_add(priv->items, g_steal_pointer(&item)); |
741 | 0 | return TRUE; |
742 | 0 | } |
743 | | |
744 | | /** |
745 | | * fu_config_load: |
746 | | * @self: a #FuConfig |
747 | | * @flags: some #FuConfigLoadFlags, typically %FU_CONFIG_LOAD_FLAG_NONE |
748 | | * @error: (nullable): optional return location for an error |
749 | | * |
750 | | * Loads the configuration files from all possible locations. |
751 | | * |
752 | | * Returns: %TRUE for success |
753 | | * |
754 | | * Since: 1.9.1 |
755 | | **/ |
756 | | gboolean |
757 | | fu_config_load(FuConfig *self, FuConfigLoadFlags flags, GError **error) |
758 | 0 | { |
759 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
760 | 0 | g_autofree gchar *configdir_mut = fu_path_from_kind(FU_PATH_KIND_LOCALCONFDIR_PKG); |
761 | 0 | g_autofree gchar *configdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); |
762 | |
|
763 | 0 | g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); |
764 | 0 | g_return_val_if_fail(priv->items->len == 0, FALSE); |
765 | | |
766 | | /* load the main daemon config file */ |
767 | 0 | if (!fu_config_add_location(self, configdir, FALSE, error)) |
768 | 0 | return FALSE; |
769 | 0 | if (!fu_config_add_location(self, configdir_mut, TRUE, error)) |
770 | 0 | return FALSE; |
771 | 0 | if (!fu_config_reload(self, flags, error)) |
772 | 0 | return FALSE; |
773 | | |
774 | | /* set up a notify watches */ |
775 | 0 | if (flags & FU_CONFIG_LOAD_FLAG_WATCH_FILES) { |
776 | 0 | for (guint i = 0; i < priv->items->len; i++) { |
777 | 0 | FuConfigItem *item = g_ptr_array_index(priv->items, i); |
778 | 0 | g_autoptr(GFile) file = g_file_new_for_path(item->filename); |
779 | 0 | item->monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error); |
780 | 0 | if (item->monitor == NULL) |
781 | 0 | return FALSE; |
782 | 0 | g_signal_connect(G_FILE_MONITOR(item->monitor), |
783 | 0 | "changed", |
784 | 0 | G_CALLBACK(fu_config_monitor_changed_cb), |
785 | 0 | self); |
786 | 0 | } |
787 | 0 | } |
788 | | |
789 | | /* success */ |
790 | 0 | fu_config_emit_loaded(self); |
791 | 0 | return TRUE; |
792 | 0 | } |
793 | | |
794 | | static void |
795 | | fu_config_init(FuConfig *self) |
796 | 0 | { |
797 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
798 | 0 | priv->keyfile = g_key_file_new(); |
799 | 0 | priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_config_item_free); |
800 | 0 | priv->default_values = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
801 | 0 | } |
802 | | |
803 | | static void |
804 | | fu_config_finalize(GObject *obj) |
805 | 0 | { |
806 | 0 | FuConfig *self = FU_CONFIG(obj); |
807 | 0 | FuConfigPrivate *priv = GET_PRIVATE(self); |
808 | 0 | g_key_file_unref(priv->keyfile); |
809 | 0 | g_ptr_array_unref(priv->items); |
810 | 0 | g_hash_table_unref(priv->default_values); |
811 | 0 | G_OBJECT_CLASS(fu_config_parent_class)->finalize(obj); |
812 | 0 | } |
813 | | |
814 | | static void |
815 | | fu_config_class_init(FuConfigClass *klass) |
816 | 0 | { |
817 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
818 | 0 | object_class->finalize = fu_config_finalize; |
819 | | |
820 | | /** |
821 | | * FuConfig::changed: |
822 | | * @self: the #FuConfig instance that emitted the signal |
823 | | * |
824 | | * The ::changed signal is emitted when the config file has |
825 | | * changed, for instance when a parameter has been modified. |
826 | | **/ |
827 | 0 | signals[SIGNAL_CHANGED] = g_signal_new("changed", |
828 | 0 | G_TYPE_FROM_CLASS(object_class), |
829 | 0 | G_SIGNAL_RUN_LAST, |
830 | 0 | 0, |
831 | 0 | NULL, |
832 | 0 | NULL, |
833 | 0 | g_cclosure_marshal_VOID__VOID, |
834 | 0 | G_TYPE_NONE, |
835 | 0 | 0); |
836 | | |
837 | | /** |
838 | | * FuConfig::loaded: |
839 | | * @self: the #FuConfig instance that emitted the signal |
840 | | * |
841 | | * The ::loaded signal is emitted when the config file has |
842 | | * loaded, typically at startup. |
843 | | **/ |
844 | 0 | signals[SIGNAL_LOADED] = g_signal_new("loaded", |
845 | 0 | G_TYPE_FROM_CLASS(object_class), |
846 | 0 | G_SIGNAL_RUN_LAST, |
847 | 0 | 0, |
848 | 0 | NULL, |
849 | 0 | NULL, |
850 | 0 | g_cclosure_marshal_VOID__VOID, |
851 | 0 | G_TYPE_NONE, |
852 | 0 | 0); |
853 | 0 | } |
854 | | |
855 | | /** |
856 | | * fu_config_new: |
857 | | * |
858 | | * Creates a new #FuConfig. |
859 | | * |
860 | | * Returns: (transfer full): a new #FuConfig |
861 | | * |
862 | | * Since: 1.9.1 |
863 | | **/ |
864 | | FuConfig * |
865 | | fu_config_new(void) |
866 | 0 | { |
867 | 0 | return FU_CONFIG(g_object_new(FU_TYPE_CONFIG, NULL)); |
868 | 0 | } |