root/lib/common/xml.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pcmk__xml_tree_foreach
  2. pcmk__tracking_xml_changes
  3. set_parent_flag
  4. pcmk__set_xml_doc_flag
  5. pcmk__mark_xml_node_dirty
  6. reset_xml_node_flags
  7. mark_xml_dirty_created
  8. pcmk__xml_mark_created
  9. free_deleted_object
  10. reset_xml_private_data
  11. free_private_data
  12. new_private_data
  13. xml_track_changes
  14. xml_tracking_changes
  15. xml_document_dirty
  16. pcmk__xml_position
  17. accept_attr_deletions
  18. pcmk__xml_match
  19. xml_accept_changes
  20. pcmk__xe_first_child
  21. pcmk__xe_set_score
  22. pcmk__xe_copy_attrs
  23. remove_xe_attr
  24. pcmk__xe_remove_attr
  25. pcmk__xe_remove_attr_cb
  26. pcmk__xe_remove_matching_attrs
  27. pcmk__xe_create
  28. G_GNUC_PRINTF
  29. pcmk_free_xml_subtree
  30. free_xml_with_position
  31. free_xml
  32. pcmk__xml_copy
  33. pcmk__strip_xml_text
  34. pcmk__xe_add_last_written
  35. crm_xml_sanitize_id
  36. crm_xml_set_id
  37. pcmk__xml_needs_escape
  38. pcmk__xml_escape
  39. set_attrs_flag
  40. mark_attr_deleted
  41. mark_attr_changed
  42. mark_attr_moved
  43. xml_diff_old_attrs
  44. mark_created_attrs
  45. xml_diff_attrs
  46. mark_child_deleted
  47. mark_child_moved
  48. mark_xml_changes
  49. xml_calculate_significant_changes
  50. xml_calculate_changes
  51. pcmk__xc_match
  52. pcmk__xc_update
  53. pcmk__xml_update
  54. delete_xe_if_matching
  55. pcmk__xe_delete_match
  56. replace_node
  57. replace_xe_if_matching
  58. pcmk__xe_replace_match
  59. update_xe_if_matching
  60. pcmk__xe_update_match
  61. sorted_xml
  62. pcmk__xe_next_same
  63. crm_xml_init
  64. crm_xml_cleanup
  65. expand_idref
  66. pcmk__xml_artefact_root
  67. find_artefact
  68. pcmk__xml_artefact_path
  69. pcmk__xe_set_propv
  70. pcmk__xe_set_props
  71. pcmk__xe_foreach_child
  72. find_entity
  73. crm_destroy_xml
  74. getDocPtr
  75. add_node_copy
  76. add_node_nocopy
  77. xml_has_children
  78. replace_text
  79. crm_xml_escape
  80. copy_xml
  81. create_xml_node
  82. pcmk_create_xml_text_node
  83. pcmk_create_html_node
  84. first_named_child
  85. find_xml_node
  86. crm_next_same_xml
  87. xml_remove_prop
  88. replace_xml_child
  89. update_xml_child
  90. find_xml_children
  91. fix_plus_plus_recursive
  92. copy_in_properties
  93. expand_plus_plus

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdarg.h>
  13 #include <stdint.h>                     // uint32_t
  14 #include <stdio.h>
  15 #include <stdlib.h>
  16 #include <string.h>
  17 #include <sys/stat.h>                   // stat(), S_ISREG, etc.
  18 #include <sys/types.h>
  19 
  20 #include <libxml/parser.h>
  21 #include <libxml/tree.h>
  22 
  23 #include <crm/crm.h>
  24 #include <crm/common/xml.h>
  25 #include <crm/common/xml_internal.h>    // PCMK__XML_LOG_BASE, etc.
  26 #include "crmcommon_private.h"
  27 
  28 /*!
  29  * \internal
  30  * \brief Apply a function to each XML node in a tree (pre-order, depth-first)
  31  *
  32  * \param[in,out] xml        XML tree to traverse
  33  * \param[in,out] fn         Function to call for each node (returns \c true to
  34  *                           continue traversing the tree or \c false to stop)
  35  * \param[in,out] user_data  Argument to \p fn
  36  *
  37  * \return \c false if any \p fn call returned \c false, or \c true otherwise
  38  *
  39  * \note This function is recursive.
  40  */
  41 bool
  42 pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
     /* [previous][next][first][last][top][bottom][index][help] */
  43                        void *user_data)
  44 {
  45     if (!fn(xml, user_data)) {
  46         return false;
  47     }
  48 
  49     for (xml = pcmk__xml_first_child(xml); xml != NULL;
  50          xml = pcmk__xml_next(xml)) {
  51 
  52         if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
  53             return false;
  54         }
  55     }
  56     return true;
  57 }
  58 
  59 bool
  60 pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
     /* [previous][next][first][last][top][bottom][index][help] */
  61 {
  62     if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
  63         return FALSE;
  64     } else if (!pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
  65                             pcmk__xf_tracking)) {
  66         return FALSE;
  67     } else if (lazy && !pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
  68                                     pcmk__xf_lazy)) {
  69         return FALSE;
  70     }
  71     return TRUE;
  72 }
  73 
  74 static inline void
  75 set_parent_flag(xmlNode *xml, long flag) 
     /* [previous][next][first][last][top][bottom][index][help] */
  76 {
  77     for(; xml; xml = xml->parent) {
  78         xml_node_private_t *nodepriv = xml->_private;
  79 
  80         if (nodepriv == NULL) {
  81             /* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
  82         } else {
  83             pcmk__set_xml_flags(nodepriv, flag);
  84         }
  85     }
  86 }
  87 
  88 void
  89 pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
  90 {
  91     if(xml && xml->doc && xml->doc->_private){
  92         /* During calls to xmlDocCopyNode(), xml->doc may be unset */
  93         xml_doc_private_t *docpriv = xml->doc->_private;
  94 
  95         pcmk__set_xml_flags(docpriv, flag);
  96     }
  97 }
  98 
  99 // Mark document, element, and all element's parents as changed
 100 void
 101 pcmk__mark_xml_node_dirty(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 102 {
 103     pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty);
 104     set_parent_flag(xml, pcmk__xf_dirty);
 105 }
 106 
 107 /*!
 108  * \internal
 109  * \brief Clear flags on an XML node
 110  *
 111  * \param[in,out] xml        XML node whose flags to reset
 112  * \param[in,out] user_data  Ignored
 113  *
 114  * \return \c true (to continue traversing the tree)
 115  *
 116  * \note This is compatible with \c pcmk__xml_tree_foreach().
 117  */
 118 static bool
 119 reset_xml_node_flags(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 120 {
 121     xml_node_private_t *nodepriv = xml->_private;
 122 
 123     if (nodepriv != NULL) {
 124         nodepriv->flags = pcmk__xf_none;
 125     }
 126     return true;
 127 }
 128 
 129 /*!
 130  * \internal
 131  * \brief Set the \c pcmk__xf_dirty and \c pcmk__xf_created flags on an XML node
 132  *
 133  * \param[in,out] xml        Node whose flags to set
 134  * \param[in]     user_data  Ignored
 135  *
 136  * \return \c true (to continue traversing the tree)
 137  *
 138  * \note This is compatible with \c pcmk__xml_tree_foreach().
 139  */
 140 static bool
 141 mark_xml_dirty_created(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 142 {
 143     xml_node_private_t *nodepriv = xml->_private;
 144 
 145     if (nodepriv != NULL) {
 146         pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
 147     }
 148     return true;
 149 }
 150 
 151 /*!
 152  * \internal
 153  * \brief Mark an XML tree as dirty and created, and mark its parents dirty
 154  *
 155  * Also mark the document dirty.
 156  *
 157  * \param[in,out] xml  Tree to mark as dirty and created
 158  */
 159 void
 160 pcmk__xml_mark_created(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 161 {
 162     CRM_ASSERT(xml != NULL);
 163 
 164     if (!pcmk__tracking_xml_changes(xml, false)) {
 165         // Tracking is disabled for entire document
 166         return;
 167     }
 168 
 169     // Mark all parents and document dirty
 170     pcmk__mark_xml_node_dirty(xml);
 171 
 172     pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
 173 }
 174 
 175 #define XML_DOC_PRIVATE_MAGIC   0x81726354UL
 176 #define XML_NODE_PRIVATE_MAGIC  0x54637281UL
 177 
 178 // Free an XML object previously marked as deleted
 179 static void
 180 free_deleted_object(void *data)
     /* [previous][next][first][last][top][bottom][index][help] */
 181 {
 182     if(data) {
 183         pcmk__deleted_xml_t *deleted_obj = data;
 184 
 185         g_free(deleted_obj->path);
 186         free(deleted_obj);
 187     }
 188 }
 189 
 190 // Free and NULL user, ACLs, and deleted objects in an XML node's private data
 191 static void
 192 reset_xml_private_data(xml_doc_private_t *docpriv)
     /* [previous][next][first][last][top][bottom][index][help] */
 193 {
 194     if (docpriv != NULL) {
 195         CRM_ASSERT(docpriv->check == XML_DOC_PRIVATE_MAGIC);
 196 
 197         free(docpriv->user);
 198         docpriv->user = NULL;
 199 
 200         if (docpriv->acls != NULL) {
 201             pcmk__free_acls(docpriv->acls);
 202             docpriv->acls = NULL;
 203         }
 204 
 205         if(docpriv->deleted_objs) {
 206             g_list_free_full(docpriv->deleted_objs, free_deleted_object);
 207             docpriv->deleted_objs = NULL;
 208         }
 209     }
 210 }
 211 
 212 // Free all private data associated with an XML node
 213 static void
 214 free_private_data(xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 215 {
 216     /* Note:
 217     
 218     This function frees private data assosciated with an XML node,
 219     unless the function is being called as a result of internal
 220     XSLT cleanup.
 221     
 222     That could happen through, for example, the following chain of
 223     function calls:
 224     
 225        xsltApplyStylesheetInternal
 226     -> xsltFreeTransformContext
 227     -> xsltFreeRVTs
 228     -> xmlFreeDoc
 229 
 230     And in that case, the node would fulfill three conditions:
 231     
 232     1. It would be a standalone document (i.e. it wouldn't be 
 233        part of a document)
 234     2. It would have a space-prefixed name (for reference, please
 235        see xsltInternals.h: XSLT_MARK_RES_TREE_FRAG)
 236     3. It would carry its own payload in the _private field.
 237     
 238     We do not free data in this circumstance to avoid a failed
 239     assertion on the XML_*_PRIVATE_MAGIC later.
 240     
 241     */
 242     if (node->name == NULL || node->name[0] != ' ') {
 243         if (node->_private) {
 244             if (node->type == XML_DOCUMENT_NODE) {
 245                 reset_xml_private_data(node->_private);
 246             } else {
 247                 CRM_ASSERT(((xml_node_private_t *) node->_private)->check
 248                                == XML_NODE_PRIVATE_MAGIC);
 249                 /* nothing dynamically allocated nested */
 250             }
 251             free(node->_private);
 252             node->_private = NULL;
 253         }
 254     }
 255 }
 256 
 257 // Allocate and initialize private data for an XML node
 258 static void
 259 new_private_data(xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 260 {
 261     switch (node->type) {
 262         case XML_DOCUMENT_NODE: {
 263             xml_doc_private_t *docpriv =
 264                 pcmk__assert_alloc(1, sizeof(xml_doc_private_t));
 265 
 266             docpriv->check = XML_DOC_PRIVATE_MAGIC;
 267             /* Flags will be reset if necessary when tracking is enabled */
 268             pcmk__set_xml_flags(docpriv, pcmk__xf_dirty|pcmk__xf_created);
 269             node->_private = docpriv;
 270             break;
 271         }
 272         case XML_ELEMENT_NODE:
 273         case XML_ATTRIBUTE_NODE:
 274         case XML_COMMENT_NODE: {
 275             xml_node_private_t *nodepriv =
 276                 pcmk__assert_alloc(1, sizeof(xml_node_private_t));
 277 
 278             nodepriv->check = XML_NODE_PRIVATE_MAGIC;
 279             /* Flags will be reset if necessary when tracking is enabled */
 280             pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
 281             node->_private = nodepriv;
 282             if (pcmk__tracking_xml_changes(node, FALSE)) {
 283                 /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
 284                  * not hooked up at the point we are called
 285                  */
 286                 pcmk__mark_xml_node_dirty(node);
 287             }
 288             break;
 289         }
 290         case XML_TEXT_NODE:
 291         case XML_DTD_NODE:
 292         case XML_CDATA_SECTION_NODE:
 293             break;
 294         default:
 295             /* Ignore */
 296             crm_trace("Ignoring %p %d", node, node->type);
 297             CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
 298             break;
 299     }
 300 }
 301 
 302 void
 303 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls) 
     /* [previous][next][first][last][top][bottom][index][help] */
 304 {
 305     xml_accept_changes(xml);
 306     crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
 307     pcmk__set_xml_doc_flag(xml, pcmk__xf_tracking);
 308     if(enforce_acls) {
 309         if(acl_source == NULL) {
 310             acl_source = xml;
 311         }
 312         pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_enabled);
 313         pcmk__unpack_acl(acl_source, xml, user);
 314         pcmk__apply_acl(xml);
 315     }
 316 }
 317 
 318 bool xml_tracking_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 319 {
 320     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
 321            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
 322                           pcmk__xf_tracking);
 323 }
 324 
 325 bool xml_document_dirty(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 326 {
 327     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
 328            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
 329                           pcmk__xf_dirty);
 330 }
 331 
 332 /*!
 333  * \internal
 334  * \brief Return ordinal position of an XML node among its siblings
 335  *
 336  * \param[in] xml            XML node to check
 337  * \param[in] ignore_if_set  Don't count siblings with this flag set
 338  *
 339  * \return Ordinal position of \p xml (starting with 0)
 340  */
 341 int
 342 pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 343 {
 344     int position = 0;
 345 
 346     for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
 347         xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
 348 
 349         if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
 350             position++;
 351         }
 352     }
 353 
 354     return position;
 355 }
 356 
 357 /*!
 358  * \internal
 359  * \brief Remove all attributes marked as deleted from an XML node
 360  *
 361  * \param[in,out] xml        XML node whose deleted attributes to remove
 362  * \param[in,out] user_data  Ignored
 363  *
 364  * \return \c true (to continue traversing the tree)
 365  *
 366  * \note This is compatible with \c pcmk__xml_tree_foreach().
 367  */
 368 static bool
 369 accept_attr_deletions(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 370 {
 371     reset_xml_node_flags(xml, NULL);
 372     pcmk__xe_remove_matching_attrs(xml, pcmk__marked_as_deleted, NULL);
 373     return true;
 374 }
 375 
 376 /*!
 377  * \internal
 378  * \brief Find first child XML node matching another given XML node
 379  *
 380  * \param[in] haystack  XML whose children should be checked
 381  * \param[in] needle    XML to match (comment content or element name and ID)
 382  * \param[in] exact     If true and needle is a comment, position must match
 383  */
 384 xmlNode *
 385 pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
     /* [previous][next][first][last][top][bottom][index][help] */
 386 {
 387     CRM_CHECK(needle != NULL, return NULL);
 388 
 389     if (needle->type == XML_COMMENT_NODE) {
 390         return pcmk__xc_match(haystack, needle, exact);
 391 
 392     } else {
 393         const char *id = pcmk__xe_id(needle);
 394         const char *attr = (id == NULL)? NULL : PCMK_XA_ID;
 395 
 396         return pcmk__xe_first_child(haystack, (const char *) needle->name, attr,
 397                                     id);
 398     }
 399 }
 400 
 401 void
 402 xml_accept_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 403 {
 404     xmlNode *top = NULL;
 405     xml_doc_private_t *docpriv = NULL;
 406 
 407     if(xml == NULL) {
 408         return;
 409     }
 410 
 411     crm_trace("Accepting changes to %p", xml);
 412     docpriv = xml->doc->_private;
 413     top = xmlDocGetRootElement(xml->doc);
 414 
 415     reset_xml_private_data(xml->doc->_private);
 416 
 417     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
 418         docpriv->flags = pcmk__xf_none;
 419         return;
 420     }
 421 
 422     docpriv->flags = pcmk__xf_none;
 423     pcmk__xml_tree_foreach(top, accept_attr_deletions, NULL);
 424 }
 425 
 426 /*!
 427  * \internal
 428  * \brief Find first XML child element matching given criteria
 429  *
 430  * \param[in] parent     XML element to search (can be \c NULL)
 431  * \param[in] node_name  If not \c NULL, only match children of this type
 432  * \param[in] attr_n     If not \c NULL, only match children with an attribute
 433  *                       of this name.
 434  * \param[in] attr_v     If \p attr_n and this are not NULL, only match children
 435  *                       with an attribute named \p attr_n and this value
 436  *
 437  * \return Matching XML child element, or \c NULL if none found
 438  */
 439 xmlNode *
 440 pcmk__xe_first_child(const xmlNode *parent, const char *node_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 441                      const char *attr_n, const char *attr_v)
 442 {
 443     xmlNode *child = NULL;
 444     const char *parent_name = "<null>";
 445 
 446     CRM_CHECK((attr_v == NULL) || (attr_n != NULL), return NULL);
 447 
 448     if (parent != NULL) {
 449         child = parent->children;
 450         while ((child != NULL) && (child->type != XML_ELEMENT_NODE)) {
 451             child = child->next;
 452         }
 453 
 454         parent_name = (const char *) parent->name;
 455     }
 456 
 457     for (; child != NULL; child = pcmk__xe_next(child)) {
 458         const char *value = NULL;
 459 
 460         if ((node_name != NULL) && !pcmk__xe_is(child, node_name)) {
 461             // Node name mismatch
 462             continue;
 463         }
 464         if (attr_n == NULL) {
 465             // No attribute match needed
 466             return child;
 467         }
 468 
 469         value = crm_element_value(child, attr_n);
 470 
 471         if ((attr_v == NULL) && (value != NULL)) {
 472             // attr_v == NULL: Attribute attr_n must be set (to any value)
 473             return child;
 474         }
 475         if ((attr_v != NULL) && (pcmk__str_eq(value, attr_v, pcmk__str_none))) {
 476             // attr_v != NULL: Attribute attr_n must be set to value attr_v
 477             return child;
 478         }
 479     }
 480 
 481     if (node_name == NULL) {
 482         node_name = "(any)";    // For logging
 483     }
 484     if (attr_n != NULL) {
 485         crm_trace("XML child node <%s %s=%s> not found in %s",
 486                   node_name, attr_n, attr_v, parent_name);
 487     } else {
 488         crm_trace("XML child node <%s> not found in %s",
 489                   node_name, parent_name);
 490     }
 491     return NULL;
 492 }
 493 
 494 /*!
 495  * \internal
 496  * \brief Set an XML attribute, expanding \c ++ and \c += where appropriate
 497  *
 498  * If \p target already has an attribute named \p name set to an integer value
 499  * and \p value is an addition assignment expression on \p name, then expand
 500  * \p value to an integer and set attribute \p name to the expanded value in
 501  * \p target.
 502  *
 503  * Otherwise, set attribute \p name on \p target using the literal \p value.
 504  *
 505  * The original attribute value in \p target and the number in an assignment
 506  * expression in \p value are parsed and added as scores (that is, their values
 507  * are capped at \c INFINITY and \c -INFINITY). For more details, refer to
 508  * \c char2score().
 509  *
 510  * For example, suppose \p target has an attribute named \c "X" with value
 511  * \c "5", and that \p name is \c "X".
 512  * * If \p value is \c "X++", the new value of \c "X" in \p target is \c "6".
 513  * * If \p value is \c "X+=3", the new value of \c "X" in \p target is \c "8".
 514  * * If \p value is \c "val", the new value of \c "X" in \p target is \c "val".
 515  * * If \p value is \c "Y++", the new value of \c "X" in \p target is \c "Y++".
 516  *
 517  * \param[in,out] target  XML node whose attribute to set
 518  * \param[in]     name    Name of the attribute to set
 519  * \param[in]     value   New value of attribute to set
 520  *
 521  * \return Standard Pacemaker return code (specifically, \c EINVAL on invalid
 522  *         argument, or \c pcmk_rc_ok otherwise)
 523  */
 524 int
 525 pcmk__xe_set_score(xmlNode *target, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 526 {
 527     const char *old_value = NULL;
 528 
 529     CRM_CHECK((target != NULL) && (name != NULL), return EINVAL);
 530 
 531     if (value == NULL) {
 532         return pcmk_rc_ok;
 533     }
 534 
 535     old_value = crm_element_value(target, name);
 536 
 537     // If no previous value, skip to default case and set the value unexpanded.
 538     if (old_value != NULL) {
 539         const char *n = name;
 540         const char *v = value;
 541 
 542         // Stop at first character that differs between name and value
 543         for (; (*n == *v) && (*n != '\0'); n++, v++);
 544 
 545         // If value begins with name followed by a "++" or "+="
 546         if ((*n == '\0')
 547             && (*v++ == '+')
 548             && ((*v == '+') || (*v == '='))) {
 549 
 550             // If we're expanding ourselves, no previous value was set; use 0
 551             int old_value_i = (old_value != value)? char2score(old_value) : 0;
 552 
 553             /* value="X++": new value of X is old_value + 1
 554              * value="X+=Y": new value of X is old_value + Y (for some number Y)
 555              */
 556             int add = (*v == '+')? 1 : char2score(++v);
 557 
 558             crm_xml_add_int(target, name, pcmk__add_scores(old_value_i, add));
 559             return pcmk_rc_ok;
 560         }
 561     }
 562 
 563     // Default case: set the attribute unexpanded (with value treated literally)
 564     if (old_value != value) {
 565         crm_xml_add(target, name, value);
 566     }
 567     return pcmk_rc_ok;
 568 }
 569 
 570 /*!
 571  * \internal
 572  * \brief Copy XML attributes from a source element to a target element
 573  *
 574  * This is similar to \c xmlCopyPropList() except that attributes are marked
 575  * as dirty for change tracking purposes.
 576  *
 577  * \param[in,out] target  XML element to receive copied attributes from \p src
 578  * \param[in]     src     XML element whose attributes to copy to \p target
 579  * \param[in]     flags   Group of <tt>enum pcmk__xa_flags</tt>
 580  *
 581  * \return Standard Pacemaker return code
 582  */
 583 int
 584 pcmk__xe_copy_attrs(xmlNode *target, const xmlNode *src, uint32_t flags)
     /* [previous][next][first][last][top][bottom][index][help] */
 585 {
 586     CRM_CHECK((src != NULL) && (target != NULL), return EINVAL);
 587 
 588     for (xmlAttr *attr = pcmk__xe_first_attr(src); attr != NULL;
 589          attr = attr->next) {
 590 
 591         const char *name = (const char *) attr->name;
 592         const char *value = pcmk__xml_attr_value(attr);
 593 
 594         if (pcmk_is_set(flags, pcmk__xaf_no_overwrite)
 595             && (crm_element_value(target, name) != NULL)) {
 596             continue;
 597         }
 598 
 599         if (pcmk_is_set(flags, pcmk__xaf_score_update)) {
 600             pcmk__xe_set_score(target, name, value);
 601         } else {
 602             crm_xml_add(target, name, value);
 603         }
 604     }
 605 
 606     return pcmk_rc_ok;
 607 }
 608 
 609 /*!
 610  * \internal
 611  * \brief Remove an XML attribute from an element
 612  *
 613  * \param[in,out] element  XML element that owns \p attr
 614  * \param[in,out] attr     XML attribute to remove from \p element
 615  *
 616  * \return Standard Pacemaker return code (\c EPERM if ACLs prevent removal of
 617  *         attributes from \p element, or \c pcmk_rc_ok otherwise)
 618  */
 619 static int
 620 remove_xe_attr(xmlNode *element, xmlAttr *attr)
     /* [previous][next][first][last][top][bottom][index][help] */
 621 {
 622     if (attr == NULL) {
 623         return pcmk_rc_ok;
 624     }
 625 
 626     if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) {
 627         // ACLs apply to element, not to particular attributes
 628         crm_trace("ACLs prevent removal of attributes from %s element",
 629                   (const char *) element->name);
 630         return EPERM;
 631     }
 632 
 633     if (pcmk__tracking_xml_changes(element, false)) {
 634         // Leave in place (marked for removal) until after diff is calculated
 635         set_parent_flag(element, pcmk__xf_dirty);
 636         pcmk__set_xml_flags((xml_node_private_t *) attr->_private,
 637                             pcmk__xf_deleted);
 638     } else {
 639         xmlRemoveProp(attr);
 640     }
 641     return pcmk_rc_ok;
 642 }
 643 
 644 /*!
 645  * \internal
 646  * \brief Remove a named attribute from an XML element
 647  *
 648  * \param[in,out] element  XML element to remove an attribute from
 649  * \param[in]     name     Name of attribute to remove
 650  */
 651 void
 652 pcmk__xe_remove_attr(xmlNode *element, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 653 {
 654     if (name != NULL) {
 655         remove_xe_attr(element, xmlHasProp(element, (pcmkXmlStr) name));
 656     }
 657 }
 658 
 659 /*!
 660  * \internal
 661  * \brief Remove a named attribute from an XML element
 662  *
 663  * This is a wrapper for \c pcmk__xe_remove_attr() for use with
 664  * \c pcmk__xml_tree_foreach().
 665  *
 666  * \param[in,out] xml        XML element to remove an attribute from
 667  * \param[in]     user_data  Name of attribute to remove
 668  *
 669  * \return \c true (to continue traversing the tree)
 670  *
 671  * \note This is compatible with \c pcmk__xml_tree_foreach().
 672  */
 673 bool
 674 pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 675 {
 676     const char *name = user_data;
 677 
 678     pcmk__xe_remove_attr(xml, name);
 679     return true;
 680 }
 681 
 682 /*!
 683  * \internal
 684  * \brief Remove an XML element's attributes that match some criteria
 685  *
 686  * \param[in,out] element    XML element to modify
 687  * \param[in]     match      If not NULL, only remove attributes for which
 688  *                           this function returns true
 689  * \param[in,out] user_data  Data to pass to \p match
 690  */
 691 void
 692 pcmk__xe_remove_matching_attrs(xmlNode *element,
     /* [previous][next][first][last][top][bottom][index][help] */
 693                                bool (*match)(xmlAttrPtr, void *),
 694                                void *user_data)
 695 {
 696     xmlAttrPtr next = NULL;
 697 
 698     for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
 699         next = a->next; // Grab now because attribute might get removed
 700         if ((match == NULL) || match(a, user_data)) {
 701             if (remove_xe_attr(element, a) != pcmk_rc_ok) {
 702                 return;
 703             }
 704         }
 705     }
 706 }
 707 
 708 /*!
 709  * \internal
 710  * \brief Create a new XML element under a given parent
 711  *
 712  * \param[in,out] parent  XML element that will be the new element's parent
 713  *                        (\c NULL to create a new XML document with the new
 714  *                        node as root)
 715  * \param[in]     name    Name of new element
 716  *
 717  * \return Newly created XML element (guaranteed not to be \c NULL)
 718  */
 719 xmlNode *
 720 pcmk__xe_create(xmlNode *parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 721 {
 722     xmlNode *node = NULL;
 723 
 724     CRM_ASSERT(!pcmk__str_empty(name));
 725 
 726     if (parent == NULL) {
 727         xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
 728 
 729         pcmk__mem_assert(doc);
 730 
 731         node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
 732         pcmk__mem_assert(node);
 733 
 734         xmlDocSetRootElement(doc, node);
 735 
 736     } else {
 737         node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
 738         pcmk__mem_assert(node);
 739     }
 740 
 741     pcmk__xml_mark_created(node);
 742     return node;
 743 }
 744 
 745 /*!
 746  * \internal
 747  * \brief Set a formatted string as an XML node's content
 748  *
 749  * \param[in,out] node    Node whose content to set
 750  * \param[in]     format  <tt>printf(3)</tt>-style format string
 751  * \param[in]     ...     Arguments for \p format
 752  *
 753  * \note This function escapes special characters. \c xmlNodeSetContent() does
 754  *       not.
 755  */
 756 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 757 void
 758 pcmk__xe_set_content(xmlNode *node, const char *format, ...)
 759 {
 760     if (node != NULL) {
 761         const char *content = NULL;
 762         char *buf = NULL;
 763 
 764         if (strchr(format, '%') == NULL) {
 765             // Nothing to format
 766             content = format;
 767 
 768         } else {
 769             va_list ap;
 770 
 771             va_start(ap, format);
 772 
 773             if (pcmk__str_eq(format, "%s", pcmk__str_none)) {
 774                 // No need to make a copy
 775                 content = va_arg(ap, const char *);
 776 
 777             } else {
 778                 CRM_ASSERT(vasprintf(&buf, format, ap) >= 0);
 779                 content = buf;
 780             }
 781             va_end(ap);
 782         }
 783 
 784         xmlNodeSetContent(node, (pcmkXmlStr) content);
 785         free(buf);
 786     }
 787 }
 788 
 789 /*!
 790  * Free an XML element and all of its children, removing it from its parent
 791  *
 792  * \param[in,out] xml  XML element to free
 793  */
 794 void
 795 pcmk_free_xml_subtree(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 796 {
 797     xmlUnlinkNode(xml); // Detaches from parent and siblings
 798     xmlFreeNode(xml);   // Frees
 799 }
 800 
 801 static void
 802 free_xml_with_position(xmlNode *child, int position)
     /* [previous][next][first][last][top][bottom][index][help] */
 803 {
 804     xmlDoc *doc = NULL;
 805     xml_node_private_t *nodepriv = NULL;
 806 
 807     if (child == NULL) {
 808         return;
 809     }
 810     doc = child->doc;
 811     nodepriv = child->_private;
 812 
 813     if ((doc != NULL) && (xmlDocGetRootElement(doc) == child)) {
 814         // Free everything
 815         xmlFreeDoc(doc);
 816         return;
 817     }
 818 
 819     if (!pcmk__check_acl(child, NULL, pcmk__xf_acl_write)) {
 820         GString *xpath = NULL;
 821 
 822         pcmk__if_tracing({}, return);
 823         xpath = pcmk__element_xpath(child);
 824         qb_log_from_external_source(__func__, __FILE__,
 825                                     "Cannot remove %s %x", LOG_TRACE,
 826                                     __LINE__, 0, xpath->str, nodepriv->flags);
 827         g_string_free(xpath, TRUE);
 828         return;
 829     }
 830 
 831     if ((doc != NULL) && pcmk__tracking_xml_changes(child, false)
 832         && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 833 
 834         xml_doc_private_t *docpriv = doc->_private;
 835         GString *xpath = pcmk__element_xpath(child);
 836 
 837         if (xpath != NULL) {
 838             pcmk__deleted_xml_t *deleted_obj = NULL;
 839 
 840             crm_trace("Deleting %s %p from %p", xpath->str, child, doc);
 841 
 842             deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
 843             deleted_obj->path = g_string_free(xpath, FALSE);
 844             deleted_obj->position = -1;
 845 
 846             // Record the position only for XML comments for now
 847             if (child->type == XML_COMMENT_NODE) {
 848                 if (position >= 0) {
 849                     deleted_obj->position = position;
 850 
 851                 } else {
 852                     deleted_obj->position = pcmk__xml_position(child,
 853                                                                pcmk__xf_skip);
 854                 }
 855             }
 856 
 857             docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
 858                                                   deleted_obj);
 859             pcmk__set_xml_doc_flag(child, pcmk__xf_dirty);
 860         }
 861     }
 862     pcmk_free_xml_subtree(child);
 863 }
 864 
 865 
 866 void
 867 free_xml(xmlNode * child)
     /* [previous][next][first][last][top][bottom][index][help] */
 868 {
 869     free_xml_with_position(child, -1);
 870 }
 871 
 872 /*!
 873  * \internal
 874  * \brief Make a deep copy of an XML node under a given parent
 875  *
 876  * \param[in,out] parent  XML element that will be the copy's parent (\c NULL
 877  *                        to create a new XML document with the copy as root)
 878  * \param[in]     src     XML node to copy
 879  *
 880  * \return Deep copy of \p src, or \c NULL if \p src is \c NULL
 881  */
 882 xmlNode *
 883 pcmk__xml_copy(xmlNode *parent, xmlNode *src)
     /* [previous][next][first][last][top][bottom][index][help] */
 884 {
 885     xmlNode *copy = NULL;
 886 
 887     if (src == NULL) {
 888         return NULL;
 889     }
 890 
 891     if (parent == NULL) {
 892         xmlDoc *doc = NULL;
 893 
 894         // The copy will be the root element of a new document
 895         CRM_ASSERT(src->type == XML_ELEMENT_NODE);
 896 
 897         doc = xmlNewDoc(PCMK__XML_VERSION);
 898         pcmk__mem_assert(doc);
 899 
 900         copy = xmlDocCopyNode(src, doc, 1);
 901         pcmk__mem_assert(copy);
 902 
 903         xmlDocSetRootElement(doc, copy);
 904 
 905     } else {
 906         copy = xmlDocCopyNode(src, parent->doc, 1);
 907         pcmk__mem_assert(copy);
 908 
 909         xmlAddChild(parent, copy);
 910     }
 911 
 912     pcmk__xml_mark_created(copy);
 913     return copy;
 914 }
 915 
 916 /*!
 917  * \internal
 918  * \brief Remove XML text nodes from specified XML and all its children
 919  *
 920  * \param[in,out] xml  XML to strip text from
 921  */
 922 void
 923 pcmk__strip_xml_text(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 924 {
 925     xmlNode *iter = xml->children;
 926 
 927     while (iter) {
 928         xmlNode *next = iter->next;
 929 
 930         switch (iter->type) {
 931             case XML_TEXT_NODE:
 932                 /* Remove it */
 933                 pcmk_free_xml_subtree(iter);
 934                 break;
 935 
 936             case XML_ELEMENT_NODE:
 937                 /* Search it */
 938                 pcmk__strip_xml_text(iter);
 939                 break;
 940 
 941             default:
 942                 /* Leave it */
 943                 break;
 944         }
 945 
 946         iter = next;
 947     }
 948 }
 949 
 950 /*!
 951  * \internal
 952  * \brief Add a "last written" attribute to an XML element, set to current time
 953  *
 954  * \param[in,out] xe  XML element to add attribute to
 955  *
 956  * \return Value that was set, or NULL on error
 957  */
 958 const char *
 959 pcmk__xe_add_last_written(xmlNode *xe)
     /* [previous][next][first][last][top][bottom][index][help] */
 960 {
 961     char *now_s = pcmk__epoch2str(NULL, 0);
 962     const char *result = NULL;
 963 
 964     result = crm_xml_add(xe, PCMK_XA_CIB_LAST_WRITTEN,
 965                          pcmk__s(now_s, "Could not determine current time"));
 966     free(now_s);
 967     return result;
 968 }
 969 
 970 /*!
 971  * \brief Sanitize a string so it is usable as an XML ID
 972  *
 973  * \param[in,out] id  String to sanitize
 974  */
 975 void
 976 crm_xml_sanitize_id(char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
 977 {
 978     char *c;
 979 
 980     for (c = id; *c; ++c) {
 981         /* @TODO Sanitize more comprehensively */
 982         switch (*c) {
 983             case ':':
 984             case '#':
 985                 *c = '.';
 986         }
 987     }
 988 }
 989 
 990 /*!
 991  * \brief Set the ID of an XML element using a format
 992  *
 993  * \param[in,out] xml  XML element
 994  * \param[in]     fmt  printf-style format
 995  * \param[in]     ...  any arguments required by format
 996  */
 997 void
 998 crm_xml_set_id(xmlNode *xml, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
 999 {
1000     va_list ap;
1001     int len = 0;
1002     char *id = NULL;
1003 
1004     /* equivalent to crm_strdup_printf() */
1005     va_start(ap, format);
1006     len = vasprintf(&id, format, ap);
1007     va_end(ap);
1008     CRM_ASSERT(len > 0);
1009 
1010     crm_xml_sanitize_id(id);
1011     crm_xml_add(xml, PCMK_XA_ID, id);
1012     free(id);
1013 }
1014 
1015 /*!
1016  * \internal
1017  * \brief Check whether a string has XML special characters that must be escaped
1018  *
1019  * See \c pcmk__xml_escape() and \c pcmk__xml_escape_type for more details.
1020  *
1021  * \param[in] text  String to check
1022  * \param[in] type  Type of escaping
1023  *
1024  * \return \c true if \p text has special characters that need to be escaped, or
1025  *         \c false otherwise
1026  */
1027 bool
1028 pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
     /* [previous][next][first][last][top][bottom][index][help] */
1029 {
1030     if (text == NULL) {
1031         return false;
1032     }
1033 
1034     while (*text != '\0') {
1035         switch (type) {
1036             case pcmk__xml_escape_text:
1037                 switch (*text) {
1038                     case '<':
1039                     case '>':
1040                     case '&':
1041                         return true;
1042                     case '\n':
1043                     case '\t':
1044                         break;
1045                     default:
1046                         if (g_ascii_iscntrl(*text)) {
1047                             return true;
1048                         }
1049                         break;
1050                 }
1051                 break;
1052 
1053             case pcmk__xml_escape_attr:
1054                 switch (*text) {
1055                     case '<':
1056                     case '>':
1057                     case '&':
1058                     case '"':
1059                         return true;
1060                     default:
1061                         if (g_ascii_iscntrl(*text)) {
1062                             return true;
1063                         }
1064                         break;
1065                 }
1066                 break;
1067 
1068             case pcmk__xml_escape_attr_pretty:
1069                 switch (*text) {
1070                     case '\n':
1071                     case '\r':
1072                     case '\t':
1073                     case '"':
1074                         return true;
1075                     default:
1076                         break;
1077                 }
1078                 break;
1079 
1080             default:    // Invalid enum value
1081                 CRM_ASSERT(false);
1082                 break;
1083         }
1084 
1085         text = g_utf8_next_char(text);
1086     }
1087     return false;
1088 }
1089 
1090 /*!
1091  * \internal
1092  * \brief Replace special characters with their XML escape sequences
1093  *
1094  * \param[in] text  Text to escape
1095  * \param[in] type  Type of escaping
1096  *
1097  * \return Newly allocated string equivalent to \p text but with special
1098  *         characters replaced with XML escape sequences (or \c NULL if \p text
1099  *         is \c NULL). If \p text is not \c NULL, the return value is
1100  *         guaranteed not to be \c NULL.
1101  *
1102  * \note There are libxml functions that purport to do this:
1103  *       \c xmlEncodeEntitiesReentrant() and \c xmlEncodeSpecialChars().
1104  *       However, their escaping is incomplete. See:
1105  *       https://discourse.gnome.org/t/intended-use-of-xmlencodeentitiesreentrant-vs-xmlencodespecialchars/19252
1106  * \note The caller is responsible for freeing the return value using
1107  *       \c g_free().
1108  */
1109 gchar *
1110 pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
     /* [previous][next][first][last][top][bottom][index][help] */
1111 {
1112     GString *copy = NULL;
1113 
1114     if (text == NULL) {
1115         return NULL;
1116     }
1117     copy = g_string_sized_new(strlen(text));
1118 
1119     while (*text != '\0') {
1120         // Don't escape any non-ASCII characters
1121         if ((*text & 0x80) != 0) {
1122             size_t bytes = g_utf8_next_char(text) - text;
1123 
1124             g_string_append_len(copy, text, bytes);
1125             text += bytes;
1126             continue;
1127         }
1128 
1129         switch (type) {
1130             case pcmk__xml_escape_text:
1131                 switch (*text) {
1132                     case '<':
1133                         g_string_append(copy, PCMK__XML_ENTITY_LT);
1134                         break;
1135                     case '>':
1136                         g_string_append(copy, PCMK__XML_ENTITY_GT);
1137                         break;
1138                     case '&':
1139                         g_string_append(copy, PCMK__XML_ENTITY_AMP);
1140                         break;
1141                     case '\n':
1142                     case '\t':
1143                         g_string_append_c(copy, *text);
1144                         break;
1145                     default:
1146                         if (g_ascii_iscntrl(*text)) {
1147                             g_string_append_printf(copy, "&#x%.2X;", *text);
1148                         } else {
1149                             g_string_append_c(copy, *text);
1150                         }
1151                         break;
1152                 }
1153                 break;
1154 
1155             case pcmk__xml_escape_attr:
1156                 switch (*text) {
1157                     case '<':
1158                         g_string_append(copy, PCMK__XML_ENTITY_LT);
1159                         break;
1160                     case '>':
1161                         g_string_append(copy, PCMK__XML_ENTITY_GT);
1162                         break;
1163                     case '&':
1164                         g_string_append(copy, PCMK__XML_ENTITY_AMP);
1165                         break;
1166                     case '"':
1167                         g_string_append(copy, PCMK__XML_ENTITY_QUOT);
1168                         break;
1169                     default:
1170                         if (g_ascii_iscntrl(*text)) {
1171                             g_string_append_printf(copy, "&#x%.2X;", *text);
1172                         } else {
1173                             g_string_append_c(copy, *text);
1174                         }
1175                         break;
1176                 }
1177                 break;
1178 
1179             case pcmk__xml_escape_attr_pretty:
1180                 switch (*text) {
1181                     case '"':
1182                         g_string_append(copy, "\\\"");
1183                         break;
1184                     case '\n':
1185                         g_string_append(copy, "\\n");
1186                         break;
1187                     case '\r':
1188                         g_string_append(copy, "\\r");
1189                         break;
1190                     case '\t':
1191                         g_string_append(copy, "\\t");
1192                         break;
1193                     default:
1194                         g_string_append_c(copy, *text);
1195                         break;
1196                 }
1197                 break;
1198 
1199             default:    // Invalid enum value
1200                 CRM_ASSERT(false);
1201                 break;
1202         }
1203 
1204         text = g_utf8_next_char(text);
1205     }
1206     return g_string_free(copy, FALSE);
1207 }
1208 
1209 /*!
1210  * \internal
1211  * \brief Set a flag on all attributes of an XML element
1212  *
1213  * \param[in,out] xml   XML node to set flags on
1214  * \param[in]     flag  XML private flag to set
1215  */
1216 static void
1217 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
1218 {
1219     for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
1220         pcmk__set_xml_flags((xml_node_private_t *) (attr->_private), flag);
1221     }
1222 }
1223 
1224 /*!
1225  * \internal
1226  * \brief Add an XML attribute to a node, marked as deleted
1227  *
1228  * When calculating XML changes, we need to know when an attribute has been
1229  * deleted. Add the attribute back to the new XML, so that we can check the
1230  * removal against ACLs, and mark it as deleted for later removal after
1231  * differences have been calculated.
1232  *
1233  * \param[in,out] new_xml     XML to modify
1234  * \param[in]     element     Name of XML element that changed (for logging)
1235  * \param[in]     attr_name   Name of attribute that was deleted
1236  * \param[in]     old_value   Value of attribute that was deleted
1237  */
1238 static void
1239 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
1240                   const char *old_value)
1241 {
1242     xml_doc_private_t *docpriv = new_xml->doc->_private;
1243     xmlAttr *attr = NULL;
1244     xml_node_private_t *nodepriv;
1245 
1246     // Prevent the dirty flag being set recursively upwards
1247     pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1248 
1249     // Restore the old value (and the tracking flag)
1250     attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
1251     pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1252 
1253     // Reset flags (so the attribute doesn't appear as newly created)
1254     nodepriv = attr->_private;
1255     nodepriv->flags = 0;
1256 
1257     // Check ACLs and mark restored value for later removal
1258     remove_xe_attr(new_xml, attr);
1259 
1260     crm_trace("XML attribute %s=%s was removed from %s",
1261               attr_name, old_value, element);
1262 }
1263 
1264 /*
1265  * \internal
1266  * \brief Check ACLs for a changed XML attribute
1267  */
1268 static void
1269 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
1270                   const char *old_value)
1271 {
1272     char *vcopy = crm_element_value_copy(new_xml, attr_name);
1273 
1274     crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
1275               attr_name, old_value, vcopy, element);
1276 
1277     // Restore the original value
1278     xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
1279 
1280     // Change it back to the new value, to check ACLs
1281     crm_xml_add(new_xml, attr_name, vcopy);
1282     free(vcopy);
1283 }
1284 
1285 /*!
1286  * \internal
1287  * \brief Mark an XML attribute as having changed position
1288  *
1289  * \param[in,out] new_xml     XML to modify
1290  * \param[in]     element     Name of XML element that changed (for logging)
1291  * \param[in,out] old_attr    Attribute that moved, in original XML
1292  * \param[in,out] new_attr    Attribute that moved, in \p new_xml
1293  * \param[in]     p_old       Ordinal position of \p old_attr in original XML
1294  * \param[in]     p_new       Ordinal position of \p new_attr in \p new_xml
1295  */
1296 static void
1297 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
     /* [previous][next][first][last][top][bottom][index][help] */
1298                 xmlAttr *new_attr, int p_old, int p_new)
1299 {
1300     xml_node_private_t *nodepriv = new_attr->_private;
1301 
1302     crm_trace("XML attribute %s moved from position %d to %d in %s",
1303               old_attr->name, p_old, p_new, element);
1304 
1305     // Mark document, element, and all element's parents as changed
1306     pcmk__mark_xml_node_dirty(new_xml);
1307 
1308     // Mark attribute as changed
1309     pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
1310 
1311     nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
1312     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1313 }
1314 
1315 /*!
1316  * \internal
1317  * \brief Calculate differences in all previously existing XML attributes
1318  *
1319  * \param[in,out] old_xml  Original XML to compare
1320  * \param[in,out] new_xml  New XML to compare
1321  */
1322 static void
1323 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1324 {
1325     xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
1326 
1327     while (attr_iter != NULL) {
1328         const char *name = (const char *) attr_iter->name;
1329         xmlAttr *old_attr = attr_iter;
1330         xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
1331         const char *old_value = pcmk__xml_attr_value(attr_iter);
1332 
1333         attr_iter = attr_iter->next;
1334         if (new_attr == NULL) {
1335             mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
1336                               old_value);
1337 
1338         } else {
1339             xml_node_private_t *nodepriv = new_attr->_private;
1340             int new_pos = pcmk__xml_position((xmlNode*) new_attr,
1341                                              pcmk__xf_skip);
1342             int old_pos = pcmk__xml_position((xmlNode*) old_attr,
1343                                              pcmk__xf_skip);
1344             const char *new_value = crm_element_value(new_xml, name);
1345 
1346             // This attribute isn't new
1347             pcmk__clear_xml_flags(nodepriv, pcmk__xf_created);
1348 
1349             if (strcmp(new_value, old_value) != 0) {
1350                 mark_attr_changed(new_xml, (const char *) old_xml->name, name,
1351                                   old_value);
1352 
1353             } else if ((old_pos != new_pos)
1354                        && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
1355                 mark_attr_moved(new_xml, (const char *) old_xml->name,
1356                                 old_attr, new_attr, old_pos, new_pos);
1357             }
1358         }
1359     }
1360 }
1361 
1362 /*!
1363  * \internal
1364  * \brief Check all attributes in new XML for creation
1365  *
1366  * For each of a given XML element's attributes marked as newly created, accept
1367  * (and mark as dirty) or reject the creation according to ACLs.
1368  *
1369  * \param[in,out] new_xml  XML to check
1370  */
1371 static void
1372 mark_created_attrs(xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1373 {
1374     xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
1375 
1376     while (attr_iter != NULL) {
1377         xmlAttr *new_attr = attr_iter;
1378         xml_node_private_t *nodepriv = attr_iter->_private;
1379 
1380         attr_iter = attr_iter->next;
1381         if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
1382             const char *attr_name = (const char *) new_attr->name;
1383 
1384             crm_trace("Created new attribute %s=%s in %s",
1385                       attr_name, pcmk__xml_attr_value(new_attr),
1386                       new_xml->name);
1387 
1388             /* Check ACLs (we can't use the remove-then-create trick because it
1389              * would modify the attribute position).
1390              */
1391             if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
1392                 pcmk__mark_xml_attr_dirty(new_attr);
1393             } else {
1394                 // Creation was not allowed, so remove the attribute
1395                 xmlUnsetProp(new_xml, new_attr->name);
1396             }
1397         }
1398     }
1399 }
1400 
1401 /*!
1402  * \internal
1403  * \brief Calculate differences in attributes between two XML nodes
1404  *
1405  * \param[in,out] old_xml  Original XML to compare
1406  * \param[in,out] new_xml  New XML to compare
1407  */
1408 static void
1409 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1410 {
1411     set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
1412     xml_diff_old_attrs(old_xml, new_xml);
1413     mark_created_attrs(new_xml);
1414 }
1415 
1416 /*!
1417  * \internal
1418  * \brief Add an XML child element to a node, marked as deleted
1419  *
1420  * When calculating XML changes, we need to know when a child element has been
1421  * deleted. Add the child back to the new XML, so that we can check the removal
1422  * against ACLs, and mark it as deleted for later removal after differences have
1423  * been calculated.
1424  *
1425  * \param[in,out] old_child    Child element from original XML
1426  * \param[in,out] new_parent   New XML to add marked copy to
1427  */
1428 static void
1429 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
     /* [previous][next][first][last][top][bottom][index][help] */
