Coverage Report

Created: 2026-04-09 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-quirks.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 "FuQuirks"
8
9
#include "config.h"
10
11
#include <gio/gio.h>
12
#include <string.h>
13
14
#ifdef HAVE_SQLITE
15
#include <sqlite3.h>
16
#endif
17
18
#include "fwupd-common.h"
19
#include "fwupd-enums-private.h"
20
#include "fwupd-error.h"
21
22
#include "fu-context-private.h"
23
#include "fu-path-store.h"
24
#include "fu-path.h"
25
#include "fu-quirks.h"
26
#include "fu-string.h"
27
28
/**
29
 * FuQuirks:
30
 *
31
 * Quirks can be used to modify device behavior.
32
 * When fwupd is installed in long-term support distros it's very hard to
33
 * backport new versions as new hardware is released.
34
 *
35
 * There are several reasons why we can't just include the mapping and quirk
36
 * information in the AppStream metadata:
37
 *
38
 * * The extra data is hugely specific to the installed fwupd plugin versions
39
 * * The device-id is per-device, and the mapping is usually per-plugin
40
 * * Often the information is needed before the FuDevice is created
41
 * * There are security implications in allowing plugins to handle new devices
42
 *
43
 * The idea with quirks is that the end user can drop an additional (or replace
44
 * an existing) file in a .d director with a simple format and the hardware will
45
 * magically start working. This assumes no new quirks are required, as this would
46
 * obviously need code changes, but allows us to get most existing devices working
47
 * in an easy way without the user compiling anything.
48
 *
49
 * Plugins may add support for additional quirks that are relevant only for those plugins,
50
 * and should be documented in the per-plugin `README.md` files.
51
 *
52
 * You can add quirk files in `/usr/share/fwupd/quirks.d` or `/var/lib/fwupd/quirks.d/`.
53
 *
54
 * Here is an example as seen in the CSR plugin:
55
 *
56
 * |[
57
 * [USB\VID_0A12&PID_1337]
58
 * Plugin = dfu_csr
59
 * Name = H05
60
 * Summary = Bluetooth Headphones
61
 * Icon = audio-headphones
62
 * Vendor = AIAIAI
63
 * [USB\VID_0A12&PID_1337&REV_2520]
64
 * Version = 1.2
65
 * ]|
66
 *
67
 * See also: [class@FuDevice], [class@FuPlugin]
68
 */
69
70
static void
71
fu_quirks_finalize(GObject *obj);
72
73
struct _FuQuirks {
74
  GObject parent_instance;
75
  FuContext *ctx;
76
  FuQuirksLoadFlags load_flags;
77
  GHashTable *possible_keys;
78
  GPtrArray *invalid_keys;
79
  XbSilo *silo;
80
  XbQuery *query_kv;
81
  XbQuery *query_vs;
82
  gboolean verbose;
83
  gboolean loaded;
84
#ifdef HAVE_SQLITE
85
  sqlite3 *db;
86
#endif
87
};
88
89
0
G_DEFINE_TYPE(FuQuirks, fu_quirks, G_TYPE_OBJECT)
90
0
91
0
#ifdef HAVE_SQLITE
92
0
G_DEFINE_AUTOPTR_CLEANUP_FUNC(sqlite3_stmt, sqlite3_finalize);
93
0
#endif
94
0
95
0
static gchar *
96
0
fu_quirks_build_group_key(const gchar *group)
97
0
{
98
0
  if (fwupd_guid_is_valid(group))
99
0
    return g_strdup(group);
100
0
  return fwupd_guid_hash_string(group);
101
0
}
102
103
static gboolean
104
fu_quirks_validate_flags(const gchar *value, GError **error)
105
0
{
106
0
  g_return_val_if_fail(value != NULL, FALSE);
107
0
  for (gsize i = 0; value[i] != '\0'; i++) {
108
0
    gchar tmp = value[i];
109
110
    /* allowed special chars */
111
0
    if (tmp == ',' || tmp == '~' || tmp == '-')
112
0
      continue;
113
0
    if (!g_ascii_isalnum(tmp)) {
114
0
      g_set_error(error,
115
0
            FWUPD_ERROR,
116
0
            FWUPD_ERROR_INVALID_DATA,
117
0
            "%c is not alphanumeric",
118
0
            tmp);
119
0
      return FALSE;
120
0
    }
121
0
    if (g_ascii_isalpha(tmp) && !g_ascii_islower(tmp)) {
122
0
      g_set_error(error,
123
0
            FWUPD_ERROR,
124
0
            FWUPD_ERROR_INVALID_DATA,
125
0
            "%c is not lowercase",
126
0
            tmp);
127
0
      return FALSE;
128
0
    }
129
0
  }
130
131
  /* success */
132
0
  return TRUE;
133
0
}
134
135
typedef struct {
136
  GString *group;
137
  XbBuilderNode *bn;
138
  XbBuilderNode *root;
139
} FuQuirksConvertHelper;
140
141
static void
142
fu_quirks_convert_helper_free(FuQuirksConvertHelper *helper)
143
0
{
144
0
  g_string_free(helper->group, TRUE);
145
0
  g_object_unref(helper->root);
146
0
  if (helper->bn != NULL)
147
0
    g_object_unref(helper->bn);
148
0
  g_free(helper);
149
0
}
150
151
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuQuirksConvertHelper, fu_quirks_convert_helper_free)
152
153
static gboolean
154
fu_quirks_convert_keyfile_to_xml_cb(GString *token,
155
            guint token_idx,
156
            gpointer user_data,
157
            GError **error)
