/* Chrysalide - Outil d'analyse de fichiers binaires * pglist.c - gestion de l'ensemble des greffons * * Copyright (C) 2009-2019 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide 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 3 of the License, or * (at your option) any later version. * * Chrysalide 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "pglist.h" #include #include #include #include #include #include #include #include "manager.h" #include "plugin-int.h" #include "../common/cpp.h" #include "../common/extstr.h" // REMME ? #include "../core/logs.h" #include "../core/nox.h" #include "../core/paths.h" /** * Prototype de la fonction de création, à garder synchronisé avec * NATIVE_PLUGIN_ENTRYPOINT() (cf. native-int.h). */ typedef GPluginModule * (* get_plugin_instance_cb) (GModule *); /* Liste de l'ensemble des greffons */ static GPluginModule **_pg_list = NULL; static size_t _pg_count = 0; /* Accès à cette liste */ static GRWLock _pg_lock; /* Filtre les répertoire et les modules de greffons pootentels. */ static int filter_dirs_or_mods(const struct dirent *); /* Part à la recherche de greffons sous forme de modules. */ static void browse_directory_for_plugins(const char *); /* Suit les variations du compteur de références d'un greffon. */ static void on_plugin_ref_toggle(gpointer, GPluginModule *, gboolean); /****************************************************************************** * * * Paramètres : load = procéde à un chargement dans la foulée ? * * * * Description : Procède au chargement des différents greffons trouvés. * * * * Retour : Toujours true (même s'il y a des erreurs de chargement). * * * * Remarques : - * * * ******************************************************************************/ bool init_all_plugins(bool load) { bool result; /* Bilan à retourner */ char *edir; /* Répertoire de base effectif */ char *env; /* Contenu environnemental */ char *saveptr; /* Sauvegarde pour parcours */ char *udir; /* Répertoire supplémentaire ? */ result = true; g_rw_lock_init(&_pg_lock); edir = get_effective_directory_new(TDT_PLUGINS_LIB); browse_directory_for_plugins(edir); free(edir); env = getenv("CHRYSALIDE_PLUGINS_PATH"); if (env != NULL) { env = strdup(env); for (udir = strtok_r(env, ":", &saveptr); udir != NULL; udir = strtok_r(NULL, ":", &saveptr)) browse_directory_for_plugins(udir); free(env); } if (load) load_remaning_plugins(); exit: return result; } /****************************************************************************** * * * Paramètres : - * * * * Description : Procède au déchargement des différents greffons présents. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void exit_all_plugins(void) { #if 0 ////// size_t i; /* Boucle de parcours */ const plugin_interface *pg_iface; /* Définition du greffon */ lock_plugin_list_for_reading(); if (_pg_list != NULL) { for (i = 0; i < _pg_count; i++) { assert(_pg_list[i] != NULL); /** * Si le greffon a conduit à la mise en place d'autres greffons, le * système de dépendances ne suffit pas pour le décompte des références : * le greffon voit à un instant T son compteur décroître ici ; à un * instant T+1, un greffon fils décrémente à son tour le compteur vers * le greffon principal. * * Le compteur du conteneur tombe alors à 0, et le code correspondant * est retiré. Lorsque que le flot d'exécution revient à la procédure * de sortie du second greffon, son code n'est plus en mémoire. * * On s'assure donc que les greffons qui génèrent d'autres greffons * sont bien traités en dernier. */ pg_iface = g_plugin_module_get_interface(_pg_list[i]); if (pg_iface != NULL && pg_iface->container) g_object_ref(_pg_list[i]); g_object_unref(_pg_list[i]); } for (i = 0; i < _pg_count; i++) { if (_pg_list[i] == NULL) continue; pg_iface = g_plugin_module_get_interface(_pg_list[i]); if (pg_iface == NULL || !pg_iface->container) continue; g_object_unref(_pg_list[i]); } free(_pg_list); } unlock_plugin_list_for_reading(); g_rw_lock_clear(&_pg_lock); #endif } /****************************************************************************** * * * Paramètres : lock = type d'action à mener. * * * * Description : Verrouille ou déverrouille l'accès en lecture à la liste. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void _lock_unlock_plugin_list_for_reading(bool lock) { if (lock) g_rw_lock_reader_lock(&_pg_lock); else g_rw_lock_reader_unlock(&_pg_lock); } /****************************************************************************** * * * Paramètres : entry = entrée de répertoire à analyser. * * * * Description : Filtre les répertoire et les modules de greffons pootentels. * * * * Retour : Valeur non nulle pour garder l'élément. * * * * Remarques : - * * * ******************************************************************************/ static int filter_dirs_or_mods(const struct dirent *entry) { int result; /* Conclusion à remonter */ if (entry->d_type == DT_DIR) result = strcmp(entry->d_name, ".") * strcmp(entry->d_name, ".."); else result = (strrcmp(entry->d_name, "." G_MODULE_SUFFIX) == 0 ? 1 : 0); return result; } /****************************************************************************** * * * Paramètres : dir = répertoire à parcourir en quête de greffons (sans /). * * * * Description : Indique la version (NOX/UI) associée à un nom de fichier. * * * * Retour : true si la version complémentaire existe ou false. * * * * Remarques : - * * * ******************************************************************************/ static bool check_for_plugin_versions(const char *dir, const char *filename, bool *is_nox, bool *is_ui) { bool result; /* Bilan à renvoyer */ size_t length; /* Taille du nom de fichier */ char *alt_path; /* Autre chemin complet testé */ int ret; /* Bilan d'une impression */ #ifdef _WIN32 # define SHARED_SUFFIX ".dll" #else # define SHARED_SUFFIX ".so" #endif #define UI_SHARED_SUFFIX "ui" SHARED_SUFFIX result = false; /* Propriétés du fichier courant */ length = strlen(filename); if (length < STATIC_STR_SIZE(UI_SHARED_SUFFIX)) *is_ui = false; else *is_ui = (strcmp(filename + length - STATIC_STR_SIZE(UI_SHARED_SUFFIX), UI_SHARED_SUFFIX) == 0); if (*is_ui) *is_nox = false; else { if (length < STATIC_STR_SIZE(SHARED_SUFFIX)) *is_nox = false; else *is_nox = (strcmp(filename + length - STATIC_STR_SIZE(SHARED_SUFFIX), SHARED_SUFFIX) == 0); } /* Recherche d'une version alternative */ if (*is_nox || *is_ui) { if (*is_nox) ret = asprintf(&alt_path, "%s%s%.*s%s", dir, G_DIR_SEPARATOR_S, (int)(length - STATIC_STR_SIZE(SHARED_SUFFIX)), filename, UI_SHARED_SUFFIX); else ret = asprintf(&alt_path, "%s%s%.*s%s", dir, G_DIR_SEPARATOR_S, (int)(length - STATIC_STR_SIZE(SHARED_SUFFIX)), filename, SHARED_SUFFIX); if (ret <= 0) { LOG_ERROR_N("asprintf"); goto exit; } ret = access(alt_path, R_OK | X_OK); result = (ret == 0); free(alt_path); } exit: return result; } /****************************************************************************** * * * Paramètres : dir = répertoire à parcourir en quête de greffons (sans /). * * * * Description : Part à la recherche de greffons sous forme de modules. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void browse_directory_for_plugins(const char *dir) { struct dirent **namelist; /* Eléments trouvés */ int ret; /* Bilan d'un appel */ int k; /* Boucle de parcours */ bool nox_mode; /* Absence de support graphique*/ char *filename; /* Elément à ausculter */ bool is_nox; /* Chemin de version basique ? */ bool is_ui; /* Chemin de version graphique */ bool has_alt; /* Existence d'une alternative */ GModule *module; /* Abstration de manipulation */ get_plugin_instance_cb get_instance; /* Point d'entrée exporté */ GPluginModule *plugin; /* Greffon à intégrer ou pas */ ret = scandir(dir, &namelist, filter_dirs_or_mods, alphasort); if (ret < 0) { LOG_ERROR_N("scandir"); return; } nox_mode = run_in_nox_mode(); for (k = ret; k--; ) { ret = asprintf(&filename, "%s%s%s", dir, G_DIR_SEPARATOR_S, namelist[k]->d_name); if (ret <= 0) { LOG_ERROR_N("asprintf"); continue; } if (namelist[k]->d_type == DT_DIR) browse_directory_for_plugins(filename); else { printf("// Candidate // %s\n", filename); has_alt = check_for_plugin_versions(dir, namelist[k]->d_name, &is_nox, &is_ui); printf(" -> nox=%d ui=%d -> alt? %d\n", is_nox, is_ui, has_alt); if ((nox_mode && is_nox) || (!nox_mode && ((is_nox && !has_alt) || is_ui))) { printf(" ---> load!\n"); module = g_module_open(filename, G_MODULE_BIND_LAZY); if (module == NULL) { log_variadic_message(LMT_ERROR, _("Error while loading the plugin candidate '%s' : %s"), filename, g_module_error()); goto next_file; } printf(" (main) module=%p '%s'\n", module, g_module_name(module)); if (!g_module_symbol(module, "get_chrysalide_plugin_instance", (gpointer *)&get_instance)) { log_variadic_message(LMT_ERROR, _("No '%s' entry in plugin candidate '%s'"), "", filename); } if (get_instance == NULL) plugin = NULL; else plugin = get_instance(module); printf(" ===> plugin: %p\n", plugin); if (plugin != NULL) { register_plugin(plugin); unref_object(plugin); } else g_module_close(module); } else log_variadic_message(LMT_INFO, _("Skipping unsuitable file for plugin: %s"), filename); } next_file: free(filename); free(namelist[k]); } free(namelist); } /****************************************************************************** * * * Paramètres : unused = adresse non utilisée ici. * * plugin = greffon àà venir effacer de la liste au besoin. * * last = indication sur la valeur du compteur de références. * * * * Description : Suit les variations du compteur de références d'un greffon. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void on_plugin_ref_toggle(gpointer unused, GPluginModule *plugin, gboolean last) { const char *name; /* Désignation du greffon */ size_t index; /* Indice du greffon */ GPluginModule *same; /* Juste pour la récupération */ if (last) { assert(g_rw_lock_writer_trylock(&_pg_lock) == FALSE); name = g_plugin_module_get_name(plugin); same = get_plugin_by_name(name, &index); assert(same != NULL); assert(same == plugin); g_clear_object(&_pg_list[index]); g_object_remove_toggle_ref(G_OBJECT(same), (GToggleNotify)on_plugin_ref_toggle, NULL); unref_object(same); } } /****************************************************************************** * * * Paramètres : plugin = greffon à ajouter aux autres disponibles. * * * * Description : Ajoute un greffon à la liste principale de greffons. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void register_plugin(GPluginModule *plugin) { size_t i; /* Boucle de parcours */ const char *name; /* Désignation du greffon */ const char *existing; /* Nom d'un greffon en place */ g_rw_lock_writer_lock(&_pg_lock); /* Recherche d'un éventuel doublon */ name = g_plugin_module_get_name(plugin); for (i = 0; i < _pg_count; i++) { existing = g_plugin_module_get_name(_pg_list[i]); if (strcmp(name, existing) == 0) { log_variadic_message(LMT_ERROR, _("Plugin '%s' already registered!"), name); break; } } /* Ajout du greffon à la liste */ if (i == _pg_count) { _pg_list = realloc(_pg_list, ++_pg_count * sizeof(GPluginModule)); _pg_list[_pg_count - 1] = plugin; ref_object(plugin); g_object_add_toggle_ref(G_OBJECT(plugin), (GToggleNotify)on_plugin_ref_toggle, NULL); } g_rw_lock_writer_unlock(&_pg_lock); } /****************************************************************************** * * * Paramètres : - * * * * Description : Charge tous les greffons restant à charger. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void load_remaning_plugins(void) { bool changed; /* Variation de dépendances */ size_t i; /* Boucle de parcours */ PluginStatusFlags flags; /* Fanions de greffon */ g_rw_lock_reader_lock(&_pg_lock); /* Etablit la liste de toutes les dépendances */ do { changed = false; for (i = 0; i < _pg_count; i++) changed |= g_plugin_module_resolve_dependencies(_pg_list[i], _pg_list, _pg_count); } while (changed); for (i = 0; i < _pg_count; i++) { flags = g_plugin_module_get_flags(_pg_list[i]); if (flags & PSF_UNKNOW_DEP) log_variadic_message(LMT_ERROR, _("There is (at least) one unknown dependency in the plugin '%s'"), g_plugin_module_get_filename(_pg_list[i])); else if (flags & PSF_DEP_LOOP) log_variadic_message(LMT_ERROR, _("There is a dependency loop in the plugin '%s'"), g_plugin_module_get_filename(_pg_list[i])); } /* Effectue les chargements possibles */ for (i = 0; i < _pg_count; i++) { flags = g_plugin_module_get_flags(_pg_list[i]); if ((flags & (BROKEN_PLUGIN_STATUS | PSF_LOADED)) == 0) g_plugin_module_load(_pg_list[i], _pg_list, _pg_count); } /* Supprime les greffons non chargés */ for (i = 0; i < _pg_count; i++) { flags = g_plugin_module_get_flags(_pg_list[i]); if ((flags & PSF_LOADED) == 0) { g_object_unref(G_OBJECT(_pg_list[i])); memmove(&_pg_list[i], &_pg_list[i + 1], (_pg_count - i - 1) * sizeof(GPluginModule *)); _pg_count--; } } g_rw_lock_reader_unlock(&_pg_lock); notify_native_plugins_loaded(); notify_all_plugins_loaded(); } /****************************************************************************** * * * Paramètres : name = désignation du greffon recherché. * * index = indice du greffon trouvé. [OUT] * * * * Description : Fournit le greffon répondant à un nom donné. * * * * Retour : Instance du greffon trouvé ou NULL si aucun. * * * * Remarques : - * * * ******************************************************************************/ GPluginModule *get_plugin_by_name(const char *name, size_t *index) { GPluginModule *result; /* Greffon trouvé à renvoyer */ size_t i; /* Boucle de parcours */ const char *current; /* Nom du greffon courant */ result = NULL; /** * L'accès à la liste doit être encadré. */ assert(g_rw_lock_writer_trylock(&_pg_lock) == FALSE); for (i = 0; i < _pg_count && result == NULL; i++) { /* Si on est en train de procéder à un nettoyage... */ if (_pg_list[i] == NULL) continue; current = g_plugin_module_get_name(_pg_list[i]); if (strcmp(current, name) == 0) { result = _pg_list[i]; ref_object(result); if (index != NULL) *index = i; } } return result; } /****************************************************************************** * * * Paramètres : count = nombre de greffons trouvés. [OUT] * * * * Description : Fournit la liste de l'ensemble des greffons. * * * * Retour : Liste de tous les greffons chargés. * * * * Remarques : - * * * ******************************************************************************/ GPluginModule **get_all_plugins(size_t *count) { GPluginModule **result; /* Liste à retourner */ size_t i; /* Boucle de parcours */ g_rw_lock_reader_lock(&_pg_lock); result = malloc(_pg_count * sizeof(GPluginModule *)); *count = _pg_count; for (i = 0; i < _pg_count; i++) { result[i] = _pg_list[i]; ref_object(result[i]); } g_rw_lock_reader_unlock(&_pg_lock); return result; }