1430 {
1431     // Re-create the child element so we can check ACLs
1432     xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
1433 
1434     // Clear flags on new child and its children
1435     pcmk__xml_tree_foreach(candidate, reset_xml_node_flags, NULL);
1436 
1437     // Check whether ACLs allow the deletion
1438     pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
1439 
1440     // Remove the child again (which will track it in document's deleted_objs)
1441     free_xml_with_position(candidate,
1442                            pcmk__xml_position(old_child, pcmk__xf_skip));
1443 
1444     if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
1445         pcmk__set_xml_flags((xml_node_private_t *) (old_child->_private),
1446                             pcmk__xf_skip);
1447     }
1448 }
1449 
1450 static void
1451 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
     /* [previous][next][first][last][top][bottom][index][help] */
1452                  int p_old, int p_new)
1453 {
1454     xml_node_private_t *nodepriv = new_child->_private;
1455 
1456     crm_trace("Child element %s with "
1457               PCMK_XA_ID "='%s' moved from position %d to %d under %s",
1458               new_child->name, pcmk__s(pcmk__xe_id(new_child), "<no id>"),
1459               p_old, p_new, new_parent->name);
1460     pcmk__mark_xml_node_dirty(new_parent);
1461     pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
1462 
1463     if (p_old > p_new) {
1464         nodepriv = old_child->_private;
1465     } else {
1466         nodepriv = new_child->_private;
1467     }
1468     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1469 }
1470 
1471 // Given original and new XML, mark new XML portions that have changed
1472 static void
1473 mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
     /* [previous][next][first][last][top][bottom][index][help] */