158
0
{
159
0
  FuQuirksConvertHelper *helper = (FuQuirksConvertHelper *)user_data;
160
0
  g_autofree gchar *key = NULL;
161
0
  g_autofree gchar *value = NULL;
162
0
  g_auto(GStrv) kv = NULL;
163
164
  /* blank line */
165
0
  if (token->len == 0)
166
0
    return TRUE;
167
168
  /* comment */
169
0
  if (token->str[0] == '#')
170
0
    return TRUE;
171
172
  /* neither a key=value or [group] */
173
0
  if (token->len < 3) {
174
0
    g_set_error(error,
175
0
          FWUPD_ERROR,
176
0
          FWUPD_ERROR_INVALID_DATA,
177
0
          "invalid line: %s",
178
0
          token->str);
179
0
    return FALSE;
180
0
  }
181
182
  /* a group */
183
0
  if (token->str[0] == '[' && token->str[token->len - 1] == ']') {
184
0
    g_autofree gchar *group_id = NULL;
185
0
    g_autofree gchar *group_tmp = NULL;
186
0
    g_autoptr(XbBuilderNode) bn_tmp = NULL;
187
188
    /* trim off the [] and convert to a GUID */
189
0
    group_tmp = g_strndup(token->str + 1, token->len - 2);
190
0
    group_id = fu_quirks_build_group_key(group_tmp);
191
0
    bn_tmp = xb_builder_node_insert(helper->root, "device", "id", group_id, NULL);
192
0
    g_set_object(&helper->bn, bn_tmp);
193
0
    g_string_assign(helper->group, group_tmp);
194
0
    return TRUE;
195
0
  }
196
197
  /* no current group */
198
0
  if (helper->bn == NULL) {
199
0
    g_set_error(error,
200
0
          FWUPD_ERROR,
201
0
          FWUPD_ERROR_INVALID_DATA,
202
0
          "invalid line when group unset: %s",
203
0
          token->str);
204
0
    return FALSE;
205
0
  }
206
207
  /* parse as key=value */
208
0
  kv = g_strsplit(token->str, "=", 2);
209
0
  if (kv[1] == NULL) {
210
0
    g_set_error(error,
211
0
          FWUPD_ERROR,
212
0
          FWUPD_ERROR_INVALID_DATA,
213
0
          "invalid line: not key=value: %s",
214
0
          token->str);
215
0
    return FALSE;
216
0
  }
217
218
  /* sanity check flags */
219
0
  key = fu_strstrip(kv[0]);
220
0
  value = fu_strstrip(kv[1]);
221
0
  if (g_strcmp0(key, FU_QUIRKS_FLAGS) == 0) {
222
0
    g_autoptr(GError) error_local = NULL;
223
0
    if (!fu_quirks_validate_flags(value, &error_local)) {
224
0
      g_warning("[%s] %s = %s is invalid: %s",
225
0
          helper->group->str,
226
0
          key,
227
0
          value,
228
0
          error_local->message);
229
0
    }
230
0
  }
231
232
  /* add */
233
0
  xb_builder_node_insert_text(helper->bn, "value", value, "key", key, NULL);
234
0
  return TRUE;
235
0
}
236
237
static GBytes *
238
fu_quirks_convert_keyfile_to_xml(FuQuirks *self, GBytes *bytes, GError **error)
239
0
{
240
0
  gsize xmlsz;
241
0
  g_autofree gchar *xml = NULL;
242
0
  g_autoptr(FuQuirksConvertHelper) helper = g_new0(FuQuirksConvertHelper, 1);
243
244
  /* split into lines */
245
0
  helper->root = xb_builder_node_new("quirk");
246
0
  helper->group = g_string_new(NULL);
247
0
  if (!fu_strsplit_full((const gchar *)g_bytes_get_data(bytes, NULL),
248
0
            g_bytes_get_size(bytes),
249
0
            "\n",
250
0
            fu_quirks_convert_keyfile_to_xml_cb,
251
0
            helper,
252
0
            error))
253
0
    return NULL;
254
255
  /* export as XML blob */
256
0
  xml = xb_builder_node_export(helper->root, XB_NODE_EXPORT_FLAG_ADD_HEADER, error);
257
0
  if (xml == NULL)
258
0
    return NULL;
259
0
  xmlsz = strlen(xml);
260
0
  return g_bytes_new_take(g_steal_pointer(&xml), xmlsz);
261
0
}
262
263
static GInputStream *
264
fu_quirks_convert_quirk_to_xml_cb(XbBuilderSource *source,
265
          XbBuilderSourceCtx *ctx,
266
          gpointer user_data,
267
          GCancellable *cancellable,
268
          GError **error)
