mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-03 04:47:56 +00:00
557 lines
17 KiB
C
557 lines
17 KiB
C
/*
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "FuQuirks"
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib-object.h>
|
|
#include <gio/gio.h>
|
|
#include <string.h>
|
|
#include <xmlb.h>
|
|
|
|
#include "fu-common.h"
|
|
#include "fu-mutex.h"
|
|
#include "fu-quirks.h"
|
|
|
|
#include "fwupd-common.h"
|
|
#include "fwupd-error.h"
|
|
#include "fwupd-remote-private.h"
|
|
|
|
/**
|
|
* FuQuirks:
|
|
*
|
|
* Quirks can be used to modify device behavior.
|
|
* When fwupd is installed in long-term support distros it's very hard to
|
|
* backport new versions as new hardware is released.
|
|
*
|
|
* There are several reasons why we can't just include the mapping and quirk
|
|
* information in the AppStream metadata:
|
|
*
|
|
* * The extra data is hugely specific to the installed fwupd plugin versions
|
|
* * The device-id is per-device, and the mapping is usually per-plugin
|
|
* * Often the information is needed before the FuDevice is created
|
|
* * There are security implications in allowing plugins to handle new devices
|
|
*
|
|
* The idea with quirks is that the end user can drop an additional (or replace
|
|
* an existing) file in a .d director with a simple format and the hardware will
|
|
* magically start working. This assumes no new quirks are required, as this would
|
|
* obviously need code changes, but allows us to get most existing devices working
|
|
* in an easy way without the user compiling anything.
|
|
*
|
|
* See also: [class@FuDevice], [class@FuPlugin]
|
|
*/
|
|
|
|
static void fu_quirks_finalize (GObject *obj);
|
|
|
|
struct _FuQuirks
|
|
{
|
|
GObject parent_instance;
|
|
FuQuirksLoadFlags load_flags;
|
|
GHashTable *possible_keys;
|
|
GPtrArray *invalid_keys;
|
|
XbSilo *silo;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuQuirks, fu_quirks, G_TYPE_OBJECT)
|
|
|
|
static gchar *
|
|
fu_quirks_build_group_key (const gchar *group)
|
|
{
|
|
const gchar *guid_prefixes[] = { "DeviceInstanceId=", "Guid=", "HwId=", NULL };
|
|
|
|
/* this is a GUID */
|
|
for (guint i = 0; guid_prefixes[i] != NULL; i++) {
|
|
if (g_str_has_prefix (group, guid_prefixes[i])) {
|
|
gsize len = strlen (guid_prefixes[i]);
|
|
g_warning ("using %s for %s in quirk files is deprecated!",
|
|
guid_prefixes[i], group);
|
|
if (fwupd_guid_is_valid (group + len))
|
|
return g_strdup (group + len);
|
|
return fwupd_guid_hash_string (group + len);
|
|
}
|
|
}
|
|
|
|
/* fallback */
|
|
if (fwupd_guid_is_valid (group))
|
|
return g_strdup (group);
|
|
return fwupd_guid_hash_string (group);
|
|
}
|
|
|
|
static GInputStream *
|
|
fu_quirks_convert_quirk_to_xml_cb (XbBuilderSource *source,
|
|
XbBuilderSourceCtx *ctx,
|
|
gpointer user_data,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FuQuirks *self = FU_QUIRKS (user_data);
|
|
g_autofree gchar *xml = NULL;
|
|
g_auto(GStrv) groups = NULL;
|
|
g_autoptr(GBytes) bytes = NULL;
|
|
g_autoptr(GKeyFile) kf = g_key_file_new ();
|
|
g_autoptr(XbBuilderNode) root = xb_builder_node_new ("quirk");
|
|
|
|
/* parse keyfile */
|
|
bytes = xb_builder_source_ctx_get_bytes (ctx, cancellable, error);
|
|
if (bytes == NULL)
|
|
return NULL;
|
|
if (!g_key_file_load_from_data (kf,
|
|
g_bytes_get_data (bytes, NULL),
|
|
g_bytes_get_size (bytes),
|
|
G_KEY_FILE_NONE,
|
|
error))
|
|
return NULL;
|
|
|
|
/* add each set of groups and keys */
|
|
groups = g_key_file_get_groups (kf, NULL);
|
|
for (guint i = 0; groups[i] != NULL; i++) {
|
|
g_auto(GStrv) keys = NULL;
|
|
g_autofree gchar *group_id = NULL;
|
|
g_autoptr(XbBuilderNode) bn = NULL;
|
|
|
|
/* sanity check group */
|
|
if (g_str_has_prefix (groups[i], "HwID") ||
|
|
g_str_has_prefix (groups[i], "DeviceInstanceID") ||
|
|
g_str_has_prefix (groups[i], "GUID")) {
|
|
g_warning ("invalid group name '%s'", groups[i]);
|
|
continue;
|
|
}
|
|
|
|
/* get all KVs for the entry */
|
|
keys = g_key_file_get_keys (kf, groups[i], NULL, error);
|
|
if (keys == NULL)
|
|
return NULL;
|
|
group_id = fu_quirks_build_group_key (groups[i]);
|
|
bn = xb_builder_node_insert (root, "device", "id", group_id, NULL);
|
|
for (guint j = 0; keys[j] != NULL; j++) {
|
|
g_autofree gchar *value = NULL;
|
|
|
|
/* sanity check key */
|
|
if ((self->load_flags & FU_QUIRKS_LOAD_FLAG_NO_VERIFY) == 0 &&
|
|
g_hash_table_lookup (self->possible_keys, keys[j]) == NULL) {
|
|
if (!g_ptr_array_find_with_equal_func (self->invalid_keys,
|
|
keys[j],
|
|
g_str_equal,
|
|
NULL)) {
|
|
g_ptr_array_add (self->invalid_keys,
|
|
g_strdup (keys[j]));
|
|
}
|
|
}
|
|
value = g_key_file_get_value (kf, groups[i], keys[j], error);
|
|
if (value == NULL)
|
|
return NULL;
|
|
xb_builder_node_insert_text (bn,
|
|
"value", value,
|
|
"key", keys[j],
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/* export as XML */
|
|
xml = xb_builder_node_export (root, XB_NODE_EXPORT_FLAG_ADD_HEADER, error);
|
|
if (xml == NULL)
|
|
return NULL;
|
|
return g_memory_input_stream_new_from_data (g_steal_pointer (&xml), -1, g_free);
|
|
}
|
|
|
|
static gint
|
|
fu_quirks_filename_sort_cb (gconstpointer a, gconstpointer b)
|
|
{
|
|
const gchar *stra = *((const gchar **) a);
|
|
const gchar *strb = *((const gchar **) b);
|
|
return g_strcmp0 (stra, strb);
|
|
}
|
|
|
|
static gboolean
|
|
fu_quirks_add_quirks_for_path (FuQuirks *self, XbBuilder *builder,
|
|
const gchar *path, GError **error)
|
|
{
|
|
const gchar *tmp;
|
|
g_autofree gchar *path_hw = NULL;
|
|
g_autoptr(GDir) dir = NULL;
|
|
g_autoptr(GPtrArray) filenames = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
/* add valid files to the array */
|
|
path_hw = g_build_filename (path, "quirks.d", NULL);
|
|
if (!g_file_test (path_hw, G_FILE_TEST_EXISTS))
|
|
return TRUE;
|
|
dir = g_dir_open (path_hw, 0, error);
|
|
if (dir == NULL)
|
|
return FALSE;
|
|
while ((tmp = g_dir_read_name (dir)) != NULL) {
|
|
if (!g_str_has_suffix (tmp, ".quirk")) {
|
|
g_debug ("skipping invalid file %s", tmp);
|
|
continue;
|
|
}
|
|
g_ptr_array_add (filenames, g_build_filename (path_hw, tmp, NULL));
|
|
}
|
|
|
|
/* sort */
|
|
g_ptr_array_sort (filenames, fu_quirks_filename_sort_cb);
|
|
|
|
/* process files */
|
|
for (guint i = 0; i < filenames->len; i++) {
|
|
const gchar *filename = g_ptr_array_index (filenames, i);
|
|
g_autoptr(GFile) file = g_file_new_for_path (filename);
|
|
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
|
|
|
|
/* load from keyfile */
|
|
#if LIBXMLB_CHECK_VERSION(0,1,15)
|
|
xb_builder_source_add_simple_adapter (source, "text/plain,.quirk",
|
|
fu_quirks_convert_quirk_to_xml_cb,
|
|
self, NULL);
|
|
#else
|
|
xb_builder_source_add_adapter (source, "text/plain,.quirk",
|
|
fu_quirks_convert_quirk_to_xml_cb,
|
|
self, NULL);
|
|
#endif
|
|
if (!xb_builder_source_load_file (source, file,
|
|
XB_BUILDER_SOURCE_FLAG_WATCH_FILE |
|
|
XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to load %s: ", filename);
|
|
return FALSE;
|
|
}
|
|
|
|
/* watch the file for changes */
|
|
xb_builder_import_source (builder, source);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
fu_quirks_strcasecmp_cb (gconstpointer a, gconstpointer b)
|
|
{
|
|
const gchar *entry1 = *((const gchar **) a);
|
|
const gchar *entry2 = *((const gchar **) b);
|
|
return g_ascii_strcasecmp (entry1, entry2);
|
|
}
|
|
|
|
static gboolean
|
|
fu_quirks_check_silo (FuQuirks *self, GError **error)
|
|
{
|
|
XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_WATCH_BLOB;
|
|
g_autofree gchar *datadir = NULL;
|
|
g_autofree gchar *localstatedir = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
g_autoptr(XbBuilder) builder = NULL;
|
|
|
|
/* everything is okay */
|
|
if (self->silo != NULL && xb_silo_is_valid (self->silo))
|
|
return TRUE;
|
|
|
|
/* system datadir */
|
|
builder = xb_builder_new ();
|
|
datadir = fu_common_get_path (FU_PATH_KIND_DATADIR_PKG);
|
|
if (!fu_quirks_add_quirks_for_path (self, builder, datadir, error))
|
|
return FALSE;
|
|
|
|
/* something we can write when using Ostree */
|
|
localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
|
|
if (!fu_quirks_add_quirks_for_path (self, builder, localstatedir, error))
|
|
return FALSE;
|
|
|
|
/* load silo */
|
|
if (self->load_flags & FU_QUIRKS_LOAD_FLAG_NO_CACHE) {
|
|
g_autoptr(GFileIOStream) iostr = NULL;
|
|
file = g_file_new_tmp (NULL, &iostr, error);
|
|
if (file == NULL)
|
|
return FALSE;
|
|
} else {
|
|
g_autofree gchar *cachedirpkg = fu_common_get_path (FU_PATH_KIND_CACHEDIR_PKG);
|
|
g_autofree gchar *xmlbfn = g_build_filename (cachedirpkg, "quirks.xmlb", NULL);
|
|
file = g_file_new_for_path (xmlbfn);
|
|
}
|
|
if (g_getenv ("FWUPD_XMLB_VERBOSE") != NULL) {
|
|
xb_builder_set_profile_flags (builder,
|
|
XB_SILO_PROFILE_FLAG_XPATH |
|
|
XB_SILO_PROFILE_FLAG_DEBUG);
|
|
}
|
|
if (self->load_flags & FU_QUIRKS_LOAD_FLAG_READONLY_FS)
|
|
compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID;
|
|
self->silo = xb_builder_ensure (builder, file, compile_flags, NULL, error);
|
|
if (self->silo == NULL)
|
|
return FALSE;
|
|
|
|
/* dump warnings to console, just once */
|
|
if (self->invalid_keys->len > 0) {
|
|
g_autofree gchar *str = NULL;
|
|
g_ptr_array_sort (self->invalid_keys, fu_quirks_strcasecmp_cb);
|
|
str = fu_common_strjoin_array (",", self->invalid_keys);
|
|
g_debug ("invalid key names: %s", str);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_quirks_lookup_by_id:
|
|
* @self: a #FuQuirks
|
|
* @group: a string group, e.g. `DeviceInstanceId=USB\VID_1235&PID_AB11`
|
|
* @key: an ID to match the entry, e.g. `Name`
|
|
*
|
|
* Looks up an entry in the hardware database using a string value.
|
|
*
|
|
* Returns: (transfer none): values from the database, or %NULL if not found
|
|
*
|
|
* Since: 1.0.1
|
|
**/
|
|
const gchar *
|
|
fu_quirks_lookup_by_id (FuQuirks *self, const gchar *group, const gchar *key)
|
|
{
|
|
g_autofree gchar *group_key = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(XbNode) n = NULL;
|
|
g_autoptr(XbQuery) query = NULL;
|
|
#if LIBXMLB_CHECK_VERSION(0,3,0)
|
|
g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT ();
|
|
#endif
|
|
|
|
g_return_val_if_fail (FU_IS_QUIRKS (self), NULL);
|
|
g_return_val_if_fail (group != NULL, NULL);
|
|
g_return_val_if_fail (key != NULL, NULL);
|
|
|
|
/* ensure up to date */
|
|
if (!fu_quirks_check_silo (self, &error)) {
|
|
g_warning ("failed to build silo: %s", error->message);
|
|
return NULL;
|
|
}
|
|
|
|
/* query */
|
|
group_key = fu_quirks_build_group_key (group);
|
|
query = xb_query_new_full (self->silo,
|
|
"quirk/device[@id=?]/value[@key=?]",
|
|
XB_QUERY_FLAG_NONE,
|
|
&error);
|
|
if (query == NULL) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
return NULL;
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
|
|
return NULL;
|
|
g_warning ("failed to build query: %s", error->message);
|
|
return NULL;
|
|
}
|
|
|
|
#if LIBXMLB_CHECK_VERSION(0,3,0)
|
|
xb_value_bindings_bind_str (xb_query_context_get_bindings (&context), 0, group_key, NULL);
|
|
xb_value_bindings_bind_str (xb_query_context_get_bindings (&context), 1, key, NULL);
|
|
n = xb_silo_query_first_with_context (self->silo, query, &context, &error);
|
|
#else
|
|
if (!xb_query_bind_str (query, 0, group_key, &error)) {
|
|
g_warning ("failed to bind 0: %s", error->message);
|
|
return NULL;
|
|
}
|
|
if (!xb_query_bind_str (query, 1, key, &error)) {
|
|
g_warning ("failed to bind 1: %s", error->message);
|
|
return NULL;
|
|
}
|
|
n = xb_silo_query_first_full (self->silo, query, &error);
|
|
#endif
|
|
|
|
if (n == NULL) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
return NULL;
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
|
|
return NULL;
|
|
g_warning ("failed to query: %s", error->message);
|
|
return NULL;
|
|
}
|
|
return xb_node_get_text (n);
|
|
}
|
|
|
|
/**
|
|
* fu_quirks_lookup_by_id_iter:
|
|
* @self: a #FuQuirks
|
|
* @group: string of group to lookup
|
|
* @iter_cb: (scope async): a function to call for each result
|
|
* @user_data: user data passed to @iter_cb
|
|
*
|
|
* Looks up all entries in the hardware database using a GUID value.
|
|
*
|
|
* Returns: %TRUE if the ID was found, and @iter was called
|
|
*
|
|
* Since: 1.3.3
|
|
**/
|
|
gboolean
|
|
fu_quirks_lookup_by_id_iter (FuQuirks *self, const gchar *group,
|
|
FuQuirksIter iter_cb, gpointer user_data)
|
|
{
|
|
g_autofree gchar *group_key = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(GPtrArray) results = NULL;
|
|
g_autoptr(XbQuery) query = NULL;
|
|
#if LIBXMLB_CHECK_VERSION(0,3,0)
|
|
g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT ();
|
|
#endif
|
|
|
|
g_return_val_if_fail (FU_IS_QUIRKS (self), FALSE);
|
|
g_return_val_if_fail (group != NULL, FALSE);
|
|
g_return_val_if_fail (iter_cb != NULL, FALSE);
|
|
|
|
/* ensure up to date */
|
|
if (!fu_quirks_check_silo (self, &error)) {
|
|
g_warning ("failed to build silo: %s", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* query */
|
|
group_key = fu_quirks_build_group_key (group);
|
|
query = xb_query_new_full (self->silo,
|
|
"quirk/device[@id=?]/value",
|
|
XB_QUERY_FLAG_NONE,
|
|
&error);
|
|
if (query == NULL) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
return FALSE;
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
|
|
return FALSE;
|
|
g_warning ("failed to build query: %s", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
#if LIBXMLB_CHECK_VERSION(0,3,0)
|
|
xb_value_bindings_bind_str (xb_query_context_get_bindings (&context), 0, group_key, NULL);
|
|
results = xb_silo_query_with_context (self->silo, query, &context, &error);
|
|
#else
|
|
if (!xb_query_bind_str (query, 0, group_key, &error)) {
|
|
g_warning ("failed to bind 0: %s", error->message);
|
|
return FALSE;
|
|
}
|
|
results = xb_silo_query_full (self->silo, query, &error);
|
|
#endif
|
|
|
|
if (results == NULL) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
return FALSE;
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
|
|
return FALSE;
|
|
g_warning ("failed to query: %s", error->message);
|
|
return FALSE;
|
|
}
|
|
for (guint i = 0; i < results->len; i++) {
|
|
XbNode *n = g_ptr_array_index (results, i);
|
|
iter_cb (self,
|
|
xb_node_get_attr (n, "key"),
|
|
xb_node_get_text (n),
|
|
user_data);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_quirks_load: (skip)
|
|
* @self: a #FuQuirks
|
|
* @load_flags: load flags
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Loads the various files that define the hardware quirks used in plugins.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 1.0.1
|
|
**/
|
|
gboolean
|
|
fu_quirks_load (FuQuirks *self, FuQuirksLoadFlags load_flags, GError **error)
|
|
{
|
|
g_return_val_if_fail (FU_IS_QUIRKS (self), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
self->load_flags = load_flags;
|
|
return fu_quirks_check_silo (self, error);
|
|
}
|
|
|
|
/**
|
|
* fu_quirks_add_possible_key:
|
|
* @self: a #FuQuirks
|
|
* @possible_key: a key name, e.g. `Flags`
|
|
*
|
|
* Adds a possible quirk key. If added by a plugin it should be namespaced
|
|
* using the plugin name, where possible.
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
void
|
|
fu_quirks_add_possible_key (FuQuirks *self, const gchar *possible_key)
|
|
{
|
|
g_return_if_fail (FU_IS_QUIRKS (self));
|
|
g_return_if_fail (possible_key != NULL);
|
|
g_hash_table_add (self->possible_keys, g_strdup (possible_key));
|
|
}
|
|
|
|
static void
|
|
fu_quirks_class_init (FuQuirksClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = fu_quirks_finalize;
|
|
}
|
|
|
|
static void
|
|
fu_quirks_init (FuQuirks *self)
|
|
{
|
|
self->possible_keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
self->invalid_keys = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
/* built in */
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_BRANCH);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_CHILDREN);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_COUNTERPART_GUID);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_FIRMWARE_SIZE);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_FIRMWARE_SIZE_MAX);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_FIRMWARE_SIZE_MIN);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_FLAGS);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_GTYPE);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_GUID);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_ICON);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_INHIBIT);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_INSTALL_DURATION);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_NAME);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_PARENT_GUID);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_PLUGIN);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_PRIORITY);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_PROTOCOL);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_PROXY_GUID);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_BATTERY_THRESHOLD);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_REMOVE_DELAY);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_SUMMARY);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_UPDATE_IMAGE);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_UPDATE_MESSAGE);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_VENDOR);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_VENDOR_ID);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_VERSION);
|
|
fu_quirks_add_possible_key (self, FU_QUIRKS_VERSION_FORMAT);
|
|
}
|
|
|
|
static void
|
|
fu_quirks_finalize (GObject *obj)
|
|
{
|
|
FuQuirks *self = FU_QUIRKS (obj);
|
|
if (self->silo != NULL)
|
|
g_object_unref (self->silo);
|
|
g_hash_table_unref (self->possible_keys);
|
|
g_ptr_array_unref (self->invalid_keys);
|
|
G_OBJECT_CLASS (fu_quirks_parent_class)->finalize (obj);
|
|
}
|
|
|
|
/**
|
|
* fu_quirks_new: (skip)
|
|
*
|
|
* Creates a new quirks object.
|
|
*
|
|
* Returns: a new #FuQuirks
|
|
*
|
|
* Since: 1.0.1
|
|
**/
|
|
FuQuirks *
|
|
fu_quirks_new (void)
|
|
{
|
|
FuQuirks *self;
|
|
self = g_object_new (FU_TYPE_QUIRKS, NULL);
|
|
return FU_QUIRKS (self);
|
|
}
|