changeset 1735:5093b5ca1572

New modules loading scheme
author Myhailo Danylenko <isbear@ukrpost.net>
date Thu, 04 Mar 2010 13:03:20 +0200
parents eae4a2637f2c
children 15e1f3957786
files mcabber/doc/HOWTO_modules.txt mcabber/doc/help/en/hlp_module.txt mcabber/mcabber/Makefile.am mcabber/mcabber/commands.c mcabber/mcabber/main.c mcabber/mcabber/modules.c mcabber/mcabber/modules.h mcabber/mcabber/settings.c mcabber/modules/beep/beep.c
diffstat 9 files changed, 700 insertions(+), 154 deletions(-) [+]
line wrap: on
line diff
--- 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_<modulename>. If module name
+contains any extra symbols except [a-z0-9_] they should
+be replaced with '_'.
 
 --------------------------------------------------------
+  #include <mcabber/modules.h>
+
+  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 <glib.h>
+  #include <gmodule.h>
+
   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 <mcabber/modules.h>
+
+  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 <mcabber/commands.h>
 
   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 <mcabber/logprint.h>
+/* module_info_t definition */
+#include <mcabber/modules.h>
+
+/* 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 <glib.h>
-#include <gmodule.h>
-
 #include <mcabber/logprint.h>
 #include <mcabber/commands.h>
+#include <mcabber/modules.h>
 
 /* 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 <glib.h>
-#include <gmodule.h>
-
 #include <mcabber/logprint.h>
 #include <mcabber/commands.h>
+#include <mcabber/modules.h>
 #include <mcabber/compl.h>
 
 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 <glib.h>
-#include <gmodule.h>
 #include <string.h>
 
 #include <mcabber/logprint.h>
@@ -359,6 +490,7 @@
 #include <mcabber/hooks.h>
 #include <mcabber/screen.h>
 #include <mcabber/settings.h>
+#include <mcabber/module.h>
 
 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 <mcabber/modules.h>
+
+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 <glib.h>
+#include <gmodule.h>
+
+#include <mcabber/main.h>
+
+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
 
--- /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]
--- 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
--- 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 <gmodule.h>
-
-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
 
--- 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
--- /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... */
--- /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 <glib.h>
+
+// Module loading process looks like this:
+//   check, if module is loaded
+//   load module (+ run g_module_check_init)
+//   check <modulename>_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
--- 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): "
--- 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 <http://www.gnu.org/licenses/>. */
 
-#include <glib.h>
-#include <gmodule.h>
 #include <string.h>
 
 #include <mcabber/logprint.h>
@@ -26,11 +24,23 @@
 #include <mcabber/hooks.h>
 #include <mcabber/screen.h>
 #include <mcabber/settings.h>
+#include <mcabber/modules.h>
+
+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);