269
0
{
270
0
  FuQuirks *self = FU_QUIRKS(user_data);
271
0
  g_autoptr(GBytes) bytes = NULL;
272
0
  g_autoptr(GBytes) bytes_xml = NULL;
273
274
0
  bytes = xb_builder_source_ctx_get_bytes(ctx, cancellable, error);
275
0
  if (bytes == NULL)
276
0
    return NULL;
277
0
  bytes_xml = fu_quirks_convert_keyfile_to_xml(self, bytes, error);
278
0
  if (bytes_xml == NULL)
279
0
    return NULL;
280
0
  return g_memory_input_stream_new_from_bytes(bytes_xml);
281
0
}
282
283
static gint
284
fu_quirks_filename_sort_cb(gconstpointer a, gconstpointer b)
285
0
{
286
0
  const gchar *stra = *((const gchar **)a);
287
0
  const gchar *strb = *((const gchar **)b);
288
0
  return g_strcmp0(stra, strb);
289
0
}
290
291
static gboolean
292
fu_quirks_add_quirks_for_path(FuQuirks *self, XbBuilder *builder, const gchar *path, GError **error)
293
0
{
294
0
  const gchar *tmp;
295
0
  g_autoptr(GDir) dir = NULL;
296
0
  g_autoptr(GPtrArray) filenames = g_ptr_array_new_with_free_func(g_free);
297
298
0
  g_info("loading quirks from %s", path);
299
300
  /* add valid files to the array */
301
0
  if (!g_file_test(path, G_FILE_TEST_EXISTS))
302
0
    return TRUE;
303
0
  dir = g_dir_open(path, 0, error);
304
0
  if (dir == NULL)
305
0
    return FALSE;
306
0
  while ((tmp = g_dir_read_name(dir)) != NULL) {
307
0
    if (!g_str_has_suffix(tmp, ".quirk") && !g_str_has_suffix(tmp, ".quirk.gz")) {
308
0
      g_debug("skipping invalid file %s", tmp);
309
0
      continue;
310
0
    }
311
0
    g_ptr_array_add(filenames, g_build_filename(path, tmp, NULL));
312
0
  }
313
314
  /* sort */
315
0
  g_ptr_array_sort(filenames, fu_quirks_filename_sort_cb);
316
317
  /* process files */
318
0
  for (guint i = 0; i < filenames->len; i++) {
319
0
    const gchar *filename = g_ptr_array_index(filenames, i);
320
0
    g_autoptr(GFile) file = g_file_new_for_path(filename);
321
0
    g_autoptr(XbBuilderSource) source = xb_builder_source_new();
322
323
    /* load from keyfile */
324
0
    xb_builder_source_add_simple_adapter(source,
325
0
                 "text/plain,application/octet-stream,.quirk",
326
0
                 fu_quirks_convert_quirk_to_xml_cb,
327
0
                 self,
328
0
                 NULL);
329
0
    if (!xb_builder_source_load_file(source,
330
0
             file,
331
0
             XB_BUILDER_SOURCE_FLAG_WATCH_FILE |
332
0
                 XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT,
333
0
             NULL,
334
0
             error)) {
335
0
      g_prefix_error(error, "failed to load %s: ", filename);
336
0
      fwupd_error_convert(error);
337
0
      return FALSE;
338
0
    }
339
340
    /* watch the file for changes */
341
0
    xb_builder_import_source(builder, source);
342
0
  }
343
344
  /* success */
345
0
  return TRUE;
346
0
}
347
348
static gint
349
fu_quirks_strcasecmp_cb(gconstpointer a, gconstpointer b)
350
0
{
351
0
  const gchar *entry1 = *((const gchar **)a);
352
0
  const gchar *entry2 = *((const gchar **)b);
353
0
  return g_ascii_strcasecmp(entry1, entry2);
354
0
}
355
356
static gboolean
357
fu_quirks_check_silo(FuQuirks *self, GError **error)
358
0
{
359
0
  XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_WATCH_BLOB;
360
0
  const gchar *datadir;
361
0
  const gchar *localstatedir = NULL;
362
0
  g_autoptr(GFile) file = NULL;
363
0
  g_autoptr(XbBuilder) builder = NULL;
364
0
  g_autoptr(XbNode) n_any = NULL;
365
366
  /* everything is okay */
367
0
  if (self->silo != NULL && xb_silo_is_valid(self->silo))
368
0
    return TRUE;
369
370
  /* system datadir */
371
0
  builder = xb_builder_new();
372
0
  datadir = fu_context_get_path(self->ctx, FU_PATH_KIND_DATADIR_QUIRKS, NULL);
373
0
  if (datadir != NULL) {
374
0
    if (!fu_quirks_add_quirks_for_path(self, builder, datadir, error))
375
0
      return FALSE;
376
0
  }
377
378
  /* something we can write when using Ostree */
379
0
  localstatedir = fu_context_get_path(self->ctx, FU_PATH_KIND_LOCALSTATEDIR_QUIRKS, NULL);
380
0
  if (localstatedir != NULL) {
381
0
    if (!fu_quirks_add_quirks_for_path(self, builder, localstatedir, error))
382
0
      return FALSE;
383
0
  }
384
385
  /* load silo */
386
0
  if (self->load_flags & FU_QUIRKS_LOAD_FLAG_NO_CACHE) {
387
0
    g_autoptr(GFileIOStream) iostr = NULL;
388
0
    file = g_file_new_tmp(NULL, &iostr, error);
389
0
    if (file == NULL)
390
0
      return FALSE;
391
0
  } else {
392
0
    g_autofree gchar *xmlbfn = NULL;
393
0
    xmlbfn = fu_context_build_filename(self->ctx,
394
0
               error,
395
0
               FU_PATH_KIND_CACHEDIR_PKG,
396
0
               "quirks.xmlb",
397
0
               NULL);
398
0
    if (xmlbfn == NULL)
399
0
      return FALSE;
400
0
    file = g_file_new_for_path(xmlbfn);
401
0
  }
402
0
  if (g_getenv("FWUPD_XMLB_VERBOSE") != NULL) {
403
0
    xb_builder_set_profile_flags(builder,
404
0
               XB_SILO_PROFILE_FLAG_XPATH |
405
0
             XB_SILO_PROFILE_FLAG_DEBUG);
406
0
  }
407
0
  if (self->load_flags & FU_QUIRKS_LOAD_FLAG_READONLY_FS)
408
0
    compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID;
409
0
  self->silo = xb_builder_ensure(builder, file, compile_flags, NULL, error);
410
0
  if (self->silo == NULL)
411
0
    return FALSE;
412
413
  /* dump warnings to console, just once */
414
0
  if (self->invalid_keys->len > 0) {
415
0
    g_autofree gchar *str = NULL;
416
0
    g_ptr_array_sort(self->invalid_keys, fu_quirks_strcasecmp_cb);
417
0
    str = fu_strjoin(",", self->invalid_keys);
418
0
    g_info("invalid key names: %s", str);
419
0
  }
420
421
  /* check if there is any quirk data to load, as older libxmlb versions will not be able to
422
   * create the prepared query with an unknown text ID */
423
0
  n_any = xb_silo_query_first(self->silo, "quirk", NULL);
424
0
  if (n_any == NULL) {
425
0
    g_debug("no quirk data, not creating prepared queries");
426
0
    return TRUE;
427
0
  }
428
429
  /* create prepared queries to save time later */
430
0
  self->query_kv = xb_query_new_full(self->silo,
431
0
             "quirk/device[@id=?]/value[@key=?]",
432
0
             XB_QUERY_FLAG_OPTIMIZE,
433
0
             error);
434
0
  if (self->query_kv == NULL) {
435
0
    g_prefix_error_literal(error, "failed to prepare query: ");
436
0
    return FALSE;
437
0
  }
438
0
  self->query_vs = xb_query_new_full(self->silo,
439
0
             "quirk/device[@id=?]/value",
440
0
             XB_QUERY_FLAG_OPTIMIZE,
441
0
             error);
442
0
  if (self->query_vs == NULL) {
443
0
    g_prefix_error_literal(error, "failed to prepare query: ");
444
0
    return FALSE;
445
0
  }
446
0
  if (!xb_silo_query_build_index(self->silo, "quirk/device", "id", error)) {
447
0
    fwupd_error_convert(error);
448
0
    return FALSE;
449
0
  }
450
0
  if (!xb_silo_query_build_index(self->silo, "quirk/device/value", "key", error)) {
451
0
    fwupd_error_convert(error);
452
0
    return FALSE;
453
0
  }
454
455
  /* success */
456
0
  return TRUE;
457
0
}
458
459
/**
460
 * fu_quirks_lookup_by_id:
461
 * @self: a #FuQuirks
462
 * @guid: GUID to lookup
463
 * @key: an ID to match the entry, e.g. `Name`
464
 *
465
 * Looks up an entry in the hardware database using a string value.
466
 *
467
 * Returns: (transfer none): values from the database, or %NULL if not found
468
 *
469
 * Since: 1.0.1
470
 **/
