Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
index 68c414493b..d6a3cf3183 100644
--- a/include/crm/common/xml.h
+++ b/include/crm/common/xml.h
@@ -1,76 +1,73 @@
/*
* Copyright 2004-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__H
#define PCMK__CRM_COMMON_XML__H
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <crm/crm.h>
#include <crm/common/nvpair.h>
#include <crm/common/schemas.h>
#include <crm/common/xml_io.h>
#include <crm/common/xml_names.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Wrappers for and extensions to libxml2
* \ingroup core
*/
typedef const xmlChar *pcmkXmlStr;
/*
* Searching & Modifying
*/
// NOTE: sbd (as of at least 1.5.2) uses this
xmlNode *get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level);
-void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
- void (*helper)(xmlNode*, void*), void *user_data);
-
bool xml_tracking_changes(xmlNode * xml);
bool xml_document_dirty(xmlNode *xml);
void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls);
void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml);
void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml);
void xml_accept_changes(xmlNode * xml);
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]);
xmlNode *xml_create_patchset(
int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version);
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version);
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest);
#ifdef __cplusplus
}
#endif
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#include <crm/common/xml_compat.h>
#endif
#endif
diff --git a/include/crm/common/xml_compat.h b/include/crm/common/xml_compat.h
index 038179ecc1..12ffb61f6c 100644
--- a/include/crm/common/xml_compat.h
+++ b/include/crm/common/xml_compat.h
@@ -1,101 +1,105 @@
/*
* Copyright 2004-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_COMPAT__H
#define PCMK__CRM_COMMON_XML_COMPAT__H
#include <glib.h> // gboolean
#include <libxml/tree.h> // xmlNode
#include <libxml/xpath.h> // xmlXPathObject
#include <crm/common/nvpair.h> // crm_xml_add()
#include <crm/common/xml_names.h> // 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.
*/
// 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;
}
// 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);
// 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
void crm_xml_sanitize_id(char *id);
//! \deprecated Do not use
char *calculate_on_disk_digest(xmlNode *input);
//! \deprecated Do not use
char *calculate_operation_digest(xmlNode *input, const char *version);
//! \deprecated Do not use
char *calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
gboolean do_filter, const char *version);
//! \deprecated Do not use
xmlXPathObjectPtr xpath_search(const xmlNode *xml_top, const char *path);
//! \deprecated Do not use
static inline int numXpathResults(xmlXPathObjectPtr xpathObj)
{
if ((xpathObj == NULL) || (xpathObj->nodesetval == NULL)) {
return 0;
}
return xpathObj->nodesetval->nodeNr;
}
//! \deprecated Do not use
xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
//! \deprecated Do not use
void freeXpathObject(xmlXPathObjectPtr xpathObj);
//! \deprecated Do not use
void dedupXpathResults(xmlXPathObjectPtr xpathObj);
+//! \deprecated Do not use
+void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
+ void (*helper)(xmlNode*, void*), void *user_data);
+
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_XML_COMPAT__H
diff --git a/lib/common/xpath.c b/lib/common/xpath.c
index e1b1e7ff09..7fd747ed73 100644
--- a/lib/common/xpath.c
+++ b/lib/common/xpath.c
@@ -1,525 +1,513 @@
/*
* Copyright 2004-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.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <string.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include "crmcommon_private.h"
#include <libxml/xpath.h> // xmlXPathObject, etc.
/*!
* \internal
* \brief Get a node from the result set of evaluating an XPath expression
*
* Evaluating an XPath expression stores the list of matching nodes in an
* \c xmlXPathObject. This function gets the node at a particular index within
* that list.
*
* \param[in,out] xpath_obj XPath object containing result nodes
* \param[in] index Index of result node to get
*
* \return Result node at the given index if possible, or \c NULL otherwise
*
* \note This has a side effect: it sets the result node at \p index to NULL
* within \p xpath_obj, so the result at a given index can be retrieved
* only once. This is a workaround to prevent a use-after-free error.
*
* All elements returned by an XPath query are pointers to elements from
* the tree, except namespace nodes (which are allocated separately for
* the XPath object's node set). Accordingly, only namespace nodes and the
* node set itself are freed when libxml2 frees a node set.
*
* This logic requires checking the type of every node in the node set.
* However, a node may have been freed already while processing an XPath
* object -- either directly (for example, with \c xmlFreeNode()) or
* indirectly (for example, with \c xmlNodeSetContent()). In that case,
* checking the freed node's type while freeing the XPath object is a
* use-after-free error.
*
* To reduce the likelihood of this, when we access a node in the XPath
* object, we remove it from the XPath object's node set by setting it to
* \c NULL. This approach is adapted from \c xpath2.c in libxml2's
* examples. That file also describes a way to reproduce the
* use-after-free error.
*
* However, there are still ways that a use-after-free can occur. For
* example, freeing the entire XML tree before freeing an XPath object
* that contains pointers to it would be an error. It's dangerous to mix
* processing XPath search results with modifications to a tree, and it
* must be done with care.
*/
xmlNode *
pcmk__xpath_result(xmlXPathObject *xpath_obj, int index)
{
xmlNode *match = NULL;
CRM_CHECK((xpath_obj != NULL) && (index >= 0), return NULL);
match = xmlXPathNodeSetItem(xpath_obj->nodesetval, index);
if (match == NULL) {
// Previously requested or out of range
return NULL;
}
if (match->type != XML_NAMESPACE_DECL) {
xpath_obj->nodesetval->nodeTab[index] = NULL;
}
return match;
}
/*!
* \internal
* \brief Get an element node corresponding to an XPath match node
*
* Each node in an XPath object's result node set may be of an arbitrary type.
* This function is guaranteed to return an element node (or \c NULL).
*
* \param[in] match XML node that matched some XPath expression
*
* \retval \p match if \p match is an element
* \retval Root element of \p match if \p match is a document
* \retval <tt>match->parent</tt> if \p match is not an element but its parent
* is an element
* \retval \c NULL otherwise
*
* \todo Phase this out. Code that relies on this behavior is likely buggy.
*/
xmlNode *
pcmk__xpath_match_element(xmlNode *match)
{
pcmk__assert(match != NULL);
switch (match->type) {
case XML_ELEMENT_NODE:
return match;
case XML_DOCUMENT_NODE:
// Happens if XPath expression is "/"; return root element instead
return xmlDocGetRootElement((xmlDoc *) match);
default:
if ((match->parent != NULL)
&& (match->parent->type == XML_ELEMENT_NODE)) {
// Probably an attribute; return parent element instead
return match->parent;
}
crm_err("Cannot get element from XPath expression match of type %s",
pcmk__xml_element_type_text(match->type));
return NULL;
}
}
/*!
* \internal
* \brief Search an XML document using an XPath expression
*
* \param[in] doc XML document to search
* \param[in] path XPath expression to evaluate in the context of \p doc
*
* \return XPath object containing result of evaluating \p path against \p doc
*/
xmlXPathObject *
pcmk__xpath_search(xmlDoc *doc, const char *path)
{
pcmkXmlStr xpath_expr = (pcmkXmlStr) path;
xmlXPathContext *xpath_context = NULL;
xmlXPathObject *xpath_obj = NULL;
CRM_CHECK((doc != NULL) && !pcmk__str_empty(path), return NULL);
xpath_context = xmlXPathNewContext(doc);
pcmk__mem_assert(xpath_context);
xpath_obj = xmlXPathEval(xpath_expr, xpath_context);
xmlXPathFreeContext(xpath_context);
return xpath_obj;
}
/*!
* \internal
* \brief Run a supplied function for each result of an XPath search
*
* \param[in,out] doc XML document to search
* \param[in] path XPath expression to evaluate in the context of
* \p doc
* \param[in] fn Function to call for each result XML element
* \param[in,out] user_data Data to pass to \p fn
*
* \note This function processes the result node set in forward order. If \p fn
* may free any part of any result node, then it is safer to process the
* result node set in reverse order. (The node set is in document order.)
* See comments in libxml's <tt>examples/xpath2.c</tt> file.
*/
void
pcmk__xpath_foreach_result(xmlDoc *doc, const char *path,
void (*fn)(xmlNode *, void *), void *user_data)
{
xmlXPathObject *xpath_obj = NULL;
int num_results = 0;
CRM_CHECK((doc != NULL) && !pcmk__str_empty(path) && (fn != NULL), return);
xpath_obj = pcmk__xpath_search(doc, path);
num_results = pcmk__xpath_num_results(xpath_obj);
for (int i = 0; i < num_results; i++) {
xmlNode *result = pcmk__xpath_result(xpath_obj, i);
if (result != NULL) {
(*fn)(result, user_data);
}
}
xmlXPathFreeObject(xpath_obj);
}
-/*!
- * \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)
-{
- xmlXPathObject *xpathObj = NULL;
- int nresults = 0;
-
- CRM_CHECK(xml != NULL, return);
-
- xpathObj = pcmk__xpath_search(xml->doc, xpath);
- nresults = pcmk__xpath_num_results(xpathObj);
-
- for (int i = 0; i < nresults; i++) {
- xmlNode *result = pcmk__xpath_result(xpathObj, i);
-
- CRM_LOG_ASSERT(result != NULL);
-
- if (result != NULL) {
- result = pcmk__xpath_match_element(result);
-
- CRM_LOG_ASSERT(result != NULL);
-
- if (result != NULL) {
- (*helper)(result, user_data);
- }
- }
- }
- xmlXPathFreeObject(xpathObj);
-}
-
xmlNode *
get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
{
int max;
xmlNode *result = NULL;
xmlXPathObject *xpathObj = NULL;
char *nodePath = NULL;
char *matchNodePath = NULL;
if (xpath == NULL) {
return xml_obj; /* or return NULL? */
}
xpathObj = pcmk__xpath_search(xml_obj->doc, xpath);
nodePath = (char *)xmlGetNodePath(xml_obj);
max = pcmk__xpath_num_results(xpathObj);
if (max == 0) {
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 = pcmk__xpath_result(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if (match != NULL) {
match = pcmk__xpath_match_element(match);
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 = pcmk__xpath_result(xpathObj, 0);
if (result != NULL) {
result = pcmk__xpath_match_element(result);
}
}
xmlXPathFreeObject(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), "<none>"));
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 <tt>cib->cmds->query()</tt>.
* \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);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/xml_compat.h>
xmlXPathObjectPtr
xpath_search(const xmlNode *xml_top, const char *path)
{
CRM_CHECK(xml_top != NULL, return NULL);
return pcmk__xpath_search(xml_top->doc, path);
}
xmlNode *
getXpathResult(xmlXPathObjectPtr xpathObj, int index)
{
xmlNode *match = NULL;
int max = pcmk__xpath_num_results(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 pcmk__xpath_result()
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
freeXpathObject(xmlXPathObjectPtr xpathObj)
{
int max = pcmk__xpath_num_results(xpathObj);
if (xpathObj == NULL) {
return;
}
for (int 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);
}
void
dedupXpathResults(xmlXPathObjectPtr xpathObj)
{
int max = pcmk__xpath_num_results(xpathObj);
if (xpathObj == NULL) {
return;
}
for (int 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;
}
}
}
}
+void
+crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
+ void (*helper)(xmlNode*, void*), void *user_data)
+{
+ xmlXPathObject *xpathObj = NULL;
+ int nresults = 0;
+
+ CRM_CHECK(xml != NULL, return);
+
+ xpathObj = pcmk__xpath_search(xml->doc, xpath);
+ nresults = pcmk__xpath_num_results(xpathObj);
+
+ for (int i = 0; i < nresults; i++) {
+ xmlNode *result = pcmk__xpath_result(xpathObj, i);
+
+ CRM_LOG_ASSERT(result != NULL);
+
+ if (result != NULL) {
+ result = pcmk__xpath_match_element(result);
+
+ CRM_LOG_ASSERT(result != NULL);
+
+ if (result != NULL) {
+ (*helper)(result, user_data);
+ }
+ }
+ }
+ xmlXPathFreeObject(xpathObj);
+}
+
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 10, 2:48 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2009784
Default Alt Text
(22 KB)

Event Timeline