static char *handle_refresh(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
+ static const char * const completions[] = { "recursively", NULL };
+ int res;
/* "module refresh <mod>" */
switch (cmd) {
case CLI_INIT:
e->command = "module refresh";
e->usage =
- "Usage: module refresh <module name>\n"
- " Unloads and loads the specified module into Asterisk.\n";
+ "Usage: module refresh <module name> [recursively]\n"
+ " Unloads and loads the specified module into Asterisk.\n"
+ " 'recursively' will attempt to unload any modules with\n"
+ " dependencies on this module for you and load them again\n"
+ " afterwards.\n";
return NULL;
case CLI_GENERATE:
- if (a->pos != e->args) {
- return NULL;
+ if (a->pos == e->args) {
+ return ast_module_helper(a->line, a->word, a->pos, a->n, a->pos, AST_MODULE_HELPER_UNLOAD);
+ } else if (a->pos == e->args + 1) {
+ return ast_cli_complete(a->word, completions, a->n);
}
- return ast_module_helper(a->line, a->word, a->pos, a->n, a->pos, AST_MODULE_HELPER_UNLOAD);
+ return NULL;
}
- if (a->argc != e->args + 1) {
+ if (a->argc < 3 || a->argc > 4) {
return CLI_SHOWUSAGE;
}
- if (ast_unload_resource(a->argv[e->args], AST_FORCE_SOFT)) {
- ast_cli(a->fd, "Unable to unload resource %s\n", a->argv[e->args]);
- return CLI_FAILURE;
- }
- if (ast_load_resource(a->argv[e->args])) {
- ast_cli(a->fd, "Unable to load module %s\n", a->argv[e->args]);
+
+ res = ast_refresh_resource(a->argv[e->args], AST_FORCE_SOFT, a->argc == 4 && !strcasecmp(a->argv[3], "recursively"));
+ if (res) {
+ ast_cli(a->fd, "Unable to %s resource %s\n", res > 0 ? "unload" : "load", a->argv[e->args]);
return CLI_FAILURE;
}
ast_cli(a->fd, "Unloaded and loaded %s\n", a->argv[e->args]);
return !res;
}
-int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode force)
+/*!
+ * \brief Whether or not this module should be able to be unloaded successfully,
+ * if we recursively unload any modules that are dependent on it.
+ * \note module_list should be locked when calling this
+ * \retval 0 if not, 1 if likely possible
+ */
+static int graceful_unload_possible(struct ast_module *target, struct ast_vector_const_string *dependents)
+{
+ struct ast_module *mod;
+ int usecount = target->usecount;
+
+ /* Check the reffed_deps of each module to see if we're one of them. */
+ AST_DLLIST_TRAVERSE(&module_list, mod, entry) {
+ if (AST_VECTOR_GET_CMP(&mod->reffed_deps, target, AST_VECTOR_ELEM_DEFAULT_CMP)) {
+ const char *name;
+ /* This module is dependent on the target.
+ * If we can unload this module gracefully,
+ * then that would decrement our use count.
+ * If any single module could not be unloaded gracefully,
+ * then we don't proceed. */
+ int unloadable;
+ if (AST_VECTOR_GET_CMP(dependents, ast_module_name(mod), !strcasecmp)) {
+ /* Already in our list, we already checked this module,
+ * and we gave it the green light. */
+ ast_debug(3, "Skipping duplicate dependent %s\n", ast_module_name(mod));
+ if (!--usecount) {
+ break;
+ }
+ continue;
+ }
+ unloadable = graceful_unload_possible(mod, dependents);
+ if (!unloadable) {
+ ast_log(LOG_NOTICE, "Can't unload %s right now because %s is dependent on it\n", ast_module_name(target), ast_module_name(mod));
+ return 0;
+ }
+ /* Insert at beginning, so later if we're loading modules again automatically, we can do so in the same order. */
+ name = ast_strdup(ast_module_name(mod));
+ if (!name) {
+ return 0;
+ }
+ ast_debug(3, "Found new dependent %s\n", ast_module_name(mod));
+ if (AST_VECTOR_INSERT_AT(dependents, 0, name)) {
+ ast_log(LOG_ERROR, "Failed to add module '%s' to vector\n", ast_module_name(mod));
+ return 0;
+ }
+ if (!--usecount) {
+ break;
+ }
+ }
+ }
+
+ if (usecount) {
+ ast_log(LOG_NOTICE, "Module %s cannot be unloaded (would still have use count %d/%d after unloading dependents)\n", ast_module_name(target), usecount, target->usecount);
+ return 0;
+ }
+ return 1;
+}
+
+/*!
+ * \brief Unload a resource
+ * \param resource_name Module name
+ * \param force
+ * \param recursive Whether to attempt to recursively unload dependents of this module and load them again afterwards
+ * \param dependents. Can be NULL if autounload is 0.
+ * \retval 0 on success, -1 on failure
+ */
+static int auto_unload_resource(const char *resource_name, enum ast_module_unload_mode force, int recursive, struct ast_vector_const_string *dependents)
{
struct ast_module *mod;
int res = -1;
goto exit; /* Skip all the intervening !error checks, only the last one is relevant. */
}
+ if (!error && mod->usecount > 0 && recursive) {
+ /* Try automatically unloading all modules dependent on the module we're trying to unload,
+ * and then, optionally, load them back again if we end up loading this module again.
+ * If any modules that have us as a dependency can't be unloaded, for whatever reason,
+ * then the entire unload operation will fail, so to try to make this an atomic operation
+ * and avoid leaving modules in a partial unload state, first check if we think we're going
+ * to be able to pull this off, and if not, abort.
+ *
+ * A race condition is technically still possible, if some depending module suddenly goes in use
+ * between this check and trying to unload it, but this takes care of the majority of
+ * easy-to-avoid cases that would fail eventually anyways.
+ *
+ * Note that we can encounter false negatives (e.g. unloading all the dependents would allow
+ * a module to unload, but graceful_unload_possible returns 0). This is because it's only
+ * checking direct module dependencies; other dependencies caused by a module registering
+ * a resource that cause its ref count to get bumped aren't accounted for here.
+ */
+ if (graceful_unload_possible(mod, dependents)) {
+ int i, res = 0;
+ size_t num_deps = AST_VECTOR_SIZE(dependents);
+ ast_debug(1, "%lu module%s will need to be unloaded\n", AST_VECTOR_SIZE(dependents), ESS(AST_VECTOR_SIZE(dependents)));
+ /* Unload from the end, since the last module was the first one added, which means it isn't a dependency of anything else. */
+ for (i = AST_VECTOR_SIZE(dependents) - 1; i >= 0; i--) {
+ const char *depname = AST_VECTOR_GET(dependents, i);
+ res = ast_unload_resource(depname, force);
+ if (res) {
+ ast_log(LOG_WARNING, "Failed to unload %lu module%s automatically (%s could not be unloaded)\n", num_deps, ESS(num_deps), depname);
+ /* To be polite, load modules that we already unloaded,
+ * to try to leave things the way they were when we started. */
+ for (i++; i < num_deps; i++) {
+ const char *depname = AST_VECTOR_GET(dependents, i);
+ res = ast_load_resource(depname);
+ if (res) {
+ ast_log(LOG_WARNING, "Could not load module '%s' again automatically\n", depname);
+ }
+ }
+ break;
+ }
+ }
+ /* Either we failed, or we successfully unloaded everything.
+ * If we succeeded, we can now proceed and unload ourselves. */
+ }
+ }
+
if (!error && (mod->usecount > 0)) {
- if (force)
- 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);
+ if (force) {
+ ast_log(LOG_WARNING, "Warning: Forcing removal of module '%s' with use count %d\n", resource_name, mod->usecount);
+ } else {
+ ast_log(LOG_WARNING, "%s unload failed, '%s' has use count %d\n", recursive ? "Recursive soft" : "Soft", resource_name, mod->usecount);
error = 1;
}
}
return res;
}
+int ast_refresh_resource(const char *resource_name, enum ast_module_unload_mode force, int recursive)
+{
+ if (recursive) {
+ /* Recursively unload dependents of this module and then load them back again */
+ int res, i;
+ struct ast_vector_const_string dependents;
+ AST_VECTOR_INIT(&dependents, 0);
+ res = auto_unload_resource(resource_name, force, recursive, &dependents);
+ if (res) {
+ AST_VECTOR_FREE(&dependents);
+ return 1;
+ }
+ /* Start by loading the target again. */
+ if (ast_load_resource(resource_name)) {
+ ast_log(LOG_WARNING, "Failed to load module '%s' again automatically\n", resource_name);
+ AST_VECTOR_FREE(&dependents);
+ return -1;
+ }
+ res = 0;
+ /* Finally, load again any modules we had to unload in order to refresh the target.
+ * We must load modules in the reverse order that we unloaded them,
+ * to preserve dependency requirements. */
+ for (i = 0; i < AST_VECTOR_SIZE(&dependents); i++) {
+ const char *depname = AST_VECTOR_GET(&dependents, i);
+ int mres = ast_load_resource(depname);
+ if (mres) {
+ ast_log(LOG_WARNING, "Could not load module '%s' again automatically\n", depname);
+ }
+ res |= mres;
+ }
+ AST_VECTOR_FREE(&dependents);
+ return res ? -1 : 0;
+ }
+
+ /* Simple case: just unload and load the module again */
+ if (ast_unload_resource(resource_name, force)) {
+ return 1;
+ }
+ return ast_load_resource(resource_name);
+}
+
+int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode force)
+{
+ return auto_unload_resource(resource_name, force, 0, NULL);
+}
+
static int module_matches_helper_type(struct ast_module *mod, enum ast_module_helper_type type)
{
switch (type) {
<enum name="load" />
<enum name="unload" />
<enum name="reload" />
+ <enum name="refresh">
+ <para>Completely unload and load again a specified module.</para>
+ </enum>
</enumlist>
<para>If no module is specified for a <literal>reload</literal> loadtype,
all modules are reloaded.</para>
</parameter>
+ <parameter name="Recursive" required="false">
+ <para>For <literal>refresh</literal> operations, attempt to recursively
+ unload any other modules that are dependent on this module, if that would
+ allow it to successfully unload, and load them again afterwards.</para>
+ </parameter>
</syntax>
<description>
<para>Loads, unloads or reloads an Asterisk module in a running system.</para>
int res;
const char *module = astman_get_header(m, "Module");
const char *loadtype = astman_get_header(m, "LoadType");
+ const char *recursive = astman_get_header(m, "Recursive");
if (!loadtype || strlen(loadtype) == 0) {
astman_send_error(s, m, "Incomplete ModuleLoad action.");
} else {
astman_send_ack(s, m, "Module unloaded.");
}
+ } else if (!strcasecmp(loadtype, "refresh")) {
+ res = ast_refresh_resource(module, AST_FORCE_SOFT, !ast_strlen_zero(recursive) && ast_true(recursive));
+ if (res) {
+ astman_send_error(s, m, "Could not refresh module.");
+ } else {
+ astman_send_ack(s, m, "Module unloaded and loaded.");
+ }
} else if (!strcasecmp(loadtype, "reload")) {
/* TODO: Unify the ack/error messages here with action_reload */
if (!ast_strlen_zero(module)) {