471
const gchar *
472
fu_quirks_lookup_by_id(FuQuirks *self, const gchar *guid, const gchar *key)
473
0
{
474
0
  g_autoptr(GError) error = NULL;
475
0
  g_autoptr(XbNode) n = NULL;
476
0
  g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT();
477
478
0
  g_return_val_if_fail(FU_IS_QUIRKS(self), NULL);
479
0
  g_return_val_if_fail(self->loaded, NULL);
480
0
  g_return_val_if_fail(guid != NULL, NULL);
481
0
  g_return_val_if_fail(key != NULL, NULL);
482
483
#ifdef HAVE_SQLITE
484
  /* this is generated from usb.ids and other static sources */
485
  if (self->db != NULL && (self->load_flags & FU_QUIRKS_LOAD_FLAG_NO_CACHE) == 0) {
486
    g_autoptr(sqlite3_stmt) stmt = NULL;
487
    if (sqlite3_prepare_v2(self->db,
488
               "SELECT key, value FROM quirks WHERE guid = ?1 "
489
               "AND key = ?2 LIMIT 1",
490
               -1,
491
               &stmt,
492
               NULL) != SQLITE_OK) {
493
      g_warning("failed to prepare SQL: %s", sqlite3_errmsg(self->db));
494
      return NULL;
495
    }
496
    sqlite3_bind_text(stmt, 1, guid, -1, SQLITE_STATIC);
497
    sqlite3_bind_text(stmt, 2, key, -1, SQLITE_STATIC);
498
    if (sqlite3_step(stmt) == SQLITE_ROW) {
499
      const gchar *value = (const gchar *)sqlite3_column_text(stmt, 1);
500
      if (value != NULL)
501
        return g_intern_string(value);
502
    }
503
  }
504
#endif
505
506
  /* ensure up to date */
507
0
  if (!fu_quirks_check_silo(self, &error)) {
508
0
    g_warning("failed to build silo: %s", error->message);
509
0
    return NULL;
510
0
  }
511
512
  /* no quirk data */
513
0
  if (self->query_kv == NULL)
514
0
    return NULL;
515
516
  /* query */
517
0
  xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES);
518
0
  xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL);
519
0
  xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 1, key, NULL);
520
0
  n = xb_silo_query_first_with_context(self->silo, self->query_kv, &context, &error);
521
0
  if (n == NULL) {
522
0
    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
523
0
      return NULL;
524
0
    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
525
0
      return NULL;
526
0
    g_warning("failed to query: %s", error->message);
527
0
    return NULL;
528
0
  }
529
0
  if (self->verbose)
530
0
    g_debug("%s:%s → %s", guid, key, xb_node_get_text(n));
531
0
  return xb_node_get_text(n);
532
0
}
533
534
/**
535
 * fu_quirks_lookup_by_id_iter:
536
 * @self: a #FuQuirks
537
 * @guid: GUID to lookup
538
 * @key: (nullable): an ID to match the entry, e.g. `Name`, or %NULL for all keys
539
 * @iter_cb: (scope call) (closure user_data): a function to call for each result
540
 * @user_data: user data passed to @iter_cb
541
 *
542
 * Looks up all entries in the hardware database using a GUID value.
543
 *
544
 * Returns: %TRUE if the ID was found, and @iter was called
545
 *
546
 * Since: 1.3.3
547
 **/
548
gboolean
549
fu_quirks_lookup_by_id_iter(FuQuirks *self,
550
          const gchar *guid,
551
          const gchar *key,
552
          FuQuirksIter iter_cb,
553
          gpointer user_data)
554
0
{
555
0
  g_autoptr(GError) error = NULL;
556
0
  g_autoptr(GPtrArray) results = NULL;
557
0
  g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT();
558
559
0
  g_return_val_if_fail(FU_IS_QUIRKS(self), FALSE);
560
0
  g_return_val_if_fail(self->loaded, FALSE);
561
0
  g_return_val_if_fail(guid != NULL, FALSE);
562
0
  g_return_val_if_fail(iter_cb != NULL, FALSE);
563
564
#ifdef HAVE_SQLITE
565
  /* this is generated from usb.ids and other static sources */
566
  if (self->db != NULL && (self->load_flags & FU_QUIRKS_LOAD_FLAG_NO_CACHE) == 0) {
567
    g_autoptr(sqlite3_stmt) stmt = NULL;
568
    if (key == NULL) {
569
      if (sqlite3_prepare_v2(self->db,
570
                 "SELECT key, value FROM quirks WHERE guid = ?1",
571
                 -1,
572
                 &stmt,
573
                 NULL) != SQLITE_OK) {
574
        g_warning("failed to prepare SQL: %s", sqlite3_errmsg(self->db));
575
        return FALSE;
576
      }
577
      sqlite3_bind_text(stmt, 1, guid, -1, SQLITE_STATIC);
578
    } else {
579
      if (sqlite3_prepare_v2(self->db,
580
                 "SELECT key, value FROM quirks WHERE guid = ?1 "
581
                 "AND key = ?2",
582
                 -1,
583
                 &stmt,
584
                 NULL) != SQLITE_OK) {
585
        g_warning("failed to prepare SQL: %s", sqlite3_errmsg(self->db));
586
        return FALSE;
587
      }
588
      sqlite3_bind_text(stmt, 1, guid, -1, SQLITE_STATIC);
589
      sqlite3_bind_text(stmt, 2, key, -1, SQLITE_STATIC);
590
    }
591
    while (sqlite3_step(stmt) == SQLITE_ROW) {
592
      const gchar *key_tmp = (const gchar *)sqlite3_column_text(stmt, 0);
593
      const gchar *value = (const gchar *)sqlite3_column_text(stmt, 1);
594
      iter_cb(self, key_tmp, value, FU_CONTEXT_QUIRK_SOURCE_DB, user_data);
595
    }
596
  }
597
#endif
598
599
  /* ensure up to date */
600
0
  if (!fu_quirks_check_silo(self, &error)) {
601
0
    g_warning("failed to build silo: %s", error->message);
602
0
    return FALSE;
603
0
  }
604
605
  /* no quirk data */
606
0
  if (self->query_vs == NULL)
607
0
    return FALSE;
608
609
  /* query */
610
0
  xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES);
