# HG changeset patch # User Myhailo Danylenko # Date 1267700600 -7200 # Node ID 5093b5ca1572d4a7bf51627c1b0a9dce27b4b3e4 # Parent eae4a2637f2c96167823d7ba4cc566448f0699bc New modules loading scheme diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/doc/HOWTO_modules.txt --- a/mcabber/doc/HOWTO_modules.txt Tue Mar 02 13:47:43 2010 +0100 +++ b/mcabber/doc/HOWTO_modules.txt Thu Mar 04 13:03:20 2010 +0200 @@ -5,18 +5,48 @@ =========================================== -Mcabber loads modules via glib's GModule. - -Thus, in your module you can provide functions +To obtain information on module mcabber uses struct +module_info_t, that module should provide in public +variable with name info_. If module name +contains any extra symbols except [a-z0-9_] they should +be replaced with '_'. -------------------------------------------------------- + #include + + typedef void (*module_init_t)(void); + typedef void (*module_uninit_t)(void); + + typedef struct { + const gchar *mcabber_version; + module_init_t init; + module_uninit_t uninit; + const gchar **requires; + } module_info_t; +-------------------------------------------------------- + +Callbacks init and uninit will be called after module +and it's dependencies loading. 'requires' should contain +NULL-terminated list of module names, that should be loaded +before this. 'mcabber_version' is required and should contain +mcabber version, that this module is designed to work with. +Three other fields may be NULL. + +To load modules, mcabber uses glib's GModule, thus, in your +module you can also use functions + +-------------------------------------------------------- + #include + #include + const gchar* g_module_check_init (GModule *module); void g_module_unload (GModule *module); -------------------------------------------------------- -to do something when module is loaded and unloaded. On -success g_module_check_init should return NULL, and -error message otherwise. +to do something before any version/dependency checks will +be performed when module is loaded/unloaded. On success +g_module_check_init should return NULL, and error message +otherwise. As module is loaded, you can use mcabber functions, declared in mcabber's header files (though you should @@ -28,6 +58,46 @@ especially for module writers. -------------------------------------------------------- + #include + + const gchar *module_load (const gchar *name, + gboolean manual, + gboolean force); + const gchar *module_unload (const gchar *name, + gboolean manual, + gboolean force); +-------------------------------------------------------- + +These functions load and unload modules respectively. +You can use them to handle optional dependencies. What +happens, when module is loaded: + - check if module is present, and if present just + increase it's reference count + - load .so via glib (and call g_module_check_init, if + present) + - check for information structure presence + - check target mcabber version compatibility + - load modules, that this module requires (note, that + dependency problems will be reported as error + invariably, force flag have no effect on this check) + - module placed into a list of modules + - module init routine is called +And when unloaded: + - check if module is present + - decrease reference count, if it is not zero, return + - run module uninit routine + - unload modules, that were loaded as dependencies for + this + - remove from modules list +They return error message or NULL in case of success. +'manual' flag indicates, that module will be loaded by +direct user request. It serves the purpose of tracking +user and automatic references (user can have only one). +'force' flag on module loading causes mcabber to ignore +most of the loading errors. On unload it forces +unloading even if reference count is not zero. + +-------------------------------------------------------- #include void cmd_add (const char *name, const char *help, @@ -200,10 +270,10 @@ Now, compile this file (hello.c) with libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \ - gmodule-2.0` -c hello.c + gmodule-2.0 mcabber` -c hello.c libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ - `pkg-config --libs glib-2.0 gmodule-2.0` -o libhello.la \ - hello.lo + `pkg-config --libs glib-2.0 gmodule-2.0 mcabber` \ + -o libhello.la hello.lo (you should substitute /usr/lib/mcabber to directory, where your modules are located) and then install obtained module with @@ -216,13 +286,68 @@ wide library, but maybe some systems require that. Now, set modules_dir mcabber variable to point to your modules -dir, and try to run /load hello. If all goes well, you should -see in status buffer message "Hello World!". -Now unload module by running command /unload hello, that -should bring up message "Bye, World!". +dir, and try to run /module -f load hello. If all goes well, +you should see in status buffer message "Hello World!" (as +well as some complaints, as we forced module loading). +Now unload module by running command /module unload hello, +that should bring up message "Bye, World!". That's it, you just created very simple dynamically loadable -mcabber module. +mcabber module. But, as you noticed, it needs force to be +loaded. Now, let's add information structure, that mcabber +wants. + +========================== + + Example: info struct + +========================== + +-------------------------------------------------------- +#include +/* module_info_t definition */ +#include + +/* Print something on module loading */ +void hello_init (void) +{ + scr_LogPrint (LPRINT_NORMAL, "Hello, World!"); +} + +/* ... and unloading */ +void hello_uninit (void) +{ + scr_LogPrint (LPRINT_NORMAL, "Bye, World!"); +} + +module_info_t info_hello = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = hello_init, + .uninit = hello_uninit, +}; + +/* The End */ +-------------------------------------------------------- + +Here we now do not use glib nor gmodule, so, we can omit +them in compilation lines: + +libtool --mode=compile gcc `pkg-config --cflags mcabber` \ + -c hello.c +libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ + `pkg-config --libs mcabber` -o libhello.la hello.lo + +Again compile it, copy, and try to load, now without -f +flag. As you may notice, when loading previous example, +mcabber first printed "Hello, World!", and only then +complaint about module not having information struct. +That's because g_module_check_init is called right +after module loading, before mcabber even have a chance +to look at module, while .init from info struct is +called afterwards by mcabber itself. You can try to +introduce some error (eg too high or missing target +mcabber version) and see the difference. ======================= @@ -233,11 +358,9 @@ Now, let's allow our module to do some real work. -------------------------------------------------------- -#include -#include - #include #include +#include /* Handler for command */ void do_hello (char *args) @@ -249,18 +372,24 @@ } /* Register command */ -const gchar* g_module_check_init (GModule *module) +void hello_init (void) { cmd_add ("hello", "", 0, 0, do_hello, NULL); - return NULL; } /* Unregister command */ -void g_module_unload (GModule *module) +void hello_uninit (void) { cmd_del ("hello"); } +module_info_t hello_info = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = hello_init, + .uninit = hello_uninit, +} + /* The End */ -------------------------------------------------------- @@ -281,11 +410,9 @@ their IDs are listed in compl.h. -------------------------------------------------------- -#include -#include - #include #include +#include #include static guint hello_cid = 0; @@ -303,7 +430,7 @@ } /* Initialization */ -const gchar* g_module_check_init (GModule *module) +void hello_init (void) { /* Obtain handle for our completion * category */ @@ -315,11 +442,10 @@ "World"); cmd_add ("hello", "", hello_cid, 0, do_hello, NULL); - return NULL; } /* Deinitialization */ -void g_module_unload (GModule *module) +void hello_uninit (void) { /* Give back category handle */ if (hello_cid) @@ -327,6 +453,13 @@ cmd_del ("hello"); } +module_info_t hello_info = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = hello_init, + .uninit = hello_uninit, +} + /* The End */ -------------------------------------------------------- @@ -349,8 +482,6 @@ muc conference message, not just ones, directed to me. -------------------------------------------------------- -#include -#include #include #include @@ -359,6 +490,7 @@ #include #include #include +#include static guint beep_cid = 0; @@ -400,7 +532,7 @@ } /* Initialization */ -const gchar* g_module_check_init (GModule *module) +void beep_init (void) { /* Create completions */ beep_cid = compl_new_category (); @@ -414,11 +546,10 @@ * We are only interested in incoming message events */ hk_add_handler (beep_hh, HOOK_MESSAGE_IN, NULL); - return NULL; } /* Deinitialization */ -void g_module_unload (GModule *module) +void beep_uninit (void) { /* Unregister event handler */ hk_del_handler (beep_hh, NULL); @@ -429,20 +560,16 @@ compl_del_category (beep_cid); } +module_info_t beep_info = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = beep_init, + .uninit = beep_uninit, +} + /* The End */ -------------------------------------------------------- -Note, that to compile this we also need to add loudmouth-1.0 -to pkg-config command line, so, you will have something like - -libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \ - gmodule-2.0 loudmouth-1.0` -c beep.c -libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \ - `pkg-config --cflags glib-2.0 gmodule-2.0` -o libbeep.la \ - beep.lo -libtool --mode=install install libbeep.la \ - /usr/lib/mcabber/libbeep.la - If you use CMake (as do I), corresponding CMakeLists.txt snippet: @@ -450,31 +577,86 @@ cmake_minimum_required(VERSION 2.6) project(beep C) -set(MCABBER_INCLUDE_DIR "/usr/include" CACHE FILEPATH - "Path to mcabber headers") - find_package(PkgConfig REQUIRED) -pkg_check_modules(GLIB REQUIRED glib-2.0) -pkg_check_modules(GMODULE REQUIRED gmodule-2.0) -pkg_check_modules(LM REQUIRED loudmouth-1.0) +pkg_check_modules(MCABBER REQUIRED mcabber) # this one should be before any target definitions -link_directories(${GLIB_LIBRARY_DIRS} - ${GMODULE_LIBRARY_DIRS}) +link_directories(${MCABBER_LIBRARY_DIRS}) add_library(beep MODULE beep.c) -include_directories(SYSTEM ${GLIB_INCLUDE_DIRS} - ${GMODULE_INCLUDE_DIRS} - ${LM_INCLUDE_DIRS} - ${MCABBER_INCLUDE_DIR}) -target_link_libraries(beep ${GLIB_LIBRARIES} - ${GMODULE_LIBRARIES}) +include_directories(SYSTEM ${MCABBER_INCLUDE_DIRS}) +target_link_libraries(beep ${MCABBER_LIBRARIES) include_directories(${beep_SOURCE_DIR} ${beep_BINARY_DIR}) install(TARGETS beep DESTINATION lib/mcabber) -------------------------------------------------------- +=========================== + + Example: dependencies + +=========================== + +I will not provide here a complete example of two +modules, one of which depends on other, only some +use cases. + +Info struct for module, that depends on two other +modules: + +-------------------------------------------------------- +#include + +const gchar *a_deps[] = { "b", "c", NULL }; + +module_info_t info_a = { + .mcabber_version = "0.10.0", + .requires = a_deps, + .init = a_init, + .uninit = a_uninit, +}; +-------------------------------------------------------- + +If your module needs to "authenticate" mcabber version +too, this can be done in g_module_check_init: + +-------------------------------------------------------- +#include +#include + +#include + +const gchar *g_module_check_init (GModule *module) +{ + char *ver = mcabber_version (); + + // ver now contains version in format + // X.X.X[-xxx][ (XXXXXXXXX)] + if (...) + return "Incompatible mcabber version"; + + g_free (ver); + return NULL; +} +-------------------------------------------------------- + +Also you can use glib check_init routine to modify +module information, that will be checked by mcabber, +eg. if you want your module to always pass mcabber +version check, you can assign version, obtained from +mcabber_version() to corresponding field in your struct. +Or you can modify your module's dependencies, though +direct module_load() will have the same effect, and +can be used for optional dependencies, that your module +can work without. + +Note: remember, that g_module_check_init will be always +called, even if later module will not pass checks, thus: + - do not use functions from other modules there; + - provide g_module_unload to undo anything, check_init + has done. + ============== Further @@ -509,5 +691,5 @@ -- Myhailo Danylenko -- mailto:isbear@ukrpost.net -- xmpp:isbear@unixzone.org.ua - -- Mon, 18 Jan 2010 15:52:40 +0200 + -- Thu, 04 Mar 2010 09:32:38 +0200 diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/doc/help/en/hlp_module.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/doc/help/en/hlp_module.txt Thu Mar 04 13:03:20 2010 +0200 @@ -0,0 +1,13 @@ + + /MODULE [-f] load|unload module + /MODULE [list] + +Load or unload module. + +/module [-f] load module + Loads specified module. If -f flag is specified, most of module loading errors will be ignored. +/module [-f] unload module + Unloads specified module. + Note: The force flag will not remove any dependent on this modules! +/module [list] + Lists modules in a format: [modulename] [reference count] ([manually/automatically loaded]) [loaded modules, that this module depends on] diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/mcabber/Makefile.am --- a/mcabber/mcabber/Makefile.am Tue Mar 02 13:47:43 2010 +0100 +++ b/mcabber/mcabber/Makefile.am Thu Mar 04 13:03:20 2010 +0200 @@ -32,6 +32,7 @@ endif if INSTALL_HEADERS +mcabber_SOURCES += modules.c modules.h mcabberinclude_HEADERS = main.h roster.h events.h \ commands.h compl.h \ hbuf.h screen.h logprint.h \ @@ -40,7 +41,8 @@ xmpp.h xmpp_helper.h xmpp_defines.h \ xmpp_iq.h xmpp_iqrequest.h \ xmpp_muc.h xmpp_s10n.h \ - caps.h fifo.h help.h $(top_srcdir)/include/config.h + caps.h fifo.h help.h modules.h \ + $(top_srcdir)/include/config.h if OTR mcabberinclude_HEADERS += otr.h nohtml.h diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/mcabber/commands.c --- a/mcabber/mcabber/commands.c Tue Mar 02 13:47:43 2010 +0100 +++ b/mcabber/mcabber/commands.c Thu Mar 04 13:03:20 2010 +0200 @@ -98,17 +98,9 @@ static GSList *Commands; #ifdef MODULES_ENABLE -#include - -static void do_load(char *arg); -static void do_unload(char *arg); - -typedef struct { - char *name; - GModule *module; -} loaded_module_t; - -GSList *loaded_modules = NULL; +#include "modules.h" + +static void do_module(char *arg); gpointer cmd_del(const char *name) { @@ -205,8 +197,7 @@ COMPL_JID, COMPL_STATUS, &do_status_to); cmd_add("version", "Show mcabber version", 0, 0, &do_version); #ifdef MODULES_ENABLE - cmd_add("load", "Load module", 0, 0, &do_load); - cmd_add("unload", "Unload module", 0, 0, &do_unload); + cmd_add("module", "Manipulations with modules", 0, 0, &do_module); #endif // Status category @@ -343,23 +334,6 @@ compl_add_category_word(COMPL_COLOR, "mucnick"); } -#ifdef MODULES_ENABLE -void cmd_deinit () -{ - GSList *el = loaded_modules; - while (el) { - loaded_module_t *module = el->data; - if (!g_module_close ((GModule *) module->module)) - scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s", - g_module_error ()); - g_free (module->name); - g_free (module); - el = g_slist_next (el); - } - g_slist_free (loaded_modules); -} -#endif - // expandalias(line) // If there is one, expand the alias in line and returns a new allocated line // If no alias is found, returns line @@ -2980,65 +2954,33 @@ } #ifdef MODULES_ENABLE -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); -} - -static void do_load(char *arg) +static void do_module(char *arg) { - GModule *mod; - GSList *lmod; - char *mdir, *path; - if (!arg || !*arg) { - scr_LogPrint(LPRINT_LOGNORM, "Missing modulename."); - return; - } - lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator); - if (lmod) { - scr_LogPrint(LPRINT_LOGNORM, "Module %s is already loaded.", arg); - return; + gboolean force = FALSE; + char **args; + + if (arg[0] == '-' && arg[1] == 'f') { + force = TRUE; + arg +=2; + while (*arg && *arg == ' ') + ++arg; } - mdir = expand_filename(settings_opt_get("modules_dir")); - path = g_module_build_path(mdir ? mdir : PKGLIB_DIR, arg); - mod = g_module_open(path, G_MODULE_BIND_LAZY); - if (!mod) - scr_LogPrint(LPRINT_LOGNORM, "Module loading failed: %s", - g_module_error()); - else { - loaded_module_t *module = g_new(loaded_module_t, 1); - module->name = g_strdup(arg); - module->module = mod; - loaded_modules = g_slist_prepend(loaded_modules, module); - scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg); + + args = split_arg(arg, 2, 0); + if (!args[0] || !strcmp(args[0], "list")) { + module_list_print(); + } else { + const gchar *error; + if (!strcmp(args[0], "load")) + error = module_load(args[1], TRUE, force); + else if (!strcmp(args[0], "unload")) + error = module_unload(args[1], TRUE, force); + else + error = "Unknown subcommand"; + if (error) + scr_LogPrint(LPRINT_LOGNORM, "Error: %s.", error); } - g_free(path); - g_free(mdir); -} - -static void do_unload(char *arg) -{ - GSList *module; - if (!arg || !*arg) { - scr_LogPrint(LPRINT_LOGNORM, "Missing modulename."); - return; - } - module = g_slist_find_custom(loaded_modules, arg, module_list_comparator); - if (module) { - loaded_module_t *mod = module->data; - if (!g_module_close(mod->module)) - scr_LogPrint(LPRINT_LOGNORM, "Module unloading failed: %s", - g_module_error()); - else { - g_free(mod->name); - g_free(mod); - loaded_modules = g_slist_delete_link(loaded_modules, module); - scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg); - } - } else - scr_LogPrint(LPRINT_LOGNORM, "Module %s not loaded.", arg); + free_arg_lst(args); } #endif diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/mcabber/main.c --- a/mcabber/mcabber/main.c Tue Mar 02 13:47:43 2010 +0100 +++ b/mcabber/mcabber/main.c Thu Mar 04 13:03:20 2010 +0200 @@ -47,6 +47,10 @@ #include "help.h" #include "events.h" +#ifdef MODULES_ENABLE +# include "modules.h" +#endif + #ifdef ENABLE_HGCSET # include "hgcset.h" #endif @@ -361,6 +365,9 @@ settings_init(); scr_init_bindings(); caps_init(); +#ifdef MODULES_ENABLE + modules_init(); +#endif /* Initialize charset */ scr_InitLocaleCharSet(); ut_InitDebug(); @@ -471,7 +478,7 @@ evs_deinit(); scr_TerminateCurses(); #ifdef MODULES_ENABLE - cmd_deinit(); + modules_deinit(); #endif fifo_deinit(); #ifdef HAVE_LIBOTR diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/mcabber/modules.c --- /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 + * + * 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 +#include +#include + +#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... */ diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/mcabber/modules.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/modules.h Thu Mar 04 13:03:20 2010 +0200 @@ -0,0 +1,39 @@ +#ifndef __MCABBER_MODULES_H__ +#define __MCABBER_MODULES_H__ 1 + +#include + +// Module loading process looks like this: +// check, if module is loaded +// load module (+ run g_module_check_init) +// check _info variable +// check version +// load dependencies +// run initialization callback +// module loaded +// ... +// run uninitialization callback +// unload module (+ run g_module_unload) +// unload dependencies +// module unloaded + +typedef void (*module_init_t)(void); +typedef void (*module_uninit_t)(void); + +// public module-describing structure +typedef struct { + const gchar *mcabber_version; // Contains mcabber version string, that this module is written to work with + module_init_t init; // Initialization callback to be called after all dependencies will be loaded + module_uninit_t uninit; // Uninitialization callback to be called before module unloading + const gchar **requires; // NULL-terminated list of module names, that must be loaded before this module +} module_info_t; + +const gchar *module_load(const gchar *name, gboolean manual, gboolean force); +const gchar *module_unload(const gchar *name, gboolean manual, gboolean force); + +void module_list_print(void); + +void modules_init(void); +void modules_deinit(void); + +#endif diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/mcabber/settings.c --- a/mcabber/mcabber/settings.c Tue Mar 02 13:47:43 2010 +0100 +++ b/mcabber/mcabber/settings.c Thu Mar 04 13:03:20 2010 +0200 @@ -187,7 +187,7 @@ startswith(line, "source ", FALSE) || startswith(line, "color ", FALSE) || #ifdef MODULES_ENABLE - startswith(line, "load ", FALSE) || + startswith(line, "module ", FALSE) || #endif startswith(line, "status ", FALSE) || startswith(line, "otrpolicy", FALSE)) { @@ -201,7 +201,7 @@ !startswith(line, "status ", FALSE) && !startswith(line, "color ", FALSE) && #ifdef MODULES_ENABLE - !startswith(line, "load ", FALSE) && + !startswith(line, "module ", FALSE) && #endif !startswith(line, "otrpolicy ", FALSE)) { scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): " diff -r eae4a2637f2c -r 5093b5ca1572 mcabber/modules/beep/beep.c --- a/mcabber/modules/beep/beep.c Tue Mar 02 13:47:43 2010 +0100 +++ b/mcabber/modules/beep/beep.c Thu Mar 04 13:03:20 2010 +0200 @@ -16,8 +16,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include -#include #include #include @@ -26,11 +24,23 @@ #include #include #include +#include + +static void beep_init (void); +static void beep_uninit (void); + +/* Module description */ +module_info_t info_beep = { + .mcabber_version = "0.10.0", + .requires = NULL, + .init = beep_init, + .uninit = beep_uninit, +}; static guint beep_cid = 0; /* Event handler */ -void beep_hh (guint32 hid, hk_arg_t *args, gpointer userdata) +static void beep_hh (guint32 hid, hk_arg_t *args, gpointer userdata) { /* Check if beeping is enabled */ if (settings_opt_get_int ("beep_enable")) @@ -39,7 +49,7 @@ } /* beep command handler */ -void do_beep (char *args) +static void do_beep (char *args) { /* Check arguments, and if recognized, * set mcabber option accordingly */ @@ -67,7 +77,7 @@ } /* Initialization */ -const gchar* g_module_check_init (GModule *module) +static void beep_init (void) { /* Create completions */ beep_cid = compl_new_category (); @@ -81,11 +91,10 @@ * We are only interested in incoming message events */ hk_add_handler (beep_hh, HOOK_MESSAGE_IN, NULL); - return NULL; } /* Deinitialization */ -void g_module_unload (GModule *module) +static void beep_uninit (void) { /* Unregister event handler */ hk_del_handler (beep_hh, NULL);