summaryrefslogtreecommitdiffstats
path: root/lib/common/xml.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common/xml.c')
-rw-r--r--lib/common/xml.c2825
1 files changed, 1407 insertions, 1418 deletions
diff --git a/lib/common/xml.c b/lib/common/xml.c
index 53ebff7..73d6025 100644
--- a/lib/common/xml.c
+++ b/lib/common/xml.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2023 the Pacemaker project contributors
+ * Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -9,41 +9,52 @@
#include <crm_internal.h>
+#include <stdarg.h>
+#include <stdint.h> // uint32_t
#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <time.h>
-#include <string.h>
#include <stdlib.h>
-#include <stdarg.h>
-#include <bzlib.h>
+#include <string.h>
+#include <sys/stat.h> // stat(), S_ISREG, etc.
+#include <sys/types.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
-#include <libxml/xmlIO.h> /* xmlAllocOutputBuffer */
#include <crm/crm.h>
-#include <crm/msg_xml.h>
#include <crm/common/xml.h>
-#include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
+#include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
#include "crmcommon_private.h"
-// Define this as 1 in development to get insanely verbose trace messages
-#ifndef XML_PARSER_DEBUG
-#define XML_PARSER_DEBUG 0
-#endif
-
-/* @TODO XML_PARSE_RECOVER allows some XML errors to be silently worked around
- * by libxml2, which is potentially ambiguous and dangerous. We should drop it
- * when we can break backward compatibility with configurations that might be
- * relying on it (i.e. pacemaker 3.0.0).
+/*!
+ * \internal
+ * \brief Apply a function to each XML node in a tree (pre-order, depth-first)
*
- * It might be a good idea to have a transitional period where we first try
- * parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with
- * it, logging a warning if it succeeds.
+ * \param[in,out] xml XML tree to traverse
+ * \param[in,out] fn Function to call for each node (returns \c true to
+ * continue traversing the tree or \c false to stop)
+ * \param[in,out] user_data Argument to \p fn
+ *
+ * \return \c false if any \p fn call returned \c false, or \c true otherwise
+ *
+ * \note This function is recursive.
*/
-#define PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER (XML_PARSE_NOBLANKS)
-#define PCMK__XML_PARSE_OPTS_WITH_RECOVER (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER)
+bool
+pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
+ void *user_data)
+{
+ if (!fn(xml, user_data)) {
+ return false;
+ }
+
+ for (xml = pcmk__xml_first_child(xml); xml != NULL;
+ xml = pcmk__xml_next(xml)) {
+
+ if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
+ return false;
+ }
+ }
+ return true;
+}
bool
pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
@@ -93,43 +104,72 @@ pcmk__mark_xml_node_dirty(xmlNode *xml)
set_parent_flag(xml, pcmk__xf_dirty);
}
-// Clear flags on XML node and its children
-static void
-reset_xml_node_flags(xmlNode *xml)
+/*!
+ * \internal
+ * \brief Clear flags on an XML node
+ *
+ * \param[in,out] xml XML node whose flags to reset
+ * \param[in,out] user_data Ignored
+ *
+ * \return \c true (to continue traversing the tree)
+ *
+ * \note This is compatible with \c pcmk__xml_tree_foreach().
+ */
+static bool
+reset_xml_node_flags(xmlNode *xml, void *user_data)
{
- xmlNode *cIter = NULL;
xml_node_private_t *nodepriv = xml->_private;
- if (nodepriv) {
- nodepriv->flags = 0;
+ if (nodepriv != NULL) {
+ nodepriv->flags = pcmk__xf_none;
}
+ return true;
+}
- for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
- cIter = pcmk__xml_next(cIter)) {
- reset_xml_node_flags(cIter);
+/*!
+ * \internal
+ * \brief Set the \c pcmk__xf_dirty and \c pcmk__xf_created flags on an XML node
+ *
+ * \param[in,out] xml Node whose flags to set
+ * \param[in] user_data Ignored
+ *
+ * \return \c true (to continue traversing the tree)
+ *
+ * \note This is compatible with \c pcmk__xml_tree_foreach().
+ */
+static bool
+mark_xml_dirty_created(xmlNode *xml, void *user_data)
+{
+ xml_node_private_t *nodepriv = xml->_private;
+
+ if (nodepriv != NULL) {
+ pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
}
+ return true;
}
-// Set xpf_created flag on XML node and any children
+/*!
+ * \internal
+ * \brief Mark an XML tree as dirty and created, and mark its parents dirty
+ *
+ * Also mark the document dirty.
+ *
+ * \param[in,out] xml Tree to mark as dirty and created
+ */
void
-pcmk__mark_xml_created(xmlNode *xml)
+pcmk__xml_mark_created(xmlNode *xml)
{
- xmlNode *cIter = NULL;
- xml_node_private_t *nodepriv = NULL;
-
CRM_ASSERT(xml != NULL);
- nodepriv = xml->_private;
- if (nodepriv && pcmk__tracking_xml_changes(xml, FALSE)) {
- if (!pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
- pcmk__set_xml_flags(nodepriv, pcmk__xf_created);
- pcmk__mark_xml_node_dirty(xml);
- }
- for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
- cIter = pcmk__xml_next(cIter)) {
- pcmk__mark_xml_created(cIter);
- }
+ if (!pcmk__tracking_xml_changes(xml, false)) {
+ // Tracking is disabled for entire document
+ return;
}
+
+ // Mark all parents and document dirty
+ pcmk__mark_xml_node_dirty(xml);
+
+ pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
}
#define XML_DOC_PRIVATE_MAGIC 0x81726354UL
@@ -142,7 +182,7 @@ free_deleted_object(void *data)
if(data) {
pcmk__deleted_xml_t *deleted_obj = data;
- free(deleted_obj->path);
+ g_free(deleted_obj->path);
free(deleted_obj);
}
}
@@ -220,9 +260,9 @@ new_private_data(xmlNode *node)
{
switch (node->type) {
case XML_DOCUMENT_NODE: {
- xml_doc_private_t *docpriv = NULL;
- docpriv = calloc(1, sizeof(xml_doc_private_t));
- CRM_ASSERT(docpriv != NULL);
+ xml_doc_private_t *docpriv =
+ pcmk__assert_alloc(1, sizeof(xml_doc_private_t));
+
docpriv->check = XML_DOC_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
pcmk__set_xml_flags(docpriv, pcmk__xf_dirty|pcmk__xf_created);
@@ -232,9 +272,9 @@ new_private_data(xmlNode *node)
case XML_ELEMENT_NODE:
case XML_ATTRIBUTE_NODE:
case XML_COMMENT_NODE: {
- xml_node_private_t *nodepriv = NULL;
- nodepriv = calloc(1, sizeof(xml_node_private_t));
- CRM_ASSERT(nodepriv != NULL);
+ xml_node_private_t *nodepriv =
+ pcmk__assert_alloc(1, sizeof(xml_node_private_t));
+
nodepriv->check = XML_NODE_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
@@ -314,21 +354,23 @@ pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
return position;
}
-// Remove all attributes marked as deleted from an XML node
-static void
-accept_attr_deletions(xmlNode *xml)
+/*!
+ * \internal
+ * \brief Remove all attributes marked as deleted from an XML node
+ *
+ * \param[in,out] xml XML node whose deleted attributes to remove
+ * \param[in,out] user_data Ignored
+ *
+ * \return \c true (to continue traversing the tree)
+ *
+ * \note This is compatible with \c pcmk__xml_tree_foreach().
+ */
+static bool
+accept_attr_deletions(xmlNode *xml, void *user_data)
{
- // Clear XML node's flags
- ((xml_node_private_t *) xml->_private)->flags = pcmk__xf_none;
-
- // Remove this XML node's attributes that were marked as deleted
+ reset_xml_node_flags(xml, NULL);
pcmk__xe_remove_matching_attrs(xml, pcmk__marked_as_deleted, NULL);
-
- // Recursively do the same for this XML node's children
- for (xmlNodePtr cIter = pcmk__xml_first_child(xml); cIter != NULL;
- cIter = pcmk__xml_next(cIter)) {
- accept_attr_deletions(cIter);
- }
+ return true;
}
/*!
@@ -348,10 +390,11 @@ pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
return pcmk__xc_match(haystack, needle, exact);
} else {
- const char *id = ID(needle);
- const char *attr = (id == NULL)? NULL : XML_ATTR_ID;
+ const char *id = pcmk__xe_id(needle);
+ const char *attr = (id == NULL)? NULL : PCMK_XA_ID;
- return pcmk__xe_match(haystack, (const char *) needle->name, attr, id);
+ return pcmk__xe_first_child(haystack, (const char *) needle->name, attr,
+ id);
}
}
@@ -377,207 +420,263 @@ xml_accept_changes(xmlNode * xml)
}
docpriv->flags = pcmk__xf_none;
- accept_attr_deletions(top);
-}
-
-xmlNode *
-find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
-{
- xmlNode *a_child = NULL;
- const char *name = (root == NULL)? "<NULL>" : (const char *) root->name;
-
- if (search_path == NULL) {
- crm_warn("Will never find <NULL>");
- return NULL;
- }
-
- for (a_child = pcmk__xml_first_child(root); a_child != NULL;
- a_child = pcmk__xml_next(a_child)) {
- if (strcmp((const char *)a_child->name, search_path) == 0) {
- return a_child;
- }
- }
-
- if (must_find) {
- crm_warn("Could not find %s in %s.", search_path, name);
- } else if (root != NULL) {
- crm_trace("Could not find %s in %s.", search_path, name);
- } else {
- crm_trace("Could not find %s in <NULL>.", search_path);
- }
-
- return NULL;
+ pcmk__xml_tree_foreach(top, accept_attr_deletions, NULL);
}
-#define attr_matches(c, n, v) pcmk__str_eq(crm_element_value((c), (n)), \
- (v), pcmk__str_none)
-
/*!
* \internal
* \brief Find first XML child element matching given criteria
*
- * \param[in] parent XML element to search
- * \param[in] node_name If not NULL, only match children of this type
- * \param[in] attr_n If not NULL, only match children with an attribute
+ * \param[in] parent XML element to search (can be \c NULL)
+ * \param[in] node_name If not \c NULL, only match children of this type
+ * \param[in] attr_n If not \c NULL, only match children with an attribute
* of this name.
* \param[in] attr_v If \p attr_n and this are not NULL, only match children
* with an attribute named \p attr_n and this value
*
- * \return Matching XML child element, or NULL if none found
+ * \return Matching XML child element, or \c NULL if none found
*/
xmlNode *
-pcmk__xe_match(const xmlNode *parent, const char *node_name,
- const char *attr_n, const char *attr_v)
+pcmk__xe_first_child(const xmlNode *parent, const char *node_name,
+ const char *attr_n, const char *attr_v)
{
- CRM_CHECK(parent != NULL, return NULL);
- CRM_CHECK(attr_v == NULL || attr_n != NULL, return NULL);
+ xmlNode *child = NULL;
+ const char *parent_name = "<null>";
- for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL;
- child = pcmk__xml_next(child)) {
- if (pcmk__str_eq(node_name, (const char *) (child->name),
- pcmk__str_null_matches)
- && ((attr_n == NULL) ||
- (attr_v == NULL && xmlHasProp(child, (pcmkXmlStr) attr_n)) ||
- (attr_v != NULL && attr_matches(child, attr_n, attr_v)))) {
- return child;
+ CRM_CHECK((attr_v == NULL) || (attr_n != NULL), return NULL);
+
+ if (parent != NULL) {
+ child = parent->children;
+ while ((child != NULL) && (child->type != XML_ELEMENT_NODE)) {
+ child = child->next;
}
+
+ parent_name = (const char *) parent->name;
}
- crm_trace("XML child node <%s%s%s%s%s> not found in %s",
- (node_name? node_name : "(any)"),
- (attr_n? " " : ""),
- (attr_n? attr_n : ""),
- (attr_n? "=" : ""),
- (attr_n? attr_v : ""),
- (const char *) parent->name);
- return NULL;
-}
-void
-copy_in_properties(xmlNode *target, const xmlNode *src)
-{
- if (src == NULL) {
- crm_warn("No node to copy properties from");
+ for (; child != NULL; child = pcmk__xe_next(child)) {
+ const char *value = NULL;
- } else if (target == NULL) {
- crm_err("No node to copy properties into");
+ if ((node_name != NULL) && !pcmk__xe_is(child, node_name)) {
+ // Node name mismatch
+ continue;
+ }
+ if (attr_n == NULL) {
+ // No attribute match needed
+ return child;
+ }
- } else {
- for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
- const char *p_name = (const char *) a->name;
- const char *p_value = pcmk__xml_attr_value(a);
+ value = crm_element_value(child, attr_n);
- expand_plus_plus(target, p_name, p_value);
- if (xml_acl_denied(target)) {
- crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name);
- return;
- }
+ if ((attr_v == NULL) && (value != NULL)) {
+ // attr_v == NULL: Attribute attr_n must be set (to any value)
+ return child;
+ }
+ if ((attr_v != NULL) && (pcmk__str_eq(value, attr_v, pcmk__str_none))) {
+ // attr_v != NULL: Attribute attr_n must be set to value attr_v
+ return child;
}
}
- return;
+ if (node_name == NULL) {
+ node_name = "(any)"; // For logging
+ }
+ if (attr_n != NULL) {
+ crm_trace("XML child node <%s %s=%s> not found in %s",
+ node_name, attr_n, attr_v, parent_name);
+ } else {
+ crm_trace("XML child node <%s> not found in %s",
+ node_name, parent_name);
+ }
+ return NULL;
}
/*!
- * \brief Parse integer assignment statements on this node and all its child
- * nodes
+ * \internal
+ * \brief Set an XML attribute, expanding \c ++ and \c += where appropriate
+ *
+ * If \p target already has an attribute named \p name set to an integer value
+ * and \p value is an addition assignment expression on \p name, then expand
+ * \p value to an integer and set attribute \p name to the expanded value in
+ * \p target.
+ *
+ * Otherwise, set attribute \p name on \p target using the literal \p value.
+ *
+ * The original attribute value in \p target and the number in an assignment
+ * expression in \p value are parsed and added as scores (that is, their values
+ * are capped at \c INFINITY and \c -INFINITY). For more details, refer to
+ * \c char2score().
*
- * \param[in,out] target Root XML node to be processed
+ * For example, suppose \p target has an attribute named \c "X" with value
+ * \c "5", and that \p name is \c "X".
+ * * If \p value is \c "X++", the new value of \c "X" in \p target is \c "6".
+ * * If \p value is \c "X+=3", the new value of \c "X" in \p target is \c "8".
+ * * If \p value is \c "val", the new value of \c "X" in \p target is \c "val".
+ * * If \p value is \c "Y++", the new value of \c "X" in \p target is \c "Y++".
*
- * \note This function is recursive
+ * \param[in,out] target XML node whose attribute to set
+ * \param[in] name Name of the attribute to set
+ * \param[in] value New value of attribute to set
+ *
+ * \return Standard Pacemaker return code (specifically, \c EINVAL on invalid
+ * argument, or \c pcmk_rc_ok otherwise)
*/
-void
-fix_plus_plus_recursive(xmlNode *target)
+int
+pcmk__xe_set_score(xmlNode *target, const char *name, const char *value)
{
- /* TODO: Remove recursion and use xpath searches for value++ */
- xmlNode *child = NULL;
+ const char *old_value = NULL;
- for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
- const char *p_name = (const char *) a->name;
- const char *p_value = pcmk__xml_attr_value(a);
+ CRM_CHECK((target != NULL) && (name != NULL), return EINVAL);
- expand_plus_plus(target, p_name, p_value);
+ if (value == NULL) {
+ return pcmk_rc_ok;
}
- for (child = pcmk__xml_first_child(target); child != NULL;
- child = pcmk__xml_next(child)) {
- fix_plus_plus_recursive(child);
+
+ old_value = crm_element_value(target, name);
+
+ // If no previous value, skip to default case and set the value unexpanded.
+ if (old_value != NULL) {
+ const char *n = name;
+ const char *v = value;
+
+ // Stop at first character that differs between name and value
+ for (; (*n == *v) && (*n != '\0'); n++, v++);
+
+ // If value begins with name followed by a "++" or "+="
+ if ((*n == '\0')
+ && (*v++ == '+')
+ && ((*v == '+') || (*v == '='))) {
+
+ // If we're expanding ourselves, no previous value was set; use 0
+ int old_value_i = (old_value != value)? char2score(old_value) : 0;
+
+ /* value="X++": new value of X is old_value + 1
+ * value="X+=Y": new value of X is old_value + Y (for some number Y)
+ */
+ int add = (*v == '+')? 1 : char2score(++v);
+
+ crm_xml_add_int(target, name, pcmk__add_scores(old_value_i, add));
+ return pcmk_rc_ok;
+ }
+ }
+
+ // Default case: set the attribute unexpanded (with value treated literally)
+ if (old_value != value) {
+ crm_xml_add(target, name, value);
}
+ return pcmk_rc_ok;
}
/*!
- * \brief Update current XML attribute value per parsed integer assignment
- statement
- *
- * \param[in,out] target an XML node, containing a XML attribute that is
- * initialized to some numeric value, to be processed
- * \param[in] name name of the XML attribute, e.g. X, whose value
- * should be updated
- * \param[in] value assignment statement, e.g. "X++" or
- * "X+=5", to be applied to the initialized value.
- *
- * \note The original XML attribute value is treated as 0 if non-numeric and
- * truncated to be an integer if decimal-point-containing.
- * \note The final XML attribute value is truncated to not exceed 1000000.
- * \note Undefined behavior if unexpected input.
+ * \internal
+ * \brief Copy XML attributes from a source element to a target element
+ *
+ * This is similar to \c xmlCopyPropList() except that attributes are marked
+ * as dirty for change tracking purposes.
+ *
+ * \param[in,out] target XML element to receive copied attributes from \p src
+ * \param[in] src XML element whose attributes to copy to \p target
+ * \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
+ *
+ * \return Standard Pacemaker return code
*/
-void
-expand_plus_plus(xmlNode * target, const char *name, const char *value)
+int
+pcmk__xe_copy_attrs(xmlNode *target, const xmlNode *src, uint32_t flags)
{
- int offset = 1;
- int name_len = 0;
- int int_value = 0;
- int value_len = 0;
+ CRM_CHECK((src != NULL) && (target != NULL), return EINVAL);
- const char *old_value = NULL;
+ for (xmlAttr *attr = pcmk__xe_first_attr(src); attr != NULL;
+ attr = attr->next) {
- if (target == NULL || value == NULL || name == NULL) {
- return;
- }
+ const char *name = (const char *) attr->name;
+ const char *value = pcmk__xml_attr_value(attr);
- old_value = crm_element_value(target, name);
-
- if (old_value == NULL) {
- /* if no previous value, set unexpanded */
- goto set_unexpanded;
+ if (pcmk_is_set(flags, pcmk__xaf_no_overwrite)
+ && (crm_element_value(target, name) != NULL)) {
+ continue;
+ }
- } else if (strstr(value, name) != value) {
- goto set_unexpanded;
+ if (pcmk_is_set(flags, pcmk__xaf_score_update)) {
+ pcmk__xe_set_score(target, name, value);
+ } else {
+ crm_xml_add(target, name, value);
+ }
}
- name_len = strlen(name);
- value_len = strlen(value);
- if (value_len < (name_len + 2)
- || value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
- goto set_unexpanded;
- }
+ return pcmk_rc_ok;
+}
- /* if we are expanding ourselves,
- * then no previous value was set and leave int_value as 0
- */
- if (old_value != value) {
- int_value = char2score(old_value);
+/*!
+ * \internal
+ * \brief Remove an XML attribute from an element
+ *
+ * \param[in,out] element XML element that owns \p attr
+ * \param[in,out] attr XML attribute to remove from \p element
+ *
+ * \return Standard Pacemaker return code (\c EPERM if ACLs prevent removal of
+ * attributes from \p element, or \c pcmk_rc_ok otherwise)
+ */
+static int
+remove_xe_attr(xmlNode *element, xmlAttr *attr)
+{
+ if (attr == NULL) {
+ return pcmk_rc_ok;
}
- if (value[name_len + 1] != '+') {
- const char *offset_s = value + (name_len + 2);
+ if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) {
+ // ACLs apply to element, not to particular attributes
+ crm_trace("ACLs prevent removal of attributes from %s element",
+ (const char *) element->name);
+ return EPERM;
+ }
- offset = char2score(offset_s);
+ if (pcmk__tracking_xml_changes(element, false)) {
+ // Leave in place (marked for removal) until after diff is calculated
+ set_parent_flag(element, pcmk__xf_dirty);
+ pcmk__set_xml_flags((xml_node_private_t *) attr->_private,
+ pcmk__xf_deleted);
+ } else {
+ xmlRemoveProp(attr);
}
- int_value += offset;
+ return pcmk_rc_ok;
+}
- if (int_value > INFINITY) {
- int_value = (int)INFINITY;
+/*!
+ * \internal
+ * \brief Remove a named attribute from an XML element
+ *
+ * \param[in,out] element XML element to remove an attribute from
+ * \param[in] name Name of attribute to remove
+ */
+void
+pcmk__xe_remove_attr(xmlNode *element, const char *name)
+{
+ if (name != NULL) {
+ remove_xe_attr(element, xmlHasProp(element, (pcmkXmlStr) name));
}
+}
- crm_xml_add_int(target, name, int_value);
- return;
+/*!
+ * \internal
+ * \brief Remove a named attribute from an XML element
+ *
+ * This is a wrapper for \c pcmk__xe_remove_attr() for use with
+ * \c pcmk__xml_tree_foreach().
+ *
+ * \param[in,out] xml XML element to remove an attribute from
+ * \param[in] user_data Name of attribute to remove
+ *
+ * \return \c true (to continue traversing the tree)
+ *
+ * \note This is compatible with \c pcmk__xml_tree_foreach().
+ */
+bool
+pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data)
+{
+ const char *name = user_data;
- set_unexpanded:
- if (old_value == value) {
- /* the old value is already set, nothing to do */
- return;
- }
- crm_xml_add(target, name, value);
- return;
+ pcmk__xe_remove_attr(xml, name);
+ return true;
}
/*!
@@ -599,102 +698,92 @@ pcmk__xe_remove_matching_attrs(xmlNode *element,
for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
next = a->next; // Grab now because attribute might get removed
if ((match == NULL) || match(a, user_data)) {
- if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) {
- crm_trace("ACLs prevent removal of attributes (%s and "
- "possibly others) from %s element",
- (const char *) a->name, (const char *) element->name);
- return; // ACLs apply to element, not particular attributes
- }
-
- if (pcmk__tracking_xml_changes(element, false)) {
- // Leave (marked for removal) until after diff is calculated
- set_parent_flag(element, pcmk__xf_dirty);
- pcmk__set_xml_flags((xml_node_private_t *) a->_private,
- pcmk__xf_deleted);
- } else {
- xmlRemoveProp(a);
+ if (remove_xe_attr(element, a) != pcmk_rc_ok) {
+ return;
}
}
}
}
+/*!
+ * \internal
+ * \brief Create a new XML element under a given parent
+ *
+ * \param[in,out] parent XML element that will be the new element's parent
+ * (\c NULL to create a new XML document with the new
+ * node as root)
+ * \param[in] name Name of new element
+ *
+ * \return Newly created XML element (guaranteed not to be \c NULL)
+ */
xmlNode *
-add_node_copy(xmlNode * parent, xmlNode * src_node)
-{
- xmlNode *child = NULL;
-
- CRM_CHECK((parent != NULL) && (src_node != NULL), return NULL);
-
- child = xmlDocCopyNode(src_node, parent->doc, 1);
- if (child == NULL) {
- return NULL;
- }
- xmlAddChild(parent, child);
- pcmk__mark_xml_created(child);
- return child;
-}
-
-xmlNode *
-create_xml_node(xmlNode * parent, const char *name)
+pcmk__xe_create(xmlNode *parent, const char *name)
{
- xmlDoc *doc = NULL;
xmlNode *node = NULL;
- if (pcmk__str_empty(name)) {
- CRM_CHECK(name != NULL && name[0] == 0, return NULL);
- return NULL;
- }
+ CRM_ASSERT(!pcmk__str_empty(name));
if (parent == NULL) {
- doc = xmlNewDoc((pcmkXmlStr) "1.0");
- if (doc == NULL) {
- return NULL;
- }
+ xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
+
+ pcmk__mem_assert(doc);
node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
- if (node == NULL) {
- xmlFreeDoc(doc);
- return NULL;
- }
+ pcmk__mem_assert(node);
+
xmlDocSetRootElement(doc, node);
} else {
node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
- if (node == NULL) {
- return NULL;
- }
+ pcmk__mem_assert(node);
}
- pcmk__mark_xml_created(node);
+
+ pcmk__xml_mark_created(node);
return node;
}
-xmlNode *
-pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content)
+/*!
+ * \internal
+ * \brief Set a formatted string as an XML node's content
+ *
+ * \param[in,out] node Node whose content to set
+ * \param[in] format <tt>printf(3)</tt>-style format string
+ * \param[in] ... Arguments for \p format
+ *
+ * \note This function escapes special characters. \c xmlNodeSetContent() does
+ * not.
+ */
+G_GNUC_PRINTF(2, 3)
+void
+pcmk__xe_set_content(xmlNode *node, const char *format, ...)
{
- xmlNode *node = create_xml_node(parent, name);
-
if (node != NULL) {
- xmlNodeSetContent(node, (pcmkXmlStr) content);
- }
+ const char *content = NULL;
+ char *buf = NULL;
- return node;
-}
+ if (strchr(format, '%') == NULL) {
+ // Nothing to format
+ content = format;
-xmlNode *
-pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
- const char *class_name, const char *text)
-{
- xmlNode *node = pcmk_create_xml_text_node(parent, element_name, text);
+ } else {
+ va_list ap;
- if (class_name != NULL) {
- crm_xml_add(node, "class", class_name);
- }
+ va_start(ap, format);
- if (id != NULL) {
- crm_xml_add(node, "id", id);
- }
+ if (pcmk__str_eq(format, "%s", pcmk__str_none)) {
+ // No need to make a copy
+ content = va_arg(ap, const char *);
- return node;
+ } else {
+ CRM_ASSERT(vasprintf(&buf, format, ap) >= 0);
+ content = buf;
+ }
+ va_end(ap);
+ }
+
+ xmlNodeSetContent(node, (pcmkXmlStr) content);
+ free(buf);
+ }
}
/*!
@@ -710,72 +799,67 @@ pcmk_free_xml_subtree(xmlNode *xml)
}
static void
-free_xml_with_position(xmlNode * child, int position)
+free_xml_with_position(xmlNode *child, int position)
{
- if (child != NULL) {
- xmlNode *top = NULL;
- xmlDoc *doc = child->doc;
- xml_node_private_t *nodepriv = child->_private;
- xml_doc_private_t *docpriv = NULL;
-
- if (doc != NULL) {
- top = xmlDocGetRootElement(doc);
- }
+ xmlDoc *doc = NULL;
+ xml_node_private_t *nodepriv = NULL;
- if (doc != NULL && top == child) {
- /* Free everything */
- xmlFreeDoc(doc);
+ if (child == NULL) {
+ return;
+ }
+ doc = child->doc;
+ nodepriv = child->_private;
- } else if (pcmk__check_acl(child, NULL, pcmk__xf_acl_write) == FALSE) {
- GString *xpath = NULL;
+ if ((doc != NULL) && (xmlDocGetRootElement(doc) == child)) {
+ // Free everything
+ xmlFreeDoc(doc);
+ return;
+ }
- pcmk__if_tracing({}, return);
- xpath = pcmk__element_xpath(child);
- qb_log_from_external_source(__func__, __FILE__,
- "Cannot remove %s %x", LOG_TRACE,
- __LINE__, 0, (const char *) xpath->str,
- nodepriv->flags);
- g_string_free(xpath, TRUE);
- return;
+ if (!pcmk__check_acl(child, NULL, pcmk__xf_acl_write)) {
+ GString *xpath = NULL;
- } else {
- if (doc && pcmk__tracking_xml_changes(child, FALSE)
- && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
-
- GString *xpath = pcmk__element_xpath(child);
+ pcmk__if_tracing({}, return);
+ xpath = pcmk__element_xpath(child);
+ qb_log_from_external_source(__func__, __FILE__,
+ "Cannot remove %s %x", LOG_TRACE,
+ __LINE__, 0, xpath->str, nodepriv->flags);
+ g_string_free(xpath, TRUE);
+ return;
+ }
- if (xpath != NULL) {
- pcmk__deleted_xml_t *deleted_obj = NULL;
+ if ((doc != NULL) && pcmk__tracking_xml_changes(child, false)
+ && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
- crm_trace("Deleting %s %p from %p",
- (const char *) xpath->str, child, doc);
+ xml_doc_private_t *docpriv = doc->_private;
+ GString *xpath = pcmk__element_xpath(child);
- deleted_obj = calloc(1, sizeof(pcmk__deleted_xml_t));
- deleted_obj->path = strdup((const char *) xpath->str);
+ if (xpath != NULL) {
+ pcmk__deleted_xml_t *deleted_obj = NULL;
- CRM_ASSERT(deleted_obj->path != NULL);
- g_string_free(xpath, TRUE);
+ crm_trace("Deleting %s %p from %p", xpath->str, child, doc);
- deleted_obj->position = -1;
- /* Record the "position" only for XML comments for now */
- if (child->type == XML_COMMENT_NODE) {
- if (position >= 0) {
- deleted_obj->position = position;
+ deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
+ deleted_obj->path = g_string_free(xpath, FALSE);
+ deleted_obj->position = -1;
- } else {
- deleted_obj->position = pcmk__xml_position(child,
- pcmk__xf_skip);
- }
- }
+ // Record the position only for XML comments for now
+ if (child->type == XML_COMMENT_NODE) {
+ if (position >= 0) {
+ deleted_obj->position = position;
- docpriv = doc->_private;
- docpriv->deleted_objs = g_list_append(docpriv->deleted_objs, deleted_obj);
- pcmk__set_xml_doc_flag(child, pcmk__xf_dirty);
+ } else {
+ deleted_obj->position = pcmk__xml_position(child,
+ pcmk__xf_skip);
}
}
- pcmk_free_xml_subtree(child);
+
+ docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
+ deleted_obj);
+ pcmk__set_xml_doc_flag(child, pcmk__xf_dirty);
}
}
+ pcmk_free_xml_subtree(child);
}
@@ -785,171 +869,48 @@ free_xml(xmlNode * child)
free_xml_with_position(child, -1);
}
+/*!
+ * \internal
+ * \brief Make a deep copy of an XML node under a given parent
+ *
+ * \param[in,out] parent XML element that will be the copy's parent (\c NULL
+ * to create a new XML document with the copy as root)
+ * \param[in] src XML node to copy
+ *
+ * \return Deep copy of \p src, or \c NULL if \p src is \c NULL
+ */
xmlNode *
-copy_xml(xmlNode * src)
-{
- xmlDoc *doc = xmlNewDoc((pcmkXmlStr) "1.0");
- xmlNode *copy = xmlDocCopyNode(src, doc, 1);
-
- CRM_ASSERT(copy != NULL);
- xmlDocSetRootElement(doc, copy);
- return copy;
-}
-
-xmlNode *
-string2xml(const char *input)
-{
- xmlNode *xml = NULL;
- xmlDocPtr output = NULL;
- xmlParserCtxtPtr ctxt = NULL;
- const xmlError *last_error = NULL;
-
- if (input == NULL) {
- crm_err("Can't parse NULL input");
- return NULL;
- }
-
- /* create a parser context */
- ctxt = xmlNewParserCtxt();
- CRM_CHECK(ctxt != NULL, return NULL);
-
- xmlCtxtResetLastError(ctxt);
- xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
- output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
- PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
-
- if (output == NULL) {
- output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
- PCMK__XML_PARSE_OPTS_WITH_RECOVER);
- if (output) {
- crm_warn("Successfully recovered from XML errors "
- "(note: a future release will treat this as a fatal failure)");
- }
- }
-
- if (output) {
- xml = xmlDocGetRootElement(output);
- }
- last_error = xmlCtxtGetLastError(ctxt);
- if (last_error && last_error->code != XML_ERR_OK) {
- /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
- /*
- * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
- * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
- */
- crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
- last_error->domain, last_error->level, last_error->code, last_error->message);
-
- if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
- CRM_LOG_ASSERT("Cannot parse an empty string");
-
- } else if (last_error->code != XML_ERR_DOCUMENT_END) {
- crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
- input);
- if (xml != NULL) {
- crm_log_xml_err(xml, "Partial");
- }
-
- } else {
- int len = strlen(input);
- int lpc = 0;
-
- while(lpc < len) {
- crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
- lpc += 80;
- }
-
- CRM_LOG_ASSERT("String parsing error");
- }
- }
-
- xmlFreeParserCtxt(ctxt);
- return xml;
-}
-
-xmlNode *
-stdin2xml(void)
-{
- size_t data_length = 0;
- size_t read_chars = 0;
-
- char *xml_buffer = NULL;
- xmlNode *xml_obj = NULL;
-
- do {
- xml_buffer = pcmk__realloc(xml_buffer, data_length + PCMK__BUFFER_SIZE);
- read_chars = fread(xml_buffer + data_length, 1, PCMK__BUFFER_SIZE,
- stdin);
- data_length += read_chars;
- } while (read_chars == PCMK__BUFFER_SIZE);
-
- if (data_length == 0) {
- crm_warn("No XML supplied on stdin");
- free(xml_buffer);
- return NULL;
- }
-
- xml_buffer[data_length] = '\0';
- xml_obj = string2xml(xml_buffer);
- free(xml_buffer);
-
- crm_log_xml_trace(xml_obj, "Created fragment");
- return xml_obj;
-}
-
-static char *
-decompress_file(const char *filename)
+pcmk__xml_copy(xmlNode *parent, xmlNode *src)
{
- char *buffer = NULL;
- int rc = 0;
- size_t length = 0, read_len = 0;
- BZFILE *bz_file = NULL;
- FILE *input = fopen(filename, "r");
+ xmlNode *copy = NULL;
- if (input == NULL) {
- crm_perror(LOG_ERR, "Could not open %s for reading", filename);
+ if (src == NULL) {
return NULL;
}
- bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
- rc = pcmk__bzlib2rc(rc);
-
- if (rc != pcmk_rc_ok) {
- crm_err("Could not prepare to read compressed %s: %s "
- CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
- BZ2_bzReadClose(&rc, bz_file);
- fclose(input);
- return NULL;
- }
+ if (parent == NULL) {
+ xmlDoc *doc = NULL;
- rc = BZ_OK;
- // cppcheck seems not to understand the abort-logic in pcmk__realloc
- // cppcheck-suppress memleak
- while (rc == BZ_OK) {
- buffer = pcmk__realloc(buffer, PCMK__BUFFER_SIZE + length + 1);
- read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
+ // The copy will be the root element of a new document
+ CRM_ASSERT(src->type == XML_ELEMENT_NODE);
- crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
+ doc = xmlNewDoc(PCMK__XML_VERSION);
+ pcmk__mem_assert(doc);
- if (rc == BZ_OK || rc == BZ_STREAM_END) {
- length += read_len;
- }
- }
+ copy = xmlDocCopyNode(src, doc, 1);
+ pcmk__mem_assert(copy);
- buffer[length] = '\0';
+ xmlDocSetRootElement(doc, copy);
- rc = pcmk__bzlib2rc(rc);
+ } else {
+ copy = xmlDocCopyNode(src, parent->doc, 1);
+ pcmk__mem_assert(copy);
- if (rc != pcmk_rc_ok) {
- crm_err("Could not read compressed %s: %s " CRM_XS " rc=%d",
- filename, pcmk_rc_str(rc), rc);
- free(buffer);
- buffer = NULL;
+ xmlAddChild(parent, copy);
}
- BZ2_bzReadClose(&rc, bz_file);
- fclose(input);
- return buffer;
+ pcmk__xml_mark_created(copy);
+ return copy;
}
/*!
@@ -986,97 +947,6 @@ pcmk__strip_xml_text(xmlNode *xml)
}
}
-xmlNode *
-filename2xml(const char *filename)
-{
- xmlNode *xml = NULL;
- xmlDocPtr output = NULL;
- bool uncompressed = true;
- xmlParserCtxtPtr ctxt = NULL;
- const xmlError *last_error = NULL;
-
- /* create a parser context */
- ctxt = xmlNewParserCtxt();
- CRM_CHECK(ctxt != NULL, return NULL);
-
- xmlCtxtResetLastError(ctxt);
- xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
-
- if (filename) {
- uncompressed = !pcmk__ends_with_ext(filename, ".bz2");
- }
-
- if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) {
- /* STDIN_FILENO == fileno(stdin) */
- output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
- PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
-
- if (output == NULL) {
- output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
- PCMK__XML_PARSE_OPTS_WITH_RECOVER);
- if (output) {
- crm_warn("Successfully recovered from XML errors "
- "(note: a future release will treat this as a fatal failure)");
- }
- }
-
- } else if (uncompressed) {
- output = xmlCtxtReadFile(ctxt, filename, NULL,
- PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
-
- if (output == NULL) {
- output = xmlCtxtReadFile(ctxt, filename, NULL,
- PCMK__XML_PARSE_OPTS_WITH_RECOVER);
- if (output) {
- crm_warn("Successfully recovered from XML errors "
- "(note: a future release will treat this as a fatal failure)");
- }
- }
-
- } else {
- char *input = decompress_file(filename);
-
- output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
- PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
-
- if (output == NULL) {
- output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
- PCMK__XML_PARSE_OPTS_WITH_RECOVER);
- if (output) {
- crm_warn("Successfully recovered from XML errors "
- "(note: a future release will treat this as a fatal failure)");
- }
- }
-
- free(input);
- }
-
- if (output && (xml = xmlDocGetRootElement(output))) {
- pcmk__strip_xml_text(xml);
- }
-
- last_error = xmlCtxtGetLastError(ctxt);
- if (last_error && last_error->code != XML_ERR_OK) {
- /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
- /*
- * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
- * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
- */
- crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
- last_error->domain, last_error->level, last_error->code, last_error->message);
-
- if (last_error && last_error->code != XML_ERR_OK) {
- crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
- if (xml != NULL) {
- crm_log_xml_err(xml, "Partial");
- }
- }
- }
-
- xmlFreeParserCtxt(ctxt);
- return xml;
-}
-
/*!
* \internal
* \brief Add a "last written" attribute to an XML element, set to current time
@@ -1091,7 +961,7 @@ pcmk__xe_add_last_written(xmlNode *xe)
char *now_s = pcmk__epoch2str(NULL, 0);
const char *result = NULL;
- result = crm_xml_add(xe, XML_CIB_ATTR_WRITTEN,
+ result = crm_xml_add(xe, PCMK_XA_CIB_LAST_WRITTEN,
pcmk__s(now_s, "Could not determine current time"));
free(now_s);
return result;
@@ -1138,598 +1008,202 @@ crm_xml_set_id(xmlNode *xml, const char *format, ...)
CRM_ASSERT(len > 0);
crm_xml_sanitize_id(id);
- crm_xml_add(xml, XML_ATTR_ID, id);
+ crm_xml_add(xml, PCMK_XA_ID, id);
free(id);
}
/*!
* \internal
- * \brief Write XML to a file stream
+ * \brief Check whether a string has XML special characters that must be escaped
*
- * \param[in] xml XML to write
- * \param[in] filename Name of file being written (for logging only)
- * \param[in,out] stream Open file stream corresponding to filename
- * \param[in] compress Whether to compress XML before writing
- * \param[out] nbytes Number of bytes written
+ * See \c pcmk__xml_escape() and \c pcmk__xml_escape_type for more details.
*
- * \return Standard Pacemaker return code
- */
-static int
-write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
- bool compress, unsigned int *nbytes)
-{
- int rc = pcmk_rc_ok;
- char *buffer = NULL;
-
- *nbytes = 0;
- crm_log_xml_trace(xml, "writing");
-
- buffer = dump_xml_formatted(xml);
- CRM_CHECK(buffer && strlen(buffer),
- crm_log_xml_warn(xml, "formatting failed");
- rc = pcmk_rc_error;
- goto bail);
-
- if (compress) {
- unsigned int in = 0;
- BZFILE *bz_file = NULL;
-
- rc = BZ_OK;
- bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
- rc = pcmk__bzlib2rc(rc);
-
- if (rc != pcmk_rc_ok) {
- crm_warn("Not compressing %s: could not prepare file stream: %s "
- CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
- } else {
- BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
- rc = pcmk__bzlib2rc(rc);
-
- if (rc != pcmk_rc_ok) {
- crm_warn("Not compressing %s: could not compress data: %s "
- CRM_XS " rc=%d errno=%d",
- filename, pcmk_rc_str(rc), rc, errno);
- }
- }
-
- if (rc == pcmk_rc_ok) {
- BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes);
- rc = pcmk__bzlib2rc(rc);
-
- if (rc != pcmk_rc_ok) {
- crm_warn("Not compressing %s: could not write compressed data: %s "
- CRM_XS " rc=%d errno=%d",
- filename, pcmk_rc_str(rc), rc, errno);
- *nbytes = 0; // retry without compression
- } else {
- crm_trace("Compressed XML for %s from %u bytes to %u",
- filename, in, *nbytes);
- }
- }
- rc = pcmk_rc_ok; // Either true, or we'll retry without compression
- }
-
- if (*nbytes == 0) {
- rc = fprintf(stream, "%s", buffer);
- if (rc < 0) {
- rc = errno;
- crm_perror(LOG_ERR, "writing %s", filename);
- } else {
- *nbytes = (unsigned int) rc;
- rc = pcmk_rc_ok;
- }
- }
-
- bail:
-
- if (fflush(stream) != 0) {
- rc = errno;
- crm_perror(LOG_ERR, "flushing %s", filename);
- }
-
- /* Don't report error if the file does not support synchronization */
- if (fsync(fileno(stream)) < 0 && errno != EROFS && errno != EINVAL) {
- rc = errno;
- crm_perror(LOG_ERR, "synchronizing %s", filename);
- }
-
- fclose(stream);
-
- crm_trace("Saved %d bytes to %s as XML", *nbytes, filename);
- free(buffer);
-
- return rc;
-}
-
-/*!
- * \brief Write XML to a file descriptor
- *
- * \param[in] xml XML to write
- * \param[in] filename Name of file being written (for logging only)
- * \param[in] fd Open file descriptor corresponding to filename
- * \param[in] compress Whether to compress XML before writing
+ * \param[in] text String to check
+ * \param[in] type Type of escaping
*
- * \return Number of bytes written on success, -errno otherwise
+ * \return \c true if \p text has special characters that need to be escaped, or
+ * \c false otherwise
*/
-int
-write_xml_fd(const xmlNode *xml, const char *filename, int fd,
- gboolean compress)
-{
- FILE *stream = NULL;
- unsigned int nbytes = 0;
- int rc = pcmk_rc_ok;
-
- CRM_CHECK((xml != NULL) && (fd > 0), return -EINVAL);
- stream = fdopen(fd, "w");
- if (stream == NULL) {
- return -errno;
- }
- rc = write_xml_stream(xml, filename, stream, compress, &nbytes);
- if (rc != pcmk_rc_ok) {
- return pcmk_rc2legacy(rc);
- }
- return (int) nbytes;
-}
-
-/*!
- * \brief Write XML to a file
- *
- * \param[in] xml XML to write
- * \param[in] filename Name of file to write
- * \param[in] compress Whether to compress XML before writing
- *
- * \return Number of bytes written on success, -errno otherwise
- */
-int
-write_xml_file(const xmlNode *xml, const char *filename, gboolean compress)
+bool
+pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
{
- FILE *stream = NULL;
- unsigned int nbytes = 0;
- int rc = pcmk_rc_ok;
+ if (text == NULL) {
+ return false;
+ }
+
+ while (*text != '\0') {
+ switch (type) {
+ case pcmk__xml_escape_text:
+ switch (*text) {
+ case '<':
+ case '>':
+ case '&':
+ return true;
+ case '\n':
+ case '\t':
+ break;
+ default:
+ if (g_ascii_iscntrl(*text)) {
+ return true;
+ }
+ break;
+ }
+ break;
- CRM_CHECK((xml != NULL) && (filename != NULL), return -EINVAL);
- stream = fopen(filename, "w");
- if (stream == NULL) {
- return -errno;
- }
- rc = write_xml_stream(xml, filename, stream, compress, &nbytes);
- if (rc != pcmk_rc_ok) {
- return pcmk_rc2legacy(rc);
- }
- return (int) nbytes;
-}
+ case pcmk__xml_escape_attr:
+ switch (*text) {
+ case '<':
+ case '>':
+ case '&':
+ case '"':
+ return true;
+ default:
+ if (g_ascii_iscntrl(*text)) {
+ return true;
+ }
+ break;
+ }
+ break;
-// Replace a portion of a dynamically allocated string (reallocating memory)
-static char *
-replace_text(char *text, int start, size_t *length, const char *replace)
-{
- size_t offset = strlen(replace) - 1; // We have space for 1 char already
+ case pcmk__xml_escape_attr_pretty:
+ switch (*text) {
+ case '\n':
+ case '\r':
+ case '\t':
+ case '"':
+ return true;
+ default:
+ break;
+ }
+ break;
- *length += offset;
- text = pcmk__realloc(text, *length);
+ default: // Invalid enum value
+ CRM_ASSERT(false);
+ break;
+ }
- for (size_t lpc = (*length) - 1; lpc > (start + offset); lpc--) {
- text[lpc] = text[lpc - offset];
+ text = g_utf8_next_char(text);
}
-
- memcpy(text + start, replace, offset + 1);
- return text;
+ return false;
}
/*!
+ * \internal
* \brief Replace special characters with their XML escape sequences
*
* \param[in] text Text to escape
+ * \param[in] type Type of escaping
*
* \return Newly allocated string equivalent to \p text but with special
- * characters replaced with XML escape sequences (or NULL if \p text
- * is NULL)
+ * characters replaced with XML escape sequences (or \c NULL if \p text
+ * is \c NULL). If \p text is not \c NULL, the return value is
+ * guaranteed not to be \c NULL.
+ *
+ * \note There are libxml functions that purport to do this:
+ * \c xmlEncodeEntitiesReentrant() and \c xmlEncodeSpecialChars().
+ * However, their escaping is incomplete. See:
+ * https://discourse.gnome.org/t/intended-use-of-xmlencodeentitiesreentrant-vs-xmlencodespecialchars/19252
+ * \note The caller is responsible for freeing the return value using
+ * \c g_free().
*/
-char *
-crm_xml_escape(const char *text)
+gchar *
+pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
{
- size_t length;
- char *copy;
-
- /*
- * When xmlCtxtReadDoc() parses &lt; and friends in a
- * value, it converts them to their human readable
- * form.
- *
- * If one uses xmlNodeDump() to convert it back to a
- * string, all is well, because special characters are
- * converted back to their escape sequences.
- *
- * However xmlNodeDump() is randomly dog slow, even with the same
- * input. So we need to replicate the escaping in our custom
- * version so that the result can be re-parsed by xmlCtxtReadDoc()
- * when necessary.
- */
+ GString *copy = NULL;
if (text == NULL) {
return NULL;
}
+ copy = g_string_sized_new(strlen(text));
- length = 1 + strlen(text);
- copy = strdup(text);
- CRM_ASSERT(copy != NULL);
- for (size_t index = 0; index < length; index++) {
- if(copy[index] & 0x80 && copy[index+1] & 0x80){
- index++;
- break;
- }
- switch (copy[index]) {
- case 0:
- break;
- case '<':
- copy = replace_text(copy, index, &length, "&lt;");
- break;
- case '>':
- copy = replace_text(copy, index, &length, "&gt;");
- break;
- case '"':
- copy = replace_text(copy, index, &length, "&quot;");
- break;
- case '\'':
- copy = replace_text(copy, index, &length, "&apos;");
- break;
- case '&':
- copy = replace_text(copy, index, &length, "&amp;");
- break;
- case '\t':
- /* Might as well just expand to a few spaces... */
- copy = replace_text(copy, index, &length, " ");
- break;
- case '\n':
- copy = replace_text(copy, index, &length, "\\n");
- break;
- case '\r':
- copy = replace_text(copy, index, &length, "\\r");
- break;
- default:
- /* Check for and replace non-printing characters with their octal equivalent */
- if(copy[index] < ' ' || copy[index] > '~') {
- char *replace = crm_strdup_printf("\\%.3o", copy[index]);
+ while (*text != '\0') {
+ // Don't escape any non-ASCII characters
+ if ((*text & 0x80) != 0) {
+ size_t bytes = g_utf8_next_char(text) - text;
- copy = replace_text(copy, index, &length, replace);
- free(replace);
- }
- }
- }
- return copy;
-}
-
-/*!
- * \internal
- * \brief Append a string representation of an XML element to a buffer
- *
- * \param[in] data XML whose representation to append
- * \param[in] options Group of \p pcmk__xml_fmt_options flags
- * \param[in,out] buffer Where to append the content (must not be \p NULL)
- * \param[in] depth Current indentation level
- */
-static void
-dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
- int depth)
-{
- bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
- bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
- int spaces = pretty? (2 * depth) : 0;
-
- for (int lpc = 0; lpc < spaces; lpc++) {
- g_string_append_c(buffer, ' ');
- }
-
- pcmk__g_strcat(buffer, "<", data->name, NULL);
-
- for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
- attr = attr->next) {
-
- if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
- pcmk__dump_xml_attr(attr, buffer);
+ g_string_append_len(copy, text, bytes);
+ text += bytes;
+ continue;
}
- }
-
- if (data->children == NULL) {
- g_string_append(buffer, "/>");
-
- } else {
- g_string_append_c(buffer, '>');
- }
- if (pretty) {
- g_string_append_c(buffer, '\n');
- }
-
- if (data->children) {
- for (const xmlNode *child = data->children; child != NULL;
- child = child->next) {
- pcmk__xml2text(child, options, buffer, depth + 1);
- }
+ switch (type) {
+ case pcmk__xml_escape_text:
+ switch (*text) {
+ case '<':
+ g_string_append(copy, PCMK__XML_ENTITY_LT);
+ break;
+ case '>':
+ g_string_append(copy, PCMK__XML_ENTITY_GT);
+ break;
+ case '&':
+ g_string_append(copy, PCMK__XML_ENTITY_AMP);
+ break;
+ case '\n':
+ case '\t':
+ g_string_append_c(copy, *text);
+ break;
+ default:
+ if (g_ascii_iscntrl(*text)) {
+ g_string_append_printf(copy, "&#x%.2X;", *text);
+ } else {
+ g_string_append_c(copy, *text);
+ }
+ break;
+ }
+ break;
- for (int lpc = 0; lpc < spaces; lpc++) {
- g_string_append_c(buffer, ' ');
- }
+ case pcmk__xml_escape_attr:
+ switch (*text) {
+ case '<':
+ g_string_append(copy, PCMK__XML_ENTITY_LT);
+ break;
+ case '>':
+ g_string_append(copy, PCMK__XML_ENTITY_GT);
+ break;
+ case '&':
+ g_string_append(copy, PCMK__XML_ENTITY_AMP);
+ break;
+ case '"':
+ g_string_append(copy, PCMK__XML_ENTITY_QUOT);
+ break;
+ default:
+ if (g_ascii_iscntrl(*text)) {
+ g_string_append_printf(copy, "&#x%.2X;", *text);
+ } else {
+ g_string_append_c(copy, *text);
+ }
+ break;
+ }
+ break;
- pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
+ case pcmk__xml_escape_attr_pretty:
+ switch (*text) {
+ case '"':
+ g_string_append(copy, "\\\"");
+ break;
+ case '\n':
+ g_string_append(copy, "\\n");
+ break;
+ case '\r':
+ g_string_append(copy, "\\r");
+ break;
+ case '\t':
+ g_string_append(copy, "\\t");
+ break;
+ default:
+ g_string_append_c(copy, *text);
+ break;
+ }
+ break;
- if (pretty) {
- g_string_append_c(buffer, '\n');
+ default: // Invalid enum value
+ CRM_ASSERT(false);
+ break;
}
- }
-}
-
-/*!
- * \internal
- * \brief Append XML text content to a buffer
- *
- * \param[in] data XML whose content to append
- * \param[in] options Group of \p xml_log_options flags
- * \param[in,out] buffer Where to append the content (must not be \p NULL)
- * \param[in] depth Current indentation level
- */
-static void
-dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
- int depth)
-{
- /* @COMPAT: Remove when log_data_element() is removed. There are no internal
- * code paths to this, except through the deprecated log_data_element().
- */
- bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
- int spaces = pretty? (2 * depth) : 0;
-
- for (int lpc = 0; lpc < spaces; lpc++) {
- g_string_append_c(buffer, ' ');
- }
-
- g_string_append(buffer, (const gchar *) data->content);
-
- if (pretty) {
- g_string_append_c(buffer, '\n');
- }
-}
-
-/*!
- * \internal
- * \brief Append XML CDATA content to a buffer
- *
- * \param[in] data XML whose content to append
- * \param[in] options Group of \p pcmk__xml_fmt_options flags
- * \param[in,out] buffer Where to append the content (must not be \p NULL)
- * \param[in] depth Current indentation level
- */
-static void
-dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
- int depth)
-{
- bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
- int spaces = pretty? (2 * depth) : 0;
-
- for (int lpc = 0; lpc < spaces; lpc++) {
- g_string_append_c(buffer, ' ');
- }
-
- pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
- NULL);
- if (pretty) {
- g_string_append_c(buffer, '\n');
+ text = g_utf8_next_char(text);
}
-}
-
-/*!
- * \internal
- * \brief Append an XML comment to a buffer
- *
- * \param[in] data XML whose content to append
- * \param[in] options Group of \p pcmk__xml_fmt_options flags
- * \param[in,out] buffer Where to append the content (must not be \p NULL)
- * \param[in] depth Current indentation level
- */
-static void
-dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
- int depth)
-{
- bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
- int spaces = pretty? (2 * depth) : 0;
-
- for (int lpc = 0; lpc < spaces; lpc++) {
- g_string_append_c(buffer, ' ');
- }
-
- pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
-
- if (pretty) {
- g_string_append_c(buffer, '\n');
- }
-}
-
-/*!
- * \internal
- * \brief Get a string representation of an XML element type
- *
- * \param[in] type XML element type
- *
- * \return String representation of \p type
- */
-static const char *
-xml_element_type2str(xmlElementType type)
-{
- static const char *const element_type_names[] = {
- [XML_ELEMENT_NODE] = "element",
- [XML_ATTRIBUTE_NODE] = "attribute",
- [XML_TEXT_NODE] = "text",
- [XML_CDATA_SECTION_NODE] = "CDATA section",
- [XML_ENTITY_REF_NODE] = "entity reference",
- [XML_ENTITY_NODE] = "entity",
- [XML_PI_NODE] = "PI",
- [XML_COMMENT_NODE] = "comment",
- [XML_DOCUMENT_NODE] = "document",
- [XML_DOCUMENT_TYPE_NODE] = "document type",
- [XML_DOCUMENT_FRAG_NODE] = "document fragment",
- [XML_NOTATION_NODE] = "notation",
- [XML_HTML_DOCUMENT_NODE] = "HTML document",
- [XML_DTD_NODE] = "DTD",
- [XML_ELEMENT_DECL] = "element declaration",
- [XML_ATTRIBUTE_DECL] = "attribute declaration",
- [XML_ENTITY_DECL] = "entity declaration",
- [XML_NAMESPACE_DECL] = "namespace declaration",
- [XML_XINCLUDE_START] = "XInclude start",
- [XML_XINCLUDE_END] = "XInclude end",
- };
-
- if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
- return "unrecognized type";
- }
- return element_type_names[type];
-}
-
-/*!
- * \internal
- * \brief Create a text representation of an XML object
- *
- * \param[in] data XML to convert
- * \param[in] options Group of \p pcmk__xml_fmt_options flags
- * \param[in,out] buffer Where to store the text (must not be \p NULL)
- * \param[in] depth Current indentation level
- */
-void
-pcmk__xml2text(const xmlNode *data, uint32_t options, GString *buffer,
- int depth)
-{
- if (data == NULL) {
- crm_trace("Nothing to dump");
- return;
- }
-
- CRM_ASSERT(buffer != NULL);
- CRM_CHECK(depth >= 0, depth = 0);
-
- switch(data->type) {
- case XML_ELEMENT_NODE:
- /* Handle below */
- dump_xml_element(data, options, buffer, depth);
- break;
- case XML_TEXT_NODE:
- if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
- dump_xml_text(data, options, buffer, depth);
- }
- break;
- case XML_COMMENT_NODE:
- dump_xml_comment(data, options, buffer, depth);
- break;
- case XML_CDATA_SECTION_NODE:
- dump_xml_cdata(data, options, buffer, depth);
- break;
- default:
- crm_warn("Cannot convert XML %s node to text " CRM_XS " type=%d",
- xml_element_type2str(data->type), data->type);
- break;
- }
-}
-
-char *
-dump_xml_formatted_with_text(const xmlNode *xml)
-{
- /* libxml's xmlNodeDumpOutput() would work here since we're not specifically
- * filtering out any nodes. However, use pcmk__xml2text() for consistency,
- * to escape attribute values, and to allow a const argument.
- */
- char *buffer = NULL;
- GString *g_buffer = g_string_sized_new(1024);
-
- pcmk__xml2text(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, g_buffer, 0);
-
- pcmk__str_update(&buffer, g_buffer->str);
- g_string_free(g_buffer, TRUE);
- return buffer;
-}
-
-char *
-dump_xml_formatted(const xmlNode *xml)
-{
- char *buffer = NULL;
- GString *g_buffer = g_string_sized_new(1024);
-
- pcmk__xml2text(xml, pcmk__xml_fmt_pretty, g_buffer, 0);
-
- pcmk__str_update(&buffer, g_buffer->str);
- g_string_free(g_buffer, TRUE);
- return buffer;
-}
-
-char *
-dump_xml_unformatted(const xmlNode *xml)
-{
- char *buffer = NULL;
- GString *g_buffer = g_string_sized_new(1024);
-
- pcmk__xml2text(xml, 0, g_buffer, 0);
-
- pcmk__str_update(&buffer, g_buffer->str);
- g_string_free(g_buffer, TRUE);
- return buffer;
-}
-
-int
-pcmk__xml2fd(int fd, xmlNode *cur)
-{
- bool success;
-
- xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
- CRM_ASSERT(fd_out != NULL);
- xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
-
- success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
-
- success = xmlOutputBufferClose(fd_out) != -1 && success;
-
- if (!success) {
- return EIO;
- }
-
- fsync(fd);
- return pcmk_rc_ok;
-}
-
-void
-xml_remove_prop(xmlNode * obj, const char *name)
-{
- if (crm_element_value(obj, name) == NULL) {
- return;
- }
-
- if (pcmk__check_acl(obj, NULL, pcmk__xf_acl_write) == FALSE) {
- crm_trace("Cannot remove %s from %s", name, obj->name);
-
- } else if (pcmk__tracking_xml_changes(obj, FALSE)) {
- /* Leave in place (marked for removal) until after the diff is calculated */
- xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name);
- xml_node_private_t *nodepriv = attr->_private;
-
- set_parent_flag(obj, pcmk__xf_dirty);
- pcmk__set_xml_flags(nodepriv, pcmk__xf_deleted);
- } else {
- xmlUnsetProp(obj, (pcmkXmlStr) name);
- }
-}
-
-void
-save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
-{
- char *f = NULL;
-
- if (filename == NULL) {
- char *uuid = crm_generate_uuid();
-
- f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
- filename = f;
- free(uuid);
- }
-
- crm_info("Saving %s to %s", desc, filename);
- write_xml_file(xml, filename, FALSE);
- free(f);
+ return g_string_free(copy, FALSE);
}
/*!
@@ -1781,7 +1255,7 @@ mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
nodepriv->flags = 0;
// Check ACLs and mark restored value for later removal
- xml_remove_prop(new_xml, attr_name);
+ remove_xe_attr(new_xml, attr);
crm_trace("XML attribute %s=%s was removed from %s",
attr_name, old_value, element);
@@ -1955,10 +1429,10 @@ static void
mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
{
// Re-create the child element so we can check ACLs
- xmlNode *candidate = add_node_copy(new_parent, old_child);
+ xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
// Clear flags on new child and its children
- reset_xml_node_flags(candidate);
+ pcmk__xml_tree_foreach(candidate, reset_xml_node_flags, NULL);
// Check whether ACLs allow the deletion
pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
@@ -1979,8 +1453,9 @@ mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
{
xml_node_private_t *nodepriv = new_child->_private;
- crm_trace("Child element %s with id='%s' moved from position %d to %d under %s",
- new_child->name, (ID(new_child)? ID(new_child) : "<no id>"),
+ crm_trace("Child element %s with "
+ PCMK_XA_ID "='%s' moved from position %d to %d under %s",
+ new_child->name, pcmk__s(pcmk__xe_id(new_child), "<no id>"),
p_old, p_new, new_parent->name);
pcmk__mark_xml_node_dirty(new_parent);
pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
@@ -1997,12 +1472,13 @@ mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
static void
mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
{
- xmlNode *cIter = NULL;
+ xmlNode *old_child = NULL;
+ xmlNode *new_child = NULL;
xml_node_private_t *nodepriv = NULL;
CRM_CHECK(new_xml != NULL, return);
if (old_xml == NULL) {
- pcmk__mark_xml_created(new_xml);
+ pcmk__xml_mark_created(new_xml);
pcmk__apply_creation_acl(new_xml, check_top);
return;
}
@@ -2019,13 +1495,13 @@ mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
xml_diff_attrs(old_xml, new_xml);
// Check for differences in the original children
- for (cIter = pcmk__xml_first_child(old_xml); cIter != NULL; ) {
- xmlNode *old_child = cIter;
- xmlNode *new_child = pcmk__xml_match(new_xml, cIter, true);
+ for (old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
+ old_child = pcmk__xml_next(old_child)) {
+
+ new_child = pcmk__xml_match(new_xml, old_child, true);
- cIter = pcmk__xml_next(cIter);
- if(new_child) {
- mark_xml_changes(old_child, new_child, TRUE);
+ if (new_child != NULL) {
+ mark_xml_changes(old_child, new_child, true);
} else {
mark_child_deleted(old_child, new_xml);
@@ -2033,16 +1509,19 @@ mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
}
// Check for moved or created children
- for (cIter = pcmk__xml_first_child(new_xml); cIter != NULL; ) {
- xmlNode *new_child = cIter;
- xmlNode *old_child = pcmk__xml_match(old_xml, cIter, true);
+ new_child = pcmk__xml_first_child(new_xml);
+ while (new_child != NULL) {
+ xmlNode *next = pcmk__xml_next(new_child);
+
+ old_child = pcmk__xml_match(old_xml, new_child, true);
- cIter = pcmk__xml_next(cIter);
- if(old_child == NULL) {
+ if (old_child == NULL) {
// This is a newly created child
nodepriv = new_child->_private;
pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
- mark_xml_changes(old_child, new_child, TRUE);
+
+ // May free new_child
+ mark_xml_changes(old_child, new_child, true);
} else {
/* Check for movement, we already checked for differences */
@@ -2053,6 +1532,8 @@ mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
}
}
+
+ new_child = next;
}
}
@@ -2069,7 +1550,8 @@ xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
{
CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
&& pcmk__xe_is(old_xml, (const char *) new_xml->name)
- && pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_none),
+ && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
+ pcmk__str_none),
return);
if(xml_tracking_changes(new_xml) == FALSE) {
@@ -2079,44 +1561,6 @@ xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
mark_xml_changes(old_xml, new_xml, FALSE);
}
-gboolean
-can_prune_leaf(xmlNode * xml_node)
-{
- xmlNode *cIter = NULL;
- gboolean can_prune = TRUE;
-
- CRM_CHECK(xml_node != NULL, return FALSE);
-
- if (pcmk__strcase_any_of((const char *) xml_node->name,
- XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF,
- XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1,
- NULL)) {
- return FALSE;
- }
-
- for (xmlAttrPtr a = pcmk__xe_first_attr(xml_node); a != NULL; a = a->next) {
- const char *p_name = (const char *) a->name;
-
- if (strcmp(p_name, XML_ATTR_ID) == 0) {
- continue;
- }
- can_prune = FALSE;
- }
-
- cIter = pcmk__xml_first_child(xml_node);
- while (cIter) {
- xmlNode *child = cIter;
-
- cIter = pcmk__xml_next(cIter);
- if (can_prune_leaf(child)) {
- free_xml(child);
- } else {
- can_prune = FALSE;
- }
- }
- return can_prune;
-}
-
/*!
* \internal
* \brief Find a comment with matching content in specified XML
@@ -2185,7 +1629,7 @@ pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
}
if (target == NULL) {
- add_node_copy(parent, update);
+ pcmk__xml_copy(parent, update);
} else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
xmlFree(target->content);
@@ -2195,82 +1639,113 @@ pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
/*!
* \internal
- * \brief Make one XML tree match another (in children and attributes)
+ * \brief Merge one XML tree into another
+ *
+ * Here, "merge" means:
+ * 1. Copy attribute values from \p update to the target, overwriting in case of
+ * conflict.
+ * 2. Descend through \p update and the target in parallel. At each level, for
+ * each child of \p update, look for a matching child of the target.
+ * a. For each child, if a match is found, go to step 1, recursively merging
+ * the child of \p update into the child of the target.
+ * b. Otherwise, copy the child of \p update as a child of the target.
+ *
+ * A match is defined as the first child of the same type within the target,
+ * with:
+ * * the \c PCMK_XA_ID attribute matching, if set in \p update; otherwise,
+ * * the \c PCMK_XA_ID_REF attribute matching, if set in \p update
+ *
+ * This function does not delete any elements or attributes from the target. It
+ * may add elements or overwrite attributes, as described above.
*
* \param[in,out] parent If \p target is NULL and this is not, add or update
* child of this XML node that matches \p update
* \param[in,out] target If not NULL, update this XML
- * \param[in] update Make the desired XML match this (must not be NULL)
- * \param[in] as_diff If false, expand "++" when making attributes match
+ * \param[in] update Make the desired XML match this (must not be \c NULL)
+ * \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
+ * \param[in] as_diff If \c true, preserve order of attributes (deprecated
+ * since 2.0.5)
*
- * \note At least one of \p parent and \p target must be non-NULL
+ * \note At least one of \p parent and \p target must be non-<tt>NULL</tt>.
+ * \note This function is recursive. For the top-level call, \p parent is
+ * \c NULL and \p target is not \c NULL. For recursive calls, \p target is
+ * \c NULL and \p parent is not \c NULL.
*/
void
pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
- bool as_diff)
+ uint32_t flags, bool as_diff)
{
- xmlNode *a_child = NULL;
- const char *object_name = NULL,
- *object_href = NULL,
- *object_href_val = NULL;
+ /* @COMPAT Refactor further and staticize after v1 patchset deprecation.
+ *
+ * @COMPAT Drop as_diff argument when apply_xml_diff() is dropped.
+ */
+ const char *update_name = NULL;
+ const char *update_id_attr = NULL;
+ const char *update_id_val = NULL;
+ char *trace_s = NULL;
-#if XML_PARSER_DEBUG
- crm_log_xml_trace(update, "update:");
- crm_log_xml_trace(target, "target:");
-#endif
+ crm_log_xml_trace(update, "update");
+ crm_log_xml_trace(target, "target");
- CRM_CHECK(update != NULL, return);
+ CRM_CHECK(update != NULL, goto done);
if (update->type == XML_COMMENT_NODE) {
pcmk__xc_update(parent, target, update);
- return;
+ goto done;
}
- object_name = (const char *) update->name;
- object_href_val = ID(update);
- if (object_href_val != NULL) {
- object_href = XML_ATTR_ID;
+ update_name = (const char *) update->name;
+
+ CRM_CHECK(update_name != NULL, goto done);
+ CRM_CHECK((target != NULL) || (parent != NULL), goto done);
+
+ update_id_val = pcmk__xe_id(update);
+ if (update_id_val != NULL) {
+ update_id_attr = PCMK_XA_ID;
+
} else {
- object_href_val = crm_element_value(update, XML_ATTR_IDREF);
- object_href = (object_href_val == NULL) ? NULL : XML_ATTR_IDREF;
+ update_id_val = crm_element_value(update, PCMK_XA_ID_REF);
+ if (update_id_val != NULL) {
+ update_id_attr = PCMK_XA_ID_REF;
+ }
}
- CRM_CHECK(object_name != NULL, return);
- CRM_CHECK(target != NULL || parent != NULL, return);
+ pcmk__if_tracing(
+ {
+ if (update_id_attr != NULL) {
+ trace_s = crm_strdup_printf("<%s %s=%s/>",
+ update_name, update_id_attr,
+ update_id_val);
+ } else {
+ trace_s = crm_strdup_printf("<%s/>", update_name);
+ }
+ },
+ {}
+ );
if (target == NULL) {
- target = pcmk__xe_match(parent, object_name,
- object_href, object_href_val);
+ // Recursive call
+ target = pcmk__xe_first_child(parent, update_name, update_id_attr,
+ update_id_val);
}
if (target == NULL) {
- target = create_xml_node(parent, object_name);
- CRM_CHECK(target != NULL, return);
-#if XML_PARSER_DEBUG
- crm_trace("Added <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
- object_href ? " " : "",
- object_href ? object_href : "",
- object_href ? "=" : "",
- object_href ? object_href_val : "");
+ // Recursive call with no existing matching child
+ target = pcmk__xe_create(parent, update_name);
+ crm_trace("Added %s", pcmk__s(trace_s, update_name));
} else {
- crm_trace("Found node <%s%s%s%s%s/> to update",
- pcmk__s(object_name, "<null>"),
- object_href ? " " : "",
- object_href ? object_href : "",
- object_href ? "=" : "",
- object_href ? object_href_val : "");
-#endif
+ // Either recursive call with match, or top-level call
+ crm_trace("Found node %s to update", pcmk__s(trace_s, update_name));
}
CRM_CHECK(pcmk__xe_is(target, (const char *) update->name), return);
- if (as_diff == FALSE) {
- /* So that expand_plus_plus() gets called */
- copy_in_properties(target, update);
+ if (!as_diff) {
+ pcmk__xe_copy_attrs(target, update, flags);
} else {
- /* No need for expand_plus_plus(), just raw speed */
+ // Preserve order of attributes. Don't use pcmk__xe_copy_attrs().
for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
a = a->next) {
const char *p_value = pcmk__xml_attr_value(a);
@@ -2281,175 +1756,316 @@ pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
}
}
- for (a_child = pcmk__xml_first_child(update); a_child != NULL;
- a_child = pcmk__xml_next(a_child)) {
-#if XML_PARSER_DEBUG
- crm_trace("Updating child <%s%s%s%s%s/>",
- pcmk__s(object_name, "<null>"),
- object_href ? " " : "",
- object_href ? object_href : "",
- object_href ? "=" : "",
- object_href ? object_href_val : "");
-#endif
- pcmk__xml_update(target, NULL, a_child, as_diff);
- }
-
-#if XML_PARSER_DEBUG
- crm_trace("Finished with <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
- object_href ? " " : "",
- object_href ? object_href : "",
- object_href ? "=" : "",
- object_href ? object_href_val : "");
-#endif
-}
+ for (xmlNode *child = pcmk__xml_first_child(update); child != NULL;
+ child = pcmk__xml_next(child)) {
-gboolean
-update_xml_child(xmlNode * child, xmlNode * to_update)
-{
- gboolean can_update = TRUE;
- xmlNode *child_of_child = NULL;
+ crm_trace("Updating child of %s", pcmk__s(trace_s, update_name));
+ pcmk__xml_update(target, NULL, child, flags, as_diff);
+ }
- CRM_CHECK(child != NULL, return FALSE);
- CRM_CHECK(to_update != NULL, return FALSE);
+ crm_trace("Finished with %s", pcmk__s(trace_s, update_name));
- if (!pcmk__xe_is(to_update, (const char *) child->name)) {
- can_update = FALSE;
+done:
+ free(trace_s);
+}
- } else if (!pcmk__str_eq(ID(to_update), ID(child), pcmk__str_none)) {
- can_update = FALSE;
+/*!
+ * \internal
+ * \brief Delete an XML subtree if it matches a search element
+ *
+ * A match is defined as follows:
+ * * \p xml and \p user_data are both element nodes of the same type.
+ * * If \p user_data has attributes set, \p xml has those attributes set to the
+ * same values. (\p xml may have additional attributes set to arbitrary
+ * values.)
+ *
+ * \param[in,out] xml XML subtree to delete upon match
+ * \param[in] user_data Search element
+ *
+ * \return \c true to continue traversing the tree, or \c false to stop (because
+ * \p xml was deleted)
+ *
+ * \note This is compatible with \c pcmk__xml_tree_foreach().
+ */
+static bool
+delete_xe_if_matching(xmlNode *xml, void *user_data)
+{
+ xmlNode *search = user_data;
- } else if (can_update) {
-#if XML_PARSER_DEBUG
- crm_log_xml_trace(child, "Update match found...");
-#endif
- pcmk__xml_update(NULL, child, to_update, false);
+ if (!pcmk__xe_is(search, (const char *) xml->name)) {
+ // No match: either not both elements, or different element types
+ return true;
}
- for (child_of_child = pcmk__xml_first_child(child); child_of_child != NULL;
- child_of_child = pcmk__xml_next(child_of_child)) {
- /* only update the first one */
- if (can_update) {
- break;
+ for (const xmlAttr *attr = pcmk__xe_first_attr(search); attr != NULL;
+ attr = attr->next) {
+
+ const char *search_val = pcmk__xml_attr_value(attr);
+ const char *xml_val = crm_element_value(xml, (const char *) attr->name);
+
+ if (!pcmk__str_eq(search_val, xml_val, pcmk__str_casei)) {
+ // No match: an attr in xml doesn't match the attr in search
+ return true;
}
- can_update = update_xml_child(child_of_child, to_update);
}
- return can_update;
+ crm_log_xml_trace(xml, "delete-match");
+ crm_log_xml_trace(search, "delete-search");
+ free_xml(xml);
+
+ // Found a match and deleted it; stop traversing tree
+ return false;
}
+/*!
+ * \internal
+ * \brief Search an XML tree depth-first and delete the first matching element
+ *
+ * This function does not attempt to match the tree root (\p xml).
+ *
+ * A match with a node \c node is defined as follows:
+ * * \c node and \p search are both element nodes of the same type.
+ * * If \p search has attributes set, \c node has those attributes set to the
+ * same values. (\c node may have additional attributes set to arbitrary
+ * values.)
+ *
+ * \param[in,out] xml XML subtree to search
+ * \param[in] search Element to match against
+ *
+ * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
+ * successful deletion and an error code otherwise)
+ */
int
-find_xml_children(xmlNode ** children, xmlNode * root,
- const char *tag, const char *field, const char *value, gboolean search_matches)
+pcmk__xe_delete_match(xmlNode *xml, xmlNode *search)
{
- int match_found = 0;
+ // See @COMPAT comment in pcmk__xe_replace_match()
+ CRM_CHECK((xml != NULL) && (search != NULL), return EINVAL);
- CRM_CHECK(root != NULL, return FALSE);
- CRM_CHECK(children != NULL, return FALSE);
-
- if ((tag != NULL) && !pcmk__xe_is(root, tag)) {
+ for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
+ xml = pcmk__xe_next(xml)) {
- } else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) {
-
- } else {
- if (*children == NULL) {
- *children = create_xml_node(NULL, __func__);
+ if (!pcmk__xml_tree_foreach(xml, delete_xe_if_matching, search)) {
+ // Found and deleted an element
+ return pcmk_rc_ok;
}
- add_node_copy(*children, root);
- match_found = 1;
}
- if (search_matches || match_found == 0) {
- xmlNode *child = NULL;
+ // No match found in this subtree
+ return ENXIO;
+}
- for (child = pcmk__xml_first_child(root); child != NULL;
- child = pcmk__xml_next(child)) {
- match_found += find_xml_children(children, child, tag, field, value, search_matches);
- }
- }
+/*!
+ * \internal
+ * \brief Replace one XML node with a copy of another XML node
+ *
+ * This function handles change tracking and applies ACLs.
+ *
+ * \param[in,out] old XML node to replace
+ * \param[in] new XML node to copy as replacement for \p old
+ *
+ * \note This frees \p old.
+ */
+static void
+replace_node(xmlNode *old, xmlNode *new)
+{
+ new = xmlCopyNode(new, 1);
+ pcmk__mem_assert(new);
- return match_found;
+ // May be unnecessary but avoids slight changes to some test outputs
+ pcmk__xml_tree_foreach(new, reset_xml_node_flags, NULL);
+
+ old = xmlReplaceNode(old, new);
+
+ if (xml_tracking_changes(new)) {
+ // Replaced sections may have included relevant ACLs
+ pcmk__apply_acl(new);
+ }
+ xml_calculate_changes(old, new);
+ xmlFreeNode(old);
}
-gboolean
-replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
+/*!
+ * \internal
+ * \brief Replace one XML subtree with a copy of another if the two match
+ *
+ * A match is defined as follows:
+ * * \p xml and \p user_data are both element nodes of the same type.
+ * * If \p user_data has the \c PCMK_XA_ID attribute set, then \p xml has
+ * \c PCMK_XA_ID set to the same value.
+ *
+ * \param[in,out] xml XML subtree to replace with \p user_data upon match
+ * \param[in] user_data XML to replace \p xml with a copy of upon match
+ *
+ * \return \c true to continue traversing the tree, or \c false to stop (because
+ * \p xml was replaced by \p user_data)
+ *
+ * \note This is compatible with \c pcmk__xml_tree_foreach().
+ */
+static bool
+replace_xe_if_matching(xmlNode *xml, void *user_data)
{
- gboolean can_delete = FALSE;
- xmlNode *child_of_child = NULL;
+ xmlNode *replace = user_data;
+ const char *xml_id = NULL;
+ const char *replace_id = NULL;
- const char *up_id = NULL;
- const char *child_id = NULL;
- const char *right_val = NULL;
+ xml_id = pcmk__xe_id(xml);
+ replace_id = pcmk__xe_id(replace);
- CRM_CHECK(child != NULL, return FALSE);
- CRM_CHECK(update != NULL, return FALSE);
+ if (!pcmk__xe_is(replace, (const char *) xml->name)) {
+ // No match: either not both elements, or different element types
+ return true;
+ }
- up_id = ID(update);
- child_id = ID(child);
+ if ((replace_id != NULL)
+ && !pcmk__str_eq(replace_id, xml_id, pcmk__str_none)) {
- if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
- can_delete = TRUE;
+ // No match: ID was provided in replace and doesn't match xml's ID
+ return true;
}
- if (!pcmk__xe_is(update, (const char *) child->name)) {
- can_delete = FALSE;
- }
- if (can_delete && delete_only) {
- for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
- a = a->next) {
- const char *p_name = (const char *) a->name;
- const char *p_value = pcmk__xml_attr_value(a);
- right_val = crm_element_value(child, p_name);
- if (!pcmk__str_eq(p_value, right_val, pcmk__str_casei)) {
- can_delete = FALSE;
- }
+ crm_log_xml_trace(xml, "replace-match");
+ crm_log_xml_trace(replace, "replace-with");
+ replace_node(xml, replace);
+
+ // Found a match and replaced it; stop traversing tree
+ return false;
+}
+
+/*!
+ * \internal
+ * \brief Search an XML tree depth-first and replace the first matching element
+ *
+ * This function does not attempt to match the tree root (\p xml).
+ *
+ * A match with a node \c node is defined as follows:
+ * * \c node and \p replace are both element nodes of the same type.
+ * * If \p replace has the \c PCMK_XA_ID attribute set, then \c node has
+ * \c PCMK_XA_ID set to the same value.
+ *
+ * \param[in,out] xml XML tree to search
+ * \param[in] replace XML to replace a matching element with a copy of
+ *
+ * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
+ * successful replacement and an error code otherwise)
+ */
+int
+pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace)
+{
+ /* @COMPAT Some of this behavior (like not matching the tree root, which is
+ * allowed by pcmk__xe_update_match()) is questionable for general use but
+ * required for backward compatibility by cib_process_replace() and
+ * cib_process_delete(). Behavior can change at a major version release if
+ * desired.
+ */
+ CRM_CHECK((xml != NULL) && (replace != NULL), return EINVAL);
+
+ for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
+ xml = pcmk__xe_next(xml)) {
+
+ if (!pcmk__xml_tree_foreach(xml, replace_xe_if_matching, replace)) {
+ // Found and replaced an element
+ return pcmk_rc_ok;
}
}
- if (can_delete && parent != NULL) {
- crm_log_xml_trace(child, "Delete match found...");
- if (delete_only || update == NULL) {
- free_xml(child);
+ // No match found in this subtree
+ return ENXIO;
+}
- } else {
- xmlNode *old = child;
- xmlNode *new = xmlCopyNode(update, 1);
+//! User data for \c update_xe_if_matching()
+struct update_data {
+ xmlNode *update; //!< Update source
+ uint32_t flags; //!< Group of <tt>enum pcmk__xa_flags</tt>
+};
- CRM_ASSERT(new != NULL);
+/*!
+ * \internal
+ * \brief Update one XML subtree with another if the two match
+ *
+ * "Update" means to merge a source subtree into a target subtree (see
+ * \c pcmk__xml_update()).
+ *
+ * A match is defined as follows:
+ * * \p xml and \p user_data->update are both element nodes of the same type.
+ * * \p xml and \p user_data->update have the same \c PCMK_XA_ID attribute
+ * value, or \c PCMK_XA_ID is unset in both
+ *
+ * \param[in,out] xml XML subtree to update with \p user_data->update
+ * upon match
+ * \param[in] user_data <tt>struct update_data</tt> object
+ *
+ * \return \c true to continue traversing the tree, or \c false to stop (because
+ * \p xml was updated by \p user_data->update)
+ *
+ * \note This is compatible with \c pcmk__xml_tree_foreach().
+ */
+static bool
+update_xe_if_matching(xmlNode *xml, void *user_data)
+{
+ struct update_data *data = user_data;
+ xmlNode *update = data->update;
- // May be unnecessary but avoids slight changes to some test outputs
- reset_xml_node_flags(new);
+ if (!pcmk__xe_is(update, (const char *) xml->name)) {
+ // No match: either not both elements, or different element types
+ return true;
+ }
- old = xmlReplaceNode(old, new);
+ if (!pcmk__str_eq(pcmk__xe_id(xml), pcmk__xe_id(update), pcmk__str_none)) {
+ // No match: ID mismatch
+ return true;
+ }
- if (xml_tracking_changes(new)) {
- // Replaced sections may have included relevant ACLs
- pcmk__apply_acl(new);
- }
- xml_calculate_changes(old, new);
- xmlFreeNode(old);
- }
- return TRUE;
+ crm_log_xml_trace(xml, "update-match");
+ crm_log_xml_trace(update, "update-with");
+ pcmk__xml_update(NULL, xml, update, data->flags, false);
- } else if (can_delete) {
- crm_log_xml_debug(child, "Cannot delete the search root");
- can_delete = FALSE;
- }
+ // Found a match and replaced it; stop traversing tree
+ return false;
+}
- child_of_child = pcmk__xml_first_child(child);
- while (child_of_child) {
- xmlNode *next = pcmk__xml_next(child_of_child);
+/*!
+ * \internal
+ * \brief Search an XML tree depth-first and update the first matching element
+ *
+ * "Update" means to merge a source subtree into a target subtree (see
+ * \c pcmk__xml_update()).
+ *
+ * A match with a node \c node is defined as follows:
+ * * \c node and \p update are both element nodes of the same type.
+ * * \c node and \p update have the same \c PCMK_XA_ID attribute value, or
+ * \c PCMK_XA_ID is unset in both
+ *
+ * \param[in,out] xml XML tree to search
+ * \param[in] update XML to update a matching element with
+ * \param[in] flags Group of <tt>enum pcmk__xa_flags</tt>
+ *
+ * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
+ * successful update and an error code otherwise)
+ */
+int
+pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags)
+{
+ /* @COMPAT In pcmk__xe_delete_match() and pcmk__xe_replace_match(), we
+ * compare IDs only if the equivalent of the update argument has an ID.
+ * Here, we're stricter: we consider it a mismatch if only one element has
+ * an ID attribute, or if both elements have IDs but they don't match.
+ *
+ * Perhaps we should align the behavior at a major version release.
+ */
+ struct update_data data = {
+ .update = update,
+ .flags = flags,
+ };
- can_delete = replace_xml_child(child, child_of_child, update, delete_only);
+ CRM_CHECK((xml != NULL) && (update != NULL), return EINVAL);
- /* only delete the first one */
- if (can_delete) {
- child_of_child = NULL;
- } else {
- child_of_child = next;
- }
+ if (!pcmk__xml_tree_foreach(xml, update_xe_if_matching, &data)) {
+ // Found and updated an element
+ return pcmk_rc_ok;
}
- return can_delete;
+ // No match found in this subtree
+ return ENXIO;
}
xmlNode *
@@ -2461,61 +2077,42 @@ sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
CRM_CHECK(input != NULL, return NULL);
- result = create_xml_node(parent, (const char *) input->name);
+ result = pcmk__xe_create(parent, (const char *) input->name);
nvpairs = pcmk_xml_attrs2nvpairs(input);
nvpairs = pcmk_sort_nvpairs(nvpairs);
pcmk_nvpairs2xml_attrs(nvpairs, result);
pcmk_free_nvpairs(nvpairs);
- for (child = pcmk__xml_first_child(input); child != NULL;
- child = pcmk__xml_next(child)) {
+ for (child = pcmk__xe_first_child(input, NULL, NULL, NULL); child != NULL;
+ child = pcmk__xe_next(child)) {
if (recursive) {
sorted_xml(child, result, recursive);
} else {
- add_node_copy(result, child);
+ pcmk__xml_copy(result, child);
}
}
return result;
}
-xmlNode *
-first_named_child(const xmlNode *parent, const char *name)
-{
- xmlNode *match = NULL;
-
- for (match = pcmk__xe_first_child(parent); match != NULL;
- match = pcmk__xe_next(match)) {
- /*
- * name == NULL gives first child regardless of name; this is
- * semantically incorrect in this function, but may be necessary
- * due to prior use of xml_child_iter_filter
- */
- if (pcmk__str_eq(name, (const char *)match->name, pcmk__str_null_matches)) {
- return match;
- }
- }
- return NULL;
-}
-
/*!
- * \brief Get next instance of same XML tag
+ * \internal
+ * \brief Get next sibling XML element with the same name as a given element
*
- * \param[in] sibling XML tag to start from
+ * \param[in] node XML element to start from
*
- * \return Next sibling XML tag with same name
+ * \return Next sibling XML element with same name
*/
xmlNode *
-crm_next_same_xml(const xmlNode *sibling)
+pcmk__xe_next_same(const xmlNode *node)
{
- xmlNode *match = pcmk__xe_next(sibling);
+ for (xmlNode *match = pcmk__xe_next(node); match != NULL;
+ match = pcmk__xe_next(match)) {
- while (match != NULL) {
- if (pcmk__xe_is(match, (const char *) sibling->name)) {
+ if (pcmk__xe_is(match, (const char *) node->name)) {
return match;
}
- match = pcmk__xe_next(match);
}
return NULL;
}
@@ -2554,31 +2151,32 @@ crm_xml_cleanup(void)
xmlNode *
expand_idref(xmlNode * input, xmlNode * top)
{
+ char *xpath = NULL;
const char *ref = NULL;
- xmlNode *result = input;
+ xmlNode *result = NULL;
- if (result == NULL) {
+ if (input == NULL) {
return NULL;
-
- } else if (top == NULL) {
- top = input;
}
- ref = crm_element_value(result, XML_ATTR_IDREF);
- if (ref != NULL) {
- char *xpath_string = crm_strdup_printf("//%s[@" XML_ATTR_ID "='%s']",
- result->name, ref);
+ ref = crm_element_value(input, PCMK_XA_ID_REF);
+ if (ref == NULL) {
+ return input;
+ }
- result = get_xpath_object(xpath_string, top, LOG_ERR);
- if (result == NULL) {
- char *nodePath = (char *)xmlGetNodePath(top);
+ if (top == NULL) {
+ top = input;
+ }
- crm_err("No match for %s found in %s: Invalid configuration",
- xpath_string, pcmk__s(nodePath, "unrecognizable path"));
- free(nodePath);
- }
- free(xpath_string);
+ xpath = crm_strdup_printf("//%s[@" PCMK_XA_ID "='%s']", input->name, ref);
+ result = get_xpath_object(xpath, top, LOG_DEBUG);
+ if (result == NULL) { // Not possible with schema validation enabled
+ pcmk__config_err("Ignoring invalid %s configuration: "
+ PCMK_XA_ID_REF " '%s' does not reference "
+ "a valid object " CRM_XS " xpath=%s",
+ input->name, ref, xpath);
}
+ free(xpath);
return result;
}
@@ -2610,25 +2208,52 @@ pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
return ret;
}
-char *
-pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
+static char *
+find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
{
- char *base = pcmk__xml_artefact_root(ns), *ret = NULL;
+ char *ret = NULL;
switch (ns) {
case pcmk__xml_artefact_ns_legacy_rng:
case pcmk__xml_artefact_ns_base_rng:
- ret = crm_strdup_printf("%s/%s.rng", base, filespec);
+ if (pcmk__ends_with(filespec, ".rng")) {
+ ret = crm_strdup_printf("%s/%s", path, filespec);
+ } else {
+ ret = crm_strdup_printf("%s/%s.rng", path, filespec);
+ }
break;
case pcmk__xml_artefact_ns_legacy_xslt:
case pcmk__xml_artefact_ns_base_xslt:
- ret = crm_strdup_printf("%s/%s.xsl", base, filespec);
+ if (pcmk__ends_with(filespec, ".xsl")) {
+ ret = crm_strdup_printf("%s/%s", path, filespec);
+ } else {
+ ret = crm_strdup_printf("%s/%s.xsl", path, filespec);
+ }
break;
default:
crm_err("XML artefact family specified as %u not recognized", ns);
}
+
+ return ret;
+}
+
+char *
+pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
+{
+ struct stat sb;
+ char *base = pcmk__xml_artefact_root(ns);
+ char *ret = NULL;
+
+ ret = find_artefact(ns, base, filespec);
free(base);
+ if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
+ const char *remote_schema_dir = pcmk__remote_schema_dir();
+
+ free(ret);
+ ret = find_artefact(ns, remote_schema_dir, filespec);
+ }
+
return ret;
}
@@ -2669,8 +2294,9 @@ pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
CRM_ASSERT(handler != NULL);
for (xmlNode *node = children; node != NULL; node = node->next) {
- if (node->type == XML_ELEMENT_NODE &&
- pcmk__str_eq(child_element_name, (const char *) node->name, pcmk__str_null_matches)) {
+ if ((node->type == XML_ELEMENT_NODE)
+ && ((child_element_name == NULL)
+ || pcmk__xe_is(node, child_element_name))) {
int rc = handler(node, userdata);
if (rc != pcmk_rc_ok) {
@@ -2690,8 +2316,8 @@ pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
xmlNode *
find_entity(xmlNode *parent, const char *node_name, const char *id)
{
- return pcmk__xe_match(parent, node_name,
- ((id == NULL)? id : XML_ATTR_ID), id);
+ return pcmk__xe_first_child(parent, node_name,
+ ((id == NULL)? id : PCMK_XA_ID), id);
}
void
@@ -2709,12 +2335,28 @@ getDocPtr(xmlNode *node)
doc = node->doc;
if (doc == NULL) {
- doc = xmlNewDoc((pcmkXmlStr) "1.0");
+ doc = xmlNewDoc(PCMK__XML_VERSION);
xmlDocSetRootElement(doc, node);
}
return doc;
}
+xmlNode *
+add_node_copy(xmlNode *parent, xmlNode *src_node)
+{
+ xmlNode *child = NULL;
+
+ CRM_CHECK((parent != NULL) && (src_node != NULL), return NULL);
+
+ child = xmlDocCopyNode(src_node, parent->doc, 1);
+ if (child == NULL) {
+ return NULL;
+ }
+ xmlAddChild(parent, child);
+ pcmk__xml_mark_created(child);
+ return child;
+}
+
int
add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child)
{
@@ -2732,5 +2374,352 @@ xml_has_children(const xmlNode * xml_root)
return FALSE;
}
+static char *
+replace_text(char *text, size_t *index, size_t *length, const char *replace)
+{
+ // We have space for 1 char already
+ size_t offset = strlen(replace) - 1;
+
+ if (offset > 0) {
+ *length += offset;
+ text = pcmk__realloc(text, *length + 1);
+
+ // Shift characters to the right to make room for the replacement string
+ for (size_t i = *length; i > (*index + offset); i--) {
+ text[i] = text[i - offset];
+ }
+ }
+
+ // Replace the character at index by the replacement string
+ memcpy(text + *index, replace, offset + 1);
+
+ // Reset index to the end of replacement string
+ *index += offset;
+ return text;
+}
+
+char *
+crm_xml_escape(const char *text)
+{
+ size_t length = 0;
+ char *copy = NULL;
+
+ if (text == NULL) {
+ return NULL;
+ }
+
+ length = strlen(text);
+ copy = pcmk__str_copy(text);
+ for (size_t index = 0; index <= length; index++) {
+ if(copy[index] & 0x80 && copy[index+1] & 0x80){
+ index++;
+ continue;
+ }
+ switch (copy[index]) {
+ case 0:
+ // Sanity only; loop should stop at the last non-null byte
+ break;
+ case '<':
+ copy = replace_text(copy, &index, &length, "&lt;");
+ break;
+ case '>':
+ copy = replace_text(copy, &index, &length, "&gt;");
+ break;
+ case '"':
+ copy = replace_text(copy, &index, &length, "&quot;");
+ break;
+ case '\'':
+ copy = replace_text(copy, &index, &length, "&apos;");
+ break;
+ case '&':
+ copy = replace_text(copy, &index, &length, "&amp;");
+ break;
+ case '\t':
+ /* Might as well just expand to a few spaces... */
+ copy = replace_text(copy, &index, &length, " ");
+ break;
+ case '\n':
+ copy = replace_text(copy, &index, &length, "\\n");
+ break;
+ case '\r':
+ copy = replace_text(copy, &index, &length, "\\r");
+ break;
+ default:
+ /* Check for and replace non-printing characters with their octal equivalent */
+ if(copy[index] < ' ' || copy[index] > '~') {
+ char *replace = crm_strdup_printf("\\%.3o", copy[index]);
+
+ copy = replace_text(copy, &index, &length, replace);
+ free(replace);
+ }
+ }
+ }
+ return copy;
+}
+
+xmlNode *
+copy_xml(xmlNode *src)
+{
+ xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
+ xmlNode *copy = NULL;
+
+ pcmk__mem_assert(doc);
+
+ copy = xmlDocCopyNode(src, doc, 1);
+ pcmk__mem_assert(copy);
+
+ xmlDocSetRootElement(doc, copy);
+ return copy;
+}
+
+xmlNode *
+create_xml_node(xmlNode *parent, const char *name)
+{
+ // Like pcmk__xe_create(), but returns NULL on failure
+ xmlNode *node = NULL;
+
+ CRM_CHECK(!pcmk__str_empty(name), return NULL);
+
+ if (parent == NULL) {
+ xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
+
+ if (doc == NULL) {
+ return NULL;
+ }
+
+ node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
+ if (node == NULL) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+ xmlDocSetRootElement(doc, node);
+
+ } else {
+ node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
+ if (node == NULL) {
+ return NULL;
+ }
+ }
+ pcmk__xml_mark_created(node);
+ return node;
+}
+
+xmlNode *
+pcmk_create_xml_text_node(xmlNode *parent, const char *name,
+ const char *content)
+{
+ xmlNode *node = pcmk__xe_create(parent, name);
+
+ pcmk__xe_set_content(node, "%s", content);
+ return node;
+}
+
+xmlNode *
+pcmk_create_html_node(xmlNode *parent, const char *element_name, const char *id,
+ const char *class_name, const char *text)
+{
+ xmlNode *node = pcmk__html_create(parent, element_name, id, class_name);
+
+ pcmk__xe_set_content(node, "%s", text);
+ return node;
+}
+
+xmlNode *
+first_named_child(const xmlNode *parent, const char *name)
+{
+ return pcmk__xe_first_child(parent, name, NULL, NULL);
+}
+
+xmlNode *
+find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
+{
+ xmlNode *result = NULL;
+
+ if (search_path == NULL) {
+ crm_warn("Will never find <NULL>");
+ return NULL;
+ }
+
+ result = pcmk__xe_first_child(root, search_path, NULL, NULL);
+
+ if (must_find && (result == NULL)) {
+ crm_warn("Could not find %s in %s",
+ search_path,
+ ((root != NULL)? (const char *) root->name : "<NULL>"));
+ }
+
+ return result;
+}
+
+xmlNode *
+crm_next_same_xml(const xmlNode *sibling)
+{
+ return pcmk__xe_next_same(sibling);
+}
+
+void
+xml_remove_prop(xmlNode * obj, const char *name)
+{
+ pcmk__xe_remove_attr(obj, name);
+}
+
+gboolean
+replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
+{
+ bool is_match = false;
+ const char *child_id = NULL;
+ const char *update_id = NULL;
+
+ CRM_CHECK(child != NULL, return FALSE);
+ CRM_CHECK(update != NULL, return FALSE);
+
+ child_id = pcmk__xe_id(child);
+ update_id = pcmk__xe_id(update);
+
+ /* Match element name and (if provided in update XML) element ID. Don't
+ * match search root (child is search root if parent == NULL).
+ */
+ is_match = (parent != NULL)
+ && pcmk__xe_is(update, (const char *) child->name)
+ && ((update_id == NULL)
+ || pcmk__str_eq(update_id, child_id, pcmk__str_none));
+
+ /* For deletion, match all attributes provided in update. A matching node
+ * can have additional attributes, but values must match for provided ones.
+ */
+ if (is_match && delete_only) {
+ for (xmlAttr *attr = pcmk__xe_first_attr(update); attr != NULL;
+ attr = attr->next) {
+ const char *name = (const char *) attr->name;
+ const char *update_val = pcmk__xml_attr_value(attr);
+ const char *child_val = crm_element_value(child, name);
+
+ if (!pcmk__str_eq(update_val, child_val, pcmk__str_casei)) {
+ is_match = false;
+ break;
+ }
+ }
+ }
+
+ if (is_match) {
+ if (delete_only) {
+ crm_log_xml_trace(child, "delete-match");
+ crm_log_xml_trace(update, "delete-search");
+ free_xml(child);
+
+ } else {
+ crm_log_xml_trace(child, "replace-match");
+ crm_log_xml_trace(update, "replace-with");
+ replace_node(child, update);
+ }
+ return TRUE;
+ }
+
+ // Current node not a match; search the rest of the subtree depth-first
+ parent = child;
+ for (child = pcmk__xml_first_child(parent); child != NULL;
+ child = pcmk__xml_next(child)) {
+
+ // Only delete/replace the first match
+ if (replace_xml_child(parent, child, update, delete_only)) {
+ return TRUE;
+ }
+ }
+
+ // No match found in this subtree
+ return FALSE;
+}
+
+gboolean
+update_xml_child(xmlNode *child, xmlNode *to_update)
+{
+ return pcmk__xe_update_match(child, to_update,
+ pcmk__xaf_score_update) == pcmk_rc_ok;
+}
+
+int
+find_xml_children(xmlNode **children, xmlNode *root, const char *tag,
+ const char *field, const char *value, gboolean search_matches)
+{
+ int match_found = 0;
+
+ CRM_CHECK(root != NULL, return FALSE);
+ CRM_CHECK(children != NULL, return FALSE);
+
+ if ((tag != NULL) && !pcmk__xe_is(root, tag)) {
+
+ } else if ((value != NULL)
+ && !pcmk__str_eq(value, crm_element_value(root, field),
+ pcmk__str_casei)) {
+
+ } else {
+ if (*children == NULL) {
+ *children = pcmk__xe_create(NULL, __func__);
+ }
+ pcmk__xml_copy(*children, root);
+ match_found = 1;
+ }
+
+ if (search_matches || match_found == 0) {
+ xmlNode *child = NULL;
+
+ for (child = pcmk__xml_first_child(root); child != NULL;
+ child = pcmk__xml_next(child)) {
+ match_found += find_xml_children(children, child, tag, field, value,
+ search_matches);
+ }
+ }
+
+ return match_found;
+}
+
+void
+fix_plus_plus_recursive(xmlNode *target)
+{
+ /* TODO: Remove recursion and use xpath searches for value++ */
+ xmlNode *child = NULL;
+
+ for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
+ const char *p_name = (const char *) a->name;
+ const char *p_value = pcmk__xml_attr_value(a);
+
+ expand_plus_plus(target, p_name, p_value);
+ }
+ for (child = pcmk__xe_first_child(target, NULL, NULL, NULL); child != NULL;
+ child = pcmk__xe_next(child)) {
+
+ fix_plus_plus_recursive(child);
+ }
+}
+
+void
+copy_in_properties(xmlNode *target, const xmlNode *src)
+{
+ if (src == NULL) {
+ crm_warn("No node to copy properties from");
+
+ } else if (target == NULL) {
+ crm_err("No node to copy properties into");
+
+ } else {
+ for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
+ const char *p_name = (const char *) a->name;
+ const char *p_value = pcmk__xml_attr_value(a);
+
+ expand_plus_plus(target, p_name, p_value);
+ if (xml_acl_denied(target)) {
+ crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name);
+ return;
+ }
+ }
+ }
+}
+
+void
+expand_plus_plus(xmlNode * target, const char *name, const char *value)
+{
+ pcmk__xe_set_score(target, name, value);
+}
+
// LCOV_EXCL_STOP
// End deprecated API