Coverage Report

Created: 2025-07-12 06:33

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