Coverage Report

Created: 2025-10-10 07:05

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