Newer
Older
/*
* Asterisk -- A telephony toolkit for Linux.
*
* Module Loader
*
Mark Spencer
committed
* Copyright (C) 1999-2004, Digium, Inc.
Mark Spencer
committed
* Mark Spencer <markster@digium.com>
*
* This program is free software, distributed under the terms of
* the GNU General Public License
*/
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <asterisk/module.h>
#include <asterisk/options.h>
#include <asterisk/config.h>
James Golovich
committed
#include <asterisk/lock.h>
#ifdef __APPLE__
#include <asterisk/dlfcn-compat.h>
#else
static char expected_key[] =
{ 0x8e, 0x93, 0x22, 0x83, 0xf5, 0xc3, 0xc0, 0x75,
0xff, 0x8b, 0xa9, 0xbe, 0x7c, 0x43, 0x74, 0x63 };
struct module {
int (*load_module)(void);
int (*unload_module)(void);
int (*usecount)(void);
char *(*description)(void);
void *lib;
char resource[256];
struct module *next;
};
static int printdigest(unsigned char *d)
{
int x;
char buf[256];
char buf2[16];
snprintf(buf, sizeof(buf), "Unexpected signature:");
for (x=0;x<16;x++) {
snprintf(buf2, sizeof(buf2), " %02x", *(d++));
strcat(buf, buf2);
}
strcat(buf, "\n");
return 0;
}
static int key_matches(char *key1, char *key2)
{
int match = 1;
int x;
for (x=0;x<16;x++) {
match &= (key1[x] == key2[x]);
}
return match;
}
static int verify_key(char *key)
{
struct MD5Context c;
char digest[16];
MD5Init(&c);
MD5Update(&c, key, strlen(key));
MD5Final(digest, &c);
if (key_matches(expected_key, digest))
return 0;
printdigest(digest);
return -1;
}
static struct loadupdate {
int (*updater)(void);
struct loadupdate *next;
} *updaters = NULL;
AST_MUTEX_DEFINE_STATIC(modlock);
AST_MUTEX_DEFINE_STATIC(reloadlock);
int ast_unload_resource(const char *resource_name, int force)
{
struct module *m, *ml = NULL;
int res = -1;
if (ast_mutex_lock(&modlock))
ast_log(LOG_WARNING, "Failed to lock\n");
m = module_list;
while(m) {
if (!strcasecmp(m->resource, resource_name)) {
if ((res = m->usecount()) > 0) {
if (force)
ast_log(LOG_WARNING, "Warning: Forcing removal of module %s with use count %d\n", resource_name, res);
else {
ast_log(LOG_WARNING, "Soft unload failed, '%s' has use count %d\n", resource_name, res);
ast_mutex_unlock(&modlock);
return -1;
}
}
res = m->unload_module();
if (res) {
ast_log(LOG_WARNING, "Firm unload failed for %s\n", resource_name);
if (force <= AST_FORCE_FIRM) {
ast_mutex_unlock(&modlock);
return -1;
} else
ast_log(LOG_WARNING, "** Dangerous **: Unloading resource anyway, at user request\n");
}
if (ml)
ml->next = m->next;
else
module_list = m->next;
dlclose(m->lib);
free(m);
ast_mutex_unlock(&modlock);
char *ast_module_helper(char *line, char *word, int pos, int state, int rpos, int needsreload)
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
int which=0;
char *ret;
if (pos != rpos)
return NULL;
ast_mutex_lock(&modlock);
m = module_list;
while(m) {
if (!strncasecmp(word, m->resource, strlen(word)) && (m->reload || !needsreload)) {
if (++which > state)
break;
}
m = m->next;
}
if (m) {
ret = strdup(m->resource);
} else {
ret = NULL;
if (!strncasecmp(word, "astconfig", strlen(word))) {
if (++which > state)
ret = strdup("astconfig");
} else if (!strncasecmp(word, "manager", strlen(word))) {
if (++which > state)
ret = strdup("manager");
} else if (!strncasecmp(word, "enum", strlen(word))) {
if (++which > state)
ret = strdup("enum");
} else if (!strncasecmp(word, "rtp", strlen(word))) {
if (++which > state)
ret = strdup("rtp");
}
}
ast_mutex_unlock(&modlock);
return ret;
}
int ast_module_reload(const char *name)
{
struct module *m;
int reloaded = 0;
int oldversion;
int (*reload)(void);
/* We'll do the logger and manager the favor of calling its reload here first */
Martin Pycko
committed
if (ast_mutex_trylock(&reloadlock)) {
ast_verbose("The previous reload command didn't finish yet\n");
Martin Pycko
committed
}
if (!name || !strcasecmp(name, "astconfig")) {
read_ast_cust_config();
reloaded = 2;
}
if (!name || !strcasecmp(name, "manager")) {
reloaded = 2;
}
if (!name || !strcasecmp(name, "enum")) {
ast_enum_reload();
reloaded = 2;
}
if (!name || !strcasecmp(name, "rtp")) {
reloaded = 2;
}
ast_mutex_lock(&modlock);
Mark Spencer
committed
if (!name || !strcasecmp(name, m->resource)) {
if (reloaded < 1)
reloaded = 1;
reload = m->reload;
ast_mutex_unlock(&modlock);
if (reload) {
Mark Spencer
committed
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Reloading module '%s' (%s)\n", m->resource, m->description());
Mark Spencer
committed
}
ast_mutex_lock(&modlock);
if (oldversion != modlistver)
break;
ast_mutex_unlock(&modlock);
Martin Pycko
committed
ast_mutex_unlock(&reloadlock);
static int __load_resource(const char *resource_name, const struct ast_config *cfg)
{
static char fn[256];
int errors=0;
int res;
if ((val = ast_variable_retrieve(cfg, "global", resource_name))
&& ast_true(val))
flags |= RTLD_GLOBAL;
}
/* Resource modules are always loaded global and lazy */
if (ast_mutex_lock(&modlock))
ast_log(LOG_WARNING, "Failed to lock\n");
m = module_list;
while(m) {
if (!strcasecmp(m->resource, resource_name)) {
ast_log(LOG_WARNING, "Module '%s' already exists\n", resource_name);
ast_mutex_unlock(&modlock);
return -1;
}
m = m->next;
}
m = malloc(sizeof(struct module));
if (!m) {
ast_log(LOG_WARNING, "Out of memory\n");
ast_mutex_unlock(&modlock);
strncpy(m->resource, resource_name, sizeof(m->resource)-1);
snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_MODULE_DIR, resource_name);
if (!m->lib) {
ast_log(LOG_WARNING, "%s\n", dlerror());
free(m);
ast_mutex_unlock(&modlock);
return -1;
}
m->load_module = dlsym(m->lib, "load_module");
if (m->load_module == NULL)
m->load_module = dlsym(m->lib, "_load_module");
if (!m->load_module) {
ast_log(LOG_WARNING, "No load_module in module %s\n", fn);
errors++;
}
m->unload_module = dlsym(m->lib, "unload_module");
if (m->unload_module == NULL)
m->unload_module = dlsym(m->lib, "_unload_module");
if (!m->unload_module) {
ast_log(LOG_WARNING, "No unload_module in module %s\n", fn);
errors++;
}
m->usecount = dlsym(m->lib, "usecount");
if (m->usecount == NULL)
m->usecount = dlsym(m->lib, "_usecount");
if (!m->usecount) {
ast_log(LOG_WARNING, "No usecount in module %s\n", fn);
errors++;
}
m->description = dlsym(m->lib, "description");
if (m->description == NULL)
m->description = dlsym(m->lib, "_description");
if (!m->description) {
ast_log(LOG_WARNING, "No description in module %s\n", fn);
errors++;
}
if (m->key == NULL)
m->key = dlsym(m->lib, "_key");
if (!m->key) {
ast_log(LOG_WARNING, "No key routine in module %s\n", fn);
errors++;
}
m->reload = dlsym(m->lib, "reload");
if (m->reload == NULL)
m->reload = dlsym(m->lib, "_reload");
ast_log(LOG_WARNING, "Key routine returned NULL in module %s\n", fn);
key = NULL;
if (key && verify_key(key)) {
ast_log(LOG_WARNING, "Unexpected key returned by module %s\n", fn);
errors++;
}
if (errors) {
ast_log(LOG_WARNING, "%d error(s) loading module %s, aborted\n", errors, fn);
dlclose(m->lib);
free(m);
ast_mutex_unlock(&modlock);
ast_verbose( " => (%s)\n", term_color(tmp, m->description(), COLOR_BROWN, COLOR_BLACK, sizeof(tmp)));
if (option_console && !option_verbose)
ast_verbose( ".");
} else {
if (option_verbose)
ast_verbose(VERBOSE_PREFIX_1 "Loaded %s => (%s)\n", fn, m->description());
}
/* add module 'm' to end of module_list chain
so reload commands will be issued in same order modules were loaded */
m->next = NULL;
if (module_list == NULL) {
/* empty list so far, add at front */
module_list = m;
}
else {
struct module *i;
/* find end of chain, and add there */
for (i = module_list; i->next; i = i->next)
;
i->next = m;
}
ast_mutex_unlock(&modlock);
ast_log(LOG_WARNING, "%s: load_module failed, returning %d\n", m->resource, res);
}
int ast_load_resource(const char *resource_name)
{
int o;
struct ast_config *cfg = NULL;
int res;
/* Keep the module file parsing silent */
o = option_verbose;
option_verbose = 0;
cfg = ast_load(AST_MODULE_CONFIG);
option_verbose = o;
res = __load_resource(resource_name, cfg);
if (cfg)
ast_destroy(cfg);
return res;
if (ast_mutex_lock(&modlock))
ast_log(LOG_WARNING, "Failed to lock\n");
m = module_list;
while(m) {
if (!strcasecmp(resource, m->resource))
break;
m = m->next;
}
ast_mutex_unlock(&modlock);
if (m)
return -1;
else
return 0;
}
static const char *loadorder[] =
{
"res_",
"chan_",
"pbx_",
NULL,
};
int load_modules()
{
struct ast_config *cfg;
struct ast_variable *v;
if (option_verbose)
ast_verbose( "Asterisk Dynamic Loader Starting:\n");
cfg = ast_load(AST_MODULE_CONFIG);
if (cfg) {
/* Load explicitly defined modules */
v = ast_variable_browse(cfg, "modules");
while(v) {
if (!strcasecmp(v->name, "load")) {
if (option_debug && !option_verbose)
ast_log(LOG_DEBUG, "Loading module %s\n", v->value);
if (option_verbose) {
ast_verbose( VERBOSE_PREFIX_1 "[%s]", term_color(tmp, v->value, COLOR_BRWHITE, 0, sizeof(tmp)));
if (__load_resource(v->value, cfg)) {
ast_log(LOG_WARNING, "Loading module %s failed!\n", v->value);
if (cfg)
ast_destroy(cfg);
return -1;
}
}
v=v->next;
}
}
if (!cfg || ast_true(ast_variable_retrieve(cfg, "modules", "autoload"))) {
/* Load all modules */
DIR *mods;
struct dirent *d;
/* Loop through each order */
for (x=0;x<sizeof(loadorder) / sizeof(loadorder[0]);x++) {
mods = opendir((char *)ast_config_AST_MODULE_DIR);
if (mods) {
while((d = readdir(mods))) {
/* Must end in .so to load it. */
if ((strlen(d->d_name) > 3) &&
(!loadorder[x] || !strncasecmp(d->d_name, loadorder[x], strlen(loadorder[x]))) &&
!strcasecmp(d->d_name + strlen(d->d_name) - 3, ".so") &&
!ast_resource_exists(d->d_name)) {
/* It's a shared library -- Just be sure we're allowed to load it -- kinda
an inefficient way to do it, but oh well. */
if (cfg) {
v = ast_variable_browse(cfg, "modules");
while(v) {
if (!strcasecmp(v->name, "noload") &&
!strcasecmp(v->value, d->d_name))
break;
v = v->next;
}
if (v) {
if (option_verbose) {
ast_verbose( VERBOSE_PREFIX_1 "[skipping %s]\n", d->d_name);
fflush(stdout);
}
continue;
}
if (option_debug && !option_verbose)
ast_log(LOG_DEBUG, "Loading module %s\n", d->d_name);
if (option_verbose) {
ast_verbose( VERBOSE_PREFIX_1 "[%s]", term_color(tmp, d->d_name, COLOR_BRWHITE, 0, sizeof(tmp)));
if (__load_resource(d->d_name, cfg)) {
ast_log(LOG_WARNING, "Loading module %s failed!\n", d->d_name);
if (cfg)
ast_destroy(cfg);
return -1;
closedir(mods);
} else {
if (!option_quiet)
ast_log(LOG_WARNING, "Unable to open modules directory %s.\n", (char *)ast_config_AST_MODULE_DIR);
}
}
ast_destroy(cfg);
return 0;
}
void ast_update_use_count(void)
{
/* Notify any module monitors that the use count for a
resource has changed */
struct loadupdate *m;
if (ast_mutex_lock(&modlock))
ast_log(LOG_WARNING, "Failed to lock\n");
m = updaters;
while(m) {
m->updater();
m = m->next;
}
ast_mutex_unlock(&modlock);
int ast_update_module_list(int (*modentry)(char *module, char *description, int usecnt, char *like), char *like)
int total_mod_loaded = 0;
if (ast_mutex_trylock(&modlock))
total_mod_loaded += modentry(m->resource, m->description(), m->usecount(), like);
ast_mutex_unlock(&modlock);
return total_mod_loaded;
}
int ast_loader_register(int (*v)(void))
{
struct loadupdate *tmp;
/* XXX Should be more flexible here, taking > 1 verboser XXX */
if ((tmp = malloc(sizeof (struct loadupdate)))) {
tmp->updater = v;
if (ast_mutex_lock(&modlock))
ast_log(LOG_WARNING, "Failed to lock\n");
tmp->next = updaters;
updaters = tmp;
ast_mutex_unlock(&modlock);
return 0;
}
return -1;
}
int ast_loader_unregister(int (*v)(void))
{
int res = -1;
struct loadupdate *tmp, *tmpl=NULL;
if (ast_mutex_lock(&modlock))
ast_log(LOG_WARNING, "Failed to lock\n");
tmp = updaters;
while(tmp) {
if (tmp->updater == v) {
if (tmpl)
tmpl->next = tmp->next;
else
updaters = tmp->next;
break;
}
tmpl = tmp;
tmp = tmp->next;
}
if (tmp)
res = 0;
ast_mutex_unlock(&modlock);