611
0
  xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL);
612
0
  if (key != NULL) {
613
0
    xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 1, key, NULL);
614
0
    results = xb_silo_query_with_context(self->silo, self->query_kv, &context, &error);
615
0
  } else {
616
0
    results = xb_silo_query_with_context(self->silo, self->query_vs, &context, &error);
617
0
  }
618
0
  if (results == NULL) {
619
0
    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
620
0
      return FALSE;
621
0
    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
622
0
      return FALSE;
623
0
    g_warning("failed to query: %s", error->message);
624
0
    return FALSE;
625
0
  }
626
0
  for (guint i = 0; i < results->len; i++) {
627
0
    XbNode *n = g_ptr_array_index(results, i);
628
0
    if (self->verbose)
629
0
      g_debug("%s → %s", guid, xb_node_get_text(n));
630
0
    iter_cb(self,
631
0
      xb_node_get_attr(n, "key"),
632
0
      xb_node_get_text(n),
633
0
      FU_CONTEXT_QUIRK_SOURCE_FILE,
634
0
      user_data);
635
0
  }
636
637
0
  return TRUE;
638
0
}
639
640
#ifdef HAVE_SQLITE
641
642
typedef struct {
643
  FuQuirks *self;
644
  sqlite3_stmt *stmt;
645
  const gchar *subsystem;
646
  const gchar *title_vid;
647
  const gchar *title_pid;
648
  GString *vid;
649
} FuQuirksDbHelper;
650
651
static void
652
fu_quirks_db_helper_free(FuQuirksDbHelper *helper)
653
{
654
  if (helper->vid != NULL)
655
    g_string_free(helper->vid, TRUE);
656
  g_free(helper);
657
}
658
659
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuQuirksDbHelper, fu_quirks_db_helper_free)
660
661
static gboolean
662
fu_quirks_db_add_vendor_entry(FuQuirksDbHelper *helper,
663
            const gchar *vid,
664
            const gchar *name,
665
            GError **error)
666
{
667
  FuQuirks *self = FU_QUIRKS(helper->self);
668
  g_autofree gchar *guid = NULL;
669
  g_autofree gchar *instance_id = NULL;
670
  g_autofree gchar *vid_strup = g_ascii_strup(vid, -1);
671
672
  instance_id = g_strdup_printf("%s\\%s_%s", helper->subsystem, helper->title_vid, vid_strup);
673
  guid = fwupd_guid_hash_string(instance_id);
674
  sqlite3_reset(helper->stmt);
675
  sqlite3_bind_text(helper->stmt, 1, guid, -1, SQLITE_STATIC);
676
  sqlite3_bind_text(helper->stmt, 2, FWUPD_RESULT_KEY_VENDOR, -1, SQLITE_STATIC);
677
  sqlite3_bind_text(helper->stmt, 3, name, -1, SQLITE_STATIC);
678
  if (sqlite3_step(helper->stmt) != SQLITE_DONE) {
679
    g_set_error(error,
680
          FWUPD_ERROR,
681
          FWUPD_ERROR_WRITE,
682
          "failed to execute prepared statement: %s",
683
          sqlite3_errmsg(self->db));
684
    return FALSE;
685
  }
686
687
  /* success */
688
  return TRUE;
689
}
690
691
static gboolean
692
fu_quirks_db_add_name_entry(FuQuirksDbHelper *helper,
693
          const gchar *vid,
694
          const gchar *pid,
695
          const gchar *name,
696
          GError **error)
