module-dir.c revision 8e56d284c950a645cac5c4cb2a984b6e65b0cc6a
/* Copyright (C) 2003 Timo Sirainen */
#include "lib.h"
#include "array.h"
#include "module-dir.h"
#ifdef HAVE_MODULES
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <dlfcn.h>
#ifndef RTLD_GLOBAL
# define RTLD_GLOBAL 0
#endif
#ifndef RTLD_NOW
# define RTLD_NOW 0
#endif
void *module_get_symbol(struct module *module, const char *symbol)
{
const char *error;
void *ret;
/* get our init func */
ret = dlsym(module->handle, symbol);
error = dlerror();
if (error != NULL) {
i_error("module %s: dlsym(%s) failed: %s",
module->path, symbol, error);
ret = NULL;
}
return ret;
}
const char *module_file_get_name(const char *fname)
{
const char *p;
/* [lib][nn_]name(.so) */
if (strncmp(fname, "lib", 3) == 0)
fname += 3;
for (p = fname; *p != '\0'; p++) {
if (*p < '0' || *p > '9')
break;
}
if (*p == '_')
fname = p + 1;
p = strstr(fname, ".so");
if (p == NULL)
return fname;
return t_strdup_until(fname, p);
}
static void *get_symbol(struct module *module, const char *symbol, bool quiet)
{
if (quiet)
return dlsym(module->handle, symbol);
return module_get_symbol(module, symbol);
}
static void module_free(struct module *module)
{
if (module->deinit != NULL)
module->deinit();
if (dlclose(module->handle) != 0)
i_error("dlclose(%s) failed: %m", module->path);
i_free(module->path);
i_free(module->name);
i_free(module);
}
static struct module *
module_load(const char *path, const char *name, bool require_init_funcs)
{
void *handle;
void (*init)(void);
struct module *module;
handle = dlopen(path, RTLD_GLOBAL | RTLD_NOW);
if (handle == NULL) {
i_error("dlopen(%s) failed: %s", path, dlerror());
return NULL;
}
module = i_new(struct module, 1);
module->path = i_strdup(path);
module->name = i_strdup(name);
module->handle = handle;
/* get our init func */
init = (void (*)(void))
get_symbol(module, t_strconcat(name, "_init", NULL),
!require_init_funcs);
module->deinit = init == NULL ? NULL : (void (*)(void))
get_symbol(module, t_strconcat(name, "_deinit", NULL),
!require_init_funcs);
if ((init == NULL || module->deinit == NULL) && require_init_funcs) {
module->deinit = NULL;
module_free(module);
return NULL;
}
if (getenv("DEBUG") != NULL)
i_info("Module loaded: %s", path);
if (init != NULL)
init();
return module;
}
static int module_name_cmp(const void *p1, const void *p2)
{
const char *const *n1 = p1, *const *n2 = p2;
const char *s1 = *n1, *s2 = *n2;
if (strncmp(s1, "lib", 3) == 0)
s1 += 3;
if (strncmp(s2, "lib", 3) == 0)
s2 += 3;
return strcmp(s1, s2);
}
static bool module_want_load(const char **names, const char *name)
{
size_t len;
if (names == NULL)
return TRUE;
len = strlen(name);
if (len > 7 && strcmp(name + len - 7, "_plugin") == 0)
name = t_strndup(name, len - 7);
for (; *names != NULL; names++) {
if (strcmp(*names, name) == 0) {
*names = "";
return TRUE;
}
}
return FALSE;
}
struct module *module_dir_load(const char *dir, const char *module_names,
bool require_init_funcs)
{
DIR *dirp;
struct dirent *d;
const char *name, *path, *p, *stripped_name, **names_p;
const char **module_names_arr;
struct module *modules, *module;
unsigned int i, count;
array_t ARRAY_DEFINE(names, const char *);
pool_t pool;
if (getenv("DEBUG") != NULL)
i_info("Loading modules from directory: %s", dir);
dirp = opendir(dir);
if (dirp == NULL) {
if (errno != ENOENT)
i_error("opendir(%s) failed: %m", dir);
return NULL;
}
pool = pool_alloconly_create("module loader", 1024);
ARRAY_CREATE(&names, pool, const char *, 32);
modules = NULL;
while ((d = readdir(dirp)) != NULL) {
name = d->d_name;
if (name[0] == '.')
continue;
p = strstr(name, ".so");
if (p == NULL || strlen(p) != 3)
continue;
name = p_strdup(pool, d->d_name);
array_append(&names, &name, 1);
}
names_p = array_get_modifyable(&names, NULL);
count = array_count(&names);
qsort(names_p, count, sizeof(const char *), module_name_cmp);
t_push();
if (module_names == NULL)
module_names_arr = NULL;
else {
module_names_arr = t_strsplit_spaces(module_names, ", ");
/* allow giving the module names also in non-base form.
conver them in here. */
for (i = 0; module_names_arr[i] != NULL; i++) {
module_names_arr[i] =
module_file_get_name(module_names_arr[i]);
}
}
for (i = 0; i < count; i++) {
const char *name = names_p[i];
t_push();
stripped_name = module_file_get_name(name);
if (!module_want_load(module_names_arr, stripped_name))
module = NULL;
else {
path = t_strconcat(dir, "/", name, NULL);
module = module_load(path, stripped_name,
require_init_funcs);
if (module == NULL && module_names_arr != NULL)
exit(FATAL_DEFAULT);
}
t_pop();
if (module != NULL) {
module->next = modules;
modules = module;
}
}
if (module_names_arr != NULL) {
/* make sure all modules were found */
for (; *module_names_arr != NULL; module_names_arr++) {
if (**module_names_arr != '\0') {
i_fatal("Plugin %s not found from directory %s",
*module_names_arr, dir);
}
}
}
t_pop();
pool_unref(pool);
if (closedir(dirp) < 0)
i_error("closedir(%s) failed: %m", dir);
return modules;
}
void module_dir_deinit(struct module *modules)
{
struct module *module;
for (module = modules; module != NULL; module = module->next) {
if (module->deinit != NULL) {
module->deinit();
module->deinit = NULL;
}
}
}
void module_dir_unload(struct module **modules)
{
struct module *module, *next;
for (module = *modules; module != NULL; module = next) {
next = module->next;
module_free(module);
}
*modules = NULL;
}
#else
struct module *module_dir_load(const char *dir __attr_unused__,
bool require_init_funcs __attr_unused__)
{
i_error("Dynamically loadable module support not built in");
return NULL;
}
void module_dir_deinit(struct module *modules __attr_unused__)
{
}
void module_dir_unload(struct module **modules __attr_unused__)
{
}
#endif