diff --git a/include/asterisk/module.h b/include/asterisk/module.h index 84ff4b9da0..8ba29f32a9 100644 --- a/include/asterisk/module.h +++ b/include/asterisk/module.h @@ -149,6 +149,20 @@ enum ast_module_helper_type { */ enum ast_module_load_result ast_load_resource(const char *resource_name); +/*! + * \brief Unload and load a module again. + * \param resource_name The name of the module to unload. + * \param ast_module_unload_mode The force flag. This should be set using one of the AST_FORCE flags. + * \param recursive Attempt to recursively unload any dependents of this module + * if that will allow the module to unload, and load them back again afterwards. + * + * + * \retval 0 on success. + * \retval 1 on error unloading modules. + * \retval -1 on error loading modules back. + */ +int ast_refresh_resource(const char *resource_name, enum ast_module_unload_mode force, int recursive); + /*! * \brief Unload a module. * \param resource_name The name of the module to unload. diff --git a/main/cli.c b/main/cli.c index 219a839d8d..19355ab6db 100644 --- a/main/cli.c +++ b/main/cli.c @@ -807,30 +807,35 @@ static char *handle_logger_mute(struct ast_cli_entry *e, int cmd, struct ast_cli 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 " */ switch (cmd) { case CLI_INIT: e->command = "module refresh"; e->usage = - "Usage: module refresh \n" - " Unloads and loads the specified module into Asterisk.\n"; + "Usage: module refresh [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]); diff --git a/main/loader.c b/main/loader.c index ecad2346e1..f2009f94e2 100644 --- a/main/loader.c +++ b/main/loader.c @@ -1216,7 +1216,73 @@ int modules_shutdown(void) 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; @@ -1244,13 +1310,55 @@ int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode f 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; } } @@ -1296,6 +1404,52 @@ exit: 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) { diff --git a/main/manager.c b/main/manager.c index cc4363a042..1149dfcab2 100644 --- a/main/manager.c +++ b/main/manager.c @@ -1142,10 +1142,18 @@ + + Completely unload and load again a specified module. + If no module is specified for a reload loadtype, all modules are reloaded. + + For refresh 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. + Loads, unloads or reloads an Asterisk module in a running system. @@ -7275,6 +7283,7 @@ static int manager_moduleload(struct mansession *s, const struct message *m) 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."); @@ -7297,6 +7306,13 @@ static int manager_moduleload(struct mansession *s, const struct message *m) } 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)) {