mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-15 10:29:47 +00:00
Move the database of supported devices out into runtime loaded files
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. This allows us to fix issues like https://github.com/hughsie/fwupd/issues/265
This commit is contained in:
parent
78c1e6c009
commit
9c028f06b5
@ -208,6 +208,8 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg
|
|||||||
%{_unitdir}/fwupd.service
|
%{_unitdir}/fwupd.service
|
||||||
%{_unitdir}/system-update.target.wants/
|
%{_unitdir}/system-update.target.wants/
|
||||||
%dir %{_localstatedir}/lib/fwupd
|
%dir %{_localstatedir}/lib/fwupd
|
||||||
|
%dir %{_datadir}/fwupd/quirks.d
|
||||||
|
%{_datadir}/fwupd/quirks.d/*.quirk
|
||||||
%{_localstatedir}/lib/fwupd/builder/README.md
|
%{_localstatedir}/lib/fwupd/builder/README.md
|
||||||
%{_libdir}/libfwupd*.so.*
|
%{_libdir}/libfwupd*.so.*
|
||||||
%{_libdir}/girepository-1.0/Fwupd-2.0.typelib
|
%{_libdir}/girepository-1.0/Fwupd-2.0.typelib
|
||||||
|
12
data/tests/quirks.d/tests.quirk
Normal file
12
data/tests/quirks.d/tests.quirk
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[fwupd-plugin-test]
|
||||||
|
|
||||||
|
USB\VID_0A5C&PID_6412=ignore-runtime
|
||||||
|
|
||||||
|
# this is an empty key
|
||||||
|
USB\VID_FFFF&PID_FFFF=
|
||||||
|
|
||||||
|
# this is a key with a space
|
||||||
|
ACME Inc.=awesome
|
||||||
|
|
||||||
|
# this is a wildcard
|
||||||
|
CORP*=town
|
@ -45,6 +45,7 @@
|
|||||||
<xi:include href="xml/fu-device.xml"/>
|
<xi:include href="xml/fu-device.xml"/>
|
||||||
<xi:include href="xml/fu-device-locker.xml"/>
|
<xi:include href="xml/fu-device-locker.xml"/>
|
||||||
<xi:include href="xml/fu-common.xml"/>
|
<xi:include href="xml/fu-common.xml"/>
|
||||||
|
<xi:include href="xml/fu-quirks.xml"/>
|
||||||
<xi:include href="xml/fu-device-metadata.xml"/>
|
<xi:include href="xml/fu-device-metadata.xml"/>
|
||||||
</reference>
|
</reference>
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
fwupd_client_get_type
|
fwupd_client_get_type
|
||||||
fwupd_device_get_type
|
fwupd_device_get_type
|
||||||
|
fwupd_quirks_get_type
|
||||||
fwupd_release_get_type
|
fwupd_release_get_type
|
||||||
fwupd_remote_get_type
|
fwupd_remote_get_type
|
||||||
fwupd_result_get_type
|
fwupd_result_get_type
|
||||||
|
@ -75,6 +75,7 @@ struct _FuEngine
|
|||||||
GPtrArray *supported_guids;
|
GPtrArray *supported_guids;
|
||||||
FuSmbios *smbios;
|
FuSmbios *smbios;
|
||||||
FuHwids *hwids;
|
FuHwids *hwids;
|
||||||
|
FuQuirks *quirks;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -2662,6 +2663,7 @@ fu_engine_load_plugins (FuEngine *self, GError **error)
|
|||||||
fu_plugin_set_hwids (plugin, self->hwids);
|
fu_plugin_set_hwids (plugin, self->hwids);
|
||||||
fu_plugin_set_smbios (plugin, self->smbios);
|
fu_plugin_set_smbios (plugin, self->smbios);
|
||||||
fu_plugin_set_supported (plugin, self->supported_guids);
|
fu_plugin_set_supported (plugin, self->supported_guids);
|
||||||
|
fu_plugin_set_quirks (plugin, self->quirks);
|
||||||
g_debug ("adding plugin %s", filename);
|
g_debug ("adding plugin %s", filename);
|
||||||
if (!fu_plugin_open (plugin, filename, &error_local)) {
|
if (!fu_plugin_open (plugin, filename, &error_local)) {
|
||||||
g_warning ("failed to open plugin %s: %s",
|
g_warning ("failed to open plugin %s: %s",
|
||||||
@ -2867,6 +2869,7 @@ fu_engine_cleanup_state (GError **error)
|
|||||||
gboolean
|
gboolean
|
||||||
fu_engine_load (FuEngine *self, GError **error)
|
fu_engine_load (FuEngine *self, GError **error)
|
||||||
{
|
{
|
||||||
|
g_autoptr(GError) error_quirks = NULL;
|
||||||
g_autoptr(GError) error_hwids = NULL;
|
g_autoptr(GError) error_hwids = NULL;
|
||||||
g_autoptr(GError) error_smbios = NULL;
|
g_autoptr(GError) error_smbios = NULL;
|
||||||
|
|
||||||
@ -2879,6 +2882,10 @@ fu_engine_load (FuEngine *self, GError **error)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* load the quirk files */
|
||||||
|
if (!fu_quirks_load (self->quirks, &error_quirks))
|
||||||
|
g_warning ("Failed to load quirks: %s", error_quirks->message);
|
||||||
|
|
||||||
/* load AppStream metadata */
|
/* load AppStream metadata */
|
||||||
as_store_add_filter (self->store, AS_APP_KIND_FIRMWARE);
|
as_store_add_filter (self->store, AS_APP_KIND_FIRMWARE);
|
||||||
if (!fu_engine_load_metadata_store (self, error)) {
|
if (!fu_engine_load_metadata_store (self, error)) {
|
||||||
@ -2967,6 +2974,7 @@ fu_engine_init (FuEngine *self)
|
|||||||
self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_engine_item_free);
|
self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_engine_item_free);
|
||||||
self->smbios = fu_smbios_new ();
|
self->smbios = fu_smbios_new ();
|
||||||
self->hwids = fu_hwids_new ();
|
self->hwids = fu_hwids_new ();
|
||||||
|
self->quirks = fu_quirks_new ();
|
||||||
self->pending = fu_pending_new ();
|
self->pending = fu_pending_new ();
|
||||||
self->profile = as_profile_new ();
|
self->profile = as_profile_new ();
|
||||||
self->store = as_store_new ();
|
self->store = as_store_new ();
|
||||||
@ -2989,6 +2997,7 @@ fu_engine_finalize (GObject *obj)
|
|||||||
g_hash_table_unref (self->plugins_hash);
|
g_hash_table_unref (self->plugins_hash);
|
||||||
g_object_unref (self->config);
|
g_object_unref (self->config);
|
||||||
g_object_unref (self->smbios);
|
g_object_unref (self->smbios);
|
||||||
|
g_object_unref (self->quirks);
|
||||||
g_object_unref (self->hwids);
|
g_object_unref (self->hwids);
|
||||||
g_object_unref (self->pending);
|
g_object_unref (self->pending);
|
||||||
g_object_unref (self->profile);
|
g_object_unref (self->profile);
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#ifndef __FU_PLUGIN_PRIVATE_H
|
#ifndef __FU_PLUGIN_PRIVATE_H
|
||||||
#define __FU_PLUGIN_PRIVATE_H
|
#define __FU_PLUGIN_PRIVATE_H
|
||||||
|
|
||||||
|
#include "fu-quirks.h"
|
||||||
#include "fu-plugin.h"
|
#include "fu-plugin.h"
|
||||||
#include "fu-smbios.h"
|
#include "fu-smbios.h"
|
||||||
|
|
||||||
@ -37,6 +38,8 @@ void fu_plugin_set_hwids (FuPlugin *plugin,
|
|||||||
FuHwids *hwids);
|
FuHwids *hwids);
|
||||||
void fu_plugin_set_supported (FuPlugin *plugin,
|
void fu_plugin_set_supported (FuPlugin *plugin,
|
||||||
GPtrArray *supported_guids);
|
GPtrArray *supported_guids);
|
||||||
|
void fu_plugin_set_quirks (FuPlugin *plugin,
|
||||||
|
FuQuirks *quirks);
|
||||||
void fu_plugin_set_smbios (FuPlugin *plugin,
|
void fu_plugin_set_smbios (FuPlugin *plugin,
|
||||||
FuSmbios *smbios);
|
FuSmbios *smbios);
|
||||||
guint fu_plugin_get_order (FuPlugin *plugin);
|
guint fu_plugin_get_order (FuPlugin *plugin);
|
||||||
|
@ -56,6 +56,7 @@ typedef struct {
|
|||||||
GPtrArray *rules[FU_PLUGIN_RULE_LAST];
|
GPtrArray *rules[FU_PLUGIN_RULE_LAST];
|
||||||
gchar *name;
|
gchar *name;
|
||||||
FuHwids *hwids;
|
FuHwids *hwids;
|
||||||
|
FuQuirks *quirks;
|
||||||
GPtrArray *supported_guids;
|
GPtrArray *supported_guids;
|
||||||
FuSmbios *smbios;
|
FuSmbios *smbios;
|
||||||
GHashTable *devices; /* platform_id:GObject */
|
GHashTable *devices; /* platform_id:GObject */
|
||||||
@ -657,6 +658,78 @@ fu_plugin_set_supported (FuPlugin *plugin, GPtrArray *supported_guids)
|
|||||||
priv->supported_guids = g_ptr_array_ref (supported_guids);
|
priv->supported_guids = g_ptr_array_ref (supported_guids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fu_plugin_set_quirks (FuPlugin *plugin, FuQuirks *quirks)
|
||||||
|
{
|
||||||
|
FuPluginPrivate *priv = GET_PRIVATE (plugin);
|
||||||
|
g_set_object (&priv->quirks, quirks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_plugin_get_quirks:
|
||||||
|
* @plugin: A #FuPlugin
|
||||||
|
*
|
||||||
|
* Returns the hardware database object. This can be used to discover device
|
||||||
|
* quirks or other device-specific settings.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none): a #FuQuirks, or %NULL if not set
|
||||||
|
*
|
||||||
|
* Since: 1.0.1
|
||||||
|
**/
|
||||||
|
FuQuirks *
|
||||||
|
fu_plugin_get_quirks (FuPlugin *plugin)
|
||||||
|
{
|
||||||
|
FuPluginPrivate *priv = GET_PRIVATE (plugin);
|
||||||
|
return priv->quirks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_plugin_lookup_quirk_by_id:
|
||||||
|
* @plugin: A #FuPlugin
|
||||||
|
* @prefix: A string prefix that matches the quirks file basename, e.g. "dfu-quirks"
|
||||||
|
* @id: An ID to match the entry, e.g. "012345"
|
||||||
|
*
|
||||||
|
* 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_plugin_lookup_quirk_by_id (FuPlugin *plugin, const gchar *prefix, const gchar *id)
|
||||||
|
{
|
||||||
|
FuPluginPrivate *priv = GET_PRIVATE (plugin);
|
||||||
|
g_return_val_if_fail (FU_IS_PLUGIN (plugin), NULL);
|
||||||
|
|
||||||
|
/* wildcard */
|
||||||
|
if (g_strstr_len (id, -1, "*") != NULL)
|
||||||
|
return fu_quirks_lookup_by_glob (priv->quirks, prefix, id);
|
||||||
|
|
||||||
|
/* exact ID */
|
||||||
|
return fu_quirks_lookup_by_id (priv->quirks, prefix, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_plugin_lookup_quirk_by_usb_device:
|
||||||
|
* @plugin: A #FuPlugin
|
||||||
|
* @prefix: A string prefix that matches the quirks file basename, e.g. "dfu-quirks"
|
||||||
|
* @dev: A #GUsbDevice
|
||||||
|
*
|
||||||
|
* Looks up an entry in the hardware database using various keys generated
|
||||||
|
* from @dev.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none): values from the database, or %NULL if not found
|
||||||
|
*
|
||||||
|
* Since: 1.0.1
|
||||||
|
**/
|
||||||
|
const gchar *
|
||||||
|
fu_plugin_lookup_quirk_by_usb_device (FuPlugin *plugin, const gchar *prefix, GUsbDevice *dev)
|
||||||
|
{
|
||||||
|
FuPluginPrivate *priv = GET_PRIVATE (plugin);
|
||||||
|
g_return_val_if_fail (FU_IS_PLUGIN (plugin), NULL);
|
||||||
|
return fu_quirks_lookup_by_usb_device (priv->quirks, prefix, dev);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fu_plugin_get_supported:
|
* fu_plugin_get_supported:
|
||||||
* @plugin: A #FuPlugin
|
* @plugin: A #FuPlugin
|
||||||
@ -1386,6 +1459,8 @@ fu_plugin_finalize (GObject *object)
|
|||||||
g_object_unref (priv->usb_ctx);
|
g_object_unref (priv->usb_ctx);
|
||||||
if (priv->hwids != NULL)
|
if (priv->hwids != NULL)
|
||||||
g_object_unref (priv->hwids);
|
g_object_unref (priv->hwids);
|
||||||
|
if (priv->quirks != NULL)
|
||||||
|
g_object_unref (priv->quirks);
|
||||||
if (priv->supported_guids != NULL)
|
if (priv->supported_guids != NULL)
|
||||||
g_ptr_array_unref (priv->supported_guids);
|
g_ptr_array_unref (priv->supported_guids);
|
||||||
if (priv->smbios != NULL)
|
if (priv->smbios != NULL)
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "fu-common.h"
|
#include "fu-common.h"
|
||||||
#include "fu-device.h"
|
#include "fu-device.h"
|
||||||
#include "fu-device-locker.h"
|
#include "fu-device-locker.h"
|
||||||
|
#include "fu-quirks.h"
|
||||||
#include "fu-hwids.h"
|
#include "fu-hwids.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
@ -135,6 +136,13 @@ GBytes *fu_plugin_get_smbios_data (FuPlugin *plugin,
|
|||||||
void fu_plugin_add_rule (FuPlugin *plugin,
|
void fu_plugin_add_rule (FuPlugin *plugin,
|
||||||
FuPluginRule rule,
|
FuPluginRule rule,
|
||||||
const gchar *name);
|
const gchar *name);
|
||||||
|
FuQuirks *fu_plugin_get_quirks (FuPlugin *plugin);
|
||||||
|
const gchar *fu_plugin_lookup_quirk_by_id (FuPlugin *plugin,
|
||||||
|
const gchar *prefix,
|
||||||
|
const gchar *id);
|
||||||
|
const gchar *fu_plugin_lookup_quirk_by_usb_device (FuPlugin *plugin,
|
||||||
|
const gchar *prefix,
|
||||||
|
GUsbDevice *dev);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
369
src/fu-quirks.c
Normal file
369
src/fu-quirks.c
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License Version 2
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <fnmatch.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "fu-quirks.h"
|
||||||
|
|
||||||
|
#include "fwupd-error.h"
|
||||||
|
#include "fwupd-remote-private.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fu-quirks
|
||||||
|
* @short_description: device quirks
|
||||||
|
*
|
||||||
|
* Quirks can be used to modify device behaviour.
|
||||||
|
* 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: #FuDevice, #FuPlugin
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void fu_quirks_finalize (GObject *obj);
|
||||||
|
|
||||||
|
struct _FuQuirks
|
||||||
|
{
|
||||||
|
GObject parent_instance;
|
||||||
|
GPtrArray *monitors;
|
||||||
|
GHashTable *hash; /* of prefix/id:string */
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (FuQuirks, fu_quirks, G_TYPE_OBJECT)
|
||||||
|
|
||||||
|
static void
|
||||||
|
fu_quirks_monitor_changed_cb (GFileMonitor *monitor,
|
||||||
|
GFile *file,
|
||||||
|
GFile *other_file,
|
||||||
|
GFileMonitorEvent event_type,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
FuQuirks *self = FU_QUIRKS (user_data);
|
||||||
|
g_autoptr(GError) error = NULL;
|
||||||
|
g_autofree gchar *filename = g_file_get_path (file);
|
||||||
|
g_debug ("%s changed, reloading all configs", filename);
|
||||||
|
if (!fu_quirks_load (self, &error))
|
||||||
|
g_warning ("failed to rescan quirks: %s", error->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fu_quirks_add_inotify (FuQuirks *self, const gchar *filename, GError **error)
|
||||||
|
{
|
||||||
|
GFileMonitor *monitor;
|
||||||
|
g_autoptr(GFile) file = g_file_new_for_path (filename);
|
||||||
|
|
||||||
|
/* set up a notify watch */
|
||||||
|
monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, error);
|
||||||
|
if (monitor == NULL)
|
||||||
|
return FALSE;
|
||||||
|
g_signal_connect (monitor, "changed",
|
||||||
|
G_CALLBACK (fu_quirks_monitor_changed_cb), self);
|
||||||
|
g_ptr_array_add (self->monitors, monitor);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_quirks_lookup_by_id:
|
||||||
|
* @self: A #FuPlugin
|
||||||
|
* @prefix: A string prefix that matches the quirks file basename, e.g. "dfu-quirks"
|
||||||
|
* @id: An ID to match the entry, e.g. "012345"
|
||||||
|
*
|
||||||
|
* 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 *prefix, const gchar *id)
|
||||||
|
{
|
||||||
|
g_autofree gchar *key = NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FU_IS_QUIRKS (self), NULL);
|
||||||
|
g_return_val_if_fail (prefix != NULL, NULL);
|
||||||
|
g_return_val_if_fail (id != NULL, NULL);
|
||||||
|
|
||||||
|
key = g_strdup_printf ("%s/%s", prefix, id);
|
||||||
|
return g_hash_table_lookup (self->hash, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_quirks_lookup_by_glob:
|
||||||
|
* @self: A #FuPlugin
|
||||||
|
* @prefix: A string prefix that matches the quirks file basename, e.g. "dfu-quirks"
|
||||||
|
* @glob: An glob to match the entry, e.g. "foo*bar?baz"
|
||||||
|
*
|
||||||
|
* Looks up an entry in the hardware database using a key glob.
|
||||||
|
* NOTE: This is *much* slower than using fu_quirks_lookup_by_id() as each key
|
||||||
|
* in the quirk database is compared.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none): values from the database, or %NULL if not found
|
||||||
|
*
|
||||||
|
* Since: 1.0.1
|
||||||
|
**/
|
||||||
|
const gchar *
|
||||||
|
fu_quirks_lookup_by_glob (FuQuirks *self, const gchar *prefix, const gchar *glob)
|
||||||
|
{
|
||||||
|
g_autoptr(GList) keys = NULL;
|
||||||
|
gsize prefix_len;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FU_IS_QUIRKS (self), NULL);
|
||||||
|
g_return_val_if_fail (prefix != NULL, NULL);
|
||||||
|
g_return_val_if_fail (glob != NULL, NULL);
|
||||||
|
|
||||||
|
prefix_len = strlen (prefix);
|
||||||
|
keys = g_hash_table_get_keys (self->hash);
|
||||||
|
for (GList *l = keys; l != NULL; l = l->next) {
|
||||||
|
const gchar *id = l->data;
|
||||||
|
if (strncmp (id, prefix, prefix_len) != 0)
|
||||||
|
continue;
|
||||||
|
id += prefix_len + 1;
|
||||||
|
if (fnmatch (glob, id, 0) == 0)
|
||||||
|
return fu_quirks_lookup_by_id (self, prefix, id);
|
||||||
|
if (fnmatch (id, glob, 0) == 0)
|
||||||
|
return fu_quirks_lookup_by_id (self, prefix, id);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_quirks_lookup_by_usb_device:
|
||||||
|
* @self: A #FuPlugin
|
||||||
|
* @prefix: A string prefix that matches the quirks file basename, e.g. "dfu-quirks"
|
||||||
|
* @dev: A #GUsbDevice
|
||||||
|
*
|
||||||
|
* Looks up an entry in the hardware database using various keys generated
|
||||||
|
* from @dev.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none): values from the database, or %NULL if not found
|
||||||
|
*
|
||||||
|
* Since: 1.0.1
|
||||||
|
**/
|
||||||
|
const gchar *
|
||||||
|
fu_quirks_lookup_by_usb_device (FuQuirks *self, const gchar *prefix, GUsbDevice *dev)
|
||||||
|
{
|
||||||
|
const gchar *tmp;
|
||||||
|
g_autofree gchar *key1 = NULL;
|
||||||
|
g_autofree gchar *key2 = NULL;
|
||||||
|
g_autofree gchar *key3 = NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FU_IS_QUIRKS (self), NULL);
|
||||||
|
g_return_val_if_fail (prefix != NULL, NULL);
|
||||||
|
g_return_val_if_fail (G_USB_IS_DEVICE (dev), NULL);
|
||||||
|
|
||||||
|
/* prefer an exact match, VID:PID:REV */
|
||||||
|
key1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&REV_%04X",
|
||||||
|
g_usb_device_get_vid (dev),
|
||||||
|
g_usb_device_get_pid (dev),
|
||||||
|
g_usb_device_get_release (dev));
|
||||||
|
tmp = fu_quirks_lookup_by_id (self, prefix, key1);
|
||||||
|
if (tmp != NULL)
|
||||||
|
return tmp;
|
||||||
|
|
||||||
|
/* VID:PID */
|
||||||
|
key2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
||||||
|
g_usb_device_get_vid (dev),
|
||||||
|
g_usb_device_get_pid (dev));
|
||||||
|
tmp = fu_quirks_lookup_by_id (self, prefix, key2);
|
||||||
|
if (tmp != NULL)
|
||||||
|
return tmp;
|
||||||
|
|
||||||
|
/* VID */
|
||||||
|
key3 = g_strdup_printf ("USB\\VID_%04X", g_usb_device_get_vid (dev));
|
||||||
|
return fu_quirks_lookup_by_id (self, prefix, key3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fu_quirks_add_quirks_from_filename (FuQuirks *self, const gchar *filename, GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(GKeyFile) kf = g_key_file_new ();
|
||||||
|
g_auto(GStrv) groups = NULL;
|
||||||
|
|
||||||
|
/* load keyfile */
|
||||||
|
if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_NONE, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
keys = g_key_file_get_keys (kf, groups[i], NULL, error);
|
||||||
|
if (keys == NULL)
|
||||||
|
return FALSE;
|
||||||
|
for (guint j = 0; keys[j] != NULL; j++) {
|
||||||
|
g_autofree gchar *tmp = NULL;
|
||||||
|
tmp = g_key_file_get_string (kf, groups[i], keys[j], error);
|
||||||
|
if (tmp == NULL)
|
||||||
|
return FALSE;
|
||||||
|
g_hash_table_insert (self->hash,
|
||||||
|
g_strdup_printf ("%s/%s", groups[i], keys[j]),
|
||||||
|
g_steal_pointer (&tmp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_debug ("now %u quirk entries", g_hash_table_size (self->hash));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 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)) {
|
||||||
|
g_debug ("no %s, skipping", path_hw);
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* load from keyfile */
|
||||||
|
g_debug ("loading quirks from %s", filename);
|
||||||
|
if (!fu_quirks_add_quirks_from_filename (self, filename, error)) {
|
||||||
|
g_prefix_error (error, "failed to load %s: ", filename);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* watch the file for changes */
|
||||||
|
if (!fu_quirks_add_inotify (self, filename, error))
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* success */
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_quirks_load: (skip)
|
||||||
|
* @self: A #FuQuirks
|
||||||
|
* @error: A #GError, or %NULL
|
||||||
|
*
|
||||||
|
* 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, GError **error)
|
||||||
|
{
|
||||||
|
g_autofree gchar *localstate_fwupd = NULL;
|
||||||
|
g_return_val_if_fail (FU_IS_QUIRKS (self), FALSE);
|
||||||
|
|
||||||
|
/* ensure empty in case we're called from a monitor change */
|
||||||
|
g_ptr_array_set_size (self->monitors, 0);
|
||||||
|
g_hash_table_remove_all (self->hash);
|
||||||
|
|
||||||
|
/* system datadir */
|
||||||
|
if (!fu_quirks_add_quirks_for_path (self, FWUPDDATADIR, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* something we can write when using Ostree */
|
||||||
|
localstate_fwupd = g_build_filename (LOCALSTATEDIR, "lib", "fwupd", NULL);
|
||||||
|
if (!fu_quirks_add_quirks_for_path (self, localstate_fwupd, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* success */
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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->monitors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
||||||
|
self->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fu_quirks_finalize (GObject *obj)
|
||||||
|
{
|
||||||
|
FuQuirks *self = FU_QUIRKS (obj);
|
||||||
|
g_ptr_array_unref (self->monitors);
|
||||||
|
g_hash_table_unref (self->hash);
|
||||||
|
G_OBJECT_CLASS (fu_quirks_parent_class)->finalize (obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fu_quirks_new: (skip)
|
||||||
|
*
|
||||||
|
* Creates a new quirks object.
|
||||||
|
*
|
||||||
|
* Return value: a new #FuQuirks
|
||||||
|
**/
|
||||||
|
FuQuirks *
|
||||||
|
fu_quirks_new (void)
|
||||||
|
{
|
||||||
|
FuQuirks *self;
|
||||||
|
self = g_object_new (FU_TYPE_QUIRKS, NULL);
|
||||||
|
return FU_QUIRKS (self);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||||
*
|
*
|
||||||
* Copyright (C) 2016 Mario Limonciello <mario_limonciello@dell.com>
|
* Copyright (C) 2016 Mario Limonciello <mario_limonciello@dell.com>
|
||||||
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
||||||
*
|
*
|
||||||
* Licensed under the GNU General Public License Version 2
|
* Licensed under the GNU General Public License Version 2
|
||||||
*
|
*
|
||||||
@ -22,6 +23,30 @@
|
|||||||
#ifndef __FU_QUIRKS_H
|
#ifndef __FU_QUIRKS_H
|
||||||
#define __FU_QUIRKS_H
|
#define __FU_QUIRKS_H
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <gusb.h>
|
||||||
|
|
||||||
|
#define FU_TYPE_QUIRKS (fu_quirks_get_type ())
|
||||||
|
G_DECLARE_FINAL_TYPE (FuQuirks, fu_quirks, FU, QUIRKS, GObject)
|
||||||
|
|
||||||
|
FuQuirks *fu_quirks_new (void);
|
||||||
|
gboolean fu_quirks_load (FuQuirks *self,
|
||||||
|
GError **error);
|
||||||
|
const gchar *fu_quirks_lookup_by_id (FuQuirks *self,
|
||||||
|
const gchar *prefix,
|
||||||
|
const gchar *id);
|
||||||
|
const gchar *fu_quirks_lookup_by_glob (FuQuirks *self,
|
||||||
|
const gchar *prefix,
|
||||||
|
const gchar *glob);
|
||||||
|
const gchar *fu_quirks_lookup_by_usb_device (FuQuirks *self,
|
||||||
|
const gchar *prefix,
|
||||||
|
GUsbDevice *dev);
|
||||||
|
|
||||||
|
#include <appstream-glib.h>
|
||||||
|
|
||||||
|
/* FIXME: port to above */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const gchar *sys_vendor;
|
const gchar *sys_vendor;
|
||||||
const gchar *identifier;
|
const gchar *identifier;
|
||||||
@ -35,5 +60,6 @@ static const FuVendorQuirks quirk_table[] = {
|
|||||||
{ NULL, NULL, AS_VERSION_PARSE_FLAG_NONE }
|
{ NULL, NULL, AS_VERSION_PARSE_FLAG_NONE }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* __FU_QUIRKS_H */
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif /* __FU_QUIRKS_H */
|
||||||
|
@ -29,8 +29,10 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "fu-config.h"
|
||||||
#include "fu-device-private.h"
|
#include "fu-device-private.h"
|
||||||
#include "fu-engine.h"
|
#include "fu-engine.h"
|
||||||
|
#include "fu-quirks.h"
|
||||||
#include "fu-keyring.h"
|
#include "fu-keyring.h"
|
||||||
#include "fu-pending.h"
|
#include "fu-pending.h"
|
||||||
#include "fu-plugin-private.h"
|
#include "fu-plugin-private.h"
|
||||||
@ -413,6 +415,45 @@ _plugin_device_register_cb (FuPlugin *plugin, FuDevice *device, gpointer user_da
|
|||||||
fu_plugin_runner_device_register (plugin, device);
|
fu_plugin_runner_device_register (plugin, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fu_plugin_quirks_func (void)
|
||||||
|
{
|
||||||
|
const gchar *tmp;
|
||||||
|
gboolean ret;
|
||||||
|
g_autoptr(FuQuirks) quirks = fu_quirks_new ();
|
||||||
|
g_autoptr(FuPlugin) plugin = fu_plugin_new ();
|
||||||
|
g_autoptr(GError) error = NULL;
|
||||||
|
|
||||||
|
ret = fu_quirks_load (quirks, &error);
|
||||||
|
g_assert_no_error (error);
|
||||||
|
g_assert (ret);
|
||||||
|
fu_plugin_set_quirks (plugin, quirks);
|
||||||
|
|
||||||
|
/* exact */
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-plugin-test", "USB\\VID_0A5C&PID_6412");
|
||||||
|
g_assert_cmpstr (tmp, ==, "ignore-runtime");
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-plugin-test", "ACME Inc.");
|
||||||
|
g_assert_cmpstr (tmp, ==, "awesome");
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-plugin-test", "CORP*");
|
||||||
|
g_assert_cmpstr (tmp, ==, "town");
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-plugin-test", "USB\\VID_FFFF&PID_FFFF");
|
||||||
|
g_assert_cmpstr (tmp, ==, "");
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-Unfound", "baz");
|
||||||
|
g_assert_cmpstr (tmp, ==, NULL);
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-tests", "unfound");
|
||||||
|
g_assert_cmpstr (tmp, ==, NULL);
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-unfound", "unfound");
|
||||||
|
g_assert_cmpstr (tmp, ==, NULL);
|
||||||
|
|
||||||
|
/* glob */
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-plugin-test", "ACME*");
|
||||||
|
g_assert_cmpstr (tmp, ==, "awesome");
|
||||||
|
tmp = fu_quirks_lookup_by_glob (quirks, "fwupd-plugin-test", "CORPORATION");
|
||||||
|
g_assert_cmpstr (tmp, ==, "town");
|
||||||
|
tmp = fu_plugin_lookup_quirk_by_id (plugin, "fwupd-plugin-test", "unfound*");
|
||||||
|
g_assert_cmpstr (tmp, ==, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
fu_plugin_module_func (void)
|
fu_plugin_module_func (void)
|
||||||
{
|
{
|
||||||
@ -927,6 +968,7 @@ main (int argc, char **argv)
|
|||||||
g_test_add_func ("/fwupd/pending", fu_pending_func);
|
g_test_add_func ("/fwupd/pending", fu_pending_func);
|
||||||
g_test_add_func ("/fwupd/plugin{delay}", fu_plugin_delay_func);
|
g_test_add_func ("/fwupd/plugin{delay}", fu_plugin_delay_func);
|
||||||
g_test_add_func ("/fwupd/plugin{module}", fu_plugin_module_func);
|
g_test_add_func ("/fwupd/plugin{module}", fu_plugin_module_func);
|
||||||
|
g_test_add_func ("/fwupd/plugin{quirks}", fu_plugin_quirks_func);
|
||||||
g_test_add_func ("/fwupd/keyring{gpg}", fu_keyring_gpg_func);
|
g_test_add_func ("/fwupd/keyring{gpg}", fu_keyring_gpg_func);
|
||||||
g_test_add_func ("/fwupd/keyring{pkcs7}", fu_keyring_pkcs7_func);
|
g_test_add_func ("/fwupd/keyring{pkcs7}", fu_keyring_pkcs7_func);
|
||||||
g_test_add_func ("/fwupd/common{spawn)", fu_common_spawn_func);
|
g_test_add_func ("/fwupd/common{spawn)", fu_common_spawn_func);
|
||||||
|
@ -28,6 +28,7 @@ libfwupdprivate = static_library(
|
|||||||
'fu-pending.c',
|
'fu-pending.c',
|
||||||
'fu-plugin.c',
|
'fu-plugin.c',
|
||||||
'fu-progressbar.c',
|
'fu-progressbar.c',
|
||||||
|
'fu-quirks.c',
|
||||||
'fu-smbios.c',
|
'fu-smbios.c',
|
||||||
'fu-test.c',
|
'fu-test.c',
|
||||||
],
|
],
|
||||||
@ -50,6 +51,7 @@ libfwupdprivate = static_library(
|
|||||||
cargs,
|
cargs,
|
||||||
'-DLOCALSTATEDIR="' + localstatedir + '"',
|
'-DLOCALSTATEDIR="' + localstatedir + '"',
|
||||||
'-DSYSFSFIRMWAREDIR="/sys/firmware"',
|
'-DSYSFSFIRMWAREDIR="/sys/firmware"',
|
||||||
|
'-DFWUPDDATADIR="' + join_paths(get_option('prefix'), get_option('datadir'), 'fwupd') + '"',
|
||||||
'-DFU_OFFLINE_DESTDIR=""',
|
'-DFU_OFFLINE_DESTDIR=""',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -129,6 +131,7 @@ executable(
|
|||||||
'fu-keyring.c',
|
'fu-keyring.c',
|
||||||
'fu-pending.c',
|
'fu-pending.c',
|
||||||
'fu-plugin.c',
|
'fu-plugin.c',
|
||||||
|
'fu-quirks.c',
|
||||||
'fu-smbios.c',
|
'fu-smbios.c',
|
||||||
],
|
],
|
||||||
include_directories : [
|
include_directories : [
|
||||||
@ -155,6 +158,7 @@ executable(
|
|||||||
'-DPLUGINDIR="' + plugin_dir + '"',
|
'-DPLUGINDIR="' + plugin_dir + '"',
|
||||||
'-DSYSFSFIRMWAREDIR="/sys/firmware"',
|
'-DSYSFSFIRMWAREDIR="/sys/firmware"',
|
||||||
'-DSYSCONFDIR="' + default_sysconfdir + '"',
|
'-DSYSCONFDIR="' + default_sysconfdir + '"',
|
||||||
|
'-DFWUPDDATADIR="' + join_paths(get_option('prefix'), get_option('datadir'), 'fwupd') + '"',
|
||||||
'-DFWUPDCONFIGDIR="' + join_paths(default_sysconfdir, 'fwupd') + '"',
|
'-DFWUPDCONFIGDIR="' + join_paths(default_sysconfdir, 'fwupd') + '"',
|
||||||
'-DFU_OFFLINE_DESTDIR=""',
|
'-DFU_OFFLINE_DESTDIR=""',
|
||||||
],
|
],
|
||||||
@ -187,6 +191,7 @@ if get_option('enable-tests')
|
|||||||
'fu-keyring-result.c',
|
'fu-keyring-result.c',
|
||||||
'fu-plugin.c',
|
'fu-plugin.c',
|
||||||
'fu-progressbar.c',
|
'fu-progressbar.c',
|
||||||
|
'fu-quirks.c',
|
||||||
'fu-smbios.c',
|
'fu-smbios.c',
|
||||||
'fu-test.c',
|
'fu-test.c',
|
||||||
],
|
],
|
||||||
@ -220,6 +225,7 @@ if get_option('enable-tests')
|
|||||||
'-DFU_OFFLINE_DESTDIR="/tmp/fwupd-self-test"',
|
'-DFU_OFFLINE_DESTDIR="/tmp/fwupd-self-test"',
|
||||||
'-DPLUGINDIR="' + testdatadir_src + '"',
|
'-DPLUGINDIR="' + testdatadir_src + '"',
|
||||||
'-DSYSFSFIRMWAREDIR="' + testdatadir_src + '"',
|
'-DSYSFSFIRMWAREDIR="' + testdatadir_src + '"',
|
||||||
|
'-DFWUPDDATADIR="' + testdatadir_src + '"',
|
||||||
'-DSYSCONFDIR="' + testdatadir_src + '"',
|
'-DSYSCONFDIR="' + testdatadir_src + '"',
|
||||||
'-DFWUPDCONFIGDIR="' + testdatadir_src + '"',
|
'-DFWUPDCONFIGDIR="' + testdatadir_src + '"',
|
||||||
],
|
],
|
||||||
@ -239,6 +245,8 @@ if get_option('enable-introspection')
|
|||||||
'fu-device-locker.h',
|
'fu-device-locker.h',
|
||||||
'fu-plugin.c',
|
'fu-plugin.c',
|
||||||
'fu-plugin.h',
|
'fu-plugin.h',
|
||||||
|
'fu-quirks.c',
|
||||||
|
'fu-quirks.h',
|
||||||
],
|
],
|
||||||
nsversion : '1.0',
|
nsversion : '1.0',
|
||||||
namespace : 'Fu',
|
namespace : 'Fu',
|
||||||
|
Loading…
Reference in New Issue
Block a user