697
{
698
  FuQuirks *self = FU_QUIRKS(helper->self);
699
  g_autofree gchar *guid = NULL;
700
  g_autofree gchar *instance_id = NULL;
701
  g_autofree gchar *vid_strup = g_ascii_strup(vid, -1);
702
  g_autofree gchar *pid_strup = g_ascii_strup(pid, -1);
703
704
  instance_id = g_strdup_printf("%s\\%s_%s&%s_%s",
705
              helper->subsystem,
706
              helper->title_vid,
707
              vid_strup,
708
              helper->title_pid,
709
              pid_strup);
710
  guid = fwupd_guid_hash_string(instance_id);
711
  sqlite3_reset(helper->stmt);
712
  sqlite3_bind_text(helper->stmt, 1, guid, -1, SQLITE_STATIC);
713
  sqlite3_bind_text(helper->stmt, 2, FWUPD_RESULT_KEY_NAME, -1, SQLITE_STATIC);
714
  sqlite3_bind_text(helper->stmt, 3, name, -1, SQLITE_STATIC);
715
  if (sqlite3_step(helper->stmt) != SQLITE_DONE) {
716
    g_set_error(error,
717
          FWUPD_ERROR,
718
          FWUPD_ERROR_WRITE,
719
          "failed to execute prepared statement: %s",
720
          sqlite3_errmsg(self->db));
721
    return FALSE;
722
  }
723
724
  /* success */
725
  return TRUE;
726
}
727
728
static gboolean
729
_g_ascii_isxstrn(const gchar *str, gsize n)
730
{
731
  for (gsize i = 0; i < n; i++) {
732
    if (!g_ascii_isxdigit(str[i]))
733
      return FALSE;
734
  }
735
  return TRUE;
736
}
737
738
static gboolean
739
fu_quirks_db_add_usbids_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
740
{
741
  FuQuirksDbHelper *helper = (FuQuirksDbHelper *)user_data;
742
743
  /* not vendor lines */
744
  if (token->len < 7)
745
    return TRUE;
746
747
  /* ignore the wrong ones */
748
  if (g_strstr_len(token->str, -1, "Wrong ID") != NULL ||
749
      g_strstr_len(token->str, -1, "wrong ID") != NULL)
750
    return TRUE;
751
752
  /* 4 hex digits */
753
  if (_g_ascii_isxstrn(token->str, 4)) {
754
    g_string_set_size(helper->vid, 0);
755
    g_string_append_len(helper->vid, token->str, 4);
756
    return fu_quirks_db_add_vendor_entry(helper,
757
                 helper->vid->str,
758
                 token->str + 6,
759
                 error);
760
  }
761
762
  /* tab, then 4 hex digits */
763
  if (helper->vid->len > 0 && token->str[0] == '\t' && _g_ascii_isxstrn(token->str + 1, 4)) {
764
    g_autofree gchar *pid = g_strndup(token->str + 1, 4);
765
    return fu_quirks_db_add_name_entry(helper,
766
               helper->vid->str,
767
               pid,
768
               token->str + 7,
769
               error);
770
  }
771
772
  /* build into XML */
773
  return TRUE;
774
}
775
776
static gboolean
777
fu_quirks_db_add_ouitxt_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
778
{
779
  FuQuirksDbHelper *helper = (FuQuirksDbHelper *)user_data;
780
  g_autofree gchar *vid = NULL;
781
782
  /* not vendor lines */
783
  if (token->len < 22)
784
    return TRUE;
785
786
  /* not 6 hex digits */
787
  if (!_g_ascii_isxstrn(token->str, 6))
788
    return TRUE;
789
790
  /* build into XML */
791
  vid = g_strndup(token->str, 6);
792
  return fu_quirks_db_add_vendor_entry(helper, vid, token->str + 22, error);
793
}
794
795
static gboolean
796
fu_quirks_db_add_pnpids_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
797
{
798
  FuQuirksDbHelper *helper = (FuQuirksDbHelper *)user_data;
799
  g_autofree gchar *vid = NULL;
800
801
  /* not vendor lines */
802
  if (token->len < 5)
803
    return TRUE;
804
805
  /* ignore the wrong ones */
806
  if (g_strstr_len(token->str, -1, "DO NOT USE") != NULL)
807
    return TRUE;
808
809
  /* build into XML */
810
  vid = g_strndup(token->str, 3);
811
  return fu_quirks_db_add_vendor_entry(helper, vid, token->str + 4, error);
812
}
813
814
typedef struct {
815
  const gchar *fn;
816
  const gchar *subsystem;
817
  const gchar *title_vid;
818
  const gchar *title_pid;
819
  FuStrsplitFunc func;
820
} FuQuirksDbItem;
821
822
static gboolean
823
fu_quirks_db_sqlite3_exec(FuQuirks *self, const gchar *sql, GError **error)
824
{
825
  gint rc;
826
827
  /* if we're running all the tests in parallel it is possible to hit this... */
828
  for (guint i = 0; i < 10; i++) {
829
    rc = sqlite3_exec(self->db, sql, NULL, NULL, NULL);
830
    if (rc != SQLITE_LOCKED)
831
      break;
832
    g_usleep(50 * 1000);
833
  }
834
  if (rc != SQLITE_OK) {
835
    g_set_error(error,
836
          FWUPD_ERROR,
837
          FWUPD_ERROR_INTERNAL,
838
          "failed to run %s: %s",
839
          sql,
840
          sqlite3_errmsg(self->db));
841
    return FALSE;
842
  }
843
  /* success */
844
  return TRUE;
845
}
846
847
static gboolean
848
fu_quirks_db_load(FuQuirks *self, FuQuirksLoadFlags load_flags, GError **error)
849
{
850
  g_autoptr(sqlite3_stmt) stmt_insert = NULL;
851
  g_autoptr(sqlite3_stmt) stmt_query = NULL;
852
  g_autoptr(GString) fn_mtimes = g_string_new("quirks");
853
  g_autofree gchar *guid_fwupd = fwupd_guid_hash_string("fwupd");
854
  const FuQuirksDbItem map[] = {
855
      {"pci.ids", "PCI", "VEN", "DEV", fu_quirks_db_add_usbids_cb},
856
      {"usb.ids", "USB", "VID", "PID", fu_quirks_db_add_usbids_cb},
857
      {"pnp.ids", "PNP", "VID", "PID", fu_quirks_db_add_pnpids_cb},
858
      {"oui.txt", "OUI", "VID", "PID", fu_quirks_db_add_ouitxt_cb},
859
  };
860
861
  /* nothing to do */
862
  if (load_flags & FU_QUIRKS_LOAD_FLAG_NO_CACHE)
863
    return TRUE;
864
865
  /* create tables and indexes */
866
  if (!fu_quirks_db_sqlite3_exec(
867
    self,
868
    "BEGIN TRANSACTION;"
869
    "CREATE TABLE IF NOT EXISTS quirks(guid, key, value);"
870
    "CREATE INDEX IF NOT EXISTS idx_quirks_guid ON quirks(guid);"
871
    "CREATE INDEX IF NOT EXISTS idx_quirks_guid_key ON quirks(guid, key);"
872
    "COMMIT;",
873
    error)) {
874
    return FALSE;
875
  }
876
877
  /* find out the mtimes of each of the files we want to load into the db */
878
  for (guint i = 0; i < G_N_ELEMENTS(map); i++) {
879
    const FuQuirksDbItem *item = &map[i];
880
    guint64 mtime;
881
    g_autofree gchar *fn = NULL;
882
    g_autoptr(GFile) file = NULL;
883
    g_autoptr(GFileInfo) info = NULL;
884
885
    fn = fu_context_build_filename(self->ctx,
886
                 NULL,
887
                 FU_PATH_KIND_DATADIR_VENDOR_IDS,
888
                 item->fn,
889
                 NULL);
890
    if (fn == NULL)
891
      continue;
892
    file = g_file_new_for_path(fn);
893
    if (!g_file_query_exists(file, NULL))
894
      continue;
895
    info = g_file_query_info(file,
896
           G_FILE_ATTRIBUTE_TIME_MODIFIED,
897
           G_FILE_QUERY_INFO_NONE,
898
           NULL,
899
           error);
900
    if (info == NULL)
901
      return FALSE;
902
    mtime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
903
    g_string_append_printf(fn_mtimes, ",%s:%" G_GUINT64_FORMAT, item->fn, mtime);
904
  }
905
906
  /* check if the mtimes match */
907
  if (sqlite3_prepare_v2(self->db,
908
             "SELECT value FROM quirks WHERE guid = ?1 and key = ?2",
909
             -1,
910
             &stmt_query,
911
             NULL) != SQLITE_OK) {
912
    g_set_error(error,
913
          FWUPD_ERROR,
914
          FWUPD_ERROR_INTERNAL,
915
          "failed to prepare SQL: %s",
916
          sqlite3_errmsg(self->db));
917
    return FALSE;
918
  }
919
  sqlite3_bind_text(stmt_query, 1, guid_fwupd, -1, SQLITE_STATIC);
920
  sqlite3_bind_text(stmt_query, 2, FWUPD_RESULT_KEY_VERSION, -1, SQLITE_STATIC);
921
  while (sqlite3_step(stmt_query) == SQLITE_ROW) {
922
    const gchar *fn_mtimes_old = (const gchar *)sqlite3_column_text(stmt_query, 0);
923
    if (g_strcmp0(fn_mtimes->str, fn_mtimes_old) == 0) {
924
      g_debug("mtimes unchanged: %s, doing nothing", fn_mtimes->str);
925
      return TRUE;
926
    }
927
    g_debug("mtimes changed %s vs %s -- regenerating", fn_mtimes_old, fn_mtimes->str);
928
  }
929
930
  /* delete any existing data */
931
  if (!fu_quirks_db_sqlite3_exec(self, "BEGIN TRANSACTION;", error))
932
    return FALSE;
933
  if (!fu_quirks_db_sqlite3_exec(self, "DELETE FROM quirks;", error))
934
    return FALSE;
935
936
  /* prepared statement for speed */
937
  if (sqlite3_prepare_v3(self->db,
938
             "INSERT INTO quirks (guid, key, value) VALUES (?1,?2,?3)",
939
             -1,
940
             SQLITE_PREPARE_PERSISTENT,
941
             &stmt_insert,
942
             NULL) != SQLITE_OK) {
943
    g_set_error(error,
944
          FWUPD_ERROR,
945
          FWUPD_ERROR_INTERNAL,
946
          "failed to prepare SQL to insert history: %s",
947
          sqlite3_errmsg(self->db));
948
    return FALSE;
949
  }
950
951
  /* populate database */
952
  for (guint i = 0; i < G_N_ELEMENTS(map); i++) {
953
    const FuQuirksDbItem *item = &map[i];
954
    g_autofree gchar *fn = NULL;
955
    g_autoptr(FuQuirksDbHelper) helper = g_new0(FuQuirksDbHelper, 1);
956
    g_autoptr(GFile) file = NULL;
957
    g_autoptr(GInputStream) stream = NULL;
958
959
    fn = fu_context_build_filename(self->ctx,
960
                 NULL,
961
                 FU_PATH_KIND_DATADIR_VENDOR_IDS,
962
                 item->fn,
963
                 NULL);
964
    if (fn == NULL)
965
      continue;
966
967
    /* split into lines */
968
    file = g_file_new_for_path(fn);
969
    if (!g_file_query_exists(file, NULL)) {
970
      g_debug("%s not found", fn);
971
      continue;
972
    }
973
    g_debug("indexing vendor IDs from %s", fn);
974
    stream = G_INPUT_STREAM(g_file_read(file, NULL, error));
975
    if (stream == NULL)
976
      return FALSE;
977
    helper->self = self;
978
    helper->subsystem = item->subsystem;
979
    helper->title_vid = item->title_vid;
980
    helper->title_pid = item->title_pid;
981
    helper->stmt = stmt_insert;
982
    helper->vid = g_string_new(NULL);
983
    if (!fu_strsplit_stream(stream, 0x0, "\n", item->func, helper, error))
984
      return FALSE;
985
  }
986
987
  /* set schema */
988
  sqlite3_reset(stmt_insert);
989
  sqlite3_bind_text(stmt_insert, 1, guid_fwupd, -1, SQLITE_STATIC);
990
  sqlite3_bind_text(stmt_insert, 2, FWUPD_RESULT_KEY_VERSION, -1, SQLITE_STATIC);
991
  sqlite3_bind_text(stmt_insert, 3, fn_mtimes->str, -1, SQLITE_STATIC);
992
  if (sqlite3_step(stmt_insert) != SQLITE_DONE) {
993
    g_set_error(error,
994
          FWUPD_ERROR,
995
          FWUPD_ERROR_WRITE,
996
          "failed to execute prepared statement: %s",
997
          sqlite3_errmsg(self->db));
998
    return FALSE;
999
  }
1000
  if (!fu_quirks_db_sqlite3_exec(self, "COMMIT;", error))
1001
    return FALSE;
1002
1003
  /* success */
1004
  return TRUE;
1005
}
1006
#endif
1007
1008
/**
1009
 * fu_quirks_load: (skip)
1010
 * @self: a #FuQuirks
1011
 * @load_flags: load flags
1012
 * @error: (nullable): optional return location for an error
1013
 *
1014
 * Loads the various files that define the hardware quirks used in plugins.
1015
 *
1016
 * Returns: %TRUE for success
1017
 *
1018
 * Since: 1.0.1
1019
 **/
