blob: ebd3d9b58cfe447937d21cfd40f108e24f2b129a [file] [log] [blame]
/*
* Copyright 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#define G_LOG_DOMAIN "FuPluginList"
#include "config.h"
#include "fu-plugin-list.h"
#include "fu-plugin-private.h"
/**
* FuPluginList:
*
* This list of plugins provides a way to get the specific plugin quickly using
* a hash table and also any plugin-list specific functionality such as
* sorting by dependency order.
*
* See also: [class@FuPlugin]
*/
static void
fu_plugin_list_finalize(GObject *obj);
struct _FuPluginList {
GObject parent_instance;
GPtrArray *plugins; /* of FuPlugin */
GHashTable *plugins_hash; /* of name : FuPlugin */
};
G_DEFINE_TYPE(FuPluginList, fu_plugin_list, G_TYPE_OBJECT)
/**
* fu_plugin_list_get_all:
* @self: a #FuPluginList
*
* Gets all the plugins that have been added.
*
* Returns: (transfer none) (element-type FuPlugin): the plugins
*
* Since: 1.0.2
**/
GPtrArray *
fu_plugin_list_get_all(FuPluginList *self)
{
g_return_val_if_fail(FU_IS_PLUGIN_LIST(self), NULL);
return self->plugins;
}
static void
fu_plugin_list_rules_changed_cb(FuPlugin *plugin, gpointer user_data)
{
FuPluginList *self = FU_PLUGIN_LIST(user_data);
GPtrArray *rules = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_CONFLICTS);
if (rules == NULL)
return;
for (guint j = 0; j < rules->len; j++) {
const gchar *plugin_name = g_ptr_array_index(rules, j);
FuPlugin *dep = fu_plugin_list_find_by_name(self, plugin_name, NULL);
if (dep == NULL)
continue;
if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED))
continue;
g_info("late disabling %s as conflicts with %s",
fu_plugin_get_name(dep),
fu_plugin_get_name(plugin));
fu_plugin_add_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED);
}
}
/**
* fu_plugin_list_add:
* @self: a #FuPluginList
* @plugin: a plugin
*
* Adds a plugin to the list. The plugin name is used for a hash key and must
* be set before calling this function.
*
* Since: 1.0.2
**/
void
fu_plugin_list_add(FuPluginList *self, FuPlugin *plugin)
{
g_return_if_fail(FU_IS_PLUGIN_LIST(self));
g_return_if_fail(FU_IS_PLUGIN(plugin));
g_return_if_fail(fu_plugin_get_name(plugin) != NULL);
g_ptr_array_add(self->plugins, g_object_ref(plugin));
g_hash_table_insert(self->plugins_hash,
g_strdup(fu_plugin_get_name(plugin)),
g_object_ref(plugin));
g_signal_connect(FU_PLUGIN(plugin),
"rules-changed",
G_CALLBACK(fu_plugin_list_rules_changed_cb),
self);
}
/**
* fu_plugin_list_remove_all:
* @self: a #FuPluginList
*
* Removed all the plugins from the list.
*
* Since: 2.0.0
**/
void
fu_plugin_list_remove_all(FuPluginList *self)
{
g_return_if_fail(FU_IS_PLUGIN_LIST(self));
for (guint i = 0; i < self->plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index(self->plugins, i);
g_signal_handlers_disconnect_by_data(plugin, self);
}
g_ptr_array_set_size(self->plugins, 0);
g_hash_table_remove_all(self->plugins_hash);
}
/**
* fu_plugin_list_find_by_name:
* @self: a #FuPluginList
* @name: a plugin name, e.g. `dfu`
* @error: (nullable): optional return location for an error
*
* Finds a specific plugin using the plugin name.
*
* Returns: (transfer none): a plugin, or %NULL
*
* Since: 1.0.2
**/
FuPlugin *
fu_plugin_list_find_by_name(FuPluginList *self, const gchar *name, GError **error)
{
FuPlugin *plugin;
g_return_val_if_fail(FU_IS_PLUGIN_LIST(self), NULL);
g_return_val_if_fail(name != NULL, NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
/* sanity check */
if (self->plugins->len == 0) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no plugins loaded");
return NULL;
}
plugin = g_hash_table_lookup(self->plugins_hash, name);
if (plugin == NULL) {
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no plugin %s found", name);
return NULL;
}
return plugin;
}
static gint
fu_plugin_list_sort_cb(gconstpointer a, gconstpointer b)
{
FuPlugin **pa = (FuPlugin **)a;
FuPlugin **pb = (FuPlugin **)b;
return fu_plugin_order_compare(*pa, *pb);
}
/**
* fu_plugin_list_depsolve:
* @self: a #FuPluginList
* @error: (nullable): optional return location for an error
*
* Depsolves the list of plugins into the correct order. Some plugin methods
* are called on all plugins and for some situations the order they are called
* may be important. Use fu_plugin_add_rule() to affect the depsolved order
* if required.
*
* Returns: %TRUE for success, or %FALSE if the set could not be depsolved
*
* Since: 1.0.2
**/
gboolean
fu_plugin_list_depsolve(FuPluginList *self, GError **error)
{
FuPlugin *dep;
GPtrArray *deps;
gboolean changes;
guint dep_loop_check = 0;
g_return_val_if_fail(FU_IS_PLUGIN_LIST(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* order by deps */
do {
changes = FALSE;
for (guint i = 0; i < self->plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index(self->plugins, i);
deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_RUN_AFTER);
if (deps == NULL)
continue;
for (guint j = 0; j < deps->len && !changes; j++) {
const gchar *plugin_name = g_ptr_array_index(deps, j);
dep = fu_plugin_list_find_by_name(self, plugin_name, NULL);
if (dep == NULL) {
g_info("cannot find plugin '%s' "
"requested by '%s'",
plugin_name,
fu_plugin_get_name(plugin));
continue;
}
if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED))
continue;
if (fu_plugin_get_order(plugin) <= fu_plugin_get_order(dep)) {
g_debug("%s [%u] to be ordered after %s [%u] "
"so promoting to [%u]",
fu_plugin_get_name(plugin),
fu_plugin_get_order(plugin),
fu_plugin_get_name(dep),
fu_plugin_get_order(dep),
fu_plugin_get_order(dep) + 1);
fu_plugin_set_order(plugin, fu_plugin_get_order(dep) + 1);
changes = TRUE;
}
}
}
for (guint i = 0; i < self->plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index(self->plugins, i);
deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_RUN_BEFORE);
if (deps == NULL)
continue;
for (guint j = 0; j < deps->len && !changes; j++) {
const gchar *plugin_name = g_ptr_array_index(deps, j);
dep = fu_plugin_list_find_by_name(self, plugin_name, NULL);
if (dep == NULL) {
g_info("cannot find plugin '%s' "
"requested by '%s'",
plugin_name,
fu_plugin_get_name(plugin));
continue;
}
if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED))
continue;
if (fu_plugin_get_order(plugin) >= fu_plugin_get_order(dep)) {
g_debug("%s [%u] to be ordered before %s [%u] "
"so promoting to [%u]",
fu_plugin_get_name(plugin),
fu_plugin_get_order(plugin),
fu_plugin_get_name(dep),
fu_plugin_get_order(dep),
fu_plugin_get_order(dep) + 1);
fu_plugin_set_order(dep, fu_plugin_get_order(plugin) + 1);
changes = TRUE;
}
}
}
/* set priority as well */
for (guint i = 0; i < self->plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index(self->plugins, i);
deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_BETTER_THAN);
if (deps == NULL)
continue;
for (guint j = 0; j < deps->len && !changes; j++) {
const gchar *plugin_name = g_ptr_array_index(deps, j);
dep = fu_plugin_list_find_by_name(self, plugin_name, NULL);
if (dep == NULL) {
g_info("cannot find plugin '%s' "
"referenced by '%s'",
plugin_name,
fu_plugin_get_name(plugin));
continue;
}
if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED))
continue;
if (fu_plugin_get_priority(plugin) <= fu_plugin_get_priority(dep)) {
g_debug("%s [%u] better than %s [%u] "
"so bumping to [%u]",
fu_plugin_get_name(plugin),
fu_plugin_get_priority(plugin),
fu_plugin_get_name(dep),
fu_plugin_get_priority(dep),
fu_plugin_get_priority(dep) + 1);
fu_plugin_set_priority(plugin,
fu_plugin_get_priority(dep) + 1);
changes = TRUE;
}
}
}
/* check we're not stuck */
if (dep_loop_check++ > 100) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"got stuck in dep loop");
return FALSE;
}
} while (changes);
/* check for conflicts */
for (guint i = 0; i < self->plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index(self->plugins, i);
if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED))
continue;
deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_CONFLICTS);
if (deps == NULL)
continue;
for (guint j = 0; j < deps->len; j++) {
const gchar *plugin_name = g_ptr_array_index(deps, j);
dep = fu_plugin_list_find_by_name(self, plugin_name, NULL);
if (dep == NULL)
continue;
if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED))
continue;
g_info("disabling %s as conflicts with %s",
fu_plugin_get_name(dep),
fu_plugin_get_name(plugin));
fu_plugin_add_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED);
}
}
/* sort by order */
g_ptr_array_sort(self->plugins, fu_plugin_list_sort_cb);
return TRUE;
}
static void
fu_plugin_list_dispose(GObject *obj)
{
FuPluginList *self = FU_PLUGIN_LIST(obj);
if (self->plugins != NULL)
g_ptr_array_set_size(self->plugins, 0);
if (self->plugins_hash != NULL)
g_hash_table_remove_all(self->plugins_hash);
G_OBJECT_CLASS(fu_plugin_list_parent_class)->dispose(obj);
}
static void
fu_plugin_list_class_init(FuPluginListClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->dispose = fu_plugin_list_dispose;
object_class->finalize = fu_plugin_list_finalize;
}
static void
fu_plugin_list_init(FuPluginList *self)
{
self->plugins = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
self->plugins_hash =
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref);
}
static void
fu_plugin_list_finalize(GObject *obj)
{
FuPluginList *self = FU_PLUGIN_LIST(obj);
g_ptr_array_unref(self->plugins);
g_hash_table_unref(self->plugins_hash);
G_OBJECT_CLASS(fu_plugin_list_parent_class)->finalize(obj);
}
/**
* fu_plugin_list_new:
*
* Creates a new plugin list.
*
* Returns: (transfer full): a #FuPluginList
*
* Since: 1.0.2
**/
FuPluginList *
fu_plugin_list_new(void)
{
FuPluginList *self;
self = g_object_new(FU_TYPE_PLUGIN_LIST, NULL);
return FU_PLUGIN_LIST(self);
}