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

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