1020
gboolean
1021
fu_quirks_load(FuQuirks *self, FuQuirksLoadFlags load_flags, GError **error)
1022
0
{
1023
0
  g_return_val_if_fail(FU_IS_QUIRKS(self), FALSE);
1024
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1025
1026
0
  self->loaded = TRUE;
1027
0
  self->load_flags = load_flags;
1028
0
  self->verbose = g_getenv("FWUPD_XMLB_VERBOSE") != NULL;
1029
1030
#ifdef HAVE_SQLITE
1031
  if (self->db == NULL && (load_flags & FU_QUIRKS_LOAD_FLAG_NO_CACHE) == 0) {
1032
    g_autofree gchar *quirksdb = NULL;
1033
1034
    quirksdb = fu_context_build_filename(self->ctx,
1035
                 error,
1036
                 FU_PATH_KIND_CACHEDIR_PKG,
1037
                 "quirks.db",
1038
                 NULL);
1039
    if (quirksdb == NULL)
1040
      return FALSE;
1041
    g_debug("open database %s", quirksdb);
1042
    if (!fu_path_mkdir_parent(quirksdb, error))
1043
      return FALSE;
1044
    if (sqlite3_open(quirksdb, &self->db) != SQLITE_OK) {
1045
      g_set_error(error,
1046
            FWUPD_ERROR,
1047
            FWUPD_ERROR_READ,
1048
            "cannot open %s: %s",
1049
            quirksdb,
1050
            sqlite3_errmsg(self->db));
1051
      return FALSE;
1052
    }
1053
    if (!fu_quirks_db_load(self, load_flags, error))
1054
      return FALSE;
1055
  }
1056
#endif
1057
1058
  /* now silo */
1059
0
  return fu_quirks_check_silo(self, error);
1060
0
}
1061
1062
/**
1063
 * fu_quirks_add_possible_key:
1064
 * @self: a #FuQuirks
1065
 * @possible_key: a key name, e.g. `Flags`
1066
 *
1067
 * Adds a possible quirk key. If added by a plugin it should be namespaced
1068
 * using the plugin name, where possible.
1069
 *
1070
 * Since: 1.5.8
1071
 **/