1474 {
1475     xmlNode *old_child = NULL;
1476     xmlNode *new_child = NULL;
1477     xml_node_private_t *nodepriv = NULL;
1478 
1479     CRM_CHECK(new_xml != NULL, return);
1480     if (old_xml == NULL) {
1481         pcmk__xml_mark_created(new_xml);
1482         pcmk__apply_creation_acl(new_xml, check_top);
1483         return;
1484     }
1485 
1486     nodepriv = new_xml->_private;
1487     CRM_CHECK(nodepriv != NULL, return);
1488 
1489     if(nodepriv->flags & pcmk__xf_processed) {
1490         /* Avoid re-comparing nodes */
1491         return;
1492     }
1493     pcmk__set_xml_flags(nodepriv, pcmk__xf_processed);
1494 
1495     xml_diff_attrs(old_xml, new_xml);
1496 
1497     // Check for differences in the original children
1498     for (old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1499          old_child = pcmk__xml_next(old_child)) {
1500 
1501         new_child = pcmk__xml_match(new_xml, old_child, true);
1502 
1503         if (new_child != NULL) {
1504             mark_xml_changes(old_child, new_child, true);
1505 
1506         } else {
1507             mark_child_deleted(old_child, new_xml);
1508         }
1509     }
1510 
1511     // Check for moved or created children
1512     new_child = pcmk__xml_first_child(new_xml);
1513     while (new_child != NULL) {
1514         xmlNode *next = pcmk__xml_next(new_child);
1515 
1516         old_child = pcmk__xml_match(old_xml, new_child, true);
1517 
1518         if (old_child == NULL) {
1519             // This is a newly created child
1520             nodepriv = new_child->_private;
1521             pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1522 
1523             // May free new_child
1524             mark_xml_changes(old_child, new_child, true);
1525 
1526         } else {
1527             /* Check for movement, we already checked for differences */
1528             int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
1529             int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
1530 
1531             if(p_old != p_new) {
1532                 mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
1533             }
1534         }
1535 
1536         new_child = next;
1537     }
1538 }
1539 
1540 void
1541 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1542 {
1543     pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy);
1544     xml_calculate_changes(old_xml, new_xml);
1545 }
1546 
1547 // Called functions may set the \p pcmk__xf_skip flag on parts of \p old_xml
1548 void
1549 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1550 {
1551     CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
1552               && pcmk__xe_is(old_xml, (const char *) new_xml->name)
1553               && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
1554                               pcmk__str_none),
1555               return);
1556 
1557     if(xml_tracking_changes(new_xml) == FALSE) {
1558         xml_track_changes(new_xml, NULL, NULL, FALSE);
1559     }
1560 
1561     mark_xml_changes(old_xml, new_xml, FALSE);
1562 }
1563 
1564 /*!
1565  * \internal
1566  * \brief Find a comment with matching content in specified XML
1567  *
1568  * \param[in] root            XML to search
1569  * \param[in] search_comment  Comment whose content should be searched for
1570  * \param[in] exact           If true, comment must also be at same position
1571  */
1572 xmlNode *
1573 pcmk__xc_match(const xmlNode *root, const xmlNode *search_comment, bool exact)
     /* [previous][next][first][last][top][bottom][index][help] */
