Implemented settings_free()
This removes many leaked memory messages from valgrind and thus makes
it easier to read its output.
/*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <glib.h>
#include <gmodule.h>
#include <string.h>
#include "settings.h"
#include "config.h"
#include "modules.h"
#include "screen.h"
#include "utils.h"
// Registry of loaded modules
GSList *loaded_modules = NULL;
const gchar *mcabber_branch = MCABBER_BRANCH;
const guint mcabber_api_version = MCABBER_API_VERSION;
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;
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 structures list
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);
if(!g_module_close(mod))
scr_LogPrint(LPRINT_LOGNORM, "Error closing module: %s.",
g_module_error());
return "Module provides no information structure";
}
scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: "
"Module provides no information structure.");
}
g_free(varname);
info = var;
}
// Find appropriate info struct
if (info) {
while (info) {
if (!info->branch || !*(info->branch)) {
scr_LogPrint(LPRINT_DEBUG, "No branch name, "
"skipping info chunk.");
} else if (strcmp(info->branch, mcabber_branch)) {
scr_LogPrint(LPRINT_DEBUG, "Unhandled branch %s, "
"skipping info chunk.", info->branch);
} else if (info->api > mcabber_api_version ||
info->api < MCABBER_API_MIN) { // XXX force?
if(!g_module_close(mod))
scr_LogPrint(LPRINT_LOGNORM, "Error closing module: %s.",
g_module_error());
return "Incompatible mcabber API version";
} else
break;
info = info->next;
}
if (!info) { // XXX force?
if(!g_module_close(mod))
scr_LogPrint(LPRINT_LOGNORM, "Error closing module: %s.",
g_module_error());
return "No supported mcabber branch description found";
}
}
// Load dependencies
if (info && info->requires) {
const gchar **dep;
GSList *deps = NULL;
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, (gpointer) *dep);
}
g_slist_free(deps);
}
{ // 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;
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();
// Unload dependencies
if (info && info->requires) {
const gchar **dep;
for (dep = info->requires; *dep; ++dep) {
const gchar *err = module_unload(*dep, FALSE, FALSE);
if (err) // XXX
scr_LogPrint(LPRINT_LOGNORM,
"Error unloading automatically loaded module %s: %s.",
*dep, err);
}
}
// XXX Prevent uninitialization routine and dep unloading to be performed again
module->info = NULL;
// Unload module
if (!g_module_close(module->module))
return g_module_error(); // XXX destroy structure?
// Destroy structure
loaded_modules = g_slist_delete_link(loaded_modules, lmod);
// Output this here, as arg may point to module->name
scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", module->name);
g_free(module->name);
g_free(module);
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;
guint module_count = 0;
if (!loaded_modules) {
scr_LogPrint(LPRINT_LOGNORM, "No modules loaded.");
return;
}
// Count 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;
module_count++;
}
// Create format string
format = g_strdup_printf("%%-%us %%2u (%%c)", (unsigned)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;
g_string_append_printf(message, format, module->name, module->refcount,
module->locked ? 'M' : 'A');
if (module->info) {
module_info_t *info = module->info;
// Module version
if (info->version) {
g_string_append(message, " version: ");
g_string_append(message, info->version);
}
// Module dependencies
if (info->requires && *(info->requires)) {
const gchar **dep;
g_string_append(message, " depends: ");
for (dep = info->requires; *dep; ++dep) {
g_string_append(message, *dep);
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_NORMAL, "%s", message->str);
if (module_count + 1 > scr_getlogwinheight()) {
scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
}
g_string_free(message, TRUE);
g_free(format);
}
// module_info_print(name)
// Prints info about specific module
void module_info_print(const gchar *name)
{
GSList *lmod;
loaded_module_t *module;
module_info_t *info;
if (!name || !name[0]) {
scr_LogPrint(LPRINT_NORMAL, "Please specify a module name.");
return;
}
lmod = g_slist_find_custom(loaded_modules, name, module_list_comparator);
if (!lmod) {
scr_LogPrint(LPRINT_NORMAL, "Module %s not found.", name);
return;
}
module = lmod->data;
info = module->info;
scr_LogPrint(LPRINT_NORMAL, "Module %s", module->name);
scr_LogPrint(LPRINT_NORMAL, " Location: %s", g_module_name(module->module));
scr_LogPrint(LPRINT_NORMAL, " Loaded: %s",
module->locked ? "Manually" : "Automatically");
scr_LogPrint(LPRINT_NORMAL, " Reference count: %u", module->refcount);
if (info) {
if (info->version)
scr_LogPrint(LPRINT_NORMAL, " Version: %s", info->version);
scr_LogPrint(LPRINT_NORMAL, " API: %s:%u", info->branch, info->api);
if (info->requires && *(info->requires)) {
GString *message = g_string_new("Depends on: ");
const gchar **dep;
for (dep = info->requires; *dep; ++dep) {
g_string_append(message, *dep);
g_string_append(message, ", ");
}
// Chop last ", "
g_string_truncate(message, message->len - 2);
scr_LogPrint(LPRINT_NORMAL, " %s", message->str);
g_string_free(message, TRUE);
}
if (info->description)
scr_LogPrint(LPRINT_NORMAL, " Description: %s", info->description);
}
scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
}
// 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 sw=2 ts=2: For Vim users... */