Coverage Report

Created: 2025-11-24 06:59

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