1072
void
1073
fu_quirks_add_possible_key(FuQuirks *self, const gchar *possible_key)
1074
0
{
1075
0
  g_return_if_fail(FU_IS_QUIRKS(self));
1076
0
  g_return_if_fail(possible_key != NULL);
1077
0
  g_hash_table_add(self->possible_keys, g_strdup(possible_key));
1078
0
}
1079
1080
static void
1081
fu_quirks_housekeeping_cb(FuContext *ctx, FuQuirks *self)
1082
0
{
1083
#ifdef HAVE_SQLITE
1084
  sqlite3_release_memory(G_MAXINT32);
1085
  if (self->db != NULL)
1086
    sqlite3_db_release_memory(self->db);
1087
#endif
1088
0
}
1089
1090
static void
1091
fu_quirks_dispose(GObject *object)
1092
0
{
1093
0
  FuQuirks *self = FU_QUIRKS(object);
1094
0
  if (self->ctx != NULL)
1095
0
    g_signal_handlers_disconnect_by_data(self->ctx, self);
1096
0
  g_clear_object(&self->ctx);
1097
0
  G_OBJECT_CLASS(fu_quirks_parent_class)->dispose(object);
1098
0
}
1099
1100
static void
1101
fu_quirks_class_init(FuQuirksClass *klass)
1102
0
{
1103
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
1104
0
  object_class->dispose = fu_quirks_dispose;
1105
0
  object_class->finalize = fu_quirks_finalize;
1106
0
}
1107
1108
static void
1109
fu_quirks_init(FuQuirks *self)
1110
0
{
1111
0
  self->possible_keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1112
0
  self->invalid_keys = g_ptr_array_new_with_free_func(g_free);
1113
1114
  /* built in */
1115
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_BRANCH);
1116
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CHILDREN);
1117
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_COUNTERPART_GUID);
1118
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_SIZE);
1119
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_SIZE_MAX);
1120
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_SIZE_MIN);
1121
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_FLAGS);
1122
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_GTYPE);
1123
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_GTYPE);
1124
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_GUID);
1125
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_ICON);
1126
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_INHIBIT);
1127
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_INSTALL_DURATION);
1128
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_ISSUE);
1129
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_NAME);
1130
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_PARENT_GUID);
1131
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_PLUGIN);
1132
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_PRIORITY);
1133
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_PROTOCOL);
1134
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_PROXY_GUID);
1135
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_BATTERY_THRESHOLD);
1136
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_REMOVE_DELAY);
1137
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_SUMMARY);
1138
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_UPDATE_IMAGE);
1139
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_UPDATE_MESSAGE);
1140
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_VENDOR);
1141
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_VENDOR_ID);
1142
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_VERSION);
1143
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_VERSION_FORMAT);
1144
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_ID);
1145
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_ID_SZ);
1146
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_CHIP_ERASE);
1147
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_BLOCK_ERASE);
1148
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_SECTOR_ERASE);
1149
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_WRITE_STATUS);
1150
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_PAGE_PROG);
1151
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_DATA);
1152
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_STATUS);
1153
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_WRITE_EN);
1154
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_PAGE_SIZE);
1155
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_SECTOR_SIZE);
1156
0
  fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_BLOCK_SIZE);
1157
0
}
1158
1159
static void
1160
fu_quirks_finalize(GObject *obj)
1161
0
{
1162
0
  FuQuirks *self = FU_QUIRKS(obj);
1163
0
  if (self->query_kv != NULL)
1164
0
    g_object_unref(self->query_kv);
1165
0
  if (self->query_vs != NULL)
1166
0
    g_object_unref(self->query_vs);
1167
0
  if (self->silo != NULL)
1168
0
    g_object_unref(self->silo);
1169
#ifdef HAVE_SQLITE
1170
  if (self->db != NULL)
1171
    sqlite3_close(self->db);
1172
#endif
1173
0
  g_hash_table_unref(self->possible_keys);
1174
0
  g_ptr_array_unref(self->invalid_keys);
1175
0
  G_OBJECT_CLASS(fu_quirks_parent_class)->finalize(obj);
1176
0
}
1177
1178
/**
1179
 * fu_quirks_new: (skip)
1180
 *
1181
 * Creates a new quirks object.
1182
 *
1183
 * Returns: a new #FuQuirks
1184
 *
1185
 * Since: 1.0.1
1186
 **/
1187
FuQuirks *
1188
fu_quirks_new(FuContext *ctx)
1189
0
{
1190
0
  FuQuirks *self;
1191
0
  self = g_object_new(FU_TYPE_QUIRKS, NULL);
1192
0
  self->ctx = g_object_ref(ctx);
1193
  g_signal_connect(self->ctx, "housekeeping", G_CALLBACK(fu_quirks_housekeeping_cb), self);
1194
0
  return FU_QUIRKS(self);
1195
0
}