diff --git a/doc/CHANGES-staging/xmldoc.txt b/doc/CHANGES-staging/xmldoc.txt new file mode 100644 index 0000000000..50324e46f5 --- /dev/null +++ b/doc/CHANGES-staging/xmldoc.txt @@ -0,0 +1,5 @@ +Subject: xmldocs + +The XML documentation can now be reloaded without restarting +Asterisk, which makes it possible to load new modules that +enforce documentation without restarting Asterisk. diff --git a/main/config_options.c b/main/config_options.c index c59a8b5806..d258f607dc 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -1099,7 +1099,9 @@ static int xmldoc_update_config_type(const char *module, const char *name, const } if (!(results = ast_xmldoc_query("/docs/configInfo[@name='%s']/configFile/configObject[@name='%s']", module, name))) { - ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!\n", name, module); + ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!" + " If this module was recently built, run 'xmldoc reload' to refresh documentation, then load the module again\n", + name, module); return XMLDOC_STRICT ? -1 : 0; } diff --git a/main/xmldoc.c b/main/xmldoc.c index f924717e9a..2b75523402 100644 --- a/main/xmldoc.c +++ b/main/xmldoc.c @@ -68,9 +68,8 @@ static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *ta /*! * \brief Container of documentation trees * - * \note A RWLIST is a sufficient container type to use here for now. - * However, some changes will need to be made to implement ref counting - * if reload support is added in the future. + * \note A RWLIST is a sufficient container type to use, provided + * the list lock is always held while there are references to the list. */ static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree); @@ -430,6 +429,8 @@ static int xmldoc_attribute_match(struct ast_xml_node *node, const char *attr, c * * \retval NULL on error. * \retval A node of type ast_xml_node. + * + * \note Must be called with a RDLOCK held on xmldoc_tree */ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *module, const char *language) { @@ -438,7 +439,6 @@ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, struct ast_xml_node *lang_match = NULL; struct documentation_tree *doctree; - AST_RWLIST_RDLOCK(&xmldoc_tree); AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) { /* the core xml documents have priority over thirdparty document. */ node = ast_xml_get_root(doctree->doc); @@ -497,7 +497,6 @@ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, break; } } - AST_RWLIST_UNLOCK(&xmldoc_tree); return node; } @@ -1253,13 +1252,18 @@ static char *_ast_xmldoc_build_syntax(struct ast_xml_node *root_node, const char char *ast_xmldoc_build_syntax(const char *type, const char *name, const char *module) { struct ast_xml_node *node; + char *syntax; + AST_RWLIST_RDLOCK(&xmldoc_tree); node = xmldoc_get_node(type, name, module, documentation_language); if (!node) { + AST_RWLIST_UNLOCK(&xmldoc_tree); return NULL; } - return _ast_xmldoc_build_syntax(node, type, name); + syntax = _ast_xmldoc_build_syntax(node, type, name); + AST_RWLIST_UNLOCK(&xmldoc_tree); + return syntax; } /*! @@ -1705,12 +1709,15 @@ char *ast_xmldoc_build_seealso(const char *type, const char *name, const char *m } /* get the application/function root node. */ + AST_RWLIST_RDLOCK(&xmldoc_tree); node = xmldoc_get_node(type, name, module, documentation_language); if (!node || !ast_xml_node_get_children(node)) { + AST_RWLIST_UNLOCK(&xmldoc_tree); return NULL; } output = _ast_xmldoc_build_seealso(node); + AST_RWLIST_UNLOCK(&xmldoc_tree); return output; } @@ -2077,18 +2084,23 @@ static char *_ast_xmldoc_build_arguments(struct ast_xml_node *node) char *ast_xmldoc_build_arguments(const char *type, const char *name, const char *module) { struct ast_xml_node *node; + char *arguments; if (ast_strlen_zero(type) || ast_strlen_zero(name)) { return NULL; } + AST_RWLIST_RDLOCK(&xmldoc_tree); node = xmldoc_get_node(type, name, module, documentation_language); if (!node || !ast_xml_node_get_children(node)) { + AST_RWLIST_UNLOCK(&xmldoc_tree); return NULL; } - return _ast_xmldoc_build_arguments(node); + arguments = _ast_xmldoc_build_arguments(node); + AST_RWLIST_UNLOCK(&xmldoc_tree); + return arguments; } /*! @@ -2192,20 +2204,27 @@ static char *_xmldoc_build_field(struct ast_xml_node *node, const char *var, int static char *xmldoc_build_field(const char *type, const char *name, const char *module, const char *var, int raw) { struct ast_xml_node *node; + char *field; if (ast_strlen_zero(type) || ast_strlen_zero(name)) { ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n"); return NULL; } + AST_RWLIST_RDLOCK(&xmldoc_tree); node = xmldoc_get_node(type, name, module, documentation_language); if (!node) { - ast_log(LOG_WARNING, "Couldn't find %s %s in XML documentation\n", type, name); + AST_RWLIST_UNLOCK(&xmldoc_tree); + ast_log(LOG_WARNING, "Couldn't find %s %s in XML documentation" + " If this module was recently built, run 'xmldoc reload' to refresh documentation\n", + type, name); return NULL; } - return _xmldoc_build_field(node, var, raw); + field = _xmldoc_build_field(node, var, raw); + AST_RWLIST_UNLOCK(&xmldoc_tree); + return field; } /*! @@ -2465,18 +2484,23 @@ static struct ast_xml_doc_item *xmldoc_build_list_responses(struct ast_xml_node struct ast_xml_doc_item *ast_xmldoc_build_list_responses(const char *type, const char *name, const char *module) { struct ast_xml_node *node; + struct ast_xml_doc_item *responses; if (ast_strlen_zero(type) || ast_strlen_zero(name)) { return NULL; } + AST_RWLIST_RDLOCK(&xmldoc_tree); node = xmldoc_get_node(type, name, module, documentation_language); if (!node || !ast_xml_node_get_children(node)) { + AST_RWLIST_UNLOCK(&xmldoc_tree); return NULL; } - return xmldoc_build_list_responses(node); + responses = xmldoc_build_list_responses(node); + AST_RWLIST_UNLOCK(&xmldoc_tree); + return responses; } /*! @@ -2530,18 +2554,23 @@ static struct ast_xml_doc_item *xmldoc_build_final_response(struct ast_xml_node struct ast_xml_doc_item *ast_xmldoc_build_final_response(const char *type, const char *name, const char *module) { struct ast_xml_node *node; + static struct ast_xml_doc_item *response; if (ast_strlen_zero(type) || ast_strlen_zero(name)) { return NULL; } + AST_RWLIST_RDLOCK(&xmldoc_tree); node = xmldoc_get_node(type, name, module, documentation_language); if (!node || !ast_xml_node_get_children(node)) { + AST_RWLIST_UNLOCK(&xmldoc_tree); return NULL; } - return xmldoc_build_final_response(node); + response = xmldoc_build_final_response(node); + AST_RWLIST_UNLOCK(&xmldoc_tree); + return response; } struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...) @@ -2860,25 +2889,35 @@ static char *handle_dump_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_a static struct ast_cli_entry cli_dump_xmldocs = AST_CLI_DEFINE(handle_dump_docs, "Dump the XML docs to the specified file"); -/*! \brief Close and unload XML documentation. */ -static void xmldoc_unload_documentation(void) +static char *handle_reload_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static struct ast_cli_entry cli_reload_xmldocs = AST_CLI_DEFINE(handle_reload_docs, "Reload the XML docs"); + +/*! \note Must be called with xmldoc_tree locked */ +static void xmldoc_purge_documentation(void) { struct documentation_tree *doctree; - ast_cli_unregister(&cli_dump_xmldocs); - - AST_RWLIST_WRLOCK(&xmldoc_tree); while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) { ast_free(doctree->filename); ast_xml_close(doctree->doc); ast_free(doctree); } +} + +/*! \brief Close and unload XML documentation. */ +static void xmldoc_unload_documentation(void) +{ + ast_cli_unregister(&cli_reload_xmldocs); + ast_cli_unregister(&cli_dump_xmldocs); + + AST_RWLIST_WRLOCK(&xmldoc_tree); + xmldoc_purge_documentation(); AST_RWLIST_UNLOCK(&xmldoc_tree); ast_xml_finish(); } -int ast_xmldoc_load_documentation(void) +static int xmldoc_load_documentation(int first_time) { struct ast_xml_node *root_node; struct ast_xml_doc *tmpdoc; @@ -2907,12 +2946,14 @@ int ast_xmldoc_load_documentation(void) ast_config_destroy(cfg); } - /* initialize the XML library. */ - ast_xml_init(); - - ast_cli_register(&cli_dump_xmldocs); - /* register function to be run when asterisk finish. */ - ast_register_cleanup(xmldoc_unload_documentation); + if (first_time) { + /* initialize the XML library. */ + ast_xml_init(); + ast_cli_register(&cli_dump_xmldocs); + ast_cli_register(&cli_reload_xmldocs); + /* register function to be run when asterisk finish. */ + ast_register_cleanup(xmldoc_unload_documentation); + } globbuf.gl_offs = 0; /* slots to reserve in gl_pathv */ @@ -2942,6 +2983,16 @@ int ast_xmldoc_load_documentation(void) ast_free(xmlpattern); AST_RWLIST_WRLOCK(&xmldoc_tree); + + if (!first_time) { + /* If we're reloading, purge the existing documentation. + * We do this with the lock held so that if somebody + * else tries to get documentation, there's no chance + * of retrieiving it after we purged the old docs + * but before we loaded the new ones. */ + xmldoc_purge_documentation(); + } + /* loop over expanded files */ for (i = 0; i < globbuf.gl_pathc; i++) { /* check for duplicates (if we already [try to] open the same file. */ @@ -2993,4 +3044,31 @@ int ast_xmldoc_load_documentation(void) return 0; } +int ast_xmldoc_load_documentation(void) +{ + return xmldoc_load_documentation(1); +} + +static int xmldoc_reload_documentation(void) +{ + return xmldoc_load_documentation(0); +} + +static char *handle_reload_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "xmldoc reload"; + e->usage = + "Usage: xmldoc reload\n" + " Reload XML documentation\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + xmldoc_reload_documentation(); + return CLI_SUCCESS; +} + #endif /* AST_XML_DOCS */