1574 {
1575     xmlNode *a_child = NULL;
1576     int search_offset = pcmk__xml_position(search_comment, pcmk__xf_skip);
1577 
1578     CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
1579 
1580     for (a_child = pcmk__xml_first_child(root); a_child != NULL;
1581          a_child = pcmk__xml_next(a_child)) {
1582         if (exact) {
1583             int offset = pcmk__xml_position(a_child, pcmk__xf_skip);
1584             xml_node_private_t *nodepriv = a_child->_private;
1585 
1586             if (offset < search_offset) {
1587                 continue;
1588 
1589             } else if (offset > search_offset) {
1590                 return NULL;
1591             }
1592 
1593             if (pcmk_is_set(nodepriv->flags, pcmk__xf_skip)) {
1594                 continue;
1595             }
1596         }
1597 
1598         if (a_child->type == XML_COMMENT_NODE
1599             && pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) {
1600             return a_child;
1601 
1602         } else if (exact) {
1603             return NULL;
1604         }
1605     }
1606 
1607     return NULL;
1608 }
1609 
1610 /*!
1611  * \internal
1612  * \brief Make one XML comment match another (in content)
1613  *
1614  * \param[in,out] parent   If \p target is NULL and this is not, add or update
1615  *                         comment child of this XML node that matches \p update
1616  * \param[in,out] target   If not NULL, update this XML comment node
1617  * \param[in]     update   Make comment content match this (must not be NULL)
1618  *
1619  * \note At least one of \parent and \target must be non-NULL
1620  */
1621 void
1622 pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
     /* [previous][next][first][last][top][bottom][index][help] */
