Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4639344
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
22 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 10, 2:48 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2009784
Default Alt Text
(22 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment