/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 | } |