1623 {
1624     CRM_CHECK(update != NULL, return);
1625     CRM_CHECK(update->type == XML_COMMENT_NODE, return);
1626 
1627     if (target == NULL) {
1628         target = pcmk__xc_match(parent, update, false);
1629     }
1630 
1631     if (target == NULL) {
1632         pcmk__xml_copy(parent, update);
1633 
1634     } else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
1635         xmlFree(target->content);
1636         target->content = xmlStrdup(update->content);
1637     }
1638 }
1639 
1640 /*!
1641  * \internal
1642  * \brief Merge one XML tree into another
1643  *
1644  * Here, "merge" means:
1645  * 1. Copy attribute values from \p update to the target, overwriting in case of
1646  *    conflict.
1647  * 2. Descend through \p update and the target in parallel. At each level, for
1648  *    each child of \p update, look for a matching child of the target.
1649  *    a. For each child, if a match is found, go to step 1, recursively merging
1650  *       the child of \p update into the child of the target.
1651  *    b. Otherwise, copy the child of \p update as a child of the target.
1652  *
1653  * A match is defined as the first child of the same type within the target,
1654  * with:
1655  * * the \c PCMK_XA_ID attribute matching, if set in \p update; otherwise,
1656  * * the \c PCMK_XA_ID_REF attribute matching, if set in \p update
1657  *
1658  * This function does not delete any elements or attributes from the target. It
1659  * may add elements or overwrite attributes, as described above.
1660  *
1661  * \param[in,out] parent   If \p target is NULL and this is not, add or update
1662  *                         child of this XML node that matches \p update
1663  * \param[in,out] target   If not NULL, update this XML
1664  * \param[in]     update   Make the desired XML match this (must not be \c NULL)
1665  * \param[in]     flags    Group of <tt>enum pcmk__xa_flags</tt>
1666  * \param[in]     as_diff  If \c true, preserve order of attributes (deprecated
1667  *                         since 2.0.5)
1668  *
1669  * \note At least one of \p parent and \p target must be non-<tt>NULL</tt>.
1670  * \note This function is recursive. For the top-level call, \p parent is
1671  *       \c NULL and \p target is not \c NULL. For recursive calls, \p target is
1672  *       \c NULL and \p parent is not \c NULL.
1673  */
1674 void
1675 pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
     /* [previous][next][first][last][top][bottom][index][help] */
