view mcabber/mcabber/modules.c @ 1736:15e1f3957786

Misc. small style changes (I.e. line length < 80 whenever possible)
author Mikael Berthe <mikael@lilotux.net>
date Sat, 06 Mar 2010 19:21:05 +0100
parents 5093b5ca1572
children 7ee390513463
line wrap: on
line source

/*
 * 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 et cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */