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