1676                  uint32_t flags, bool as_diff)
1677 {
1678     /* @COMPAT Refactor further and staticize after v1 patchset deprecation.
1679      *
1680      * @COMPAT Drop as_diff argument when apply_xml_diff() is dropped.
1681      */
1682     const char *update_name = NULL;
1683     const char *update_id_attr = NULL;
1684     const char *update_id_val = NULL;
1685     char *trace_s = NULL;
1686 
1687     crm_log_xml_trace(update, "update");
1688     crm_log_xml_trace(target, "target");
1689 
1690     CRM_CHECK(update != NULL, goto done);
1691 
1692     if (update->type == XML_COMMENT_NODE) {
1693         pcmk__xc_update(parent, target, update);
1694         goto done;
1695     }
1696 
1697     update_name = (const char *) update->name;
1698 
1699     CRM_CHECK(update_name != NULL, goto done);
1700     CRM_CHECK((target != NULL) || (parent != NULL), goto done);
1701 
1702     update_id_val = pcmk__xe_id(update);
1703     if (update_id_val != NULL) {
1704         update_id_attr = PCMK_XA_ID;
1705 
1706     } else {
1707         update_id_val = crm_element_value(update, PCMK_XA_ID_REF);
1708         if (update_id_val != NULL) {
1709             update_id_attr = PCMK_XA_ID_REF;
1710         }
1711     }
1712 
1713     pcmk__if_tracing(
1714         {
1715             if (update_id_attr != NULL) {
1716                 trace_s = crm_strdup_printf("<%s %s=%s/>",
1717                                             update_name, update_id_attr,
1718                                             update_id_val);
1719             } else {
1720                 trace_s = crm_strdup_printf("<%s/>", update_name);
1721             }
1722         },
1723         {}
1724     );
1725 
1726     if (target == NULL) {
1727         // Recursive call
1728         target = pcmk__xe_first_child(parent, update_name, update_id_attr,
1729                                       update_id_val);
1730     }
1731 
1732     if (target == NULL) {
1733         // Recursive call with no existing matching child
1734         target = pcmk__xe_create(parent, update_name);
1735         crm_trace("Added %s", pcmk__s(trace_s, update_name));
1736 
1737     } else {
1738         // Either recursive call with match, or top-level call
1739         crm_trace("Found node %s to update", pcmk__s(trace_s, update_name));
1740     }
1741 
1742     CRM_CHECK(pcmk__xe_is(target, (const char *) update->name), return);
1743 
1744     if (!as_diff) {
1745         pcmk__xe_copy_attrs(target, update, flags);
1746 
1747     } else {
1748         // Preserve order of attributes. Don't use pcmk__xe_copy_attrs().
1749         for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
1750              a = a->next) {
1751             const char *p_value = pcmk__xml_attr_value(a);
1752 
1753             /* Remove it first so the ordering of the update is preserved */
1754             xmlUnsetProp(target, a->name);
1755             xmlSetProp(target, a->name, (pcmkXmlStr) p_value);
1756         }
1757     }
1758 
1759     for (xmlNode *child = pcmk__xml_first_child(update); child != NULL;
1760          child = pcmk__xml_next(child)) {
1761 
1762         crm_trace("Updating child of %s", pcmk__s(trace_s, update_name));
1763         pcmk__xml_update(target, NULL, child, flags, as_diff);
1764     }
1765 
1766     crm_trace("Finished with %s", pcmk__s(trace_s, update_name));
1767 
1768 done:
1769     free(trace_s);
1770 }
1771 
1772 /*!
1773  * \internal
1774  * \brief Delete an XML subtree if it matches a search element
1775  *
1776  * A match is defined as follows:
1777  * * \p xml and \p user_data are both element nodes of the same type.
1778  * * If \p user_data has attributes set, \p xml has those attributes set to the
1779  *   same values. (\p xml may have additional attributes set to arbitrary
1780  *   values.)
1781  *
1782  * \param[in,out] xml        XML subtree to delete upon match
1783  * \param[in]     user_data  Search element
1784  *
1785  * \return \c true to continue traversing the tree, or \c false to stop (because
1786  *         \p xml was deleted)
1787  *
1788  * \note This is compatible with \c pcmk__xml_tree_foreach().
1789  */
1790 static bool
1791 delete_xe_if_matching(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1792 {
1793     xmlNode *search = user_data;
1794 
1795     if (!pcmk__xe_is(search, (const char *) xml->name)) {
1796         // No match: either not both elements, or different element types
1797         return true;
1798     }
1799 
1800     for (const xmlAttr *attr = pcmk__xe_first_attr(search); attr != NULL;
1801          attr = attr->next) {
1802 
1803         const char *search_val = pcmk__xml_attr_value(attr);
1804         const char *xml_val = crm_element_value(xml, (const char *) attr->name);
1805 
1806         if (!pcmk__str_eq(search_val, xml_val, pcmk__str_casei)) {
1807             // No match: an attr in xml doesn't match the attr in search
1808             return true;
1809         }
1810     }
1811 
1812     crm_log_xml_trace(xml, "delete-match");
1813     crm_log_xml_trace(search, "delete-search");
1814     free_xml(xml);
1815 
1816     // Found a match and deleted it; stop traversing tree
1817     return false;
1818 }
1819 
1820 /*!
1821  * \internal
1822  * \brief Search an XML tree depth-first and delete the first matching element
1823  *
1824  * This function does not attempt to match the tree root (\p xml).
1825  *
1826  * A match with a node \c node is defined as follows:
1827  * * \c node and \p search are both element nodes of the same type.
1828  * * If \p search has attributes set, \c node has those attributes set to the
1829  *   same values. (\c node may have additional attributes set to arbitrary
1830  *   values.)
1831  *
1832  * \param[in,out] xml     XML subtree to search
1833  * \param[in]     search  Element to match against
1834  *
1835  * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
1836  *         successful deletion and an error code otherwise)
1837  */
1838 int
1839 pcmk__xe_delete_match(xmlNode *xml, xmlNode *search)
     /* [previous][next][first][last][top][bottom][index][help] */
1840 {
1841     // See @COMPAT comment in pcmk__xe_replace_match()
1842     CRM_CHECK((xml != NULL) && (search != NULL), return EINVAL);
1843 
1844     for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
1845          xml = pcmk__xe_next(xml)) {
1846 
1847         if (!pcmk__xml_tree_foreach(xml, delete_xe_if_matching, search)) {
1848             // Found and deleted an element
1849             return pcmk_rc_ok;
1850         }
1851     }
1852 
1853     // No match found in this subtree
1854     return ENXIO;
1855 }
1856 
1857 /*!
1858  * \internal
1859  * \brief Replace one XML node with a copy of another XML node
1860  *
1861  * This function handles change tracking and applies ACLs.
1862  *
1863  * \param[in,out] old  XML node to replace
1864  * \param[in]     new  XML node to copy as replacement for \p old
1865  *
1866  * \note This frees \p old.
1867  */
1868 static void
1869 replace_node(xmlNode *old, xmlNode *new)
     /* [previous][next][first][last][top][bottom][index][help] */
1870 {
1871     new = xmlCopyNode(new, 1);
1872     pcmk__mem_assert(new);
1873 
1874     // May be unnecessary but avoids slight changes to some test outputs
1875     pcmk__xml_tree_foreach(new, reset_xml_node_flags, NULL);
1876 
1877     old = xmlReplaceNode(old, new);
1878 
1879     if (xml_tracking_changes(new)) {
1880         // Replaced sections may have included relevant ACLs
1881         pcmk__apply_acl(new);
1882     }
1883     xml_calculate_changes(old, new);
1884     xmlFreeNode(old);
1885 }
1886 
1887 /*!
1888  * \internal
1889  * \brief Replace one XML subtree with a copy of another if the two match
1890  *
1891  * A match is defined as follows:
1892  * * \p xml and \p user_data are both element nodes of the same type.
1893  * * If \p user_data has the \c PCMK_XA_ID attribute set, then \p xml has
1894  *   \c PCMK_XA_ID set to the same value.
1895  *
1896  * \param[in,out] xml        XML subtree to replace with \p user_data upon match
1897  * \param[in]     user_data  XML to replace \p xml with a copy of upon match
1898  *
1899  * \return \c true to continue traversing the tree, or \c false to stop (because
1900  *         \p xml was replaced by \p user_data)
1901  *
1902  * \note This is compatible with \c pcmk__xml_tree_foreach().
1903  */
1904 static bool
1905 replace_xe_if_matching(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1906 {
1907     xmlNode *replace = user_data;
1908     const char *xml_id = NULL;
1909     const char *replace_id = NULL;
1910 
1911     xml_id = pcmk__xe_id(xml);
1912     replace_id = pcmk__xe_id(replace);
1913 
1914     if (!pcmk__xe_is(replace, (const char *) xml->name)) {
1915         // No match: either not both elements, or different element types
1916         return true;
1917     }
1918 
1919     if ((replace_id != NULL)
1920         && !pcmk__str_eq(replace_id, xml_id, pcmk__str_none)) {
1921 
1922         // No match: ID was provided in replace and doesn't match xml's ID
1923         return true;
1924     }
1925 
1926     crm_log_xml_trace(xml, "replace-match");
1927     crm_log_xml_trace(replace, "replace-with");
1928     replace_node(xml, replace);
1929 
1930     // Found a match and replaced it; stop traversing tree
1931     return false;
1932 }
1933 
1934 /*!
1935  * \internal
1936  * \brief Search an XML tree depth-first and replace the first matching element
1937  *
1938  * This function does not attempt to match the tree root (\p xml).
1939  *
1940  * A match with a node \c node is defined as follows:
1941  * * \c node and \p replace are both element nodes of the same type.
1942  * * If \p replace has the \c PCMK_XA_ID attribute set, then \c node has
1943  *   \c PCMK_XA_ID set to the same value.
1944  *
1945  * \param[in,out] xml      XML tree to search
1946  * \param[in]     replace  XML to replace a matching element with a copy of
1947  *
1948  * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
1949  *         successful replacement and an error code otherwise)
1950  */
1951 int
1952 pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace)
     /* [previous][next][first][last][top][bottom][index][help] */
1953 {
1954     /* @COMPAT Some of this behavior (like not matching the tree root, which is
1955      * allowed by pcmk__xe_update_match()) is questionable for general use but
1956      * required for backward compatibility by cib_process_replace() and
1957      * cib_process_delete(). Behavior can change at a major version release if
1958      * desired.
1959      */
1960     CRM_CHECK((xml != NULL) && (replace != NULL), return EINVAL);
1961 
1962     for (xml = pcmk__xe_first_child(xml, NULL, NULL, NULL); xml != NULL;
1963          xml = pcmk__xe_next(xml)) {
1964 
1965         if (!pcmk__xml_tree_foreach(xml, replace_xe_if_matching, replace)) {
1966             // Found and replaced an element
1967             return pcmk_rc_ok;
1968         }
1969     }
1970 
1971     // No match found in this subtree
1972     return ENXIO;
1973 }
1974 
1975 //! User data for \c update_xe_if_matching()
1976 struct update_data {
1977     xmlNode *update;    //!< Update source
1978     uint32_t flags;     //!< Group of <tt>enum pcmk__xa_flags</tt>
1979 };
1980 
1981 /*!
1982  * \internal
1983  * \brief Update one XML subtree with another if the two match
1984  *
1985  * "Update" means to merge a source subtree into a target subtree (see
1986  * \c pcmk__xml_update()).
1987  *
1988  * A match is defined as follows:
1989  * * \p xml and \p user_data->update are both element nodes of the same type.
1990  * * \p xml and \p user_data->update have the same \c PCMK_XA_ID attribute
1991  *   value, or \c PCMK_XA_ID is unset in both
1992  *
1993  * \param[in,out] xml        XML subtree to update with \p user_data->update
1994  *                           upon match
1995  * \param[in]     user_data  <tt>struct update_data</tt> object
1996  *
1997  * \return \c true to continue traversing the tree, or \c false to stop (because
1998  *         \p xml was updated by \p user_data->update)
1999  *
2000  * \note This is compatible with \c pcmk__xml_tree_foreach().
2001  */
2002 static bool
2003 update_xe_if_matching(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
2004 {
2005     struct update_data *data = user_data;
2006     xmlNode *update = data->update;
2007 
2008     if (!pcmk__xe_is(update, (const char *) xml->name)) {
2009         // No match: either not both elements, or different element types
2010         return true;
2011     }
2012 
2013     if (!pcmk__str_eq(pcmk__xe_id(xml), pcmk__xe_id(update), pcmk__str_none)) {
2014         // No match: ID mismatch
2015         return true;
2016     }
2017 
2018     crm_log_xml_trace(xml, "update-match");
2019     crm_log_xml_trace(update, "update-with");
2020     pcmk__xml_update(NULL, xml, update, data->flags, false);
2021 
2022     // Found a match and replaced it; stop traversing tree
2023     return false;
2024 }
2025 
2026 /*!
2027  * \internal
2028  * \brief Search an XML tree depth-first and update the first matching element
2029  *
2030  * "Update" means to merge a source subtree into a target subtree (see
2031  * \c pcmk__xml_update()).
2032  *
2033  * A match with a node \c node is defined as follows:
2034  * * \c node and \p update are both element nodes of the same type.
2035  * * \c node and \p update have the same \c PCMK_XA_ID attribute value, or
2036  *   \c PCMK_XA_ID is unset in both
2037  *
2038  * \param[in,out] xml     XML tree to search
2039  * \param[in]     update  XML to update a matching element with
2040  * \param[in]     flags   Group of <tt>enum pcmk__xa_flags</tt>
2041  *
2042  * \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok on
2043  *         successful update and an error code otherwise)
2044  */
2045 int
2046 pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags)
     /* [previous][next][first][last][top][bottom][index][help] */
