| /* |
| * 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); |
| } |