From 1da5eb3795569fbd65f2eb68ce9e48c791b0318c Mon Sep 17 00:00:00 2001 From: Naveen Albert Date: Sat, 5 Nov 2022 12:11:08 +0000 Subject: [PATCH] xmldoc: Allow XML docs to be reloaded. The XML docs are currently only loaded on startup with no way to update them during runtime. This makes it impossible to load modules that use ACO/Sorcery (which require documentation) if they are added to the source tree and built while Asterisk is running (e.g. external modules). This adds a CLI command to reload the XML docs during runtime so that documentation can be updated without a full restart of Asterisk. ASTERISK-30289 #close Change-Id: I4f265b0e5517e757c5453a0f241201a5788d3a07 --- doc/CHANGES-staging/xmldoc.txt | 5 ++ main/config_options.c | 4 +- main/xmldoc.c | 124 +++++++++++++++++++++++++++------ 3 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 doc/CHANGES-staging/xmldoc.txt 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 */