2047 {
2048     /* @COMPAT In pcmk__xe_delete_match() and pcmk__xe_replace_match(), we
2049      * compare IDs only if the equivalent of the update argument has an ID.
2050      * Here, we're stricter: we consider it a mismatch if only one element has
2051      * an ID attribute, or if both elements have IDs but they don't match.
2052      *
2053      * Perhaps we should align the behavior at a major version release.
2054      */
2055     struct update_data data = {
2056         .update = update,
2057         .flags = flags,
2058     };
2059 
2060     CRM_CHECK((xml != NULL) && (update != NULL), return EINVAL);
2061 
2062     if (!pcmk__xml_tree_foreach(xml, update_xe_if_matching, &data)) {
2063         // Found and updated an element
2064         return pcmk_rc_ok;
2065     }
2066 
2067     // No match found in this subtree
2068     return ENXIO;
2069 }
2070 
2071 xmlNode *
2072 sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
     /* [previous][next][first][last][top][bottom][index][help] */
2073 {
2074     xmlNode *child = NULL;
2075     GSList *nvpairs = NULL;
2076     xmlNode *result = NULL;
2077 
2078     CRM_CHECK(input != NULL, return NULL);
2079 
2080     result = pcmk__xe_create(parent, (const char *) input->name);
2081     nvpairs = pcmk_xml_attrs2nvpairs(input);
2082     nvpairs = pcmk_sort_nvpairs(nvpairs);
2083     pcmk_nvpairs2xml_attrs(nvpairs, result);
2084     pcmk_free_nvpairs(nvpairs);
2085 
2086     for (child = pcmk__xe_first_child(input, NULL, NULL, NULL); child != NULL;
2087          child = pcmk__xe_next(child)) {
2088 
2089         if (recursive) {
2090             sorted_xml(child, result, recursive);
2091         } else {
2092             pcmk__xml_copy(result, child);
2093         }
2094     }
2095 
2096     return result;
2097 }
2098 
2099 /*!
2100  * \internal
2101  * \brief Get next sibling XML element with the same name as a given element
2102  *
2103  * \param[in] node  XML element to start from
2104  *
2105  * \return Next sibling XML element with same name
2106  */
2107 xmlNode *
2108 pcmk__xe_next_same(const xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
2109 {
2110     for (xmlNode *match = pcmk__xe_next(node); match != NULL;
2111          match = pcmk__xe_next(match)) {
2112 
2113         if (pcmk__xe_is(match, (const char *) node->name)) {
2114             return match;
2115         }
2116     }
2117     return NULL;
2118 }
2119 
2120 void
2121 crm_xml_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
2122 {
2123     static bool init = true;
2124 
2125     if(init) {
2126         init = false;
2127         /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
2128          * pcmk__realloc()s and it can take upwards of 18 seconds (yes, seconds)
2129          * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
2130          * less than 1 second.
2131          */
2132         xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
2133 
2134         /* Populate and free the _private field when nodes are created and destroyed */
2135         xmlDeregisterNodeDefault(free_private_data);
2136         xmlRegisterNodeDefault(new_private_data);
2137 
2138         crm_schema_init();
2139     }
2140 }
2141 
2142 void
2143 crm_xml_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
2144 {
2145     crm_schema_cleanup();
2146     xmlCleanupParser();
2147 }
2148 
2149 #define XPATH_MAX 512
2150 
2151 xmlNode *
2152 expand_idref(xmlNode * input, xmlNode * top)
     /* [previous][next][first][last][top][bottom][index][help] */
2153 {
2154     char *xpath = NULL;
2155     const char *ref = NULL;
2156     xmlNode *result = NULL;
2157 
2158     if (input == NULL) {
2159         return NULL;
2160     }
2161 
2162     ref = crm_element_value(input, PCMK_XA_ID_REF);
2163     if (ref == NULL) {
2164         return input;
2165     }
2166 
2167     if (top == NULL) {
2168         top = input;
2169     }
2170 
2171     xpath = crm_strdup_printf("//%s[@" PCMK_XA_ID "='%s']", input->name, ref);
2172     result = get_xpath_object(xpath, top, LOG_DEBUG);
2173     if (result == NULL) { // Not possible with schema validation enabled
2174         pcmk__config_err("Ignoring invalid %s configuration: "
2175                          PCMK_XA_ID_REF " '%s' does not reference "
2176                          "a valid object " CRM_XS " xpath=%s",
2177                          input->name, ref, xpath);
2178     }
2179     free(xpath);
2180     return result;
2181 }
2182 
2183 char *
2184 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
     /* [previous][next][first][last][top][bottom][index][help] */
2185 {
2186     static const char *base = NULL;
2187     char *ret = NULL;
2188 
2189     if (base == NULL) {
2190         base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
2191     }
2192     if (pcmk__str_empty(base)) {
2193         base = CRM_SCHEMA_DIRECTORY;
2194     }
2195 
2196     switch (ns) {
2197         case pcmk__xml_artefact_ns_legacy_rng:
2198         case pcmk__xml_artefact_ns_legacy_xslt:
2199             ret = strdup(base);
2200             break;
2201         case pcmk__xml_artefact_ns_base_rng:
2202         case pcmk__xml_artefact_ns_base_xslt:
2203             ret = crm_strdup_printf("%s/base", base);
2204             break;
2205         default:
2206             crm_err("XML artefact family specified as %u not recognized", ns);
2207     }
2208     return ret;
2209 }
2210 
2211 static char *
2212 find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
     /* [previous][next][first][last][top][bottom][index][help] */
2213 {
2214     char *ret = NULL;
2215 
2216     switch (ns) {
2217         case pcmk__xml_artefact_ns_legacy_rng:
2218         case pcmk__xml_artefact_ns_base_rng:
2219             if (pcmk__ends_with(filespec, ".rng")) {
2220                 ret = crm_strdup_printf("%s/%s", path, filespec);
2221             } else {
2222                 ret = crm_strdup_printf("%s/%s.rng", path, filespec);
2223             }
2224             break;
2225         case pcmk__xml_artefact_ns_legacy_xslt:
2226         case pcmk__xml_artefact_ns_base_xslt:
2227             if (pcmk__ends_with(filespec, ".xsl")) {
2228                 ret = crm_strdup_printf("%s/%s", path, filespec);
2229             } else {
2230                 ret = crm_strdup_printf("%s/%s.xsl", path, filespec);
2231             }
2232             break;
2233         default:
2234             crm_err("XML artefact family specified as %u not recognized", ns);
2235     }
2236 
2237     return ret;
2238 }
2239 
2240 char *
2241 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
     /* [previous][next][first][last][top][bottom][index][help] */
2242 {
2243     struct stat sb;
2244     char *base = pcmk__xml_artefact_root(ns);
2245     char *ret = NULL;
2246 
2247     ret = find_artefact(ns, base, filespec);
2248     free(base);
2249 
2250     if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
2251         const char *remote_schema_dir = pcmk__remote_schema_dir();
2252 
2253         free(ret);
2254         ret = find_artefact(ns, remote_schema_dir, filespec);
2255     }
2256 
2257     return ret;
2258 }
2259 
2260 void
2261 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
     /* [previous][next][first][last][top][bottom][index][help] */
