Coverage Report

Created: 2026-08-15 06:58

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