diff --git a/include/crm/common/xml_compat.h b/include/crm/common/xml_compat.h index d56270d0c4..01f1970fe5 100644 --- a/include/crm/common/xml_compat.h +++ b/include/crm/common/xml_compat.h @@ -1,167 +1,163 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_XML_COMPAT__H #define PCMK__CRM_COMMON_XML_COMPAT__H #include // gboolean #include // xmlNode #include // crm_xml_add() #include // PCMK_XE_CLONE #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Deprecated Pacemaker XML API * \ingroup core * \deprecated Do not include this header directly. The XML APIs in this * header, and the header itself, will be removed in a future * release. */ //! \deprecated Do not use (will be removed in a future release) #define XML_PARANOIA_CHECKS 0 //! \deprecated This function will be removed in a future release xmlDoc *getDocPtr(xmlNode *node); //! \deprecated This function will be removed in a future release int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child); //! \deprecated This function will be removed in a future release xmlNode *find_entity(xmlNode *parent, const char *node_name, const char *id); //! \deprecated This function will be removed in a future release char *xml_get_path(const xmlNode *xml); //! \deprecated This function will be removed in a future release void xml_log_changes(uint8_t level, const char *function, const xmlNode *xml); //! \deprecated This function will be removed in a future release void xml_log_patchset(uint8_t level, const char *function, const xmlNode *xml); //! \deprecated Use xml_apply_patchset() instead gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml); //! \deprecated Do not use (will be removed in a future release) void crm_destroy_xml(gpointer data); //! \deprecated Check children member directly gboolean xml_has_children(const xmlNode *root); //! \deprecated Use crm_xml_add() with "true" or "false" instead static inline const char * crm_xml_add_boolean(xmlNode *node, const char *name, gboolean value) { return crm_xml_add(node, name, (value? "true" : "false")); } // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Use name member directly static inline const char * crm_element_name(const xmlNode *xml) { return (xml == NULL)? NULL : (const char *) xml->name; } //! \deprecated Do not use char *crm_xml_escape(const char *text); // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *copy_xml(xmlNode *src_node); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *add_node_copy(xmlNode *new_parent, xmlNode *xml_node); //! \deprecated Do not use void purge_diff_markers(xmlNode *a_node); //! \deprecated Do not use xmlNode *diff_xml_object(xmlNode *left, xmlNode *right, gboolean suppress); //! \deprecated Do not use xmlNode *subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker); //! \deprecated Do not use gboolean can_prune_leaf(xmlNode *xml_node); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *create_xml_node(xmlNode *parent, const char *name); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *pcmk_create_xml_text_node(xmlNode *parent, const char *name, const char *content); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *pcmk_create_html_node(xmlNode *parent, const char *element_name, const char *id, const char *class_name, const char *text); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *first_named_child(const xmlNode *parent, const char *name); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *crm_next_same_xml(const xmlNode *sibling); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation void xml_remove_prop(xmlNode *obj, const char *name); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation gboolean replace_xml_child(xmlNode *parent, xmlNode *child, xmlNode *update, gboolean delete_only); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation gboolean update_xml_child(xmlNode *child, xmlNode *to_update); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation int find_xml_children(xmlNode **children, xmlNode *root, const char *tag, const char *field, const char *value, gboolean search_matches); -//! \deprecated Do not use Pacemaker for general-purpose XML manipulation -xmlNode *get_xpath_object_relative(const char *xpath, xmlNode *xml_obj, - int error_level); - // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Do not use gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs); // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call \c crm_log_init() or \c crm_log_cli_init() instead void crm_xml_init(void); //! \deprecated Exit with \c crm_exit() instead void crm_xml_cleanup(void); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation void pcmk_free_xml_subtree(xmlNode *xml); // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Do not use Pacemaker for general-purpose XML manipulation void free_xml(xmlNode *child); //! \deprecated Do not use Pacemaker for general-purpose XML manipulation xmlNode *expand_idref(xmlNode *input, xmlNode *top); #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_XML_COMPAT__H diff --git a/lib/common/xpath.c b/lib/common/xpath.c index 9fc95c5a5f..0eb0578877 100644 --- a/lib/common/xpath.c +++ b/lib/common/xpath.c @@ -1,396 +1,374 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include "crmcommon_private.h" /* * From xpath2.c * * All the elements returned by an XPath query are pointers to * elements from the tree *except* namespace nodes where the XPath * semantic is different from the implementation in libxml2 tree. * As a result when a returned node set is freed when * xmlXPathFreeObject() is called, that routine must check the * element type. But node from the returned set may have been removed * by xmlNodeSetContent() resulting in access to freed data. * * This can be exercised by running * valgrind xpath2 test3.xml '//discarded' discarded * * There is 2 ways around it: * - make a copy of the pointers to the nodes from the result set * then call xmlXPathFreeObject() and then modify the nodes * or * - remove the references from the node set, if they are not namespace nodes, before calling xmlXPathFreeObject(). */ void freeXpathObject(xmlXPathObjectPtr xpathObj) { int lpc, max = numXpathResults(xpathObj); if (xpathObj == NULL) { return; } for (lpc = 0; lpc < max; lpc++) { if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) { xpathObj->nodesetval->nodeTab[lpc] = NULL; } } /* _Now_ it's safe to free it */ xmlXPathFreeObject(xpathObj); } xmlNode * getXpathResult(xmlXPathObjectPtr xpathObj, int index) { xmlNode *match = NULL; int max = numXpathResults(xpathObj); CRM_CHECK(index >= 0, return NULL); CRM_CHECK(xpathObj != NULL, return NULL); if (index >= max) { crm_err("Requested index %d of only %d items", index, max); return NULL; } else if(xpathObj->nodesetval->nodeTab[index] == NULL) { /* Previously requested */ return NULL; } match = xpathObj->nodesetval->nodeTab[index]; CRM_CHECK(match != NULL, return NULL); if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) { /* See the comment for freeXpathObject() */ xpathObj->nodesetval->nodeTab[index] = NULL; } if (match->type == XML_DOCUMENT_NODE) { /* Will happen if section = '/' */ match = match->children; } else if (match->type != XML_ELEMENT_NODE && match->parent && match->parent->type == XML_ELEMENT_NODE) { /* Return the parent instead */ match = match->parent; } else if (match->type != XML_ELEMENT_NODE) { /* We only support searching nodes */ crm_err("We only support %d not %d", XML_ELEMENT_NODE, match->type); match = NULL; } return match; } void dedupXpathResults(xmlXPathObjectPtr xpathObj) { int lpc, max = numXpathResults(xpathObj); if (xpathObj == NULL) { return; } for (lpc = 0; lpc < max; lpc++) { xmlNode *xml = NULL; gboolean dedup = FALSE; if (xpathObj->nodesetval->nodeTab[lpc] == NULL) { continue; } xml = xpathObj->nodesetval->nodeTab[lpc]->parent; for (; xml; xml = xml->parent) { int lpc2 = 0; for (lpc2 = 0; lpc2 < max; lpc2++) { if (xpathObj->nodesetval->nodeTab[lpc2] == xml) { xpathObj->nodesetval->nodeTab[lpc] = NULL; dedup = TRUE; break; } } if (dedup) { break; } } } } /* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */ xmlXPathObjectPtr xpath_search(const xmlNode *xml_top, const char *path) { xmlXPathObjectPtr xpathObj = NULL; xmlXPathContextPtr xpathCtx = NULL; const xmlChar *xpathExpr = (pcmkXmlStr) path; CRM_CHECK(path != NULL, return NULL); CRM_CHECK(xml_top != NULL, return NULL); CRM_CHECK(strlen(path) > 0, return NULL); xpathCtx = xmlXPathNewContext(xml_top->doc); pcmk__mem_assert(xpathCtx); xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); xmlXPathFreeContext(xpathCtx); return xpathObj; } /*! * \brief Run a supplied function for each result of an xpath search * * \param[in,out] xml XML to search * \param[in] xpath XPath search string * \param[in] helper Function to call for each result * \param[in,out] user_data Data to pass to supplied function * * \note The helper function will be passed the XML node of the result, * and the supplied user_data. This function does not otherwise * use user_data. */ void crm_foreach_xpath_result(xmlNode *xml, const char *xpath, void (*helper)(xmlNode*, void*), void *user_data) { xmlXPathObjectPtr xpathObj = xpath_search(xml, xpath); int nresults = numXpathResults(xpathObj); int i; for (i = 0; i < nresults; i++) { xmlNode *result = getXpathResult(xpathObj, i); CRM_LOG_ASSERT(result != NULL); if (result) { (*helper)(result, user_data); } } freeXpathObject(xpathObj); } xmlNode * get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level) { int max; xmlNode *result = NULL; xmlXPathObjectPtr xpathObj = NULL; char *nodePath = NULL; char *matchNodePath = NULL; if (xpath == NULL) { return xml_obj; /* or return NULL? */ } xpathObj = xpath_search(xml_obj, xpath); nodePath = (char *)xmlGetNodePath(xml_obj); max = numXpathResults(xpathObj); if (max < 1) { if (error_level < LOG_NEVER) { do_crm_log(error_level, "No match for %s in %s", xpath, pcmk__s(nodePath, "unknown path")); crm_log_xml_explicit(xml_obj, "Unexpected Input"); } } else if (max > 1) { if (error_level < LOG_NEVER) { int lpc = 0; do_crm_log(error_level, "Too many matches for %s in %s", xpath, pcmk__s(nodePath, "unknown path")); for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); CRM_LOG_ASSERT(match != NULL); if (match != NULL) { matchNodePath = (char *) xmlGetNodePath(match); do_crm_log(error_level, "%s[%d] = %s", xpath, lpc, pcmk__s(matchNodePath, "unrecognizable match")); free(matchNodePath); } } crm_log_xml_explicit(xml_obj, "Bad Input"); } } else { result = getXpathResult(xpathObj, 0); } freeXpathObject(xpathObj); free(nodePath); return result; } /*! * \internal * \brief Get an XPath string that matches an XML element as closely as possible * * \param[in] xml The XML element for which to build an XPath string * * \return A \p GString that matches \p xml, or \p NULL if \p xml is \p NULL. * * \note The caller is responsible for freeing the string using * \p g_string_free(). */ GString * pcmk__element_xpath(const xmlNode *xml) { const xmlNode *parent = NULL; GString *xpath = NULL; const char *id = NULL; if (xml == NULL) { return NULL; } parent = xml->parent; xpath = pcmk__element_xpath(parent); if (xpath == NULL) { xpath = g_string_sized_new(256); } // Build xpath like "/" -> "/cib" -> "/cib/configuration" if (parent == NULL) { g_string_append_c(xpath, '/'); } else if (parent->parent == NULL) { g_string_append(xpath, (const gchar *) xml->name); } else { pcmk__g_strcat(xpath, "/", (const char *) xml->name, NULL); } id = pcmk__xe_id(xml); if (id != NULL) { pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", id, "']", NULL); } return xpath; } char * pcmk__xpath_node_id(const char *xpath, const char *node) { char *retval = NULL; char *patt = NULL; char *start = NULL; char *end = NULL; if (node == NULL || xpath == NULL) { return retval; } patt = crm_strdup_printf("/%s[@" PCMK_XA_ID "=", node); start = strstr(xpath, patt); if (!start) { free(patt); return retval; } start += strlen(patt); start++; end = strstr(start, "\'"); CRM_ASSERT(end); retval = strndup(start, end-start); free(patt); return retval; } static int output_attr_child(xmlNode *child, void *userdata) { pcmk__output_t *out = userdata; out->info(out, " Value: %s \t(id=%s)", crm_element_value(child, PCMK_XA_VALUE), pcmk__s(pcmk__xe_id(child), "")); return pcmk_rc_ok; } void pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search, const char *name) { if (out == NULL || name == NULL || search == NULL || search->children == NULL) { return; } out->info(out, "Multiple attributes match " PCMK_XA_NAME "=%s", name); pcmk__xe_foreach_child(search, NULL, output_attr_child, out); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include /*! * \deprecated This function will be removed in a future release * \brief Get an XPath string that matches an XML element as closely as possible * * \param[in] xml The XML element for which to build an XPath string * * \return A string that matches \p xml, or \p NULL if \p xml is \p NULL. * * \note The caller is responsible for freeing the string using free(). */ char * xml_get_path(const xmlNode *xml) { char *path = NULL; GString *g_path = pcmk__element_xpath(xml); if (g_path == NULL) { return NULL; } path = pcmk__str_copy(g_path->str); g_string_free(g_path, TRUE); return path; } -xmlNode * -get_xpath_object_relative(const char *xpath, xmlNode *xml_obj, int error_level) -{ - xmlNode *result = NULL; - char *xpath_full = NULL; - char *xpath_prefix = NULL; - - if (xml_obj == NULL || xpath == NULL) { - return NULL; - } - - xpath_prefix = (char *)xmlGetNodePath(xml_obj); - - xpath_full = crm_strdup_printf("%s%s", xpath_prefix, xpath); - - result = get_xpath_object(xpath_full, xml_obj, error_level); - - free(xpath_prefix); - free(xpath_full); - return result; -} - // LCOV_EXCL_STOP // End deprecated API