2262 {
2263     while (true) {
2264         const char *name, *value;
2265 
2266         name = va_arg(pairs, const char *);
2267         if (name == NULL) {
2268             return;
2269         }
2270 
2271         value = va_arg(pairs, const char *);
2272         if (value != NULL) {
2273             crm_xml_add(node, name, value);
2274         }
2275     }
2276 }
2277 
2278 void
2279 pcmk__xe_set_props(xmlNodePtr node, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
2280 {
2281     va_list pairs;
2282     va_start(pairs, node);
2283     pcmk__xe_set_propv(node, pairs);
2284     va_end(pairs);
2285 }
2286 
2287 int
2288 pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
     /* [previous][next][first][last][top][bottom][index][help] */
2289                        int (*handler)(xmlNode *xml, void *userdata),
2290                        void *userdata)
2291 {
2292     xmlNode *children = (xml? xml->children : NULL);
2293 
2294     CRM_ASSERT(handler != NULL);
2295 
2296     for (xmlNode *node = children; node != NULL; node = node->next) {
2297         if ((node->type == XML_ELEMENT_NODE)
2298             && ((child_element_name == NULL)
2299                 || pcmk__xe_is(node, child_element_name))) {
2300             int rc = handler(node, userdata);
2301 
2302             if (rc != pcmk_rc_ok) {
2303                 return rc;
2304             }
2305         }
2306     }
2307 
2308     return pcmk_rc_ok;
2309 }
2310 
2311 // Deprecated functions kept only for backward API compatibility
2312 // LCOV_EXCL_START
2313 
2314 #include <crm/common/xml_compat.h>
2315 
2316 xmlNode *
2317 find_entity(xmlNode *parent, const char *node_name, const char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
2318 {
2319     return pcmk__xe_first_child(parent, node_name,
2320                                 ((id == NULL)? id : PCMK_XA_ID), id);
2321 }
2322 
2323 void
2324 crm_destroy_xml(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
2325 {
2326     free_xml(data);
2327 }
2328 
2329 xmlDoc *
2330 getDocPtr(xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
2331 {
2332     xmlDoc *doc = NULL;
2333 
2334     CRM_CHECK(node != NULL, return NULL);
2335 
2336     doc = node->doc;
2337     if (doc == NULL) {
2338         doc = xmlNewDoc(PCMK__XML_VERSION);
2339         xmlDocSetRootElement(doc, node);
2340     }
2341     return doc;
2342 }
2343 
2344 xmlNode *
2345 add_node_copy(xmlNode *parent, xmlNode *src_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2346 {
2347     xmlNode *child = NULL;
2348 
2349     CRM_CHECK((parent != NULL) && (src_node != NULL), return NULL);
2350 
2351     child = xmlDocCopyNode(src_node, parent->doc, 1);
2352     if (child == NULL) {
2353         return NULL;
2354     }
2355     xmlAddChild(parent, child);
2356     pcmk__xml_mark_created(child);
2357     return child;
2358 }
2359 
2360 int
2361 add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child)
     /* [previous][next][first][last][top][bottom][index][help] */
2362 {
2363     add_node_copy(parent, child);
2364     free_xml(child);
2365     return 1;
2366 }
2367 
2368 gboolean
2369 xml_has_children(const xmlNode * xml_root)
     /* [previous][next][first][last][top][bottom][index][help] */
2370 {
2371     if (xml_root != NULL && xml_root->children != NULL) {
2372         return TRUE;
2373     }
2374     return FALSE;
2375 }
2376 
2377 static char *
2378 replace_text(char *text, size_t *index, size_t *length, const char *replace)
     /* [previous][next][first][last][top][bottom][index][help] */
2379 {
2380     // We have space for 1 char already
2381     size_t offset = strlen(replace) - 1;
2382 
2383     if (offset > 0) {
2384         *length += offset;
2385         text = pcmk__realloc(text, *length + 1);
2386 
2387         // Shift characters to the right to make room for the replacement string
2388         for (size_t i = *length; i > (*index + offset); i--) {
2389             text[i] = text[i - offset];
2390         }
2391     }
2392 
2393     // Replace the character at index by the replacement string
2394     memcpy(text + *index, replace, offset + 1);
2395 
2396     // Reset index to the end of replacement string
2397     *index += offset;
2398     return text;
2399 }
2400 
2401 char *
2402 crm_xml_escape(const char *text)
     /* [previous][next][first][last][top][bottom][index][help] */
2403 {
2404     size_t length = 0;
2405     char *copy = NULL;
2406 
2407     if (text == NULL) {
2408         return NULL;
2409     }
2410 
2411     length = strlen(text);
2412     copy = pcmk__str_copy(text);
2413     for (size_t index = 0; index <= length; index++) {
2414         if(copy[index] & 0x80 && copy[index+1] & 0x80){
2415             index++;
2416             continue;
2417         }
2418         switch (copy[index]) {
2419             case 0:
2420                 // Sanity only; loop should stop at the last non-null byte
2421                 break;
2422             case '<':
2423                 copy = replace_text(copy, &index, &length, "&lt;");
2424                 break;
2425             case '>':
2426                 copy = replace_text(copy, &index, &length, "&gt;");
2427                 break;
2428             case '"':
2429                 copy = replace_text(copy, &index, &length, "&quot;");
2430                 break;
2431             case '\'':
2432                 copy = replace_text(copy, &index, &length, "&apos;");
2433                 break;
2434             case '&':
2435                 copy = replace_text(copy, &index, &length, "&amp;");
2436                 break;
2437             case '\t':
2438                 /* Might as well just expand to a few spaces... */
2439                 copy = replace_text(copy, &index, &length, "    ");
2440                 break;
2441             case '\n':
2442                 copy = replace_text(copy, &index, &length, "\\n");
2443                 break;
2444             case '\r':
2445                 copy = replace_text(copy, &index, &length, "\\r");
2446                 break;
2447             default:
2448                 /* Check for and replace non-printing characters with their octal equivalent */
2449                 if(copy[index] < ' ' || copy[index] > '~') {
2450                     char *replace = crm_strdup_printf("\\%.3o", copy[index]);
2451 
2452                     copy = replace_text(copy, &index, &length, replace);
2453                     free(replace);
2454                 }
2455         }
2456     }
2457     return copy;
2458 }
2459 
2460 xmlNode *
2461 copy_xml(xmlNode *src)
     /* [previous][next][first][last][top][bottom][index][help] */
2462 {
2463     xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
2464     xmlNode *copy = NULL;
2465 
2466     pcmk__mem_assert(doc);
2467 
2468     copy = xmlDocCopyNode(src, doc, 1);
2469     pcmk__mem_assert(copy);
2470 
2471     xmlDocSetRootElement(doc, copy);
2472     return copy;
2473 }
2474 
2475 xmlNode *
2476 create_xml_node(xmlNode *parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2477 {
2478     // Like pcmk__xe_create(), but returns NULL on failure
2479     xmlNode *node = NULL;
2480 
2481     CRM_CHECK(!pcmk__str_empty(name), return NULL);
2482 
2483     if (parent == NULL) {
2484         xmlDoc *doc = xmlNewDoc(PCMK__XML_VERSION);
2485 
2486         if (doc == NULL) {
2487             return NULL;
2488         }
2489 
2490         node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
2491         if (node == NULL) {
2492             xmlFreeDoc(doc);
2493             return NULL;
2494         }
2495         xmlDocSetRootElement(doc, node);
2496 
2497     } else {
2498         node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
2499         if (node == NULL) {
2500             return NULL;
2501         }
2502     }
2503     pcmk__xml_mark_created(node);
2504     return node;
2505 }
2506 
2507 xmlNode *
2508 pcmk_create_xml_text_node(xmlNode *parent, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
2509                           const char *content)
2510 {
2511     xmlNode *node = pcmk__xe_create(parent, name);
2512 
2513     pcmk__xe_set_content(node, "%s", content);
2514     return node;
2515 }
2516 
2517 xmlNode *
2518 pcmk_create_html_node(xmlNode *parent, const char *element_name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
2519                       const char *class_name, const char *text)
2520 {
2521     xmlNode *node = pcmk__html_create(parent, element_name, id, class_name);
2522 
2523     pcmk__xe_set_content(node, "%s", text);
2524     return node;
2525 }
2526 
2527 xmlNode *
2528 first_named_child(const xmlNode *parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2529 {
2530     return pcmk__xe_first_child(parent, name, NULL, NULL);
2531 }
2532 
2533 xmlNode *
2534 find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
     /* [previous][next][first][last][top][bottom][index][help] */
2535 {
2536     xmlNode *result = NULL;
2537 
2538     if (search_path == NULL) {
2539         crm_warn("Will never find <NULL>");
2540         return NULL;
2541     }
2542 
2543     result = pcmk__xe_first_child(root, search_path, NULL, NULL);
2544 
2545     if (must_find && (result == NULL)) {
2546         crm_warn("Could not find %s in %s",
2547                  search_path,
2548                  ((root != NULL)? (const char *) root->name : "<NULL>"));
2549     }
2550 
2551     return result;
2552 }
2553 
2554 xmlNode *
2555 crm_next_same_xml(const xmlNode *sibling)
     /* [previous][next][first][last][top][bottom][index][help] */
2556 {
2557     return pcmk__xe_next_same(sibling);
2558 }
2559 
2560 void
2561 xml_remove_prop(xmlNode * obj, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2562 {
2563     pcmk__xe_remove_attr(obj, name);
2564 }
2565 
2566 gboolean
2567 replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
     /* [previous][next][first][last][top][bottom][index][help] */
2568 {
2569     bool is_match = false;
2570     const char *child_id = NULL;
2571     const char *update_id = NULL;
2572 
2573     CRM_CHECK(child != NULL, return FALSE);
2574     CRM_CHECK(update != NULL, return FALSE);
2575 
2576     child_id = pcmk__xe_id(child);
2577     update_id = pcmk__xe_id(update);
2578 
2579     /* Match element name and (if provided in update XML) element ID. Don't
2580      * match search root (child is search root if parent == NULL).
2581      */
2582     is_match = (parent != NULL)
2583                && pcmk__xe_is(update, (const char *) child->name)
2584                && ((update_id == NULL)
2585                    || pcmk__str_eq(update_id, child_id, pcmk__str_none));
2586 
2587     /* For deletion, match all attributes provided in update. A matching node
2588      * can have additional attributes, but values must match for provided ones.
2589      */
2590     if (is_match && delete_only) {
2591         for (xmlAttr *attr = pcmk__xe_first_attr(update); attr != NULL;
2592              attr = attr->next) {
2593             const char *name = (const char *) attr->name;
2594             const char *update_val = pcmk__xml_attr_value(attr);
2595             const char *child_val = crm_element_value(child, name);
2596 
2597             if (!pcmk__str_eq(update_val, child_val, pcmk__str_casei)) {
2598                 is_match = false;
2599                 break;
2600             }
2601         }
2602     }
2603 
2604     if (is_match) {
2605         if (delete_only) {
2606             crm_log_xml_trace(child, "delete-match");
2607             crm_log_xml_trace(update, "delete-search");
2608             free_xml(child);
2609 
2610         } else {
2611             crm_log_xml_trace(child, "replace-match");
2612             crm_log_xml_trace(update, "replace-with");
2613             replace_node(child, update);
2614         }
2615         return TRUE;
2616     }
2617 
2618     // Current node not a match; search the rest of the subtree depth-first
2619     parent = child;
2620     for (child = pcmk__xml_first_child(parent); child != NULL;
2621          child = pcmk__xml_next(child)) {
2622 
2623         // Only delete/replace the first match
2624         if (replace_xml_child(parent, child, update, delete_only)) {
2625             return TRUE;
2626         }
2627     }
2628 
2629     // No match found in this subtree
2630     return FALSE;
2631 }
2632 
2633 gboolean
2634 update_xml_child(xmlNode *child, xmlNode *to_update)
     /* [previous][next][first][last][top][bottom][index][help] */
2635 {
2636     return pcmk__xe_update_match(child, to_update,
2637                                  pcmk__xaf_score_update) == pcmk_rc_ok;
2638 }
2639 
2640 int
2641 find_xml_children(xmlNode **children, xmlNode *root, const char *tag,
     /* [previous][next][first][last][top][bottom][index][help] */
2642                   const char *field, const char *value, gboolean search_matches)
2643 {
2644     int match_found = 0;
2645 
2646     CRM_CHECK(root != NULL, return FALSE);
2647     CRM_CHECK(children != NULL, return FALSE);
2648 
2649     if ((tag != NULL) && !pcmk__xe_is(root, tag)) {
2650 
2651     } else if ((value != NULL)
2652                && !pcmk__str_eq(value, crm_element_value(root, field),
2653                                 pcmk__str_casei)) {
2654 
2655     } else {
2656         if (*children == NULL) {
2657             *children = pcmk__xe_create(NULL, __func__);
2658         }
2659         pcmk__xml_copy(*children, root);
2660         match_found = 1;
2661     }
2662 
2663     if (search_matches || match_found == 0) {
2664         xmlNode *child = NULL;
2665 
2666         for (child = pcmk__xml_first_child(root); child != NULL;
2667              child = pcmk__xml_next(child)) {
2668             match_found += find_xml_children(children, child, tag, field, value,
2669                                              search_matches);
2670         }
2671     }
2672 
2673     return match_found;
2674 }
2675 
2676 void
2677 fix_plus_plus_recursive(xmlNode *target)
     /* [previous][next][first][last][top][bottom][index][help] */
2678 {
2679     /* TODO: Remove recursion and use xpath searches for value++ */
2680     xmlNode *child = NULL;
2681 
2682     for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
2683         const char *p_name = (const char *) a->name;
2684         const char *p_value = pcmk__xml_attr_value(a);
2685 
2686         expand_plus_plus(target, p_name, p_value);
2687     }
2688     for (child = pcmk__xe_first_child(target, NULL, NULL, NULL); child != NULL;
2689          child = pcmk__xe_next(child)) {
2690 
2691         fix_plus_plus_recursive(child);
2692     }
2693 }
2694 
2695 void
2696 copy_in_properties(xmlNode *target, const xmlNode *src)
     /* [previous][next][first][last][top][bottom][index][help] */
2697 {
2698     if (src == NULL) {
2699         crm_warn("No node to copy properties from");
2700 
2701     } else if (target == NULL) {
2702         crm_err("No node to copy properties into");
2703 
2704     } else {
2705         for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
2706             const char *p_name = (const char *) a->name;
2707             const char *p_value = pcmk__xml_attr_value(a);
2708 
2709             expand_plus_plus(target, p_name, p_value);
2710             if (xml_acl_denied(target)) {
2711                 crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name);
2712                 return;
2713             }
2714         }
2715     }
2716 }
2717 
2718 void
2719 expand_plus_plus(xmlNode * target, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
2720 {
2721     pcmk__xe_set_score(target, name, value);
2722 }
2723 
2724 // LCOV_EXCL_STOP
2725 // End deprecated API

/* [previous][next][first][last][top][bottom][index][help] */