Coverage Report

Created: 2025-08-24 07:10

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