/* * Copyright (C) 2010-2014 Tobias Brunner * Copyright (C) 2007 Martin Willi * HSR Hochschule fuer Technik Rapperswil * * 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. See . * * 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. */ #define _GNU_SOURCE #include "plugin_loader.h" #include #include #include #include #ifdef HAVE_DLADDR #include #endif #include #include #include #include #include #include #include #include #include typedef struct private_plugin_loader_t private_plugin_loader_t; typedef struct registered_feature_t registered_feature_t; typedef struct provided_feature_t provided_feature_t; typedef struct plugin_entry_t plugin_entry_t; #ifdef STATIC_PLUGIN_CONSTRUCTORS /** * Statically registered constructors */ static hashtable_t *plugin_constructors = NULL; #endif /** * private data of plugin_loader */ struct private_plugin_loader_t { /** * public functions */ plugin_loader_t public; /** * List of plugins, as plugin_entry_t */ linked_list_t *plugins; /** * Hashtable for registered features, as registered_feature_t */ hashlist_t *features; /** * Loaded features (stored in reverse order), as provided_feature_t */ linked_list_t *loaded; /** * List of paths to search for plugins */ linked_list_t *paths; /** * List of names of loaded plugins */ char *loaded_plugins; /** * Statistics collected while loading features */ struct { /** Number of features that failed to load */ int failed; /** Number of features that failed because of unmet dependencies */ int depends; /** Number of features in critical plugins that failed to load */ int critical; } stats; /** * Fetch features from the given plugin, can optionally be overridden to * modify feature arrays at loading time */ int (*get_features)(plugin_t *plugin, plugin_feature_t *features[]); }; /** * Registered plugin feature */ struct registered_feature_t { /** * The registered feature */ plugin_feature_t *feature; /** * List of plugins providing this feature, as provided_feature_t */ linked_list_t *plugins; }; /** * Hash a registered feature */ static u_int registered_feature_hash(registered_feature_t *this) { return plugin_feature_hash(this->feature); } /** * Compare two registered features */ static bool registered_feature_equals(registered_feature_t *a, registered_feature_t *b) { return plugin_feature_equals(a->feature, b->feature); } /** * Feature as provided by a plugin */ struct provided_feature_t { /** * Plugin providing the feature */ plugin_entry_t *entry; /** * FEATURE_REGISTER or FEATURE_CALLBACK entry */ plugin_feature_t *reg; /** * The provided feature (followed by dependencies) */ plugin_feature_t *feature; /** * Maximum number of dependencies (following feature) */ int dependencies; /** * TRUE if currently loading this feature (to prevent loops) */ bool loading; /** * TRUE if feature loaded */ bool loaded; /** * TRUE if feature failed to load */ bool failed; }; /** * Entry for a plugin */ struct plugin_entry_t { /** * Plugin instance */ plugin_t *plugin; /** * TRUE, if the plugin is marked as critical */ bool critical; /** * dlopen handle, if in separate lib */ void *handle; /** * List of features, as provided_feature_t */ linked_list_t *features; }; /** * Destroy a plugin entry */ static void plugin_entry_destroy(plugin_entry_t *entry) { DESTROY_IF(entry->plugin); if (entry->handle) { dlclose(entry->handle); } entry->features->destroy(entry->features); free(entry); } /** * Wrapper for static plugin features */ typedef struct { /** * Implements plugin_t interface */ plugin_t public; /** * Name of the module registering these features */ char *name; /** * Optional reload function for features */ bool (*reload)(void *data); /** * User data to pass to reload function */ void *reload_data; /** * Static plugin features */ plugin_feature_t *features; /** * Number of plugin features */ int count; } static_features_t; METHOD(plugin_t, get_static_name, char*, static_features_t *this) { return this->name; } METHOD(plugin_t, get_static_features, int, static_features_t *this, plugin_feature_t *features[]) { *features = this->features; return this->count; } METHOD(plugin_t, static_reload, bool, static_features_t *this) { if (this->reload) { return this->reload(this->reload_data); } return FALSE; } METHOD(plugin_t, static_destroy, void, static_features_t *this) { free(this->features); free(this->name); free(this); } /** * Create a wrapper around static plugin features. */ static plugin_t *static_features_create(const char *name, plugin_feature_t features[], int count, bool (*reload)(void*), void *reload_data) { static_features_t *this; INIT(this, .public = { .get_name = _get_static_name, .get_features = _get_static_features, .reload = _static_reload, .destroy = _static_destroy, }, .name = strdup(name), .reload = reload, .reload_data = reload_data, .features = calloc(count, sizeof(plugin_feature_t)), .count = count, ); memcpy(this->features, features, sizeof(plugin_feature_t) * count); return &this->public; } #ifdef STATIC_PLUGIN_CONSTRUCTORS /* * Described in header. */ void plugin_constructor_register(char *name, void *constructor) { bool old = FALSE; if (lib && lib->leak_detective) { old = lib->leak_detective->set_state(lib->leak_detective, FALSE); } if (!plugin_constructors) { chunk_hash_seed(); plugin_constructors = hashtable_create(hashtable_hash_str, hashtable_equals_str, 32); } if (constructor) { plugin_constructors->put(plugin_constructors, name, constructor); } else { plugin_constructors->remove(plugin_constructors, name); if (!plugin_constructors->get_count(plugin_constructors)) { plugin_constructors->destroy(plugin_constructors); plugin_constructors = NULL; } } if (lib && lib->leak_detective) { lib->leak_detective->set_state(lib->leak_detective, old); } } #endif /** * create a plugin * returns: NOT_FOUND, if the constructor was not found * FAILED, if the plugin could not be constructed */ static status_t create_plugin(private_plugin_loader_t *this, void *handle, char *name, bool integrity, bool critical, plugin_entry_t **entry) { char create[128]; plugin_t *plugin; plugin_constructor_t constructor = NULL; if (snprintf(create, sizeof(create), "%s_plugin_create", name) >= sizeof(create)) { return FAILED; } translate(create, "-", "_"); #ifdef STATIC_PLUGIN_CONSTRUCTORS if (plugin_constructors) { constructor = plugin_constructors->get(plugin_constructors, name); } if (!constructor) #endif { constructor = dlsym(handle, create); } if (!constructor) { return NOT_FOUND; } if (integrity && lib->integrity) { if (!lib->integrity->check_segment(lib->integrity, name, constructor)) { DBG1(DBG_LIB, "plugin '%s': failed segment integrity test", name); return FAILED; } DBG1(DBG_LIB, "plugin '%s': passed file and segment integrity tests", name); } plugin = constructor(); if (plugin == NULL) { DBG1(DBG_LIB, "plugin '%s': failed to load - %s returned NULL", name, create); return FAILED; } INIT(*entry, .plugin = plugin, .critical = critical, .features = linked_list_create(), ); DBG2(DBG_LIB, "plugin '%s': loaded successfully", name); return SUCCESS; } /** * load a single plugin */ static plugin_entry_t *load_plugin(private_plugin_loader_t *this, char *name, char *file, bool critical) { plugin_entry_t *entry; void *handle; int flag = RTLD_LAZY; switch (create_plugin(this, RTLD_DEFAULT, name, FALSE, critical, &entry)) { case SUCCESS: this->plugins->insert_last(this->plugins, entry); return entry; case NOT_FOUND: if (file) { /* try to load the plugin from a file */ break; } /* fall-through */ default: return NULL; } if (lib->integrity) { if (!lib->integrity->check_file(lib->integrity, name, file)) { DBG1(DBG_LIB, "plugin '%s': failed file integrity test of '%s'", name, file); return NULL; } } if (lib->settings->get_bool(lib->settings, "%s.dlopen_use_rtld_now", FALSE, lib->ns)) { flag = RTLD_NOW; } #ifdef RTLD_NODELETE /* If supported, do not unload the library when unloading a plugin. It * really doesn't matter in productive systems, but causes many (dependency) * library reloads during unit tests. Some libraries can't handle that, e.g. * GnuTLS leaks file descriptors in its library load/unload functions. */ flag |= RTLD_NODELETE; #endif handle = dlopen(file, flag); if (handle == NULL) { DBG1(DBG_LIB, "plugin '%s' failed to load: %s", name, dlerror()); return NULL; } if (create_plugin(this, handle, name, TRUE, critical, &entry) != SUCCESS) { dlclose(handle); return NULL; } entry->handle = handle; this->plugins->insert_last(this->plugins, entry); return entry; } CALLBACK(feature_filter, bool, void *null, enumerator_t *orig, va_list args) { provided_feature_t *provided; plugin_feature_t **feature; VA_ARGS_VGET(args, feature); while (orig->enumerate(orig, &provided)) { if (provided->loaded) { *feature = provided->feature; return TRUE; } } return FALSE; } CALLBACK(plugin_filter, bool, void *null, enumerator_t *orig, va_list args) { plugin_entry_t *entry; linked_list_t **list; plugin_t **plugin; VA_ARGS_VGET(args, plugin, list); if (orig->enumerate(orig, &entry)) { *plugin = entry->plugin; if (list) { enumerator_t *features; features = enumerator_create_filter( entry->features->create_enumerator(entry->features), feature_filter, NULL, NULL); *list = linked_list_create_from_enumerator(features); } return TRUE; } return FALSE; } METHOD(plugin_loader_t, create_plugin_enumerator, enumerator_t*, private_plugin_loader_t *this) { return enumerator_create_filter( this->plugins->create_enumerator(this->plugins), plugin_filter, NULL, NULL); } METHOD(plugin_loader_t, has_feature, bool, private_plugin_loader_t *this, plugin_feature_t feature) { enumerator_t *plugins, *features; plugin_t *plugin; linked_list_t *list; plugin_feature_t *current; bool found = FALSE; plugins = create_plugin_enumerator(this); while (plugins->enumerate(plugins, &plugin, &list)) { features = list->create_enumerator(list); while (features->enumerate(features, ¤t)) { if (plugin_feature_matches(&feature, current)) { found = TRUE; break; } } features->destroy(features); list->destroy(list); } plugins->destroy(plugins); return found; } /** * Create a list of the names of all loaded plugins */ static char* loaded_plugins_list(private_plugin_loader_t *this) { int buf_len = 128, len = 0; char *buf, *name; enumerator_t *enumerator; plugin_t *plugin; buf = malloc(buf_len); buf[0] = '\0'; enumerator = create_plugin_enumerator(this); while (enumerator->enumerate(enumerator, &plugin, NULL)) { name = plugin->get_name(plugin); if (len + (strlen(name) + 1) >= buf_len) { buf_len <<= 1; buf = realloc(buf, buf_len); } len += snprintf(&buf[len], buf_len - len, "%s ", name); } enumerator->destroy(enumerator); if (len > 0 && buf[len - 1] == ' ') { buf[len - 1] = '\0'; } return buf; } /** * Check if a plugin is already loaded */ static bool plugin_loaded(private_plugin_loader_t *this, char *name) { enumerator_t *enumerator; bool found = FALSE; plugin_t *plugin; enumerator = create_plugin_enumerator(this); while (enumerator->enumerate(enumerator, &plugin, NULL)) { if (streq(plugin->get_name(plugin), name)) { found = TRUE; break; } } enumerator->destroy(enumerator); return found; } /** * Forward declaration */ static void load_provided(private_plugin_loader_t *this, provided_feature_t *provided, int level); CALLBACK(is_feature_loaded, bool, provided_feature_t *item, va_list args) { return item->loaded; } CALLBACK(is_feature_loadable, bool, provided_feature_t *item, va_list args) { return !item->loading && !item->loaded && !item->failed; } /** * Find a loaded and matching feature */ static bool loaded_feature_matches(registered_feature_t *a, registered_feature_t *b) { if (plugin_feature_matches(a->feature, b->feature)) { return b->plugins->find_first(b->plugins, is_feature_loaded, NULL); } return FALSE; } /** * Find a loadable module that equals the requested feature */ static bool loadable_feature_equals(registered_feature_t *a, registered_feature_t *b) { if (plugin_feature_equals(a->feature, b->feature)) { return b->plugins->find_first(b->plugins, is_feature_loadable, NULL); } return FALSE; } /** * Find a loadable module that matches the requested feature */ static bool loadable_feature_matches(registered_feature_t *a, registered_feature_t *b) { if (plugin_feature_matches(a->feature, b->feature)) { return b->plugins->find_first(b->plugins, is_feature_loadable, NULL); } return FALSE; } /** * Returns a compatible plugin feature for the given dependency */ static bool find_compatible_feature(private_plugin_loader_t *this, plugin_feature_t *dependency) { registered_feature_t *feature, lookup = { .feature = dependency, }; feature = this->features->get_match(this->features, &lookup, (void*)loaded_feature_matches); return feature != NULL; } /** * Load a registered plugin feature */ static void load_registered(private_plugin_loader_t *this, registered_feature_t *registered, int level) { enumerator_t *enumerator; provided_feature_t *provided; enumerator = registered->plugins->create_enumerator(registered->plugins); while (enumerator->enumerate(enumerator, &provided)) { load_provided(this, provided, level); } enumerator->destroy(enumerator); } /** * Try to load dependencies of the given feature */ static bool load_dependencies(private_plugin_loader_t *this, provided_feature_t *provided, int level) { registered_feature_t *registered, lookup; int i; /* first entry is provided feature, followed by dependencies */ for (i = 1; i < provided->dependencies; i++) { if (provided->feature[i].kind != FEATURE_DEPENDS && provided->feature[i].kind != FEATURE_SDEPEND) { /* end of dependencies */ break; } /* we load the feature even if a compatible one is already loaded, * otherwise e.g. a specific database implementation loaded before * another might cause a plugin feature loaded in-between to fail */ lookup.feature = &provided->feature[i]; do { /* prefer an exactly matching feature, could be omitted but * results in a more predictable behavior */ registered = this->features->get_match(this->features, &lookup, (void*)loadable_feature_equals); if (!registered) { /* try fuzzy matching */ registered = this->features->get_match(this->features, &lookup, (void*)loadable_feature_matches); } if (registered) { load_registered(this, registered, level); } /* we could stop after finding one but for dependencies like * DB_ANY it might be needed to load all matching features */ } while (registered); if (!find_compatible_feature(this, &provided->feature[i])) { bool soft = provided->feature[i].kind == FEATURE_SDEPEND; #ifndef USE_FUZZING char *name, *provide, *depend; int indent = level * 2; name = provided->entry->plugin->get_name(provided->entry->plugin); provide = plugin_feature_get_string(&provided->feature[0]); depend = plugin_feature_get_string(&provided->feature[i]); if (soft) { DBG3(DBG_LIB, "%*sfeature %s in plugin '%s' has unmet soft " "dependency: %s", indent, "", provide, name, depend); } else if (provided->entry->critical) { DBG1(DBG_LIB, "feature %s in critical plugin '%s' has unmet " "dependency: %s", provide, name, depend); } else { DBG2(DBG_LIB, "feature %s in plugin '%s' has unmet dependency: " "%s", provide, name, depend); } free(provide); free(depend); #endif /* !USE_FUZZING */ if (soft) { /* it's ok if we can't resolve soft dependencies */ continue; } return FALSE; } } return TRUE; } /** * Load registered plugin features */ static void load_feature(private_plugin_loader_t *this, provided_feature_t *provided, int level) { if (load_dependencies(this, provided, level)) { if (plugin_feature_load(provided->entry->plugin, provided->feature, provided->reg)) { provided->loaded = TRUE; /* insert first so we can unload the features in reverse order */ this->loaded->insert_first(this->loaded, provided); return; } #ifndef USE_FUZZING char *name, *provide; name = provided->entry->plugin->get_name(provided->entry->plugin); provide = plugin_feature_get_string(&provided->feature[0]); if (provided->entry->critical) { DBG1(DBG_LIB, "feature %s in critical plugin '%s' failed to load", provide, name); } else { DBG2(DBG_LIB, "feature %s in plugin '%s' failed to load", provide, name); } free(provide); #endif /* !USE_FUZZING */ } else { /* TODO: we could check the current level and set a different flag when * being loaded as dependency. If there are loops there is a chance the * feature can be loaded later when loading the feature directly. */ this->stats.depends++; } provided->failed = TRUE; this->stats.critical += provided->entry->critical ? 1 : 0; this->stats.failed++; } /** * Load a provided feature */ static void load_provided(private_plugin_loader_t *this, provided_feature_t *provided, int level) { if (provided->loaded || provided->failed) { return; } #ifndef USE_FUZZING char *name, *provide; int indent = level * 2; name = provided->entry->plugin->get_name(provided->entry->plugin); provide = plugin_feature_get_string(provided->feature); if (provided->loading) { /* prevent loop */ DBG3(DBG_LIB, "%*sloop detected while loading %s in plugin '%s'", indent, "", provide, name); free(provide); return; } DBG3(DBG_LIB, "%*sloading feature %s in plugin '%s'", indent, "", provide, name); free(provide); #else if (provided->loading) { return; } #endif /* USE_FUZZING */ provided->loading = TRUE; load_feature(this, provided, level + 1); provided->loading = FALSE; } /** * Load registered plugin features */ static void load_features(private_plugin_loader_t *this) { enumerator_t *enumerator, *inner; plugin_entry_t *plugin; provided_feature_t *provided; /* we do this in plugin order to allow implicit dependencies to be resolved * by reordering plugins */ enumerator = this->plugins->create_enumerator(this->plugins); while (enumerator->enumerate(enumerator, &plugin)) { inner = plugin->features->create_enumerator(plugin->features); while (inner->enumerate(inner, &provided)) { load_provided(this, provided, 0); } inner->destroy(inner); } enumerator->destroy(enumerator); } /** * Default implementation for plugin feature retrieval */ static int get_features_default(plugin_t *plugin, plugin_feature_t *features[]) { return plugin->get_features(plugin, features); } /** * Register plugin features provided by the given plugin */ static void register_features(private_plugin_loader_t *this, plugin_entry_t *entry) { plugin_feature_t *feature, *reg; registered_feature_t *registered, lookup; provided_feature_t *provided; int count, i; if (!entry->plugin->get_features) { /* feature interface not supported */ DBG1(DBG_LIB, "plugin '%s' does not provide features, deprecated", entry->plugin->get_name(entry->plugin)); return; } reg = NULL; count = this->get_features(entry->plugin, &feature); for (i = 0; i < count; i++) { switch (feature->kind) { case FEATURE_PROVIDE: lookup.feature = feature; registered = this->features->ht.get(&this->features->ht, &lookup); if (!registered) { INIT(registered, .feature = feature, .plugins = linked_list_create(), ); this->features->ht.put(&this->features->ht, registered, registered); } INIT(provided, .entry = entry, .feature = feature, .reg = reg, .dependencies = count - i, ); registered->plugins->insert_last(registered->plugins, provided); entry->features->insert_last(entry->features, provided); break; case FEATURE_REGISTER: case FEATURE_CALLBACK: reg = feature; break; default: break; } feature++; } } /** * Unregister a plugin feature */ static void unregister_feature(private_plugin_loader_t *this, provided_feature_t *provided) { registered_feature_t *registered, lookup; lookup.feature = provided->feature; registered = this->features->ht.get(&this->features->ht, &lookup); if (registered) { registered->plugins->remove(registered->plugins, provided, NULL); if (registered->plugins->get_count(registered->plugins) == 0) { this->features->ht.remove(&this->features->ht, &lookup); registered->plugins->destroy(registered->plugins); free(registered); } else if (registered->feature == provided->feature) { /* update feature in case the providing plugin gets unloaded */ provided_feature_t *first; registered->plugins->get_first(registered->plugins, (void**)&first); registered->feature = first->feature; } } free(provided); } /** * Unregister plugin features */ static void unregister_features(private_plugin_loader_t *this, plugin_entry_t *entry) { provided_feature_t *provided; enumerator_t *enumerator; enumerator = entry->features->create_enumerator(entry->features); while (enumerator->enumerate(enumerator, &provided)) { entry->features->remove_at(entry->features, enumerator); unregister_feature(this, provided); } enumerator->destroy(enumerator); } /** * Remove plugins we were not able to load any plugin features from. */ static void purge_plugins(private_plugin_loader_t *this) { enumerator_t *enumerator; plugin_entry_t *entry; enumerator = this->plugins->create_enumerator(this->plugins); while (enumerator->enumerate(enumerator, &entry)) { if (!entry->plugin->get_features) { /* feature interface not supported */ continue; } if (!entry->features->find_first(entry->features, is_feature_loaded, NULL)) { DBG2(DBG_LIB, "unloading plugin '%s' without loaded features", entry->plugin->get_name(entry->plugin)); this->plugins->remove_at(this->plugins, enumerator); unregister_features(this, entry); plugin_entry_destroy(entry); } } enumerator->destroy(enumerator); } METHOD(plugin_loader_t, add_static_features, void, private_plugin_loader_t *this, const char *name, plugin_feature_t features[], int count, bool critical, bool (*reload)(void*), void *reload_data) { plugin_entry_t *entry; plugin_t *plugin; plugin = static_features_create(name, features, count, reload, reload_data); INIT(entry, .plugin = plugin, .critical = critical, .features = linked_list_create(), ); this->plugins->insert_last(this->plugins, entry); register_features(this, entry); } /** * Tries to find the plugin with the given name in the given path. */ static bool find_plugin(char *path, char *name, char *buf, char **file) { struct stat stb; if (path && snprintf(buf, PATH_MAX, "%s/libstrongswan-%s.so", path, name) < PATH_MAX) { if (stat(buf, &stb) == 0) { *file = buf; return TRUE; } } return FALSE; } CALLBACK(find_plugin_cb, bool, char *path, va_list args) { char *name, *buf, **file; VA_ARGS_VGET(args, name, buf, file); return find_plugin(path, name, buf, file); } /** * Used to sort plugins by priority */ typedef struct { /* name of the plugin */ char *name; /* the plugins priority */ int prio; /* default priority */ int def; } plugin_priority_t; static void plugin_priority_free(const plugin_priority_t *this, int idx, void *user) { free(this->name); } /** * Sort plugins and their priority by name */ static int plugin_priority_cmp_name(const plugin_priority_t *a, const plugin_priority_t *b) { return strcmp(a->name, b->name); } /** * Sort plugins by decreasing priority or default priority then by name */ static int plugin_priority_cmp(const plugin_priority_t *a, const plugin_priority_t *b, void *user) { int diff; diff = b->prio - a->prio; if (!diff) { /* the same priority, use default order */ diff = b->def - a->def; if (!diff) { /* same default priority (i.e. both were not found in that list) */ return strcmp(a->name, b->name); } } return diff; } CALLBACK(plugin_priority_filter, bool, void *null, enumerator_t *orig, va_list args) { plugin_priority_t *prio; char **name; VA_ARGS_VGET(args, name); if (orig->enumerate(orig, &prio)) { *name = prio->name; return TRUE; } return FALSE; } /** * Determine the list of plugins to load via load option in each plugin's * config section. */ static char *modular_pluginlist(char *list) { enumerator_t *enumerator; array_t *given, *final; plugin_priority_t item, *current, found; char *plugin, *plugins = NULL; int i = 0, max_prio; bool load_def = FALSE; given = array_create(sizeof(plugin_priority_t), 0); final = array_create(sizeof(plugin_priority_t), 0); enumerator = enumerator_create_token(list, " ", " "); while (enumerator->enumerate(enumerator, &plugin)) { item.name = strdup(plugin); item.prio = i++; array_insert(given, ARRAY_TAIL, &item); } enumerator->destroy(enumerator); array_sort(given, (void*)plugin_priority_cmp_name, NULL); /* the maximum priority used for plugins not found in this list */ max_prio = i + 1; if (lib->settings->get_bool(lib->settings, "%s.load_modular", FALSE, lib->ns)) { enumerator = lib->settings->create_section_enumerator(lib->settings, "%s.plugins", lib->ns); } else { enumerator = enumerator_create_filter(array_create_enumerator(given), plugin_priority_filter, NULL, NULL); load_def = TRUE; } while (enumerator->enumerate(enumerator, &plugin)) { item.prio = lib->settings->get_int(lib->settings, "%s.plugins.%s.load", 0, lib->ns, plugin); if (!item.prio) { if (!lib->settings->get_bool(lib->settings, "%s.plugins.%s.load", load_def, lib->ns, plugin)) { continue; } item.prio = 1; } item.name = plugin; item.def = max_prio; if (array_bsearch(given, &item, (void*)plugin_priority_cmp_name, &found) != -1) { item.def = max_prio - found.prio; } array_insert(final, ARRAY_TAIL, &item); } enumerator->destroy(enumerator); array_sort(final, (void*)plugin_priority_cmp, NULL); plugins = strdup(""); enumerator = array_create_enumerator(final); while (enumerator->enumerate(enumerator, ¤t)) { char *prev = plugins; if (asprintf(&plugins, "%s %s", plugins ?: "", current->name) < 0) { plugins = prev; break; } free(prev); } enumerator->destroy(enumerator); array_destroy_function(given, (void*)plugin_priority_free, NULL); array_destroy(final); return plugins; } METHOD(plugin_loader_t, load_plugins, bool, private_plugin_loader_t *this, char *list) { enumerator_t *enumerator; char *default_path = NULL, *plugins, *token; bool critical_failed = FALSE; #ifdef PLUGINDIR default_path = PLUGINDIR; #endif /* PLUGINDIR */ plugins = modular_pluginlist(list); enumerator = enumerator_create_token(plugins, " ", " "); while (!critical_failed && enumerator->enumerate(enumerator, &token)) { plugin_entry_t *entry; bool critical = FALSE; char buf[PATH_MAX], *file = NULL; int len; token = strdup(token); len = strlen(token); if (token[len-1] == '!') { critical = TRUE; token[len-1] = '\0'; } if (plugin_loaded(this, token)) { free(token); continue; } if (this->paths) { this->paths->find_first(this->paths, find_plugin_cb, NULL, token, buf, &file); } if (!file) { find_plugin(default_path, token, buf, &file); } entry = load_plugin(this, token, file, critical); if (entry) { register_features(this, entry); } else if (critical) { critical_failed = TRUE; DBG1(DBG_LIB, "loading critical plugin '%s' failed", token); } free(token); } enumerator->destroy(enumerator); if (!critical_failed) { load_features(this); if (this->stats.critical > 0) { critical_failed = TRUE; DBG1(DBG_LIB, "failed to load %d critical plugin feature%s", this->stats.critical, this->stats.critical == 1 ? "" : "s"); } /* unload plugins that we were not able to load any features for */ purge_plugins(this); } if (!critical_failed) { free(this->loaded_plugins); this->loaded_plugins = loaded_plugins_list(this); } if (plugins != list) { free(plugins); } return !critical_failed; } /** * Unload plugin features, they are registered in reverse order */ static void unload_features(private_plugin_loader_t *this) { enumerator_t *enumerator; provided_feature_t *provided; plugin_entry_t *entry; enumerator = this->loaded->create_enumerator(this->loaded); while (enumerator->enumerate(enumerator, &provided)) { entry = provided->entry; plugin_feature_unload(entry->plugin, provided->feature, provided->reg); this->loaded->remove_at(this->loaded, enumerator); entry->features->remove(entry->features, provided, NULL); unregister_feature(this, provided); } enumerator->destroy(enumerator); } METHOD(plugin_loader_t, unload, void, private_plugin_loader_t *this) { plugin_entry_t *entry; /* unload features followed by plugins, in reverse order */ unload_features(this); while (this->plugins->remove_last(this->plugins, (void**)&entry) == SUCCESS) { if (lib->leak_detective) { /* keep handle to report leaks properly */ entry->handle = NULL; } unregister_features(this, entry); plugin_entry_destroy(entry); } free(this->loaded_plugins); this->loaded_plugins = NULL; memset(&this->stats, 0, sizeof(this->stats)); } METHOD(plugin_loader_t, add_path, void, private_plugin_loader_t *this, char *path) { if (!this->paths) { this->paths = linked_list_create(); } this->paths->insert_last(this->paths, strdupnull(path)); } /** * Reload a plugin by name, NULL for all */ static u_int reload_by_name(private_plugin_loader_t *this, char *name) { u_int reloaded = 0; enumerator_t *enumerator; plugin_t *plugin; enumerator = create_plugin_enumerator(this); while (enumerator->enumerate(enumerator, &plugin, NULL)) { if (name == NULL || streq(name, plugin->get_name(plugin))) { if (plugin->reload && plugin->reload(plugin)) { DBG2(DBG_LIB, "reloaded configuration of '%s' plugin", plugin->get_name(plugin)); reloaded++; } } } enumerator->destroy(enumerator); return reloaded; } METHOD(plugin_loader_t, reload, u_int, private_plugin_loader_t *this, char *list) { u_int reloaded = 0; enumerator_t *enumerator; char *name; if (list == NULL) { return reload_by_name(this, NULL); } enumerator = enumerator_create_token(list, " ", ""); while (enumerator->enumerate(enumerator, &name)) { reloaded += reload_by_name(this, name); } enumerator->destroy(enumerator); return reloaded; } METHOD(plugin_loader_t, loaded_plugins, char*, private_plugin_loader_t *this) { return this->loaded_plugins ?: ""; } METHOD(plugin_loader_t, status, void, private_plugin_loader_t *this, level_t level) { if (this->loaded_plugins) { dbg(DBG_LIB, level, "loaded plugins: %s", this->loaded_plugins); if (this->stats.failed) { DBG2(DBG_LIB, "unable to load %d plugin feature%s (%d due to unmet " "dependencies)", this->stats.failed, this->stats.failed == 1 ? "" : "s", this->stats.depends); } } } METHOD(plugin_loader_t, destroy, void, private_plugin_loader_t *this) { unload(this); this->features->destroy(this->features); this->loaded->destroy(this->loaded); this->plugins->destroy(this->plugins); DESTROY_FUNCTION_IF(this->paths, free); free(this->loaded_plugins); free(this); } /* * see header file */ plugin_loader_t *plugin_loader_create() { private_plugin_loader_t *this; INIT(this, .public = { .add_static_features = _add_static_features, .load = _load_plugins, .add_path = _add_path, .reload = _reload, .unload = _unload, .create_plugin_enumerator = _create_plugin_enumerator, .has_feature = _has_feature, .loaded_plugins = _loaded_plugins, .status = _status, .destroy = _destroy, }, .plugins = linked_list_create(), .loaded = linked_list_create(), .features = hashlist_create( (hashtable_hash_t)registered_feature_hash, (hashtable_equals_t)registered_feature_equals, 64), .get_features = dlsym(RTLD_DEFAULT, "plugin_loader_feature_filter"), ); if (!this->get_features) { this->get_features = get_features_default; } return &this->public; } /* * See header */ void plugin_loader_add_plugindirs(char *basedir, char *plugins) { enumerator_t *enumerator; char *name, path[PATH_MAX], dir[64]; enumerator = enumerator_create_token(plugins, " ", "!"); while (enumerator->enumerate(enumerator, &name)) { snprintf(dir, sizeof(dir), "%s", name); translate(dir, "-", "_"); snprintf(path, sizeof(path), "%s/%s/.libs", basedir, dir); lib->plugins->add_path(lib->plugins, path); } enumerator->destroy(enumerator); }