diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index c03292551a..923828a277 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -1,454 +1,402 @@ /* * Copyright 2017-2025 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_INTERNAL__H #define PCMK__CRM_COMMON_XML_INTERNAL__H /* * Internal-only wrappers for and extensions to libxml2 (libxslt) */ #include #include // uint32_t #include #include /* transitively imports qblog.h */ #include #include // PCMK_XA_ID, PCMK_XE_CLONE -// This file is a wrapper for other xml_*_internal.h headers +// This file is a wrapper for other {xml_*,xpath}_internal.h headers #include #include #include #include #include +#include #include #ifdef __cplusplus extern "C" { #endif /*! * \brief Base for directing lib{xml2,xslt} log into standard libqb backend * * This macro implements the core of what can be needed for directing * libxml2 or libxslt error messaging into standard, preconfigured * libqb-backed log stream. * * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt) * emits a single message by chunks (location is emitted separatedly from * the message itself), so we have to take the effort to combine these * chunks back to single message. Whether to do this or not is driven * with \p dechunk toggle. * * The form of a macro was chosen for implicit deriving of __FILE__, etc. * and also because static dechunking buffer should be differentiated per * library (here we assume different functions referring to this macro * will not ever be using both at once), preferably also per-library * context of use to avoid clashes altogether. * * Note that we cannot use qb_logt, because callsite data have to be known * at the moment of compilation, which it is not always the case -- xml_log * (and unfortunately there's no clear explanation of the fail to compile). * * Also note that there's no explicit guard against said libraries producing * never-newline-terminated chunks (which would just keep consuming memory), * as it's quite improbable. Termination of the program in between the * same-message chunks will raise a flag with valgrind and the likes, though. * * And lastly, regarding how dechunking combines with other non-message * parameters -- for \p priority, most important running specification * wins (possibly elevated to LOG_ERR in case of nonconformance with the * newline-termination "protocol"), \p dechunk is expected to always be * on once it was at the start, and the rest (\p postemit and \p prefix) * are picked directly from the last chunk entry finalizing the message * (also reasonable to always have it the same with all related entries). * * \param[in] priority Syslog priority for the message to be logged * \param[in] dechunk Whether to dechunk new-line terminated message * \param[in] postemit Code to be executed once message is sent out * \param[in] prefix How to prefix the message or NULL for raw passing * \param[in] fmt Format string as with printf-like functions * \param[in] ap Variable argument list to supplement \p fmt format string */ #define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \ do { \ if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \ qb_log_from_external_source_va(__func__, __FILE__, (fmt), \ (priority), __LINE__, 0, (ap)); \ (void) (postemit); \ } else { \ int CXLB_len = 0; \ char *CXLB_buf = NULL; \ static int CXLB_buffer_len = 0; \ static char *CXLB_buffer = NULL; \ static uint8_t CXLB_priority = 0; \ \ CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \ \ if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \ if (CXLB_len < 0) { \ CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\ CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \ } else if (CXLB_len > 0 /* && (dechunk) */ \ && CXLB_buf[CXLB_len - 1] == '\n') { \ CXLB_buf[CXLB_len - 1] = '\0'; \ } \ if (CXLB_buffer) { \ qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \ CXLB_priority, __LINE__, 0, \ (prefix) != NULL ? (prefix) : "", \ CXLB_buffer, CXLB_buf); \ free(CXLB_buffer); \ } else { \ qb_log_from_external_source(__func__, __FILE__, "%s%s", \ (priority), __LINE__, 0, \ (prefix) != NULL ? (prefix) : "", \ CXLB_buf); \ } \ if (CXLB_len < 0) { \ CXLB_buf = NULL; /* restore temporary override */ \ } \ CXLB_buffer = NULL; \ CXLB_buffer_len = 0; \ (void) (postemit); \ \ } else if (CXLB_buffer == NULL) { \ CXLB_buffer_len = CXLB_len; \ CXLB_buffer = CXLB_buf; \ CXLB_buf = NULL; \ CXLB_priority = (priority); /* remember as a running severest */ \ \ } else { \ CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \ memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \ CXLB_buffer_len += CXLB_len; \ CXLB_buffer[CXLB_buffer_len] = '\0'; \ CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \ } \ free(CXLB_buf); \ } \ } while (0) /*! * \internal * \brief Bit flags to control format in XML logs and dumps */ enum pcmk__xml_fmt_options { //! Exclude certain XML attributes (for calculating digests) pcmk__xml_fmt_filtered = (1 << 0), //! Include indentation and newlines pcmk__xml_fmt_pretty = (1 << 1), //! Include the opening tag of an XML element, and include XML comments pcmk__xml_fmt_open = (1 << 3), //! Include the children of an XML element pcmk__xml_fmt_children = (1 << 4), //! Include the closing tag of an XML element pcmk__xml_fmt_close = (1 << 5), // @COMPAT Can we start including text nodes unconditionally? //! Include XML text nodes pcmk__xml_fmt_text = (1 << 6), }; void pcmk__xml_init(void); void pcmk__xml_cleanup(void); int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options); int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml); -/* XML search strings for guest, remote and pacemaker_remote nodes */ - -/* search string to find CIB resources entries for cluster nodes */ -#define PCMK__XP_MEMBER_NODE_CONFIG \ - "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_NODES \ - "/" PCMK_XE_NODE \ - "[not(@" PCMK_XA_TYPE ") or @" PCMK_XA_TYPE "='" PCMK_VALUE_MEMBER "']" - -/* search string to find CIB resources entries for guest nodes */ -#define PCMK__XP_GUEST_NODE_CONFIG \ - "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ - "//" PCMK_XE_META_ATTRIBUTES "//" PCMK_XE_NVPAIR \ - "[@" PCMK_XA_NAME "='" PCMK_META_REMOTE_NODE "']" - -/* search string to find CIB resources entries for remote nodes */ -#define PCMK__XP_REMOTE_NODE_CONFIG \ - "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ - "[@" PCMK_XA_TYPE "='" PCMK_VALUE_REMOTE "']" \ - "[@" PCMK_XA_PROVIDER "='pacemaker']" - -/* search string to find CIB node status entries for pacemaker_remote nodes */ -#define PCMK__XP_REMOTE_NODE_STATUS \ - "//" PCMK_XE_CIB "//" PCMK_XE_STATUS "//" PCMK__XE_NODE_STATE \ - "[@" PCMK_XA_REMOTE_NODE "='" PCMK_VALUE_TRUE "']" - enum pcmk__xml_artefact_ns { pcmk__xml_artefact_ns_legacy_rng = 1, pcmk__xml_artefact_ns_legacy_xslt, pcmk__xml_artefact_ns_base_rng, pcmk__xml_artefact_ns_base_xslt, }; void pcmk__strip_xml_text(xmlNode *xml); -GString *pcmk__element_xpath(const xmlNode *xml); - /*! * \internal * \brief Indicators of which XML characters to escape * * XML allows the escaping of special characters by replacing them with entity * references (for example, """) or character references (for * example, " "). * * The special characters '&' (except as the beginning of an entity * reference) and '<' are not allowed in their literal forms in XML * character data. Character data is non-markup text (for example, the content * of a text node). '>' is allowed under most circumstances; we escape * it for safety and symmetry. * * For more details, see the "Character Data and Markup" section of the XML * spec, currently section 2.4: * https://www.w3.org/TR/xml/#dt-markup * * Attribute values are handled specially. * * If an attribute value is delimited by single quotes, then single quotes * must be escaped within the value. * * Similarly, if an attribute value is delimited by double quotes, then double * quotes must be escaped within the value. * * A conformant XML processor replaces a literal whitespace character (tab, * newline, carriage return, space) in an attribute value with a space * (\c '#x20') character. However, a reference to a whitespace character (for * example, \c " " for \c '\n') does not get replaced. * * For more details, see the "Attribute-Value Normalization" section of the * XML spec, currently section 3.3.3. Note that the default attribute type * is CDATA; we don't deal with NMTOKENS, etc.: * https://www.w3.org/TR/xml/#AVNormalize * * Pacemaker always delimits attribute values with double quotes, so there's no * need to escape single quotes. * * Newlines and tabs should be escaped in attribute values when XML is * serialized to text, so that future parsing preserves them rather than * normalizing them to spaces. * * We always escape carriage returns, so that they're not converted to spaces * during attribute-value normalization and because displaying them as literals * is messy. */ enum pcmk__xml_escape_type { /*! * For text nodes. * * Escape \c '<', \c '>', and \c '&' using entity references. * * Do not escape \c '\n' and \c '\t'. * * Escape other non-printing characters using character references. */ pcmk__xml_escape_text, /*! * For attribute values. * * Escape \c '<', \c '>', \c '&', and \c '"' using entity references. * * Escape \c '\n', \c '\t', and other non-printing characters using * character references. */ pcmk__xml_escape_attr, /* @COMPAT Drop escaping of at least '\n' and '\t' for * pcmk__xml_escape_attr_pretty when openstack-info, openstack-floating-ip, * and openstack-virtual-ip resource agents no longer depend on it. * * At time of writing, openstack-info may set a multiline value for the * openstack_ports node attribute. The other two agents query the value and * require it to be on one line with no spaces. */ /*! * For attribute values displayed in text output delimited by double quotes. * * Escape \c '\n' as \c "\\n" * * Escape \c '\r' as \c "\\r" * * Escape \c '\t' as \c "\\t" * * Escape \c '"' as \c "\\"" */ pcmk__xml_escape_attr_pretty, }; bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type); char *pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type); /*! * \internal * \brief Get the root directory to scan XML artefacts of given kind for * * \param[in] ns governs the hierarchy nesting against the inherent root dir * * \return root directory to scan XML artefacts of given kind for */ char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns); /*! * \internal * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT) * * \param[in] ns denotes path forming details (parent dir, suffix) * \param[in] filespec symbolic file specification to be combined with * #artefact_ns to form the final path * \return unwrapped path to particular XML artifact (RNG/XSLT) */ char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec); /*! * \internal * \brief Return first non-text child node of an XML node * * \param[in] parent XML node to check * * \return First non-text child node of \p parent (or NULL if none) */ static inline xmlNode * pcmk__xml_first_child(const xmlNode *parent) { xmlNode *child = (parent? parent->children : NULL); while (child && (child->type == XML_TEXT_NODE)) { child = child->next; } return child; } /*! * \internal * \brief Return next non-text sibling node of an XML node * * \param[in] child XML node to check * * \return Next non-text sibling of \p child (or NULL if none) */ static inline xmlNode * pcmk__xml_next(const xmlNode *child) { xmlNode *next = (child? child->next : NULL); while (next && (next->type == XML_TEXT_NODE)) { next = next->next; } return next; } void pcmk__xml_free(xmlNode *xml); void pcmk__xml_free_doc(xmlDoc *doc); xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src); /*! * \internal * \brief Flags for operations affecting XML attributes */ enum pcmk__xa_flags { //! Flag has no effect pcmk__xaf_none = 0U, //! Don't overwrite existing values pcmk__xaf_no_overwrite = (1U << 0), /*! * Treat values as score updates where possible (see * \c pcmk__xe_set_score()) */ pcmk__xaf_score_update = (1U << 1), }; void pcmk__xml_sanitize_id(char *id); -/*! - * \internal - * \brief Extract the ID attribute from an XML element - * - * \param[in] xpath String to search - * \param[in] node Node to get the ID for - * - * \return ID attribute of \p node in xpath string \p xpath - */ -char * -pcmk__xpath_node_id(const char *xpath, const char *node); - -/*! - * \internal - * \brief Print an informational message if an xpath query returned multiple - * items with the same ID. - * - * \param[in,out] out The output object - * \param[in] search The xpath search result, most typically the result of - * calling cib->cmds->query(). - * \param[in] name The name searched for - */ -void -pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search, - const char *name); - /* internal XML-related utilities */ enum xml_private_flags { pcmk__xf_none = 0x0000, pcmk__xf_dirty = 0x0001, pcmk__xf_deleted = 0x0002, pcmk__xf_created = 0x0004, pcmk__xf_modified = 0x0008, pcmk__xf_tracking = 0x0010, pcmk__xf_processed = 0x0020, pcmk__xf_skip = 0x0040, pcmk__xf_moved = 0x0080, pcmk__xf_acl_enabled = 0x0100, pcmk__xf_acl_read = 0x0200, pcmk__xf_acl_write = 0x0400, pcmk__xf_acl_deny = 0x0800, pcmk__xf_acl_create = 0x1000, pcmk__xf_acl_denied = 0x2000, pcmk__xf_lazy = 0x4000, }; void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag); bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), void *user_data); static inline const char * pcmk__xml_attr_value(const xmlAttr *attr) { return ((attr == NULL) || (attr->children == NULL))? NULL : (const char *) attr->children->content; } /*! * \internal * \brief Check whether a given CIB element was modified in a CIB patchset * * \param[in] patchset CIB XML patchset * \param[in] element XML tag of CIB element to check (\c NULL is equivalent * to \c PCMK_XE_CIB). Supported values include any CIB * element supported by \c pcmk__cib_abs_xpath_for(). * * \return \c true if \p element was modified, or \c false otherwise */ bool pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element); #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_XML_INTERNAL__H diff --git a/include/crm/common/xpath_internal.h b/include/crm/common/xpath_internal.h new file mode 100644 index 0000000000..a1e4b1ac66 --- /dev/null +++ b/include/crm/common/xpath_internal.h @@ -0,0 +1,54 @@ +/* + * Copyright 2022-2025 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_XPATH_INTERNAL__H +#define PCMK__CRM_COMMON_XPATH_INTERNAL__H + +#include // GString +#include // xmlNode + +#include // PCMK_META_*, PCMK_VALUE_* +#include // pcmk__output_t +#include // PCMK_XE_*, PCMK_XA_*, etc. +#include // PCMK__XE_* + +/* + * Internal-only wrappers for and extensions to libxml2 XPath utilities + */ + +//! XPath expression matching CIB node elements for cluster nodes +#define PCMK__XP_MEMBER_NODE_CONFIG \ + "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_NODES \ + "/" PCMK_XE_NODE \ + "[not(@" PCMK_XA_TYPE ") or @" PCMK_XA_TYPE "='" PCMK_VALUE_MEMBER "']" + +//! XPath expression matching CIB primitive meta-attribute defining a guest node +#define PCMK__XP_GUEST_NODE_CONFIG \ + "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ + "//" PCMK_XE_META_ATTRIBUTES "//" PCMK_XE_NVPAIR \ + "[@" PCMK_XA_NAME "='" PCMK_META_REMOTE_NODE "']" + +//! XPath expression matching CIB Pacemaker Remote connection resource +#define PCMK__XP_REMOTE_NODE_CONFIG \ + "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ + "[@" PCMK_XA_TYPE "='" PCMK_VALUE_REMOTE "']" \ + "[@" PCMK_XA_PROVIDER "='pacemaker']" + +//! XPath expression matching CIB node state elements for Pacemaker Remote nodes +#define PCMK__XP_REMOTE_NODE_STATUS \ + "//" PCMK_XE_CIB "//" PCMK_XE_STATUS "//" PCMK__XE_NODE_STATE \ + "[@" PCMK_XA_REMOTE_NODE "='" PCMK_VALUE_TRUE "']" + +GString *pcmk__element_xpath(const xmlNode *xml); +char *pcmk__xpath_node_id(const char *xpath, const char *node); + +void pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search, + const char *name); + +#endif // PCMK__CRM_COMMON_XPATH_INTERNAL__H diff --git a/lib/common/xpath.c b/lib/common/xpath.c index 630b3d7db5..05a16d6109 100644 --- a/lib/common/xpath.c +++ b/lib/common/xpath.c @@ -1,342 +1,360 @@ /* * 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; } switch (match->type) { case XML_ELEMENT_NODE: return match; case XML_DOCUMENT_NODE: // Searched for '/' return match->children; default: if ((match->parent != NULL) && (match->parent->type == XML_ELEMENT_NODE)) { return match->parent; } crm_warn("Unsupported XPath match type %d (bug?)", match->type); return NULL; } } 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; } +/*! + * \internal + * \brief Extract the ID attribute from an XML element + * + * \param[in] xpath String to search + * \param[in] node Node to get the ID for + * + * \return ID attribute of \p node in xpath string \p 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, "\'"); pcmk__assert(end != NULL); 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; } +/*! + * \internal + * \brief Warn if an XPath query returned multiple nodes with the same ID + * + * \param[in,out] out Output object + * \param[in] search XPath search result, most typically the result of + * calling cib->cmds->query(). + * \param[in] name Name searched for + */ 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); }