Skip to content
Snippets Groups Projects
loader.c 46 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Asterisk -- An open source telephony toolkit.
     *
     * Copyright (C) 1999 - 2006, Digium, Inc.
     *
     * Mark Spencer <markster@digium.com>
     * Kevin P. Fleming <kpfleming@digium.com>
     * Luigi Rizzo <rizzo@icir.org>
     *
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    /*! \file
     *
     * \brief Module Loader
    
     * \author Mark Spencer <markster@digium.com>
    
     * \author Kevin P. Fleming <kpfleming@digium.com>
     * \author Luigi Rizzo <rizzo@icir.org>
     * - See ModMngMnt
     */
    
    
    /*** MODULEINFO
    	<support_level>core</support_level>
     ***/
    
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    #include "asterisk/_private.h"
    
    #include "asterisk/paths.h"	/* use ast_config_AST_MODULE_DIR */
    
    #include <editline/readline.h>
    
    #include "asterisk/dlinkedlists.h"
    
    #include "asterisk/module.h"
    #include "asterisk/config.h"
    #include "asterisk/channel.h"
    #include "asterisk/term.h"
    
    #include "asterisk/manager.h"
    #include "asterisk/cdr.h"
    #include "asterisk/enum.h"
    #include "asterisk/http.h"
    #include "asterisk/lock.h"
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    #include "asterisk/features_config.h"
    
    #include "asterisk/vector.h"
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    #include "asterisk/test.h"
    
    #include "asterisk/sounds_index.h"
    
    
    #include <dlfcn.h>
    
    #include "asterisk/md5.h"
    #include "asterisk/utils.h"
    
    
    	<managerEvent language="en_US" name="Reload">
    		<managerEventInstance class="EVENT_FLAG_SYSTEM">
    			<synopsis>Raised when a module has been reloaded in Asterisk.</synopsis>
    			<syntax>
    				<parameter name="Module">
    					<para>The name of the module that was reloaded, or
    					<literal>All</literal> if all modules were reloaded</para>
    				</parameter>
    				<parameter name="Status">
    					<para>The numeric status code denoting the success or failure
    					of the reload request.</para>
    					<enumlist>
    						<enum name="0"><para>Success</para></enum>
    						<enum name="1"><para>Request queued</para></enum>
    						<enum name="2"><para>Module not found</para></enum>
    						<enum name="3"><para>Error</para></enum>
    						<enum name="4"><para>Reload already in progress</para></enum>
    						<enum name="5"><para>Module uninitialized</para></enum>
    						<enum name="6"><para>Reload not supported</para></enum>
    					</enumlist>
    				</parameter>
    			</syntax>
    		</managerEventInstance>
    	</managerEvent>
    
    #ifndef RTLD_LOCAL
    #define RTLD_LOCAL 0
    #endif
    
    
    struct ast_module_user {
    	struct ast_channel *chan;
    	AST_LIST_ENTRY(ast_module_user) entry;
    };
    
    
    AST_DLLIST_HEAD(module_user_list, ast_module_user);
    
    static const unsigned char expected_key[] =
    
    { 0x87, 0x76, 0x79, 0x35, 0x23, 0xea, 0x3a, 0xd3,
      0x25, 0x2a, 0xbb, 0x35, 0x87, 0xe4, 0x22, 0x24 };
    
    
    static char buildopt_sum[33] = AST_BUILDOPT_SUM;
    
    AST_VECTOR(module_vector, struct ast_module *);
    
    
    /*!
     * \brief Internal flag to indicate all modules have been initially loaded.
     */
    static int modules_loaded;
    
    
    struct ast_module {
    	const struct ast_module_info *info;
    
    	/*! Used to get module references into refs log */
    
    	/*! The shared lib. */
    	void *lib;
    	/*! Number of 'users' and other references currently holding the module. */
    	int usecount;
    	/*! List of users holding the module. */
    	struct module_user_list users;
    
    		/*! The module running and ready to accept requests. */
    
    		/*! The module has declined to start. */
    
    		/*! This module is being held open until it's time to shutdown. */
    
    		unsigned int keepuntilshutdown:1;
    
    	AST_DLLIST_ENTRY(ast_module) entry;
    
    static AST_DLLIST_HEAD_STATIC(module_list, ast_module);
    
    static int module_vector_strcasecmp(struct ast_module *a, struct ast_module *b)
    {
    	return strcasecmp(a->resource, b->resource);
    }
    
    
    static int module_vector_cmp(struct ast_module *a, struct ast_module *b)
    {
    	/* if load_pri is not set, default is 128.  Lower is better */
    	int a_pri = ast_test_flag(a->info, AST_MODFLAG_LOAD_ORDER)
    		? a->info->load_pri : AST_MODPRI_DEFAULT;
    	int b_pri = ast_test_flag(b->info, AST_MODFLAG_LOAD_ORDER)
    		? b->info->load_pri : AST_MODPRI_DEFAULT;
    
    	/*
    	 * Returns comparison values for a vector sorted by priority.
    	 * <0 a_pri < b_pri
    	 * =0 a_pri == b_pri
    	 * >0 a_pri > b_pri
    	 */
    	return a_pri - b_pri;
    }
    
    
    const char *ast_module_name(const struct ast_module *mod)
    {
    	if (!mod || !mod->info) {
    		return NULL;
    	}
    
    	return mod->info->name;
    }
    
    
    struct loadupdate {
    	int (*updater)(void);
    	AST_LIST_ENTRY(loadupdate) entry;
    };
    
    
    static AST_DLLIST_HEAD_STATIC(updaters, loadupdate);
    
    
    AST_MUTEX_DEFINE_STATIC(reloadlock);
    
    
    struct reload_queue_item {
    	AST_LIST_ENTRY(reload_queue_item) entry;
    	char module[0];
    };
    
    static int do_full_reload = 0;
    
    
    static AST_DLLIST_HEAD_STATIC(reload_queue, reload_queue_item);
    
    /*!
     * \internal
     *
     * This variable is set by load_dynamic_module so ast_module_register
     * can know what pointer is being registered.
     *
     * This is protected by the module_list lock.
     */
    
    static struct ast_module *resource_being_loaded;
    
    /*!
     * \internal
     * \brief Used by AST_MODULE_INFO to register with the module loader.
     *
     * This function is automatically called when each module is opened.
     * It must never be used from outside AST_MODULE_INFO.
     */
    
    void ast_module_register(const struct ast_module_info *info)
    {
    
    	struct ast_module *mod;
    
    	/*
    	 * This lock protects resource_being_loaded as well as the module
    	 * list.  Normally we already have a lock on module_list when we
    	 * begin the load but locking again from here prevents corruption
    	 * if an asterisk module is dlopen'ed from outside the module loader.
    	 */
    	AST_DLLIST_LOCK(&module_list);
    	mod = resource_being_loaded;
    	if (!mod) {
    		AST_DLLIST_UNLOCK(&module_list);
    		return;
    	}
    
    	ast_debug(5, "Registering module %s\n", info->name);
    
    	/* This tells load_dynamic_module that we're registered. */
    	resource_being_loaded = NULL;
    
    
    #ifdef REF_DEBUG
    	mod->ref_debug = ao2_t_alloc(0, NULL, info->name);
    #endif
    
    	AST_LIST_HEAD_INIT(&mod->users);
    
    
    	AST_DLLIST_INSERT_TAIL(&module_list, mod, entry);
    	AST_DLLIST_UNLOCK(&module_list);
    
    
    	/* give the module a copy of its own handle, for later use in registrations and the like */
    	*((struct ast_module **) &(info->self)) = mod;
    }
    
    
    static void module_destroy(struct ast_module *mod)
    {
    	AST_LIST_HEAD_DESTROY(&mod->users);
    #ifdef REF_DEBUG
    	ao2_cleanup(mod->ref_debug);
    #endif
    	ast_free(mod);
    }
    
    
    void ast_module_unregister(const struct ast_module_info *info)
    {
    	struct ast_module *mod = NULL;
    
    	/* it is assumed that the users list in the module structure
    	   will already be empty, or we cannot have gotten to this
    	   point
    	*/
    
    	AST_DLLIST_LOCK(&module_list);
    	AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN(&module_list, mod, entry) {
    
    			AST_DLLIST_REMOVE_CURRENT(entry);
    
    	AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_END;
    	AST_DLLIST_UNLOCK(&module_list);
    
    		ast_debug(5, "Unregistering module %s\n", info->name);
    
    		module_destroy(mod);
    
    struct ast_module_user *__ast_module_user_add(struct ast_module *mod, struct ast_channel *chan)
    
    	struct ast_module_user *u;
    
    	u = ast_calloc(1, sizeof(*u));
    	if (!u) {
    
    
    	u->chan = chan;
    
    	AST_LIST_LOCK(&mod->users);
    	AST_LIST_INSERT_HEAD(&mod->users, u, entry);
    	AST_LIST_UNLOCK(&mod->users);
    
    
    #ifdef REF_DEBUG
    	ao2_ref(mod->ref_debug, +1);
    #endif
    
    
    	ast_atomic_fetchadd_int(&mod->usecount, +1);
    
    	ast_update_use_count();
    
    	return u;
    }
    
    void __ast_module_user_remove(struct ast_module *mod, struct ast_module_user *u)
    {
    
    	AST_LIST_LOCK(&mod->users);
    
    	u = AST_LIST_REMOVE(&mod->users, u, entry);
    
    	AST_LIST_UNLOCK(&mod->users);
    
    	if (!u) {
    		/*
    		 * Was not in the list.  Either a bad pointer or
    		 * __ast_module_user_hangup_all() has been called.
    		 */
    		return;
    	}
    
    
    #ifdef REF_DEBUG
    	ao2_ref(mod->ref_debug, -1);
    #endif
    
    
    	ast_atomic_fetchadd_int(&mod->usecount, -1);
    
    
    	ast_update_use_count();
    }
    
    void __ast_module_user_hangup_all(struct ast_module *mod)
    {
    	struct ast_module_user *u;
    
    	AST_LIST_LOCK(&mod->users);
    	while ((u = AST_LIST_REMOVE_HEAD(&mod->users, entry))) {
    
    		if (u->chan) {
    			ast_softhangup(u->chan, AST_SOFTHANGUP_APPUNLOAD);
    		}
    
    
    #ifdef REF_DEBUG
    		ao2_ref(mod->ref_debug, -1);
    #endif
    
    
    		ast_atomic_fetchadd_int(&mod->usecount, -1);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	ast_update_use_count();
    
    }
    
    /*! \note
     * In addition to modules, the reload command handles some extra keywords
     * which are listed here together with the corresponding handlers.
     * This table is also used by the command completion code.
     */
    static struct reload_classes {
    	const char *name;
    	int (*reload_fn)(void);
    } reload_classes[] = {	/* list in alpha order, longest match first for cli completion */
    
    	{ "acl",         ast_named_acl_reload },
    	{ "cdr",         ast_cdr_engine_reload },
    	{ "cel",         ast_cel_engine_reload },
    	{ "dnsmgr",      dnsmgr_reload },
    	{ "dsp",         ast_dsp_reload},
    	{ "extconfig",   read_config_maps },
    	{ "enum",        ast_enum_reload },
    	{ "features",    ast_features_config_reload },
    	{ "http",        ast_http_reload },
    
    	{ "indications", ast_indications_reload },
    
    	{ "logger",      logger_reload },
    	{ "manager",     reload_manager },
    	{ "plc",         ast_plc_reload },
    	{ "sounds",      ast_sounds_reindex },
    	{ "udptl",       ast_udptl_reload },
    	{ NULL,          NULL }
    
    };
    
    static int printdigest(const unsigned char *d)
    {
    	int x, pos;
    	char buf[256]; /* large enough so we don't have to worry */
    
    	for (pos = 0, x = 0; x < 16; x++)
    
    		pos += sprintf(buf + pos, " %02hhx", *d++);
    
    	ast_debug(1, "Unexpected signature:%s\n", buf);
    
    
    	return 0;
    }
    
    static int key_matches(const unsigned char *key1, const unsigned char *key2)
    {
    	int x;
    
    	for (x = 0; x < 16; x++) {
    		if (key1[x] != key2[x])
    			return 0;
    	}
    
    	return 1;
    }
    
    static int verify_key(const unsigned char *key)
    {
    	struct MD5Context c;
    	unsigned char digest[16];
    
    	MD5Init(&c);
    	MD5Update(&c, key, strlen((char *)key));
    	MD5Final(digest, &c);
    
    	if (key_matches(expected_key, digest))
    		return 0;
    
    	printdigest(digest);
    
    	return -1;
    }
    
    
    static size_t resource_name_baselen(const char *name)
    
    	size_t len = strlen(name);
    
    	if (len > 3 && !strcasecmp(name + len - 3, ".so")) {
    		return len - 3;
    
    
    	return len;
    }
    
    static int resource_name_match(const char *name1, size_t baselen1, const char *name2)
    {
    	if (baselen1 != resource_name_baselen(name2)) {
    		return -1;
    
    	return strncasecmp(name1, name2, baselen1);
    
    }
    
    static struct ast_module *find_resource(const char *resource, int do_lock)
    {
    	struct ast_module *cur;
    
    	size_t resource_baselen = resource_name_baselen(resource);
    
    	if (do_lock) {
    		AST_DLLIST_LOCK(&module_list);
    	}
    
    	AST_DLLIST_TRAVERSE(&module_list, cur, entry) {
    
    		if (!resource_name_match(resource, resource_baselen, cur->resource)) {
    
    	if (do_lock) {
    		AST_DLLIST_UNLOCK(&module_list);
    	}
    
    /*!
     * \brief dlclose(), with failure logging.
     */
    static void logged_dlclose(const char *name, void *lib)
    {
    
    	char *error;
    
    	if (!lib) {
    		return;
    	}
    
    	/* Clear any existing error */
    	dlerror();
    	if (dlclose(lib)) {
    		error = dlerror();
    		ast_log(AST_LOG_ERROR, "Failure in dlclose for module '%s': %s\n",
    			S_OR(name, "unknown"), S_OR(error, "Unknown error"));
    
    	}
    }
    
    #if defined(HAVE_RTLD_NOLOAD)
    /*!
     * \brief Check to see if the given resource is loaded.
     *
     * \param resource_name Name of the resource, including .so suffix.
     * \return False (0) if module is not loaded.
     * \return True (non-zero) if module is loaded.
     */
    static int is_module_loaded(const char *resource_name)
    {
    	char fn[PATH_MAX] = "";
    	void *lib;
    
    	snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_MODULE_DIR,
    		resource_name);
    
    	lib = dlopen(fn, RTLD_LAZY | RTLD_NOLOAD);
    
    	if (lib) {
    		logged_dlclose(resource_name, lib);
    		return 1;
    	}
    
    	return 0;
    }
    #endif
    
    
    static void unload_dynamic_module(struct ast_module *mod)
    {
    
    #if defined(HAVE_RTLD_NOLOAD)
    
    	char *name = ast_strdupa(ast_module_name(mod));
    
    	void *lib = mod->lib;
    
    	/* WARNING: the structure pointed to by mod is going to
    	   disappear when this operation succeeds, so we can't
    	   dereference it */
    
    	logged_dlclose(ast_module_name(mod), lib);
    
    
    	/* There are several situations where the module might still be resident
    	 * in memory.
    	 *
    	 * If somehow there was another dlopen() on the same module (unlikely,
    	 * since that all is supposed to happen in loader.c).
    	 *
    	 * Or the lazy resolution of a global symbol (very likely, since that is
    	 * how we load all of our modules that export global symbols).
    	 *
    	 * Avoid the temptation of repeating the dlclose(). The other code that
    	 * dlopened the module still has its module reference, and should close
    	 * it itself. In other situations, dlclose() will happily return success
    	 * for as many times as you wish to call it.
    	 */
    #if defined(HAVE_RTLD_NOLOAD)
    	if (is_module_loaded(name)) {
    		ast_log(LOG_WARNING, "Module '%s' could not be completely unloaded\n", name);
    	}
    #endif
    
    #define MODULE_LOCAL_ONLY (void *)-1
    
    
    /*!
     * \internal
     * \brief Attempt to dlopen a module.
     *
     * \param resource_in The module name to load.
     * \param so_ext ".so" or blank if ".so" is already part of resource_in.
     * \param filename Passed directly to dlopen.
     * \param flags Passed directly to dlopen.
     * \param suppress_logging Do not log any error from dlopen.
     *
     * \return Pointer to opened module, NULL on error.
     *
     * \warning module_list must be locked before calling this function.
     */
    static struct ast_module *load_dlopen(const char *resource_in, const char *so_ext,
    	const char *filename, int flags, unsigned int suppress_logging)
    
    	ast_assert(!resource_being_loaded);
    
    
    	mod = ast_calloc(1, sizeof(*mod) + strlen(resource_in) + strlen(so_ext) + 1);
    	if (!mod) {
    		return NULL;
    
    	sprintf(mod->resource, "%s%s", resource_in, so_ext); /* safe */
    
    	resource_being_loaded = mod;
    	mod->lib = dlopen(filename, flags);
    
    	if (resource_being_loaded) {
    		resource_being_loaded = NULL;
    
    		if (mod->lib) {
    
    			ast_log(LOG_ERROR, "Module '%s' did not register itself during load\n", resource_in);
    
    			logged_dlclose(resource_in, mod->lib);
    
    		} else if (!suppress_logging) {
    
    			ast_log(LOG_WARNING, "Error loading module '%s': %s\n", resource_in, dlerror());
    		}
    
    static struct ast_module *load_dynamic_module(const char *resource_in, unsigned int global_symbols_only, unsigned int suppress_logging)
    
    {
    	char fn[PATH_MAX];
    	struct ast_module *mod;
    	size_t resource_in_len = strlen(resource_in);
    	int exports_globals;
    	const char *so_ext = "";
    
    	if (resource_in_len < 4 || strcasecmp(resource_in + resource_in_len - 3, ".so")) {
    		so_ext = ".so";
    
    	snprintf(fn, sizeof(fn), "%s/%s%s", ast_config_AST_MODULE_DIR, resource_in, so_ext);
    
    	/* Try loading in quiet mode first with flags to export global symbols.
    	 * If the module does not want to export globals we will close and reopen. */
    	mod = load_dlopen(resource_in, so_ext, fn,
    		global_symbols_only ? RTLD_LAZY | RTLD_GLOBAL : RTLD_NOW | RTLD_LOCAL,
    		suppress_logging);
    
    	exports_globals = ast_test_flag(mod->info, AST_MODFLAG_GLOBAL_SYMBOLS);
    	if ((global_symbols_only && exports_globals) || (!global_symbols_only && !exports_globals)) {
    		/* The first dlopen had the correct flags. */
    		return mod;
    	}
    
    	/* Close the module so we can reopen with correct flags. */
    	logged_dlclose(resource_in, mod->lib);
    	if (global_symbols_only) {
    		return MODULE_LOCAL_ONLY;
    	}
    
    	return load_dlopen(resource_in, so_ext, fn,
    		exports_globals ? RTLD_LAZY | RTLD_GLOBAL : RTLD_NOW | RTLD_LOCAL,
    		0);
    
    int modules_shutdown(void)
    
    {
    	struct ast_module *mod;
    
    	int somethingchanged = 1, final = 0;
    
    	/*!\note Some resources, like timers, are started up dynamically, and thus
    	 * may be still in use, even if all channels are dead.  We must therefore
    	 * check the usecount before asking modules to unload. */
    	do {
    		if (!somethingchanged) {
    			/*!\note If we go through the entire list without changing
    			 * anything, ignore the usecounts and unload, then exit. */
    			final = 1;
    		}
    
    		/* Reset flag before traversing the list */
    		somethingchanged = 0;
    
    
    		AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN(&module_list, mod, entry) {
    
    			if (!final && mod->usecount) {
    
    				ast_debug(1, "Passing on %s: its use count is %d\n",
    					mod->resource, mod->usecount);
    
    			AST_DLLIST_REMOVE_CURRENT(entry);
    
    			if (mod->flags.running && !mod->flags.declined && mod->info->unload) {
    
    				ast_verb(1, "Unloading %s\n", mod->resource);
    
    				mod->info->unload();
    			}
    
    			module_destroy(mod);
    
    			somethingchanged = 1;
    		}
    
    		AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_END;
    
    		if (!somethingchanged) {
    			AST_DLLIST_TRAVERSE(&module_list, mod, entry) {
    				if (mod->flags.keepuntilshutdown) {
    					ast_module_unref(mod);
    					mod->flags.keepuntilshutdown = 0;
    					somethingchanged = 1;
    				}
    			}
    		}
    
    	} while (somethingchanged && !final);
    
    
    	final = AST_DLLIST_EMPTY(&module_list);
    
    	AST_DLLIST_UNLOCK(&module_list);
    
    int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode force)
    {
    	struct ast_module *mod;
    	int res = -1;
    	int error = 0;
    
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	if (!(mod = find_resource(resource_name, 0))) {
    
    		AST_DLLIST_UNLOCK(&module_list);
    
    		ast_log(LOG_WARNING, "Unload failed, '%s' could not be found\n", resource_name);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	}
    
    	if (!mod->flags.running || mod->flags.declined) {
    		ast_log(LOG_WARNING, "Unload failed, '%s' is not loaded.\n", resource_name);
    
    
    	if (!error && (mod->usecount > 0)) {
    
    			ast_log(LOG_WARNING, "Warning:  Forcing removal of module '%s' with use count %d\n",
    				resource_name, mod->usecount);
    		else {
    			ast_log(LOG_WARNING, "Soft unload failed, '%s' has use count %d\n", resource_name,
    				mod->usecount);
    			error = 1;
    		}
    	}
    
    	if (!error) {
    
    		/* Request any channels attached to the module to hangup. */
    
    		__ast_module_user_hangup_all(mod);
    
    
    		ast_verb(1, "Unloading %s\n", mod->resource);
    
    		res = mod->info->unload();
    
    		if (res) {
    			ast_log(LOG_WARNING, "Firm unload failed for %s\n", resource_name);
    
    			if (force <= AST_FORCE_FIRM) {
    
    				ast_log(LOG_WARNING, "** Dangerous **: Unloading resource anyway, at user request\n");
    
    			}
    		}
    
    		if (!error) {
    			/*
    			 * Request hangup on any channels that managed to get attached
    			 * while we called the module unload function.
    			 */
    			__ast_module_user_hangup_all(mod);
    			sched_yield();
    
    		mod->flags.running = mod->flags.declined = 0;
    
    	AST_DLLIST_UNLOCK(&module_list);
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	if (!error) {
    
    		unload_dynamic_module(mod);
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    		ast_test_suite_event_notify("MODULE_UNLOAD", "Message: %s", resource_name);
    
    static int module_matches_helper_type(struct ast_module *mod, enum ast_module_helper_type type)
    
    	switch (type) {
    	case AST_MODULE_HELPER_UNLOAD:
    		return !mod->usecount && mod->flags.running && !mod->flags.declined;
    
    	case AST_MODULE_HELPER_RELOAD:
    		return mod->flags.running && mod->info->reload;
    
    	case AST_MODULE_HELPER_RUNNING:
    		return mod->flags.running;
    
    	case AST_MODULE_HELPER_LOADED:
    		/* if we have a 'struct ast_module' then we're loaded. */
    		return 1;
    	default:
    		/* This function is not called for AST_MODULE_HELPER_LOAD. */
    		/* Unknown ast_module_helper_type. Assume it doesn't match. */
    		ast_assert(0);
    
    		return 0;
    
    }
    
    static char *module_load_helper(const char *word, int state)
    {
    	struct ast_module *mod;
    	int which = 0;
    	char *name;
    	char *ret = NULL;
    	char *editline_ret;
    	char fullpath[PATH_MAX];
    	int idx = 0;
    	/* This is needed to avoid listing modules that are already running. */
    	AST_VECTOR(, char *) running_modules;
    
    	AST_VECTOR_INIT(&running_modules, 200);
    
    	AST_DLLIST_TRAVERSE(&module_list, mod, entry) {
    		if (mod->flags.running) {
    			AST_VECTOR_APPEND(&running_modules, mod->resource);
    
    
    	if (word[0] == '/') {
    		/* BUGBUG: we should not support this. */
    		ast_copy_string(fullpath, word, sizeof(fullpath));
    	} else {
    		snprintf(fullpath, sizeof(fullpath), "%s/%s", ast_config_AST_MODULE_DIR, word);
    	}
    
    	/*
    	 * This is ugly that we keep calling filename_completion_function.
    	 * The only way to avoid this would be to make a copy of the function
    	 * that skips matches found in the running_modules vector.
    	 */
    	while (!ret && (name = editline_ret = filename_completion_function(fullpath, idx++))) {
    		if (word[0] != '/') {
    			name += (strlen(ast_config_AST_MODULE_DIR) + 1);
    		}
    
    		/* Don't list files that are already loaded! */
    		if (!AST_VECTOR_GET_CMP(&running_modules, name, !strcasecmp) && ++which > state) {
    			ret = ast_strdup(name);
    		}
    
    		ast_std_free(editline_ret);
    	}
    
    	/* Do not clean-up the elements, they belong to module_list. */
    	AST_VECTOR_FREE(&running_modules);
    
    	AST_DLLIST_UNLOCK(&module_list);
    
    	return ret;
    }
    
    char *ast_module_helper(const char *line, const char *word, int pos, int state, int rpos, int _type)
    {
    	enum ast_module_helper_type type = _type;
    	struct ast_module *mod;
    	int which = 0;
    	int wordlen = strlen(word);
    	char *ret = NULL;
    
    	if (pos != rpos) {
    		return NULL;
    	}
    
    	if (type == AST_MODULE_HELPER_LOAD) {
    		return module_load_helper(word, state);
    	}
    
    	if (type == AST_MODULE_HELPER_RELOAD) {
    		int idx;
    
    		for (idx = 0; reload_classes[idx].name; idx++) {
    			if (!strncasecmp(word, reload_classes[idx].name, wordlen) && ++which > state) {
    				return ast_strdup(reload_classes[idx].name);
    
    	AST_DLLIST_LOCK(&module_list);
    	AST_DLLIST_TRAVERSE(&module_list, mod, entry) {
    		if (!module_matches_helper_type(mod, type)) {
    			continue;
    		}
    
    		if (!strncasecmp(word, mod->resource, wordlen) && ++which > state) {
    			ret = ast_strdup(mod->resource);
    			break;
    		}
    	}
    	AST_DLLIST_UNLOCK(&module_list);
    
    
    void ast_process_pending_reloads(void)
    {
    	struct reload_queue_item *item;
    
    
    
    	AST_LIST_LOCK(&reload_queue);
    
    	if (do_full_reload) {
    		do_full_reload = 0;
    		AST_LIST_UNLOCK(&reload_queue);
    		ast_log(LOG_NOTICE, "Executing deferred reload request.\n");
    		ast_module_reload(NULL);
    		return;
    	}
    
    	while ((item = AST_LIST_REMOVE_HEAD(&reload_queue, entry))) {
    		ast_log(LOG_NOTICE, "Executing deferred reload request for module '%s'.\n", item->module);
    		ast_module_reload(item->module);
    		ast_free(item);
    	}
    
    	AST_LIST_UNLOCK(&reload_queue);
    }
    
    static void queue_reload_request(const char *module)
    {
    	struct reload_queue_item *item;
    
    	AST_LIST_LOCK(&reload_queue);
    
    	if (do_full_reload) {
    		AST_LIST_UNLOCK(&reload_queue);
    		return;
    	}
    
    	if (ast_strlen_zero(module)) {
    		/* A full reload request (when module is NULL) wipes out any previous
    		   reload requests and causes the queue to ignore future ones */
    		while ((item = AST_LIST_REMOVE_HEAD(&reload_queue, entry))) {
    			ast_free(item);
    		}
    		do_full_reload = 1;
    	} else {
    		/* No reason to add the same module twice */
    		AST_LIST_TRAVERSE(&reload_queue, item, entry) {
    			if (!strcasecmp(item->module, module)) {
    				AST_LIST_UNLOCK(&reload_queue);
    				return;
    			}
    		}
    		item = ast_calloc(1, sizeof(*item) + strlen(module) + 1);
    		if (!item) {
    			ast_log(LOG_ERROR, "Failed to allocate reload queue item.\n");
    			AST_LIST_UNLOCK(&reload_queue);
    			return;
    		}
    		strcpy(item->module, module);
    		AST_LIST_INSERT_TAIL(&reload_queue, item, entry);
    	}
    	AST_LIST_UNLOCK(&reload_queue);
    }
    
    
    /*!
     * \since 12
     * \internal
     * \brief Publish a \ref stasis message regarding the reload result
     */
    static void publish_reload_message(const char *name, enum ast_module_reload_result result)
    {
    	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
    	RAII_VAR(struct ast_json_payload *, payload, NULL, ao2_cleanup);
    	RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
    	RAII_VAR(struct ast_json *, event_object, NULL, ast_json_unref);
    	char res_buffer[8];
    
    
    	if (!ast_manager_get_generic_type()) {
    		return;
    	}
    
    
    	snprintf(res_buffer, sizeof(res_buffer), "%u", result);
    
    	event_object = ast_json_pack("{s: s, s: s}",
    			"Module", S_OR(name, "All"),
    			"Status", res_buffer);
    
    	json_object = ast_json_pack("{s: s, s: i, s: o}",
    
    			"type", "Reload",
    			"class_type", EVENT_FLAG_SYSTEM,
    
    			"event", ast_json_ref(event_object));
    
    
    	if (!json_object) {
    		return;
    	}
    
    	payload = ast_json_payload_create(json_object);
    	if (!payload) {
    		return;
    	}
    
    	message = stasis_message_create(ast_manager_get_generic_type(), payload);
    	if (!message) {
    		return;
    	}
    
    	stasis_publish(ast_manager_get_topic(), message);
    }
    
    enum ast_module_reload_result ast_module_reload(const char *name)
    
    	enum ast_module_reload_result res = AST_MODULE_RELOAD_NOT_FOUND;
    
    	size_t name_baselen = name ? resource_name_baselen(name) : 0;
    
    	/* If we aren't fully booted, we just pretend we reloaded but we queue this
    	   up to run once we are booted up. */
    
    		queue_reload_request(name);
    
    		res = AST_MODULE_RELOAD_QUEUED;
    		goto module_reload_exit;
    
    	if (ast_mutex_trylock(&reloadlock)) {
    
    		ast_verb(3, "The previous reload command didn't finish yet\n");
    
    		res = AST_MODULE_RELOAD_IN_PROGRESS;