Mercurial > ~mikael > mcabber > hg
diff mcabber/mcabber/modules.c @ 1735:5093b5ca1572
New modules loading scheme
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Thu, 04 Mar 2010 13:03:20 +0200 |
parents | |
children | 15e1f3957786 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/modules.c Thu Mar 04 13:03:20 2010 +0200 @@ -0,0 +1,352 @@ +/* + * modules.c -- modules handling + * + * Copyright (C) 2010 Myhailo Danylenko <isbear@ukrpost.net> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <glib.h> +#include <gmodule.h> +#include <string.h> + +#include "settings.h" +#include "config.h" +#include "modules.h" +#include "logprint.h" +#include "utils.h" + +// Information about loaded module +typedef struct { + guint refcount; + gboolean locked; + gchar *name; + GModule *module; + GSList *dependencies; + module_info_t *info; +} loaded_module_t; + +// Registry of loaded modules +// FIXME This should be a hash table +// but this needs long thinking and will not affect external interfaces +static GSList *loaded_modules = NULL; + +static gint module_list_comparator(gconstpointer arg1, gconstpointer arg2) +{ + const loaded_module_t *module = arg1; + const char *name = arg2; + return g_strcmp0(module->name, name); +} + +// module_load(modulename, manual, force) +// Tries to load specified module and any modules, that this module +// depends on. Returns NULL on success or constant error string in a +// case of error. Error message not necessarily indicates error. +const gchar *module_load(const gchar *arg, gboolean manual, gboolean force) +{ + GModule *mod; + module_info_t *info; + GSList *deps = NULL; + + if (!arg || !*arg) + return "Missing module name"; + + { // Check if module is already loaded + GSList *lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator); + + if (lmod) { + loaded_module_t *module = lmod->data; + + if (manual) { + if (!module->locked) { + module->locked = TRUE; + module->refcount += 1; + return force ? NULL : "Module is already automatically loaded, marked as manually loaded"; + } else + return force ? NULL : "Module is already loaded"; + } else { + module->refcount += 1; + return NULL; + } + } + } + + { // Load module + gchar *mdir = expand_filename(settings_opt_get("modules_dir")); + gchar *path = g_module_build_path(mdir ? mdir : PKGLIB_DIR, arg); + g_free(mdir); + mod = g_module_open(path, G_MODULE_BIND_LAZY); + g_free(path); + if (!mod) + return g_module_error(); + } + + { // Obtain module information structure + gchar *varname = g_strdup_printf("info_%s", arg); + gpointer var = NULL; + + // convert to a valid symbol name + g_strcanon(varname, "abcdefghijklmnopqrstuvwxyz0123456789", '_'); + + if (!g_module_symbol(mod, varname, &var)) { + if (!force) { + g_free(varname); + return "Module provides no information structure"; + } + + scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Module provides no information structure."); + } + + g_free(varname); + info = var; + } + + // Version check + if (info && info->mcabber_version && *(info->mcabber_version) + && (strcmp(info->mcabber_version, PACKAGE_VERSION) > 0)) { + if (!force) { + g_module_close(mod); + return "Module requires newer version of mcabber"; + } + + scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Module requires newer version of mcabber."); + } + + // Load dependencies + if (info && info->requires) { + const gchar **dep; + + for (dep = info->requires; *dep; ++dep) { + const gchar *err = module_load(*dep, FALSE, FALSE); + + if (err) { + GSList *mel; + scr_LogPrint(LPRINT_LOGNORM, "Error loading dependency module %s: %s.", *dep, err); + + // Unload already loaded dependencies + for (mel = deps; mel; mel = mel->next) { + gchar *ldmname = mel->data; + err = module_unload(ldmname, FALSE, FALSE); + scr_LogPrint(LPRINT_LOGNORM, "Error unloading dependency module %s: %s.", ldmname, err); + g_free(ldmname); + } + g_slist_free(deps); + + // Unload module + if (!g_module_close(mod)) + scr_LogPrint(LPRINT_LOGNORM, "Error unloading module %s: %s.", arg, g_module_error()); + return "Dependency problems"; + } + + deps = g_slist_append(deps, g_strdup(*dep)); + } + } + + { // Register module + loaded_module_t *module = g_new(loaded_module_t, 1); + + module->refcount = 1; + module->locked = manual; + module->name = g_strdup(arg); + module->module = mod; + module->info = info; + module->dependencies = deps; + + loaded_modules = g_slist_prepend(loaded_modules, module); + } + + // Run initialization routine + if (info && info->init) + info->init(); + + // XXX Run hk_loaded_module hook (and move this line there) + scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg); + + return NULL; +} + +// module_unload(modulename, manual, force) +// Unload specified module and any automatically loaded modules +// that are no more required. +const gchar *module_unload(const gchar *arg, gboolean manual, gboolean force) +{ + GSList *lmod; + loaded_module_t *module; + module_info_t *info; + + if (!arg || !*arg) + return "Missing module name"; + + lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator); + if (!lmod) + return "Module not found"; + + module = lmod->data; + + // Check if user can unload this module + if (manual) { + if (!module->locked) { + if (force) + scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Manually unloading automatically loaded module."); + else + return "Module is not loaded manually"; + } + module->locked = FALSE; + } + + // Check refcount + module->refcount -= 1; + if (module->refcount > 0) { + if (force) + scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Refcount is not zero (%u).", module->refcount); + else + return manual ? "Module is required by some other modules" : NULL; + } + + info = module->info; + + // Run uninitialization routine + if (info && info->uninit) + info->uninit(); + // XXX Prevent uninitialization routine to be called again + module->info = NULL; + + // Unload module + if (!g_module_close(module->module)) + return g_module_error(); // XXX destroy structure? + + { // Unload dependencies + GSList *dep; + for (dep = module->dependencies; dep; dep = dep->next) { + gchar *ldmname = dep->data; + const gchar *err = module_unload(ldmname, FALSE, FALSE); + if (err) // XXX + scr_LogPrint(LPRINT_LOGNORM, "Error unloading automatically loaded module %s: %s.", ldmname, err); + g_free(ldmname); + } + g_slist_free(module->dependencies); + module->dependencies = NULL; + } + + // Destroy structure + loaded_modules = g_slist_delete_link(loaded_modules, lmod); + g_free(module->name); + g_free(module); + + scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg); + + return NULL; +} + +// module_list_print(void) +// Prints into status buffer and log list of the currently loaded +// modules. +void module_list_print(void) +{ + GSList *mel; + gsize maxlen = 0; + gchar *format; + GString *message; + + if (!loaded_modules) { + scr_LogPrint(LPRINT_LOGNORM, "No modules loaded."); + return; + } + + // Counnt maximum module name length + for (mel = loaded_modules; mel; mel = mel -> next) { + loaded_module_t *module = mel->data; + gsize len = strlen(module->name); + if (len > maxlen) + maxlen = len; + } + + // Create format string + format = g_strdup_printf("%%-%us %%2u (%%c)", maxlen); + + // Fill the message to be printed + message = g_string_new("Loaded modules:\n"); + for (mel = loaded_modules; mel; mel = mel -> next) { + loaded_module_t *module = mel->data; + GSList *dep; + + g_string_append_printf(message, format, module->name, module->refcount, module->locked ? 'M' : 'A'); + + // Append loaded module dependencies + if (module->dependencies) { + g_string_append(message, " depends: "); + + for (dep = module->dependencies; dep; dep = dep->next) { + const gchar *name = dep->data; + g_string_append(message, name); + g_string_append(message, ", "); + } + + // Chop extra ", " + g_string_truncate(message, message->len - 2); + } + + g_string_append_c(message, '\n'); + } + + // Chop extra "\n" + g_string_truncate(message, message->len - 1); + + scr_LogPrint(LPRINT_LOGNORM, "%s", message->str); + + g_string_free(message, TRUE); + g_free(format); +} + +// modules_init() +// Initializes module system. +void modules_init(void) +{ +} + +// modules_deinit() +// Unloads all the modules. +void modules_deinit(void) +{ + GSList *mel; + + // We need only manually loaded modules + for (mel = loaded_modules; mel; mel = mel->next) { + loaded_module_t *module = mel->data; + if (module->locked) + break; + } + + while (mel) { + loaded_module_t *module = mel->data; + const gchar *err; + + // Find next manually loaded module to treat + for (mel = mel->next; mel; mel = mel->next) { + loaded_module_t *module = mel->data; + if (module->locked) + break; + } + + // Unload module + scr_LogPrint(LPRINT_LOGNORM, "Unloading module %s.", module->name); + err = module_unload(module->name, TRUE, FALSE); + if (err) + scr_LogPrint(LPRINT_LOGNORM, "* Module unloading failed: %s.", err); + } +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0 ts=2 sw=2: For Vim users... */