Coverage Report

Created: 2026-03-11 07:30

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
      if (!g_key_file_set_comment(priv->keyfile,
151
0
                groups[i],
152
0
                NULL,
153
0
                comment_group,
154
0
                error)) {
155
0
        g_prefix_error(error,
156
0
                 "failed to set comment '%s' for [%s]: ",
157
0
                 comment_group,
158
0
                 groups[i]);
159
0
        return FALSE;
160
0
      }
161
0
    }
162
0
  }
163
164
  /* success */
165
0
  return TRUE;
166
0
}
167
168
static void
169
fu_config_migrate_keyfile(FuConfig *self)
170
0
{
171
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
172
0
  struct {
173
0
    const gchar *group;
174
0
    const gchar *key;
175
0
    const gchar *value;
176
0
  } key_values[] = {{"fwupd", "ApprovedFirmware", NULL},
177
0
        {"fwupd", "ArchiveSizeMax", "0"},
178
0
        {"fwupd", "DisabledDevices", NULL},
179
0
        {"fwupd", "EnumerateAllDevices", NULL},
180
0
        {"fwupd", "EspLocation", NULL},
181
0
        {"fwupd", "HostBkc", NULL},
182
0
        {"fwupd", "IdleTimeout", "7200"},
183
0
        {"fwupd", "IdleTimeout", NULL},
184
0
        {"fwupd", "IgnorePower", NULL},
185
0
        {"fwupd", "ShowDevicePrivate", NULL},
186
0
        {"fwupd", "TrustedUids", NULL},
187
0
        {"fwupd", "UpdateMotd", NULL},
188
0
        {"fwupd", "UriSchemes", NULL},
189
0
        {"fwupd", "VerboseDomains", NULL},
190
0
        {"fwupd", "OnlyTrusted", NULL},
191
0
        {"fwupd", "IgnorePower", NULL},
192
0
        {"fwupd", "RequireImmutableEnumeration", NULL},
193
0
        {"fwupd", "DisabledPlugins", "test;test_ble;invalid"},
194
0
        {"fwupd", "DisabledPlugins", "test;test_ble"},
195
0
        {"redfish", "IpmiDisableCreateUser", NULL},
196
0
        {"redfish", "ManagerResetTimeout", NULL},
197
0
        {"msr", "MinimumSmeKernelVersion", NULL},
198
0
        {"thunderbolt", "MinimumKernelVersion", NULL},
199
0
        {"thunderbolt", "DelayedActivation", NULL},
200
0
        {NULL, NULL, NULL}};
201
0
  for (guint i = 0; key_values[i].group != NULL; i++) {
202
0
    const gchar *default_value;
203
0
    g_autofree gchar *value = NULL;
204
0
    g_auto(GStrv) keys = NULL;
205
206
0
    value = g_key_file_get_value(priv->keyfile,
207
0
               key_values[i].group,
208
0
               key_values[i].key,
209
0
               NULL);
210
0
    if (value == NULL)
211
0
      continue;
212
0
    if (key_values[i].value == NULL) {
213
0
      g_autofree gchar *section_key =
214
0
          fu_config_build_section_key(key_values[i].group, key_values[i].key);
215
0
      default_value = g_hash_table_lookup(priv->default_values, section_key);
216
0
    } else {
217
0
      default_value = key_values[i].value;
218
0
    }
219
0
    if ((default_value != NULL && g_ascii_strcasecmp(value, default_value) == 0) ||
220
0
        (key_values[i].value == NULL && g_strcmp0(value, "") == 0)) {
221
0
      g_debug("not migrating default value of [%s] %s=%s",
222
0
        key_values[i].group,
223
0
        key_values[i].key,
224
0
        default_value);
225
0
      g_key_file_remove_comment(priv->keyfile,
226
0
              key_values[i].group,
227
0
              key_values[i].key,
228
0
              NULL);
229
0
      g_key_file_remove_key(priv->keyfile,
230
0
                key_values[i].group,
231
0
                key_values[i].key,
232
0
                NULL);
233
0
    }
234
235
    /* remove the group if there are no keys left */
236
0
    keys = g_key_file_get_keys(priv->keyfile, key_values[i].group, NULL, NULL);
237
0
    if (g_strv_length(keys) == 0) {
238
0
      g_key_file_remove_comment(priv->keyfile, key_values[i].group, NULL, NULL);
239
0
      g_key_file_remove_group(priv->keyfile, key_values[i].group, NULL);
240
0
    }
241
0
  }
242
0
}
243
244
static gboolean
245
fu_config_ensure_permissions(FuConfig *self, GError **error)
246
0
{
247
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
248
0
  for (guint i = 0; i < priv->items->len; i++) {
249
0
    FuConfigItem *item = g_ptr_array_index(priv->items, i);
250
0
    guint32 st_mode;
251
0
    g_autoptr(GFileInfo) info = NULL;
252
253
    /* check permissions */
254
0
    if (!item->is_writable) {
255
0
      g_debug("skipping mode check for %s as not writable", item->filename);
256
0
      continue;
257
0
    }
258
0
    info = g_file_query_info(item->file,
259
0
           G_FILE_ATTRIBUTE_UNIX_MODE,
260
0
           G_FILE_QUERY_INFO_NONE,
261
0
           NULL,
262
0
           error);
263
0
    if (info == NULL) {
264
0
      g_prefix_error(error, "failed to query info about %s: ", item->filename);
265
0
      return FALSE;
266
0
    }
267
0
    st_mode = g_file_info_get_attribute_uint32(info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777;
268
0
    if (st_mode != FU_CONFIG_FILE_MODE_SECURE) {
269
0
      g_info("fixing %s from mode 0%o to 0%o",
270
0
             item->filename,
271
0
             st_mode,
272
0
             (guint)FU_CONFIG_FILE_MODE_SECURE);
273
0
      g_file_info_set_attribute_uint32(info,
274
0
               G_FILE_ATTRIBUTE_UNIX_MODE,
275
0
               FU_CONFIG_FILE_MODE_SECURE);
276
0
      if (!g_file_set_attributes_from_info(item->file,
277
0
                   info,
278
0
                   G_FILE_QUERY_INFO_NONE,
279
0
                   NULL,
280
0
                   error)) {
281
0
        g_prefix_error(error,
282
0
                 "failed to set mode attribute of %s: ",
283
0
                 item->filename);
284
0
        return FALSE;
285
0
      }
286
0
    }
287
0
  }
288
289
  /* success */
290
0
  return TRUE;
291
0
}
292
293
static gboolean
294
fu_config_reload(FuConfig *self, FuConfigLoadFlags flags, GError **error)
295
0
{
296
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
297
298
#ifdef _WIN32
299
  /* not relevant */
300
  flags &= ~FU_CONFIG_LOAD_FLAG_FIX_PERMISSIONS;
301
#endif
302
303
  /* ensure mutable config files are set to the correct permissions */
304
0
  if (flags & FU_CONFIG_LOAD_FLAG_FIX_PERMISSIONS) {
305
0
    if (!fu_config_ensure_permissions(self, error))
306
0
      return FALSE;
307
0
  }
308
309
  /* we have to copy each group/key from a temporary GKeyFile as g_key_file_load_from_file()
310
   * clears all keys before loading each file, and we want to allow the mutable version to be
311
   * incomplete and just *override* a specific option */
312
0
  if (!g_key_file_load_from_data(priv->keyfile, "", -1, G_KEY_FILE_NONE, error))
313
0
    return FALSE;
314
0
  for (guint i = 0; i < priv->items->len; i++) {
315
0
    FuConfigItem *item = g_ptr_array_index(priv->items, i);
316
0
    g_autoptr(GError) error_load = NULL;
317
0
    g_autoptr(GBytes) blob_item = NULL;
318
319
0
    g_debug("trying to load config values from %s", item->filename);
320
0
    blob_item = fu_bytes_get_contents(item->filename, &error_load);
321
0
    if (blob_item == NULL) {
322
0
      if (g_error_matches(error_load,
323
0
              FWUPD_ERROR,
324
0
              FWUPD_ERROR_PERMISSION_DENIED)) {
325
0
        g_debug("ignoring config file %s: ", error_load->message);
326
0
        continue;
327
0
      } else if (g_error_matches(error_load,
328
0
               FWUPD_ERROR,
329
0
               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
  g_key_file_set_string(priv->keyfile, section, key, value);
455
0
}
456
457
/**
458
 * fu_config_set_value:
459
 * @self: a #FuConfig
460
 * @section: a settings section
461
 * @key: a settings key
462
 * @value: (nullable): a settings value
463
 * @error: (nullable): optional return location for an error
464
 *
465
 * Sets a plugin config value, saving the new data back to the default config file.
466
 *
467
 * Returns: %TRUE for success
468
 *
469
 * Since: 1.9.1
470
 **/
471
gboolean
472
fu_config_set_value(FuConfig *self,
473
        const gchar *section,
474
        const gchar *key,
475
        const gchar *value,
476
        GError **error)
477
0
{
478
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
479
480
0
  g_return_val_if_fail(FU_IS_CONFIG(self), FALSE);
481
0
  g_return_val_if_fail(section != NULL, FALSE);
482
0
  g_return_val_if_fail(key != NULL, FALSE);
483
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
484
485
  /* sanity check */
486
0
  if (priv->items->len == 0) {
487
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no config to load");
488
0
    return FALSE;
489
0
  }
490
491
  /* do not write default keys */
492
0
  fu_config_migrate_keyfile(self);
493
494
  /* only write the file to a mutable location */
495
0
  g_key_file_set_string(priv->keyfile, section, key, value);
496
0
  return fu_config_save(self, error);
497
0
}
498
499
/**
500
 * fu_config_reset_defaults:
501
 * @self: a #FuConfig
502
 * @section: a settings section
503
 *
504
 * Reset all the keys back to the default values.
505
 *
506
 * Since: 1.9.15
507
 **/
508
gboolean
509
fu_config_reset_defaults(FuConfig *self, const gchar *section, GError **error)
510
0
{
511
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
512
0
  g_autoptr(GError) error_keyfile = NULL;
513
514
0
  g_return_val_if_fail(FU_IS_CONFIG(self), FALSE);
515
0
  g_return_val_if_fail(section != NULL, FALSE);
516
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
517
518
  /* remove all keys, and save */
519
0
  if (!g_key_file_remove_group(priv->keyfile, section, &error_keyfile)) {
520
    /* allow user to reset config sections twice */
521
0
    if (!g_error_matches(error_keyfile,
522
0
             G_KEY_FILE_ERROR,
523
0
             G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
524
0
      g_propagate_error(error, g_steal_pointer(&error_keyfile));
525
0
      fwupd_error_convert(error);
526
0
      return FALSE;
527
0
    }
528
0
  }
529
530
0
  return fu_config_save(self, error);
531
0
}
532
533
/**
534
 * fu_config_get_value:
535
 * @self: a #FuConfig
536
 * @section: a settings section
537
 * @key: a settings key
538
 *
539
 * Return the value of a key, falling back to the default value if missing.
540
 *
541
 * NOTE: this function will return an empty string for `key=`.
542
 *
543
 * Returns: (transfer full): key value
544
 *
545
 * Since: 1.9.1
546
 **/
547
gchar *
548
fu_config_get_value(FuConfig *self, const gchar *section, const gchar *key)
549
0
{
550
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
551
0
  g_autofree gchar *value = NULL;
552
553
0
  g_return_val_if_fail(FU_IS_CONFIG(self), NULL);
554
0
  g_return_val_if_fail(section != NULL, NULL);
555
0
  g_return_val_if_fail(key != NULL, NULL);
556
557
0
  value = g_key_file_get_string(priv->keyfile, section, key, NULL);
558
0
  if (value == NULL) {
559
0
    g_autofree gchar *section_key = fu_config_build_section_key(section, key);
560
0
    const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key);
561
0
    return g_strdup(value_tmp);
562
0
  }
563
0
  return g_steal_pointer(&value);
564
0
}
565
566
/**
567
 * fu_config_get_value_strv:
568
 * @self: a #FuConfig
569
 * @section: a settings section
570
 * @key: a settings key
571
 *
572
 * Return the value of a key, falling back to the default value if missing.
573
 *
574
 * NOTE: this function will return an empty string for `key=`.
575
 *
576
 * Returns: (transfer full) (nullable): key value
577
 *
578
 * Since: 1.9.1
579
 **/
580
gchar **
581
fu_config_get_value_strv(FuConfig *self, const gchar *section, const gchar *key)
582
0
{
583
0
  g_autofree gchar *value = NULL;
584
0
  g_return_val_if_fail(FU_IS_CONFIG(self), NULL);
585
0
  g_return_val_if_fail(section != NULL, NULL);
586
0
  g_return_val_if_fail(key != NULL, NULL);
587
0
  value = fu_config_get_value(self, section, key);
588
0
  if (value == NULL)
589
0
    return NULL;
590
0
  return g_strsplit(value, ";", -1);
591
0
}
592
593
/**
594
 * fu_config_get_value_bool:
595
 * @self: a #FuConfig
596
 * @section: a settings section
597
 * @key: a settings key
598
 *
599
 * Return the value of a key, falling back to the default value if missing or empty.
600
 *
601
 * Returns: boolean
602
 *
603
 * Since: 1.9.1
604
 **/
605
gboolean
606
fu_config_get_value_bool(FuConfig *self, const gchar *section, const gchar *key)
607
0
{
608
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
609
0
  g_autofree gchar *value = fu_config_get_value(self, section, key);
610
0
  if (value == NULL || value[0] == '\0') {
611
0
    g_autofree gchar *section_key = fu_config_build_section_key(section, key);
612
0
    const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key);
613
0
    if (value_tmp == NULL) {
614
0
      g_critical("no default for [%s] %s", section, key);
615
0
      return FALSE;
616
0
    }
617
0
    return g_ascii_strcasecmp(value_tmp, "true") == 0;
618
0
  }
619
0
  return g_ascii_strcasecmp(value, "true") == 0;
620
0
}
621
622
/**
623
 * fu_config_get_value_u64:
624
 * @self: a #FuConfig
625
 * @section: a settings section
626
 * @key: a settings key
627
 *
628
 * Return the value of a key, falling back to the default value if missing or empty.
629
 *
630
 * Returns: uint64
631
 *
632
 * Since: 1.9.1
633
 **/
634
guint64
635
fu_config_get_value_u64(FuConfig *self, const gchar *section, const gchar *key)
636
0
{
637
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
638
0
  guint64 value = 0;
639
0
  const gchar *value_tmp;
640
0
  g_autofree gchar *tmp = fu_config_get_value(self, section, key);
641
0
  g_autoptr(GError) error_local = NULL;
642
643
0
  if (tmp == NULL || tmp[0] == '\0') {
644
0
    g_autofree gchar *section_key = fu_config_build_section_key(section, key);
645
0
    value_tmp = g_hash_table_lookup(priv->default_values, section_key);
646
0
    if (value_tmp == NULL) {
647
0
      g_critical("no default for [%s] %s", section, key);
648
0
      return G_MAXUINT64;
649
0
    }
650
0
  } else {
651
0
    value_tmp = tmp;
652
0
  }
653
0
  if (!fu_strtoull(value_tmp, &value, 0, G_MAXUINT64, FU_INTEGER_BASE_AUTO, &error_local)) {
654
0
    g_warning("failed to parse [%s] %s = %s as integer", section, key, value_tmp);
655
0
    return G_MAXUINT64;
656
0
  }
657
0
  return value;
658
0
}
659
660
/**
661
 * fu_config_set_basename:
662
 * @self: a #FuConfig
663
 * @basename: (nullable): a file basename, typically, `fwupd.conf`
664
 *
665
 * Sets the name of the filename to load.
666
 *
667
 * Since: 2.0.18
668
 **/
669
void
670
fu_config_set_basename(FuConfig *self, const gchar *basename)
671
0
{
672
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
673
0
  g_return_if_fail(FU_IS_CONFIG(self));
674
0
  if (g_strcmp0(priv->basename, basename) == 0)
675
0
    return;
676
0
  g_free(priv->basename);
677
0
  priv->basename = g_strdup(basename);
678
0
}
679
680
static gboolean
681
fu_config_add_location(FuConfig *self, const gchar *dirname, gboolean is_mutable, GError **error)
682
0
{
683
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
684
0
  g_autoptr(FuConfigItem) item = g_new0(FuConfigItem, 1);
685
686
0
  item->is_mutable = is_mutable;
687
0
  item->filename = g_build_filename(dirname, priv->basename, NULL);
688
0
  item->file = g_file_new_for_path(item->filename);
689
690
  /* is writable */
691
0
  if (g_file_query_exists(item->file, NULL)) {
692
0
    g_autoptr(GFileInfo) info = NULL;
693
694
0
    g_debug("loading config %s", item->filename);
695
0
    info = g_file_query_info(item->file,
696
0
           G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
697
0
           G_FILE_QUERY_INFO_NONE,
698
0
           NULL,
699
0
           error);
700
0
    if (info == NULL)
701
0
      return FALSE;
702
0
    item->is_writable =
703
0
        g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
704
0
    if (!item->is_writable)
705
0
      g_debug("config %s is immutable", item->filename);
706
0
  } else {
707
0
    g_debug("not loading config %s", item->filename);
708
0
  }
709
710
  /* success */
711
0
  g_ptr_array_add(priv->items, g_steal_pointer(&item));
712
0
  return TRUE;
713
0
}
714
715
/**
716
 * fu_config_load:
717
 * @self: a #FuConfig
718
 * @flags: some #FuConfigLoadFlags, typically %FU_CONFIG_LOAD_FLAG_NONE
719
 * @error: (nullable): optional return location for an error
720
 *
721
 * Loads the configuration files from all possible locations.
722
 *
723
 * Returns: %TRUE for success
724
 *
725
 * Since: 1.9.1
726
 **/
727
gboolean
728
fu_config_load(FuConfig *self, FuConfigLoadFlags flags, GError **error)
729
0
{
730
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
731
0
  const gchar *configdir_mut;
732
0
  const gchar *configdir;
733
734
0
  g_return_val_if_fail(FU_IS_CONFIG(self), FALSE);
735
0
  g_return_val_if_fail(priv->items->len == 0, FALSE);
736
737
  /* load the main daemon config file */
738
0
  configdir = fu_path_store_get_path(priv->pstore, FU_PATH_KIND_SYSCONFDIR_PKG, NULL);
739
0
  if (configdir != NULL) {
740
0
    if (!fu_config_add_location(self, configdir, FALSE, error))
741
0
      return FALSE;
742
0
  }
743
0
  configdir_mut = fu_path_store_get_path(priv->pstore, FU_PATH_KIND_LOCALCONFDIR_PKG, NULL);
744
0
  if (configdir_mut != NULL) {
745
0
    if (!fu_config_add_location(self, configdir_mut, TRUE, error))
746
0
      return FALSE;
747
0
  }
748
0
  if (!fu_config_reload(self, flags, error))
749
0
    return FALSE;
750
751
  /* set up a notify watches */
752
0
  if (flags & FU_CONFIG_LOAD_FLAG_WATCH_FILES) {
753
0
    for (guint i = 0; i < priv->items->len; i++) {
754
0
      FuConfigItem *item = g_ptr_array_index(priv->items, i);
755
0
      g_autoptr(GFile) file = g_file_new_for_path(item->filename);
756
0
      item->monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error);
757
0
      if (item->monitor == NULL)
758
0
        return FALSE;
759
0
      g_signal_connect(G_FILE_MONITOR(item->monitor),
760
0
           "changed",
761
0
           G_CALLBACK(fu_config_monitor_changed_cb),
762
0
           self);
763
0
    }
764
0
  }
765
766
  /* success */
767
0
  fu_config_emit_loaded(self);
768
0
  return TRUE;
769
0
}
770
static void
771
fu_config_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
772
0
{
773
0
  FuConfig *self = FU_CONFIG(object);
774
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
775
0
  switch (prop_id) {
776
0
  case PROP_PATH_STORE:
777
0
    g_value_set_object(value, priv->pstore);
778
0
    break;
779
0
  default:
780
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
781
0
    break;
782
0
  }
783
0
}
784
785
static void
786
fu_config_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
787
0
{
788
0
  FuConfig *self = FU_CONFIG(object);
789
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
790
0
  switch (prop_id) {
791
0
  case PROP_PATH_STORE:
792
0
    g_set_object(&priv->pstore, g_value_get_object(value));
793
0
    break;
794
0
  default:
795
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
796
0
    break;
797
0
  }
798
0
}
799
800
static void
801
fu_config_init(FuConfig *self)
802
0
{
803
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
804
0
  priv->basename = g_strdup("fwupd.conf");
805
0
  priv->keyfile = g_key_file_new();
806
0
  priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_config_item_free);
807
0
  priv->default_values = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
808
0
}
809
810
static void
811
fu_config_finalize(GObject *obj)
812
0
{
813
0
  FuConfig *self = FU_CONFIG(obj);
814
0
  FuConfigPrivate *priv = GET_PRIVATE(self);
815
0
  g_object_unref(priv->pstore);
816
0
  g_free(priv->basename);
817
0
  g_key_file_unref(priv->keyfile);
818
0
  g_ptr_array_unref(priv->items);
819
0
  g_hash_table_unref(priv->default_values);
820
0
  G_OBJECT_CLASS(fu_config_parent_class)->finalize(obj);
821
0
}
822
823
static void
824
fu_config_class_init(FuConfigClass *klass)
825
0
{
826
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
827
0
  GParamSpec *pspec;
828
829
0
  object_class->finalize = fu_config_finalize;
830
0
  object_class->get_property = fu_config_get_property;
831
0
  object_class->set_property = fu_config_set_property;
832
833
0
  pspec = g_param_spec_object("path-store",
834
0
            NULL,
835
0
            NULL,
836
0
            FU_TYPE_PATH_STORE,
837
0
            G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME);
838
0
  g_object_class_install_property(object_class, PROP_PATH_STORE, pspec);
839
840
  /**
841
   * FuConfig::changed:
842
   * @self: the #FuConfig instance that emitted the signal
843
   *
844
   * The ::changed signal is emitted when the config file has
845
   * changed, for instance when a parameter has been modified.
846
   **/
847
0
  signals[SIGNAL_CHANGED] = g_signal_new("changed",
848
0
                 G_TYPE_FROM_CLASS(object_class),
849
0
                 G_SIGNAL_RUN_LAST,
850
0
                 0,
851
0
                 NULL,
852
0
                 NULL,
853
0
                 g_cclosure_marshal_VOID__VOID,
854
0
                 G_TYPE_NONE,
855
0
                 0);
856
857
  /**
858
   * FuConfig::loaded:
859
   * @self: the #FuConfig instance that emitted the signal
860
   *
861
   * The ::loaded signal is emitted when the config file has
862
   * loaded, typically at startup.
863
   **/
864
0
  signals[SIGNAL_LOADED] = g_signal_new("loaded",
865
0
                G_TYPE_FROM_CLASS(object_class),
866
0
                G_SIGNAL_RUN_LAST,
867
0
                0,
868
0
                NULL,
869
0
                NULL,
870
0
                g_cclosure_marshal_VOID__VOID,
871
0
                G_TYPE_NONE,
872
0
                0);
873
0
}
874
875
/**
876
 * fu_config_new:
877
 * @pstore: a #FuPathStore
878
 *
879
 * Creates a new #FuConfig.
880
 *
881
 * Returns: (transfer full): a new #FuConfig
882
 *
883
 * Since: 1.9.1
884
 **/
885
FuConfig *
886
fu_config_new(FuPathStore *pstore)
887
0
{
888
0
  FuConfig *self;
889
0
  g_return_val_if_fail(FU_IS_PATH_STORE(pstore), NULL);
890
0
  self = g_object_new(FU_TYPE_CONFIG, "path-store", pstore, NULL);
891
0
  return FU_CONFIG(self);
892
0
}